Unions
Unions define a type that must be exactly one of several possible variants. There are two types of unions:
- Union expressions
- Named unions
Union expressions
Section titled âUnion expressionsâUnnamed unions, or union expressions, can be declared by combining the variants using the | operator.
alias Breed = Beagle | GermanShepherd | GoldenRetriever;In this example, Breed can be either a Beagle, a GermanShepherd, or a GoldenRetriever.
Named unions
Section titled âNamed unionsâNamed unions allow you to assign a name to the union and provide explicit variant references. Named unions are somewhat similar to enums, but instead of having string or numeric values, they use record models.
A named union can be declared with the union keyword. Its name must be an identifier.
union Breed { beagle: Beagle, shepherd: GermanShepherd, retriever: GoldenRetriever,}The above example is equivalent to the Breed alias mentioned earlier, with the difference that emitters can recognize Breed as a named entity and also identify the beagle, shepherd, and retriever names for the options. This format also allows the application of decorators to each of the options.
Constraining a union with extends
Section titled âConstraining a union with extendsâA named union can declare a base type with the extends keyword. Every variant of the union must be assignable to that base type, otherwise a diagnostic is reported on the offending variant.
model Dog { name: string;}model Beagle extends Dog { huntingSkill: string;}model GermanShepherd extends Dog { guardingSkill: string;}
union Breed extends Dog { beagle: Beagle, shepherd: GermanShepherd,}This serves two purposes:
- It prevents a common class of mistake where an unrelated type is accidentally added to a union.
- It records the common base type in the type graph, which makes it easy for emitters to represent the union with a polymorphic base type in languages that donât support unions natively.
The base type does not become a variant of the union. Breed above still has exactly two variants.
extends is a constraint, not a declaration of inheritance. A variant only needs to be assignable to the base type, it doesnât have to explicitly extends it:
model Dog { name: string;}model Beagle { name: string; huntingSkill: string;}
// Ok: `Beagle` is assignable to `Dog` even though it doesn't explicitly extend it.union Breed extends Dog { beagle: Beagle,}Requiring explicit inheritance with @strictExtends
Section titled âRequiring explicit inheritance with @strictExtendsâEmitters targeting languages without native unions represent a union with an extends clause as a polymorphic base type. That representation requires every variant to actually derive from the base type, which structural assignability alone doesnât guarantee.
Apply @strictExtends to turn that requirement into a compile time error:
model Dog { name: string;}model Beagle { name: string; huntingSkill: string;}
@strictExtendsunion Breed extends Dog { beagle: Beagle, // error: `Beagle` is assignable to `Dog` but doesn't extend it.}@strictExtends can only be used when the base type is a model, a scalar or an enum, since those are the only types that can be explicitly extended. Using it on a union whose base type is, for example, another union or unknown is an error.
A variant that is itself a union satisfies the constraint when all of its own variants do, so unions can still be composed. never, an empty union and a self referencing union describe no value at all, so they satisfy the constraint vacuously.
model Pet { name: string;}model Cat extends Pet { meow: boolean;}model Dog extends Pet { bark: boolean;}
union Cats extends Pet { cat: Cat,}
// Ok: every variant of `Cats` extends `Pet`.@strictExtendsunion Pets extends Pet { Cats, dog: Dog,}Any type expression can be used as the base type, including scalars, unions and templates.
union OperationStatus extends string { "Running", "Succeeded", "Failed",}extends also has no interaction with the @discriminator decorator.