Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Union Types #824

Merged
merged 31 commits into from
Oct 13, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e836fe1
Initial implementation of Union Types
ahejlsberg Oct 4, 2014
d70494f
Narrowing of variable types using typeof/instanceof type guards
ahejlsberg Oct 7, 2014
b8923b3
Support symbol kind for union properties
mhegazy Oct 8, 2014
5669f63
add test for quick info
mhegazy Oct 8, 2014
c439ae4
Add support for union properties in goto def
mhegazy Oct 8, 2014
95584e9
Addressing CR feedback
ahejlsberg Oct 8, 2014
fd5b808
Accepting new baselines
ahejlsberg Oct 8, 2014
3a17b02
Improved type argument inference with union types
ahejlsberg Oct 8, 2014
ea4cbbe
Merge branch 'master' into unionTypes
ahejlsberg Oct 8, 2014
5c661ba
Accepting new baselines after merge with master
ahejlsberg Oct 8, 2014
779db6e
Support find all refs on union properties
mhegazy Oct 9, 2014
2eb51ab
Use getRootSymbols for all union property needs
mhegazy Oct 9, 2014
dc43e83
Merge branch 'unionTypes' into unionTypesLS
mhegazy Oct 9, 2014
927f04f
Fix contextually typed object literal proeprties that are not propert…
mhegazy Oct 9, 2014
9f43ac0
respond to code review remarks
mhegazy Oct 10, 2014
bacb9d0
Test updates from union changes
danquirk Oct 10, 2014
8ce1760
Fixing merge conflicts
danquirk Oct 10, 2014
f5a9fee
ensure unionProperty symbols have declarations set at creation time
mhegazy Oct 10, 2014
483afea
Less aggressive subtype reduction in union types
ahejlsberg Oct 10, 2014
4e02b9f
Merge branch 'unionTypes' of https://github.com/Microsoft/TypeScript …
ahejlsberg Oct 10, 2014
c9a42c1
Accepting new baselines
ahejlsberg Oct 11, 2014
2ce627c
Handle union properties completions on apparant types
mhegazy Oct 11, 2014
4442b45
Add a temporary fix to quick info
mhegazy Oct 11, 2014
04e5309
Merge branch 'unionTypes' into unionTypesLS
mhegazy Oct 11, 2014
eee1602
Merge pull request #861 from Microsoft/unionTypesLS
mhegazy Oct 11, 2014
83d9aed
Correct contextual typing with union types
ahejlsberg Oct 13, 2014
a76a418
Accepting new baselines
ahejlsberg Oct 13, 2014
869ee41
Addressing CR feedback
ahejlsberg Oct 13, 2014
fc842b1
Merge branch 'master' into unionTypes
ahejlsberg Oct 13, 2014
4f4f59a
Merge changes from master in services.ts
mhegazy Oct 13, 2014
f5cd414
Merge branch 'master' into unionTypes
mhegazy Oct 13, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,114 changes: 851 additions & 263 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ module ts {
return array1.concat(array2);
}

export function uniqueElements<T>(array: T[]): T[] {
export function deduplicate<T>(array: T[]): T[] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'distinct'.

'deduplicate' indicates you are mutating in the array in place. 'distinct' is the linq name for getting back the set of unique elements.

if (array) {
var result: T[] = [];
for (var i = 0, len = array.length; i < len; i++) {
Expand Down
2 changes: 0 additions & 2 deletions src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,6 @@ module ts {
The_right_hand_side_of_an_arithmetic_operation_must_be_of_type_any_number_or_an_enum_type: { code: 2363, category: DiagnosticCategory.Error, key: "The right-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type." },
Invalid_left_hand_side_of_assignment_expression: { code: 2364, category: DiagnosticCategory.Error, key: "Invalid left-hand side of assignment expression." },
Operator_0_cannot_be_applied_to_types_1_and_2: { code: 2365, category: DiagnosticCategory.Error, key: "Operator '{0}' cannot be applied to types '{1}' and '{2}'." },
No_best_common_type_exists_between_0_1_and_2: { code: 2366, category: DiagnosticCategory.Error, key: "No best common type exists between '{0}', '{1}', and '{2}'." },
No_best_common_type_exists_between_0_and_1: { code: 2367, category: DiagnosticCategory.Error, key: "No best common type exists between '{0}' and '{1}'." },
Type_parameter_name_cannot_be_0: { code: 2368, category: DiagnosticCategory.Error, key: "Type parameter name cannot be '{0}'" },
A_parameter_property_is_only_allowed_in_a_constructor_implementation: { code: 2369, category: DiagnosticCategory.Error, key: "A parameter property is only allowed in a constructor implementation." },
A_rest_parameter_must_be_of_an_array_type: { code: 2370, category: DiagnosticCategory.Error, key: "A rest parameter must be of an array type." },
Expand Down
8 changes: 0 additions & 8 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -716,14 +716,6 @@
"category": "Error",
"code": 2365
},
"No best common type exists between '{0}', '{1}', and '{2}'.": {
"category": "Error",
"code": 2366
},
"No best common type exists between '{0}' and '{1}'.": {
"category": "Error",
"code": 2367
},
"Type parameter name cannot be '{0}'": {
"category": "Error",
"code": 2368
Expand Down
22 changes: 20 additions & 2 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,8 @@ module ts {
return child((<ArrayTypeNode>node).elementType);
case SyntaxKind.TupleType:
return children((<TupleTypeNode>node).elementTypes);
case SyntaxKind.UnionType:
return children((<UnionTypeNode>node).types);
case SyntaxKind.ArrayLiteral:
return children((<ArrayLiteral>node).elements);
case SyntaxKind.ObjectLiteral:
Expand Down Expand Up @@ -1729,9 +1731,9 @@ module ts {
}
}

function parseType(): TypeNode {
function parseNonUnionType(): TypeNode {
var type = parseNonArrayType();
while (type && !scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.OpenBracketToken)) {
while (!scanner.hasPrecedingLineBreak() && parseOptional(SyntaxKind.OpenBracketToken)) {
parseExpected(SyntaxKind.CloseBracketToken);
var node = <ArrayTypeNode>createNode(SyntaxKind.ArrayType, type.pos);
node.elementType = type;
Expand All @@ -1740,6 +1742,22 @@ module ts {
return type;
}

function parseType(): TypeNode {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right now it looks like it's not possible to have syntax for an array of union types. That seems like it could be problematic in the future. For example, say you have htis in your .js:

function foo(x: string | number) {
    return [x];
}

This function will have a type that is nonexpressable with the syntax of the language (and thus would be a problem for .d.ts files, as well as anyone who wants to explicitly give typings to things).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can write this as Array<string|number>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point. We'll likely have to do that if we don't make additional syntax. As htis is a perfectly reasonable workaround, i'd prefer this approach before adding more syntax. thanks!

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to have union type aliases, to keep the length of type names managable:

define number | Array<number> | Matrix<number> = DynamicMatrix;

Other values for the define keyword could be alias, type or class

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine this will fall out as a default feature, based on the type inference system of TypeScript, but I think it bears repeating. Union types should have anonymous interfaces:

class A {
    commonToBoth: string;
    uniqueToA: string;
}

class B {
    commonToBoth: string;
    uniqueToB: string;
}

var either: A | B = new A();

//Valid because there is an anonymous interface containing the common members
either.commonToBoth

The anonymous interface would have the form:

interface `unnamable {
    commonToBoth: string;
}

Or go wild and give this interface a name like:

Intersection[A | B] 
Common[A | B]
Interface[A | B] or
Infc[A | B] //for brevity

This does not have any use case I can think of, but perhaps I'm not thinking hard enough.


@DanielRosenwasser Yes I do. So consider this an upvote. =)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@drubino you mean like in #957? 😃

This is a relatively old conversation at this point - you'll find quite a bit has happened recently with respect to union types, such as #914.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haskell has union types denoted by Either A B. From conversations with members of the Haskell community, I've heard that this feature causes a lot of switch statements, leading to verbose, branching code. I'm not sure how to handle this, but perhaps one could create implicit type checks and blocks that use the order of the types as their listed on the union type. This keeps the syntax light-weight.

Something like:

var x = A | B | C;
for x do {
    ///Handle A
} or {
    ///Handle B
} or {
    //Handle c
}

When combined with the anonymous interface idea, along with syntax to combine or skip blocks if logic can be implemented by common members in certain cases, you might be able enable the user to be quite succinct for common scenarios.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might also be interesting to have a way of defining union types. Perhaps there is one master type that all others can be cast to, and the cast is specified in the definition. The compiler would then handle optimizations where casting can be proven to be unnecessary:

class DynamicMatrix contains number | Array<number> | Matrix<number> {
     super: Matrix<number>
     (x: number): Array<number> =  x => [x];
     (x: Array<number>): Matrix<number> = x => new Matrix([x]);
     (x: Array<number>): number = x => x[0] 
         when x.length === 1
         otherwise throw 'Array to number conversion is not possible';
     (x: Matrix<number>): Array<number> = x => x[0] 
          when x.numberOfRows  = 1
          otherwise throw 'Matrix to Array conversion is not possible';
} 

This is probably a bit more complex than the design team had in mind for union types, but I thought I'd throw it out there all the same.

I believe this, or features like it, would provide us with language constructs that would help to bridge the gap between dynamically typed languages and statically typed ones.

var type = parseNonUnionType();
if (token === SyntaxKind.BarToken) {
var types = <NodeArray<TypeNode>>[type];
types.pos = type.pos;
while (parseOptional(SyntaxKind.BarToken)) {
types.push(parseNonUnionType());
}
types.end = getNodeEnd();
var node = <UnionTypeNode>createNode(SyntaxKind.UnionType, type.pos);
node.types = types;
type = finishNode(node);
}
return type;
}

function parseTypeAnnotation(): TypeNode {
return parseOptional(SyntaxKind.ColonToken) ? parseType() : undefined;
}
Expand Down
71 changes: 44 additions & 27 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ module ts {
TypeLiteral,
ArrayType,
TupleType,
UnionType,
// Expression
ArrayLiteral,
ObjectLiteral,
Expand Down Expand Up @@ -224,7 +225,7 @@ module ts {
FirstFutureReservedWord = ImplementsKeyword,
LastFutureReservedWord = YieldKeyword,
FirstTypeNode = TypeReference,
LastTypeNode = TupleType,
LastTypeNode = UnionType,
FirstPunctuation = OpenBraceToken,
LastPunctuation = CaretEqualsToken,
FirstToken = EndOfFileToken,
Expand Down Expand Up @@ -337,6 +338,10 @@ module ts {
elementTypes: NodeArray<TypeNode>;
}

export interface UnionTypeNode extends TypeNode {
types: NodeArray<TypeNode>;
}

export interface StringLiteralTypeNode extends TypeNode {
text: string;
}
Expand Down Expand Up @@ -648,7 +653,7 @@ module ts {
writeSymbol(symbol: Symbol, writer: SymbolWriter, enclosingDeclaration?: Node, meaning?: SymbolFlags, flags?: SymbolFormatFlags): void;
getFullyQualifiedName(symbol: Symbol): string;
getAugmentedPropertiesOfApparentType(type: Type): Symbol[];
getRootSymbol(symbol: Symbol): Symbol;
getRootSymbols(symbol: Symbol): Symbol[];
getContextualType(node: Node): Type;
getResolvedSignature(node: CallExpression, candidatesOutArray?: Signature[]): Signature;
getSignatureFromDeclaration(declaration: SignatureDeclaration): Signature;
Expand Down Expand Up @@ -757,19 +762,22 @@ module ts {
ConstructSignature = 0x00010000, // Construct signature
IndexSignature = 0x00020000, // Index signature
TypeParameter = 0x00040000, // Type parameter
UnionProperty = 0x00080000, // Property in union type

// Export markers (see comment in declareModuleMember in binder)
ExportValue = 0x00080000, // Exported value marker
ExportType = 0x00100000, // Exported type marker
ExportNamespace = 0x00200000, // Exported namespace marker

Import = 0x00400000, // Import
Instantiated = 0x00800000, // Instantiated symbol
Merged = 0x01000000, // Merged symbol (created during program binding)
Transient = 0x02000000, // Transient symbol (created during type check)
Prototype = 0x04000000, // Symbol for the prototype property (without source code representation)

Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor,
ExportValue = 0x00100000, // Exported value marker
ExportType = 0x00200000, // Exported type marker
ExportNamespace = 0x00400000, // Exported namespace marker

Import = 0x00800000, // Import
Instantiated = 0x01000000, // Instantiated symbol
Merged = 0x02000000, // Merged symbol (created during program binding)
Transient = 0x04000000, // Transient symbol (created during type check)
Prototype = 0x08000000, // Prototype property (no source representation)
Undefined = 0x10000000, // Symbol for the undefined

Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor | UnionProperty,

Type = Class | Interface | Enum | TypeLiteral | ObjectLiteral | TypeParameter,
Namespace = ValueModule | NamespaceModule,
Module = ValueModule | NamespaceModule,
Expand Down Expand Up @@ -827,6 +835,7 @@ module ts {
mapper?: TypeMapper; // Type mapper for instantiation alias
referenced?: boolean; // True if alias symbol has been referenced as a value
exportAssignSymbol?: Symbol; // Symbol exported from external module
unionType?: UnionType; // Containing union type for union property
}

export interface TransientSymbol extends Symbol, SymbolLinks { }
Expand All @@ -849,14 +858,15 @@ module ts {
}

export interface NodeLinks {
resolvedType?: Type; // Cached type of type node
resolvedSignature?: Signature; // Cached signature of signature node or call expression
resolvedSymbol?: Symbol; // Cached name resolution result
flags?: NodeCheckFlags; // Set of flags specific to Node
enumMemberValue?: number; // Constant value of enum member
resolvedType?: Type; // Cached type of type node
resolvedSignature?: Signature; // Cached signature of signature node or call expression
resolvedSymbol?: Symbol; // Cached name resolution result
flags?: NodeCheckFlags; // Set of flags specific to Node
enumMemberValue?: number; // Constant value of enum member
isIllegalTypeReferenceInConstraint?: boolean; // Is type reference in constraint refers to the type parameter from the same list
isVisible?: boolean; // Is this node visible
localModuleName?: string; // Local name for module instance
isVisible?: boolean; // Is this node visible
localModuleName?: string; // Local name for module instance
assignmentChecks?: Map<boolean>; // Cache of assignment checks
}

export enum TypeFlags {
Expand All @@ -874,13 +884,14 @@ module ts {
Interface = 0x00000800, // Interface
Reference = 0x00001000, // Generic type reference
Tuple = 0x00002000, // Tuple
Anonymous = 0x00004000, // Anonymous
FromSignature = 0x00008000, // Created for signature assignment check
Union = 0x00004000, // Union
Anonymous = 0x00008000, // Anonymous
FromSignature = 0x00010000, // Created for signature assignment check

Intrinsic = Any | String | Number | Boolean | Void | Undefined | Null,
Intrinsic = Any | String | Number | Boolean | Void | Undefined | Null,
StringLike = String | StringLiteral,
NumberLike = Number | Enum,
ObjectType = Class | Interface | Reference | Tuple | Anonymous
ObjectType = Class | Interface | Reference | Tuple | Union | Anonymous,
}

// Properties common to all types
Expand Down Expand Up @@ -938,6 +949,10 @@ module ts {
baseArrayType: TypeReference; // Array<T> where T is best common type of element types
}

export interface UnionType extends ObjectType {
types: Type[]; // Constituent types
}

// Resolved object type
export interface ResolvedObjectType extends ObjectType {
members: SymbolTable; // Properties by name
Expand Down Expand Up @@ -970,6 +985,7 @@ module ts {
hasStringLiterals: boolean; // True if specialized
target?: Signature; // Instantiation target
mapper?: TypeMapper; // Instantiation mapper
unionSignatures?: Signature[]; // Underlying signatures of a union signature
erasedSignatureCache?: Signature; // Erased version of signature (deferred)
isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison
}
Expand All @@ -984,9 +1000,10 @@ module ts {
}

export interface InferenceContext {
typeParameters: TypeParameter[];
inferences: Type[][];
inferredTypes: Type[];
typeParameters: TypeParameter[]; // Type parameters for which inferences are made
inferenceCount: number; // Incremented for every inference made (whether new or not)
inferences: Type[][]; // Inferences made for each type parameter
inferredTypes: Type[]; // Inferred type for each type parameter
}

export interface DiagnosticMessage {
Expand Down
Loading