Many values or parameters in JavaScript can be of more than one type. For example, a function might take an object where one of the properties can be either a string or a number, but not a function.
Comments from Ron Buckton in https://typescript.codeplex.com/workitem/1364
I'd like to see type annotations provide some support for a type union. For example:
ES6 Promise
class Promise<T> {
// ...
static any<TResult>(...values: union { Promise<T>; T; }[]): Promise<T>;
static every<TResult>(...values: union { Promise<T>; T; }[]): Promise<T[]>;
static some<TResult>(...values: union { Promise<T>; T; }[]): Promise<T>;
then(resolve?: (value: T) => union { Promise<T>; T; }, reject?: (value: any) => union { Promise<T>; T; }): Promise<T>;
// ...
}
ES6 Loader
class Loader<T> {
// ...
normalize(name: string, referer?: Referer): union { string; { normalized: string; metadata?: any }; };
resolve(normalized: string, options?: { referer: Referer; metadata: any; }): union { string; { address: string; extra?: string[]; }; };
// ...
}
When static type checking is performed, it is possible to have some type issues when explicitly providing generic type arguments and having the wrong type chosen, but this exists today without supporting type unions.
The other open issue is what to do with a local that is a type union: should it act as an any
, a type that contains all of the members of both (or all) types in the union, or a type that only contains the members that are the same in both (or all) types in the union.
An implicit or explicit type cast from a type union to one of the union types would pass without the need to perform an intermediate cast to , and an explicit type cast to a more specific type for any of the union types would likewise succeed without an intermediate cast.
Assignment to a local or field that is a type union would succeed as if it were either of the types (e.g. implicit or explicit type cast from a more specific type to a less specific type specified in the type union).
There is also a question on how to properly handle the intellisense for a symbol that uses a type union. It could either be represented as a number of overloads (similar to what would have to be typed today), or preserve the type union definition.
Union Declaration
Providing a typedef-like syntax for unions would also be useful to define a reusable definition:
union Ref<T> {
Promise<T>;
T;
}
This is roughly analogous to an interface that defines multiple call signatures:
// as an interface with call signatures...
interface ResolveCallback<T> {
(value: Promise<T>): void;
(value: T): void;
(): void;
}
// ...and as a union with function types
union ResolveCallback<T> {
(value: Promise<T>) => void;
(value: T) => void;
() => void;
}
Static Analysis
Adding type unions would require changes to the static type information to be supported. The primary goal of adding type unions is to help the compiler determine the best matching type for a call expression or return type expression. The following sections discuss various ways of handling static analysis of type unions.
Assigning to a Type Union
When assigning to an identifier that is annotated with a type union, passing a value as an argument to a function for a parameter that is a type union, returning a value from a function with a type union in its return type annotation, or type-casting a value to a type union, the type of the value being assigned or returned must be compatible (as either an exact match or a superset of type information) with one of the types defined in the type union.
For example:
// assign to variable
var value: union { Promise<number>; number; };
value = 1; // legal
value = Promise.resolve<number>(1); // legal
value = new Date(); // static error
// type-cast to union
declare var n: number;
declare var p: Promise<number>;
declare var a: any;
declare var d: Date;
<union { Promise<number>; number; }>n; // legal
<union { Promise<number>; number; }>p; // legal
<union { Promise<number>; number; }>a; // legal
<union { Promise<number>; number; }>d; // legal
// union in return value
function func(type: string): union { Promise<number>; number; } {
switch (type) {
case "number":
return 1; // legal
case "promise":
return Promise.resolve<number>(1); // legal
case "date":
return new Date(); // static error
}
}
// union in invocation expression
declare function func(promiseOrValue: union { Promise<number>; number; }): void;
declare var n: number;
declare var p: Promise<number>;
declare var a: any;
declare var d: Date;
func(n); // legal
func(p); // legal
func(a); // legal
func(d); // static error
Assigning from a Type Union
When assigning to another value from a type union or type-casting from a type union, the type of the value must be compatible (as either an exact match or a subset of type information) with one of the types in the union.
For example:
// assignment
declare var value: union { Promise<number>; number; };
var n: number;
var p: Promise<number>;
var a: any;
var d: Date;
n = value; // legal
p = value; // legal
a = value; // legal
d = value; // static error
// type-cast
<Promise<number>>value; // legal
<number>value; // legal
<any>value; // legal
<Date>value; // static error