Skip to content

Commit 5b25524

Browse files
committed
Adding support for tuple types (e.g. [number, string])
1 parent c71e596 commit 5b25524

File tree

4 files changed

+189
-30
lines changed

4 files changed

+189
-30
lines changed

src/compiler/checker.ts

Lines changed: 102 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ module ts {
5656
var globalBooleanType: ObjectType;
5757
var globalRegExpType: ObjectType;
5858

59+
var tupleTypes: Map<TupleType> = {};
5960
var stringLiteralTypes: Map<StringLiteralType> = {};
6061

6162
var fullTypeCheck = false;
@@ -619,15 +620,13 @@ module ts {
619620
}
620621

621622
function isOptionalProperty(propertySymbol: Symbol): boolean {
622-
if (propertySymbol.flags & SymbolFlags.Prototype) {
623-
return false;
624-
}
625623
// class C {
626624
// constructor(public x?) { }
627625
// }
628626
//
629627
// x is an optional parameter, but it is a required property.
630-
return (propertySymbol.valueDeclaration.flags & NodeFlags.QuestionMark) && propertySymbol.valueDeclaration.kind !== SyntaxKind.Parameter;
628+
return propertySymbol.valueDeclaration && propertySymbol.valueDeclaration.flags & NodeFlags.QuestionMark &&
629+
propertySymbol.valueDeclaration.kind !== SyntaxKind.Parameter;
631630
}
632631

633632
function forEachSymbolTableInScope<T>(enclosingDeclaration: Node, callback: (symbolTable: SymbolTable) => T): T {
@@ -843,6 +842,9 @@ module ts {
843842
else if (type.flags & (TypeFlags.Class | TypeFlags.Interface | TypeFlags.Enum | TypeFlags.TypeParameter)) {
844843
writer.writeSymbol(type.symbol, enclosingDeclaration, SymbolFlags.Type);
845844
}
845+
else if (type.flags & TypeFlags.Tuple) {
846+
writeTupleType(<TupleType>type);
847+
}
846848
else if (type.flags & TypeFlags.Anonymous) {
847849
writeAnonymousType(<ObjectType>type, allowFunctionOrConstructorTypeLiteral);
848850
}
@@ -855,6 +857,15 @@ module ts {
855857
}
856858
}
857859

860+
function writeTypeList(types: Type[]) {
861+
for (var i = 0; i < types.length; i++) {
862+
if (i > 0) {
863+
writer.write(", ");
864+
}
865+
writeType(types[i], /*allowFunctionOrConstructorTypeLiteral*/ true);
866+
}
867+
}
868+
858869
function writeTypeReference(type: TypeReference) {
859870
if (type.target === globalArrayType && !(flags & TypeFormatFlags.WriteArrayAsGenericType)) {
860871
// If we are writing array element type the arrow style signatures are not allowed as
@@ -865,16 +876,17 @@ module ts {
865876
else {
866877
writer.writeSymbol(type.target.symbol, enclosingDeclaration, SymbolFlags.Type);
867878
writer.write("<");
868-
for (var i = 0; i < type.typeArguments.length; i++) {
869-
if (i > 0) {
870-
writer.write(", ");
871-
}
872-
writeType(type.typeArguments[i], /*allowFunctionOrConstructorTypeLiteral*/ true);
873-
}
879+
writeTypeList(type.typeArguments);
874880
writer.write(">");
875881
}
876882
}
877883

884+
function writeTupleType(type: TupleType) {
885+
writer.write("[");
886+
writeTypeList(type.elementTypes);
887+
writer.write("]");
888+
}
889+
878890
function writeAnonymousType(type: ObjectType, allowFunctionOrConstructorTypeLiteral: boolean) {
879891
// Always use 'typeof T' for type of class, enum, and module objects
880892
if (type.symbol && type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
@@ -1649,6 +1661,23 @@ module ts {
16491661
return [createSignature(undefined, classType.typeParameters, emptyArray, classType, 0, false, false)];
16501662
}
16511663

1664+
function createTupleTypeMemberSymbols(memberTypes: Type[]): SymbolTable {
1665+
var members: SymbolTable = {};
1666+
for (var i = 0; i < memberTypes.length; i++) {
1667+
var symbol = <TransientSymbol>createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "" + i);
1668+
symbol.type = memberTypes[i];
1669+
members[i] = symbol;
1670+
}
1671+
return members;
1672+
}
1673+
1674+
function resolveTupleTypeMembers(type: TupleType) {
1675+
var arrayType = resolveObjectTypeMembers(createArrayType(getBestCommonType(type.elementTypes)));
1676+
var members = createTupleTypeMemberSymbols(type.elementTypes);
1677+
addInheritedMembers(members, arrayType.properties);
1678+
setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType);
1679+
}
1680+
16521681
function resolveAnonymousTypeMembers(type: ObjectType) {
16531682
var symbol = type.symbol;
16541683
var members = emptySymbols;
@@ -1682,6 +1711,9 @@ module ts {
16821711
else if (type.flags & TypeFlags.Anonymous) {
16831712
resolveAnonymousTypeMembers(<ObjectType>type);
16841713
}
1714+
else if (type.flags & TypeFlags.Tuple) {
1715+
resolveTupleTypeMembers(<TupleType>type);
1716+
}
16851717
else {
16861718
resolveTypeReferenceMembers(<TypeReference>type);
16871719
}
@@ -2123,6 +2155,24 @@ module ts {
21232155
return links.resolvedType;
21242156
}
21252157

2158+
function createTupleType(elementTypes: Type[]) {
2159+
var id = getTypeListId(elementTypes);
2160+
var type = tupleTypes[id];
2161+
if (!type) {
2162+
type = tupleTypes[id] = <TupleType>createObjectType(TypeFlags.Tuple);
2163+
type.elementTypes = elementTypes;
2164+
}
2165+
return type;
2166+
}
2167+
2168+
function getTypeFromTupleTypeNode(node: TupleTypeNode): Type {
2169+
var links = getNodeLinks(node);
2170+
if (!links.resolvedType) {
2171+
links.resolvedType = createTupleType(map(node.elementTypes, t => getTypeFromTypeNode(t)));
2172+
}
2173+
return links.resolvedType;
2174+
}
2175+
21262176
function getTypeFromTypeLiteralNode(node: TypeLiteralNode): Type {
21272177
var links = getNodeLinks(node);
21282178
if (!links.resolvedType) {
@@ -2172,6 +2222,8 @@ module ts {
21722222
return getTypeFromTypeQueryNode(<TypeQueryNode>node);
21732223
case SyntaxKind.ArrayType:
21742224
return getTypeFromArrayTypeNode(<ArrayTypeNode>node);
2225+
case SyntaxKind.TupleType:
2226+
return getTypeFromTupleTypeNode(<TupleTypeNode>node);
21752227
case SyntaxKind.TypeLiteral:
21762228
return getTypeFromTypeLiteralNode(<TypeLiteralNode>node);
21772229
default:
@@ -2327,6 +2379,9 @@ module ts {
23272379
if (type.flags & TypeFlags.Reference) {
23282380
return createTypeReference((<TypeReference>type).target, instantiateList((<TypeReference>type).typeArguments, mapper, instantiateType));
23292381
}
2382+
if (type.flags & TypeFlags.Tuple) {
2383+
return createTupleType(instantiateList((<TupleType>type).elementTypes, mapper, instantiateType));
2384+
}
23302385
}
23312386
return type;
23322387
}
@@ -3015,20 +3070,16 @@ module ts {
30153070
while (isArrayType(type)) {
30163071
type = (<GenericType>type).typeArguments[0];
30173072
}
3018-
30193073
return type;
30203074
}
30213075

30223076
function getWidenedTypeOfArrayLiteral(type: Type): Type {
30233077
var elementType = (<TypeReference>type).typeArguments[0];
30243078
var widenedType = getWidenedType(elementType);
3025-
30263079
type = elementType !== widenedType ? createArrayType(widenedType) : type;
3027-
30283080
return type;
30293081
}
30303082

3031-
/* If we are widening on a literal, then we may need to the 'node' parameter for reporting purposes */
30323083
function getWidenedType(type: Type): Type {
30333084
if (type.flags & (TypeFlags.Undefined | TypeFlags.Null)) {
30343085
return anyType;
@@ -3125,9 +3176,9 @@ module ts {
31253176
inferFromTypes(sourceTypes[i], targetTypes[i]);
31263177
}
31273178
}
3128-
else if (source.flags & TypeFlags.ObjectType && (target.flags & TypeFlags.Reference || (target.flags & TypeFlags.Anonymous) &&
3129-
target.symbol && target.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral))) {
3130-
// If source is an object type, and target is a type reference, the type of a method, or a type literal, infer from members
3179+
else if (source.flags & TypeFlags.ObjectType && (target.flags & (TypeFlags.Reference | TypeFlags.Tuple) ||
3180+
(target.flags & TypeFlags.Anonymous) && target.symbol && target.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral))) {
3181+
// If source is an object type, and target is a type reference, a tuple type, the type of a method, or a type literal, infer from members
31313182
if (!isInProcess(source, target) && isWithinDepthLimit(source, sourceStack) && isWithinDepthLimit(target, targetStack)) {
31323183
if (depth === 0) {
31333184
sourceStack = [];
@@ -3574,7 +3625,19 @@ module ts {
35743625
function getContextualTypeForElementExpression(node: Expression): Type {
35753626
var arrayLiteral = <ArrayLiteral>node.parent;
35763627
var type = getContextualType(arrayLiteral);
3577-
return type ? getIndexTypeOfType(type, IndexKind.Number) : undefined;
3628+
if (type) {
3629+
if (type.flags & TypeFlags.Tuple) {
3630+
var index = indexOf(arrayLiteral.elements, node);
3631+
if (index >= 0) {
3632+
var prop = getPropertyOfType(type, "" + index);
3633+
if (prop) {
3634+
return getTypeOfSymbol(prop);
3635+
}
3636+
}
3637+
}
3638+
return getIndexTypeOfType(type, IndexKind.Number);
3639+
}
3640+
return undefined;
35783641
}
35793642

35803643
function getContextualTypeForConditionalOperand(node: Expression): Type {
@@ -3633,17 +3696,23 @@ module ts {
36333696
}
36343697

36353698
function checkArrayLiteral(node: ArrayLiteral, contextualMapper?: TypeMapper): Type {
3699+
var contextualType = getContextualType(node);
3700+
var isTupleLiteral = contextualType && (contextualType.flags & TypeFlags.Tuple) !== 0;
36363701
var elementTypes: Type[] = [];
36373702
forEach(node.elements, element => {
3638-
if (element.kind !== SyntaxKind.OmittedExpression) {
3639-
var type = checkExpression(element, contextualMapper);
3640-
if (!contains(elementTypes, type)) elementTypes.push(type);
3703+
var type = element.kind !== SyntaxKind.OmittedExpression ? checkExpression(element, contextualMapper) : undefinedType;
3704+
if (isTupleLiteral || !contains(elementTypes, type)) {
3705+
elementTypes.push(type);
36413706
}
36423707
});
3643-
var contextualType = isInferentialContext(contextualMapper) ? undefined : getContextualType(node);
3644-
var contextualElementType = contextualType && getIndexTypeOfType(contextualType, IndexKind.Number);
3708+
if (isTupleLiteral) {
3709+
return createTupleType(elementTypes);
3710+
}
3711+
var contextualElementType = contextualType && !isInferentialContext(contextualMapper) ? getIndexTypeOfType(contextualType, IndexKind.Number) : undefined;
36453712
var elementType = getBestCommonType(elementTypes, contextualElementType, true);
3646-
if (!elementType) elementType = elementTypes.length ? emptyObjectType : undefinedType;
3713+
if (!elementType) {
3714+
elementType = elementTypes.length ? emptyObjectType : undefinedType;
3715+
}
36473716
return createArrayType(elementType);
36483717
}
36493718

@@ -3711,11 +3780,11 @@ module ts {
37113780
}
37123781

37133782
function getDeclarationKindFromSymbol(s: Symbol) {
3714-
return s.flags & SymbolFlags.Prototype ? SyntaxKind.Property : s.valueDeclaration.kind;
3783+
return s.valueDeclaration ? s.valueDeclaration.kind : SyntaxKind.Property;
37153784
}
37163785

37173786
function getDeclarationFlagsFromSymbol(s: Symbol) {
3718-
return s.flags & SymbolFlags.Prototype ? NodeFlags.Public | NodeFlags.Static : s.valueDeclaration.flags;
3787+
return s.valueDeclaration ? s.valueDeclaration.flags : s.flags & SymbolFlags.Prototype ? NodeFlags.Public | NodeFlags.Static : 0;
37193788
}
37203789

37213790
function checkPropertyAccess(node: PropertyAccess) {
@@ -4991,7 +5060,11 @@ module ts {
49915060
}
49925061

49935062
function checkArrayType(node: ArrayTypeNode) {
4994-
getTypeFromArrayTypeNode(node);
5063+
checkSourceElement(node.elementType);
5064+
}
5065+
5066+
function checkTupleType(node: TupleTypeNode) {
5067+
forEach(node.elementTypes, checkSourceElement);
49955068
}
49965069

49975070
function isPrivateWithinAmbient(node: Node): boolean {
@@ -6197,6 +6270,8 @@ module ts {
61976270
return checkTypeLiteral(<TypeLiteralNode>node);
61986271
case SyntaxKind.ArrayType:
61996272
return checkArrayType(<ArrayTypeNode>node);
6273+
case SyntaxKind.TupleType:
6274+
return checkTupleType(<TupleTypeNode>node);
62006275
case SyntaxKind.FunctionDeclaration:
62016276
return checkFunctionDeclaration(<FunctionDeclaration>node);
62026277
case SyntaxKind.Block:

src/compiler/parser.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ module ts {
201201
return children((<TypeLiteralNode>node).members);
202202
case SyntaxKind.ArrayType:
203203
return child((<ArrayTypeNode>node).elementType);
204+
case SyntaxKind.TupleType:
205+
return children((<TupleTypeNode>node).elementTypes);
204206
case SyntaxKind.ArrayLiteral:
205207
return children((<ArrayLiteral>node).elements);
206208
case SyntaxKind.ObjectLiteral:
@@ -352,6 +354,7 @@ module ts {
352354
Parameters, // Parameters in parameter list
353355
TypeParameters, // Type parameters in type parameter list
354356
TypeArguments, // Type arguments in type argument list
357+
TupleElementTypes, // Element types in tuple element type list
355358
Count // Number of parsing contexts
356359
}
357360

@@ -379,6 +382,7 @@ module ts {
379382
case ParsingContext.Parameters: return Diagnostics.Parameter_declaration_expected;
380383
case ParsingContext.TypeParameters: return Diagnostics.Type_parameter_declaration_expected;
381384
case ParsingContext.TypeArguments: return Diagnostics.Type_argument_expected;
385+
case ParsingContext.TupleElementTypes: return Diagnostics.Type_expected;
382386
}
383387
};
384388

@@ -837,6 +841,7 @@ module ts {
837841
case ParsingContext.Parameters:
838842
return isParameter();
839843
case ParsingContext.TypeArguments:
844+
case ParsingContext.TupleElementTypes:
840845
return isType();
841846
}
842847

@@ -872,6 +877,7 @@ module ts {
872877
// Tokens other than ')' are here for better error recovery
873878
return token === SyntaxKind.CloseParenToken || token === SyntaxKind.SemicolonToken;
874879
case ParsingContext.ArrayLiteralMembers:
880+
case ParsingContext.TupleElementTypes:
875881
return token === SyntaxKind.CloseBracketToken;
876882
case ParsingContext.Parameters:
877883
// Tokens other than ')' and ']' (the latter for index signatures) are here for better error recovery
@@ -1390,6 +1396,17 @@ module ts {
13901396
return finishNode(node);
13911397
}
13921398

1399+
function parseTupleType(): TupleTypeNode {
1400+
var node = <TupleTypeNode>createNode(SyntaxKind.TupleType);
1401+
var startTokenPos = scanner.getTokenPos();
1402+
var startErrorCount = file.syntacticErrors.length;
1403+
node.elementTypes = parseBracketedList(ParsingContext.TupleElementTypes, parseType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken);
1404+
if (!node.elementTypes.length && file.syntacticErrors.length === startErrorCount) {
1405+
grammarErrorAtPos(startTokenPos, scanner.getStartPos() - startTokenPos, Diagnostics.Type_argument_list_cannot_be_empty);
1406+
}
1407+
return finishNode(node);
1408+
}
1409+
13931410
function parseFunctionType(signatureKind: SyntaxKind): TypeLiteralNode {
13941411
var node = <TypeLiteralNode>createNode(SyntaxKind.TypeLiteral);
13951412
var member = <SignatureDeclaration>createNode(signatureKind);
@@ -1420,6 +1437,8 @@ module ts {
14201437
return parseTypeQuery();
14211438
case SyntaxKind.OpenBraceToken:
14221439
return parseTypeLiteral();
1440+
case SyntaxKind.OpenBracketToken:
1441+
return parseTupleType();
14231442
case SyntaxKind.OpenParenToken:
14241443
case SyntaxKind.LessThanToken:
14251444
return parseFunctionType(SyntaxKind.CallSignature);
@@ -1443,6 +1462,7 @@ module ts {
14431462
case SyntaxKind.VoidKeyword:
14441463
case SyntaxKind.TypeOfKeyword:
14451464
case SyntaxKind.OpenBraceToken:
1465+
case SyntaxKind.OpenBracketToken:
14461466
case SyntaxKind.LessThanToken:
14471467
case SyntaxKind.NewKeyword:
14481468
return true;

src/compiler/types.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ module ts {
149149
TypeQuery,
150150
TypeLiteral,
151151
ArrayType,
152+
TupleType,
152153
// Expression
153154
ArrayLiteral,
154155
ObjectLiteral,
@@ -316,6 +317,10 @@ module ts {
316317
elementType: TypeNode;
317318
}
318319

320+
export interface TupleTypeNode extends TypeNode {
321+
elementTypes: NodeArray<TypeNode>;
322+
}
323+
319324
export interface StringLiteralTypeNode extends TypeNode {
320325
text: string;
321326
}
@@ -791,13 +796,14 @@ module ts {
791796
Class = 0x00000400, // Class
792797
Interface = 0x00000800, // Interface
793798
Reference = 0x00001000, // Generic type reference
794-
Anonymous = 0x00002000, // Anonymous
795-
FromSignature = 0x00004000, // Created for signature assignment check
799+
Tuple = 0x00002000, // Tuple
800+
Anonymous = 0x00004000, // Anonymous
801+
FromSignature = 0x00008000, // Created for signature assignment check
796802

797803
Intrinsic = Any | String | Number | Boolean | Void | Undefined | Null,
798804
StringLike = String | StringLiteral,
799805
NumberLike = Number | Enum,
800-
ObjectType = Class | Interface | Reference | Anonymous
806+
ObjectType = Class | Interface | Reference | Tuple | Anonymous
801807
}
802808

803809
// Properties common to all types
@@ -850,6 +856,11 @@ module ts {
850856
openReferenceChecks: Map<boolean>; // Open type reference check cache
851857
}
852858

859+
export interface TupleType extends ObjectType {
860+
elementTypes: Type[]; // Element types
861+
baseArrayType: TypeReference; // Array<T> where T is best common type of element types
862+
}
863+
853864
// Resolved object type
854865
export interface ResolvedObjectType extends ObjectType {
855866
members: SymbolTable; // Properties by name

0 commit comments

Comments
 (0)