From 5b255243c995baaff5a4011238034925be4acce9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 11 Aug 2014 12:21:26 -0700 Subject: [PATCH] Adding support for tuple types (e.g. [number, string]) --- src/compiler/checker.ts | 129 +++++++++++++++++++++++------ src/compiler/parser.ts | 20 +++++ src/compiler/types.ts | 17 +++- tests/cases/compiler/tupleTypes.ts | 53 ++++++++++++ 4 files changed, 189 insertions(+), 30 deletions(-) create mode 100644 tests/cases/compiler/tupleTypes.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 13781b271c9..3e8cd2cd435 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -56,6 +56,7 @@ module ts { var globalBooleanType: ObjectType; var globalRegExpType: ObjectType; + var tupleTypes: Map = {}; var stringLiteralTypes: Map = {}; var fullTypeCheck = false; @@ -619,15 +620,13 @@ module ts { } function isOptionalProperty(propertySymbol: Symbol): boolean { - if (propertySymbol.flags & SymbolFlags.Prototype) { - return false; - } // class C { // constructor(public x?) { } // } // // x is an optional parameter, but it is a required property. - return (propertySymbol.valueDeclaration.flags & NodeFlags.QuestionMark) && propertySymbol.valueDeclaration.kind !== SyntaxKind.Parameter; + return propertySymbol.valueDeclaration && propertySymbol.valueDeclaration.flags & NodeFlags.QuestionMark && + propertySymbol.valueDeclaration.kind !== SyntaxKind.Parameter; } function forEachSymbolTableInScope(enclosingDeclaration: Node, callback: (symbolTable: SymbolTable) => T): T { @@ -843,6 +842,9 @@ module ts { else if (type.flags & (TypeFlags.Class | TypeFlags.Interface | TypeFlags.Enum | TypeFlags.TypeParameter)) { writer.writeSymbol(type.symbol, enclosingDeclaration, SymbolFlags.Type); } + else if (type.flags & TypeFlags.Tuple) { + writeTupleType(type); + } else if (type.flags & TypeFlags.Anonymous) { writeAnonymousType(type, allowFunctionOrConstructorTypeLiteral); } @@ -855,6 +857,15 @@ module ts { } } + function writeTypeList(types: Type[]) { + for (var i = 0; i < types.length; i++) { + if (i > 0) { + writer.write(", "); + } + writeType(types[i], /*allowFunctionOrConstructorTypeLiteral*/ true); + } + } + function writeTypeReference(type: TypeReference) { if (type.target === globalArrayType && !(flags & TypeFormatFlags.WriteArrayAsGenericType)) { // If we are writing array element type the arrow style signatures are not allowed as @@ -865,16 +876,17 @@ module ts { else { writer.writeSymbol(type.target.symbol, enclosingDeclaration, SymbolFlags.Type); writer.write("<"); - for (var i = 0; i < type.typeArguments.length; i++) { - if (i > 0) { - writer.write(", "); - } - writeType(type.typeArguments[i], /*allowFunctionOrConstructorTypeLiteral*/ true); - } + writeTypeList(type.typeArguments); writer.write(">"); } } + function writeTupleType(type: TupleType) { + writer.write("["); + writeTypeList(type.elementTypes); + writer.write("]"); + } + function writeAnonymousType(type: ObjectType, allowFunctionOrConstructorTypeLiteral: boolean) { // Always use 'typeof T' for type of class, enum, and module objects if (type.symbol && type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) { @@ -1649,6 +1661,23 @@ module ts { return [createSignature(undefined, classType.typeParameters, emptyArray, classType, 0, false, false)]; } + function createTupleTypeMemberSymbols(memberTypes: Type[]): SymbolTable { + var members: SymbolTable = {}; + for (var i = 0; i < memberTypes.length; i++) { + var symbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "" + i); + symbol.type = memberTypes[i]; + members[i] = symbol; + } + return members; + } + + function resolveTupleTypeMembers(type: TupleType) { + var arrayType = resolveObjectTypeMembers(createArrayType(getBestCommonType(type.elementTypes))); + var members = createTupleTypeMemberSymbols(type.elementTypes); + addInheritedMembers(members, arrayType.properties); + setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType); + } + function resolveAnonymousTypeMembers(type: ObjectType) { var symbol = type.symbol; var members = emptySymbols; @@ -1682,6 +1711,9 @@ module ts { else if (type.flags & TypeFlags.Anonymous) { resolveAnonymousTypeMembers(type); } + else if (type.flags & TypeFlags.Tuple) { + resolveTupleTypeMembers(type); + } else { resolveTypeReferenceMembers(type); } @@ -2123,6 +2155,24 @@ module ts { return links.resolvedType; } + function createTupleType(elementTypes: Type[]) { + var id = getTypeListId(elementTypes); + var type = tupleTypes[id]; + if (!type) { + type = tupleTypes[id] = createObjectType(TypeFlags.Tuple); + type.elementTypes = elementTypes; + } + return type; + } + + function getTypeFromTupleTypeNode(node: TupleTypeNode): Type { + var links = getNodeLinks(node); + if (!links.resolvedType) { + links.resolvedType = createTupleType(map(node.elementTypes, t => getTypeFromTypeNode(t))); + } + return links.resolvedType; + } + function getTypeFromTypeLiteralNode(node: TypeLiteralNode): Type { var links = getNodeLinks(node); if (!links.resolvedType) { @@ -2172,6 +2222,8 @@ module ts { return getTypeFromTypeQueryNode(node); case SyntaxKind.ArrayType: return getTypeFromArrayTypeNode(node); + case SyntaxKind.TupleType: + return getTypeFromTupleTypeNode(node); case SyntaxKind.TypeLiteral: return getTypeFromTypeLiteralNode(node); default: @@ -2327,6 +2379,9 @@ module ts { if (type.flags & TypeFlags.Reference) { return createTypeReference((type).target, instantiateList((type).typeArguments, mapper, instantiateType)); } + if (type.flags & TypeFlags.Tuple) { + return createTupleType(instantiateList((type).elementTypes, mapper, instantiateType)); + } } return type; } @@ -3015,20 +3070,16 @@ module ts { while (isArrayType(type)) { type = (type).typeArguments[0]; } - return type; } function getWidenedTypeOfArrayLiteral(type: Type): Type { var elementType = (type).typeArguments[0]; var widenedType = getWidenedType(elementType); - type = elementType !== widenedType ? createArrayType(widenedType) : type; - return type; } - /* If we are widening on a literal, then we may need to the 'node' parameter for reporting purposes */ function getWidenedType(type: Type): Type { if (type.flags & (TypeFlags.Undefined | TypeFlags.Null)) { return anyType; @@ -3125,9 +3176,9 @@ module ts { inferFromTypes(sourceTypes[i], targetTypes[i]); } } - else if (source.flags & TypeFlags.ObjectType && (target.flags & TypeFlags.Reference || (target.flags & TypeFlags.Anonymous) && - target.symbol && target.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral))) { - // If source is an object type, and target is a type reference, the type of a method, or a type literal, infer from members + else if (source.flags & TypeFlags.ObjectType && (target.flags & (TypeFlags.Reference | TypeFlags.Tuple) || + (target.flags & TypeFlags.Anonymous) && target.symbol && target.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral))) { + // 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 if (!isInProcess(source, target) && isWithinDepthLimit(source, sourceStack) && isWithinDepthLimit(target, targetStack)) { if (depth === 0) { sourceStack = []; @@ -3574,7 +3625,19 @@ module ts { function getContextualTypeForElementExpression(node: Expression): Type { var arrayLiteral = node.parent; var type = getContextualType(arrayLiteral); - return type ? getIndexTypeOfType(type, IndexKind.Number) : undefined; + if (type) { + if (type.flags & TypeFlags.Tuple) { + var index = indexOf(arrayLiteral.elements, node); + if (index >= 0) { + var prop = getPropertyOfType(type, "" + index); + if (prop) { + return getTypeOfSymbol(prop); + } + } + } + return getIndexTypeOfType(type, IndexKind.Number); + } + return undefined; } function getContextualTypeForConditionalOperand(node: Expression): Type { @@ -3633,17 +3696,23 @@ module ts { } function checkArrayLiteral(node: ArrayLiteral, contextualMapper?: TypeMapper): Type { + var contextualType = getContextualType(node); + var isTupleLiteral = contextualType && (contextualType.flags & TypeFlags.Tuple) !== 0; var elementTypes: Type[] = []; forEach(node.elements, element => { - if (element.kind !== SyntaxKind.OmittedExpression) { - var type = checkExpression(element, contextualMapper); - if (!contains(elementTypes, type)) elementTypes.push(type); + var type = element.kind !== SyntaxKind.OmittedExpression ? checkExpression(element, contextualMapper) : undefinedType; + if (isTupleLiteral || !contains(elementTypes, type)) { + elementTypes.push(type); } }); - var contextualType = isInferentialContext(contextualMapper) ? undefined : getContextualType(node); - var contextualElementType = contextualType && getIndexTypeOfType(contextualType, IndexKind.Number); + if (isTupleLiteral) { + return createTupleType(elementTypes); + } + var contextualElementType = contextualType && !isInferentialContext(contextualMapper) ? getIndexTypeOfType(contextualType, IndexKind.Number) : undefined; var elementType = getBestCommonType(elementTypes, contextualElementType, true); - if (!elementType) elementType = elementTypes.length ? emptyObjectType : undefinedType; + if (!elementType) { + elementType = elementTypes.length ? emptyObjectType : undefinedType; + } return createArrayType(elementType); } @@ -3711,11 +3780,11 @@ module ts { } function getDeclarationKindFromSymbol(s: Symbol) { - return s.flags & SymbolFlags.Prototype ? SyntaxKind.Property : s.valueDeclaration.kind; + return s.valueDeclaration ? s.valueDeclaration.kind : SyntaxKind.Property; } function getDeclarationFlagsFromSymbol(s: Symbol) { - return s.flags & SymbolFlags.Prototype ? NodeFlags.Public | NodeFlags.Static : s.valueDeclaration.flags; + return s.valueDeclaration ? s.valueDeclaration.flags : s.flags & SymbolFlags.Prototype ? NodeFlags.Public | NodeFlags.Static : 0; } function checkPropertyAccess(node: PropertyAccess) { @@ -4991,7 +5060,11 @@ module ts { } function checkArrayType(node: ArrayTypeNode) { - getTypeFromArrayTypeNode(node); + checkSourceElement(node.elementType); + } + + function checkTupleType(node: TupleTypeNode) { + forEach(node.elementTypes, checkSourceElement); } function isPrivateWithinAmbient(node: Node): boolean { @@ -6197,6 +6270,8 @@ module ts { return checkTypeLiteral(node); case SyntaxKind.ArrayType: return checkArrayType(node); + case SyntaxKind.TupleType: + return checkTupleType(node); case SyntaxKind.FunctionDeclaration: return checkFunctionDeclaration(node); case SyntaxKind.Block: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 91a30143047..95182051e58 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -201,6 +201,8 @@ module ts { return children((node).members); case SyntaxKind.ArrayType: return child((node).elementType); + case SyntaxKind.TupleType: + return children((node).elementTypes); case SyntaxKind.ArrayLiteral: return children((node).elements); case SyntaxKind.ObjectLiteral: @@ -352,6 +354,7 @@ module ts { Parameters, // Parameters in parameter list TypeParameters, // Type parameters in type parameter list TypeArguments, // Type arguments in type argument list + TupleElementTypes, // Element types in tuple element type list Count // Number of parsing contexts } @@ -379,6 +382,7 @@ module ts { case ParsingContext.Parameters: return Diagnostics.Parameter_declaration_expected; case ParsingContext.TypeParameters: return Diagnostics.Type_parameter_declaration_expected; case ParsingContext.TypeArguments: return Diagnostics.Type_argument_expected; + case ParsingContext.TupleElementTypes: return Diagnostics.Type_expected; } }; @@ -837,6 +841,7 @@ module ts { case ParsingContext.Parameters: return isParameter(); case ParsingContext.TypeArguments: + case ParsingContext.TupleElementTypes: return isType(); } @@ -872,6 +877,7 @@ module ts { // Tokens other than ')' are here for better error recovery return token === SyntaxKind.CloseParenToken || token === SyntaxKind.SemicolonToken; case ParsingContext.ArrayLiteralMembers: + case ParsingContext.TupleElementTypes: return token === SyntaxKind.CloseBracketToken; case ParsingContext.Parameters: // Tokens other than ')' and ']' (the latter for index signatures) are here for better error recovery @@ -1390,6 +1396,17 @@ module ts { return finishNode(node); } + function parseTupleType(): TupleTypeNode { + var node = createNode(SyntaxKind.TupleType); + var startTokenPos = scanner.getTokenPos(); + var startErrorCount = file.syntacticErrors.length; + node.elementTypes = parseBracketedList(ParsingContext.TupleElementTypes, parseType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken); + if (!node.elementTypes.length && file.syntacticErrors.length === startErrorCount) { + grammarErrorAtPos(startTokenPos, scanner.getStartPos() - startTokenPos, Diagnostics.Type_argument_list_cannot_be_empty); + } + return finishNode(node); + } + function parseFunctionType(signatureKind: SyntaxKind): TypeLiteralNode { var node = createNode(SyntaxKind.TypeLiteral); var member = createNode(signatureKind); @@ -1420,6 +1437,8 @@ module ts { return parseTypeQuery(); case SyntaxKind.OpenBraceToken: return parseTypeLiteral(); + case SyntaxKind.OpenBracketToken: + return parseTupleType(); case SyntaxKind.OpenParenToken: case SyntaxKind.LessThanToken: return parseFunctionType(SyntaxKind.CallSignature); @@ -1443,6 +1462,7 @@ module ts { case SyntaxKind.VoidKeyword: case SyntaxKind.TypeOfKeyword: case SyntaxKind.OpenBraceToken: + case SyntaxKind.OpenBracketToken: case SyntaxKind.LessThanToken: case SyntaxKind.NewKeyword: return true; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5cb80687d80..272f5ec8a7f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -149,6 +149,7 @@ module ts { TypeQuery, TypeLiteral, ArrayType, + TupleType, // Expression ArrayLiteral, ObjectLiteral, @@ -316,6 +317,10 @@ module ts { elementType: TypeNode; } + export interface TupleTypeNode extends TypeNode { + elementTypes: NodeArray; + } + export interface StringLiteralTypeNode extends TypeNode { text: string; } @@ -791,13 +796,14 @@ module ts { Class = 0x00000400, // Class Interface = 0x00000800, // Interface Reference = 0x00001000, // Generic type reference - Anonymous = 0x00002000, // Anonymous - FromSignature = 0x00004000, // Created for signature assignment check + Tuple = 0x00002000, // Tuple + Anonymous = 0x00004000, // Anonymous + FromSignature = 0x00008000, // Created for signature assignment check Intrinsic = Any | String | Number | Boolean | Void | Undefined | Null, StringLike = String | StringLiteral, NumberLike = Number | Enum, - ObjectType = Class | Interface | Reference | Anonymous + ObjectType = Class | Interface | Reference | Tuple | Anonymous } // Properties common to all types @@ -850,6 +856,11 @@ module ts { openReferenceChecks: Map; // Open type reference check cache } + export interface TupleType extends ObjectType { + elementTypes: Type[]; // Element types + baseArrayType: TypeReference; // Array where T is best common type of element types + } + // Resolved object type export interface ResolvedObjectType extends ObjectType { members: SymbolTable; // Properties by name diff --git a/tests/cases/compiler/tupleTypes.ts b/tests/cases/compiler/tupleTypes.ts new file mode 100644 index 00000000000..3b22c284cb1 --- /dev/null +++ b/tests/cases/compiler/tupleTypes.ts @@ -0,0 +1,53 @@ +var v1: []; // Error +var v2: [number]; +var v3: [number, string]; +var v4: [number, [string, string]]; + +var t: [number, string]; +var t0 = t[0]; // number +var t0: number; +var t1 = t[1]; // string +var t1: string; +var t2 = t[2]; // {} +var t2: {}; + +t = []; // Error +t = [1]; // Error +t = [1, "hello"]; // Ok +t = ["hello", 1]; // Error +t = [1, "hello", 2]; // Ok + +var tf: [string, (x: string) => number] = ["hello", x => x.length]; + +declare function ff(a: T, b: [T, (x: T) => U]): U; +var ff1 = ff("hello", ["foo", x => x.length]); +var ff1: number; + +function tuple2(item0: T0, item1: T1): [T0, T1]{ + return [item0, item1]; +} + +var tt = tuple2(1, "string"); +var tt0 = tt[0]; +var tt0: number; +var tt1 = tt[1]; +var tt1: string; +var tt2 = tt[2]; +var tt2: {}; + +tt = tuple2(1, undefined); +tt = [1, undefined]; +tt = [undefined, undefined]; +tt = []; // Error + +var a: number[]; +var a1: [number, string]; +var a2: [number, number]; +var a3: [number, {}]; +a = a1; // Error +a = a2; +a = a3; // Error +a1 = a2; // Error +a1 = a3; // Error +a3 = a1; +a3 = a2;