From 5b255243c995baaff5a4011238034925be4acce9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 11 Aug 2014 12:21:26 -0700 Subject: [PATCH 1/7] 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 13781b271c99a..3e8cd2cd435e2 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 91a3014304728..95182051e587f 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 5cb80687d803d..272f5ec8a7fcb 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 0000000000000..3b22c284cb1c8 --- /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; From 3b1dbadb88b8d30c741c4a1cf482e46a64dfb5cc Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 11 Aug 2014 14:52:32 -0700 Subject: [PATCH 2/7] Addressing CR feedback, adding baselines. --- src/compiler/checker.ts | 7 +- .../baselines/reference/tupleTypes.errors.txt | 89 +++++++++++++++++++ .../baselines/reference/typeName1.errors.txt | 4 +- 3 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 tests/baselines/reference/tupleTypes.errors.txt diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3e8cd2cd435e2..8a1b9872411d7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -625,7 +625,8 @@ module ts { // } // // x is an optional parameter, but it is a required property. - return propertySymbol.valueDeclaration && propertySymbol.valueDeclaration.flags & NodeFlags.QuestionMark && + return propertySymbol.valueDeclaration && + propertySymbol.valueDeclaration.flags & NodeFlags.QuestionMark && propertySymbol.valueDeclaration.kind !== SyntaxKind.Parameter; } @@ -2069,7 +2070,7 @@ module ts { if (type.flags & (TypeFlags.Class | TypeFlags.Interface) && type.flags & TypeFlags.Reference) { var typeParameters = (type).typeParameters; if (node.typeArguments && node.typeArguments.length === typeParameters.length) { - type = createTypeReference(type, map(node.typeArguments, t => getTypeFromTypeNode(t))); + type = createTypeReference(type, map(node.typeArguments, getTypeFromTypeNode)); } else { error(node, Diagnostics.Generic_type_0_requires_1_type_argument_s, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType), typeParameters.length); @@ -2168,7 +2169,7 @@ module ts { function getTypeFromTupleTypeNode(node: TupleTypeNode): Type { var links = getNodeLinks(node); if (!links.resolvedType) { - links.resolvedType = createTupleType(map(node.elementTypes, t => getTypeFromTypeNode(t))); + links.resolvedType = createTupleType(map(node.elementTypes, getTypeFromTypeNode)); } return links.resolvedType; } diff --git a/tests/baselines/reference/tupleTypes.errors.txt b/tests/baselines/reference/tupleTypes.errors.txt new file mode 100644 index 0000000000000..b10a098b9a705 --- /dev/null +++ b/tests/baselines/reference/tupleTypes.errors.txt @@ -0,0 +1,89 @@ +==== tests/cases/compiler/tupleTypes.ts (9 errors) ==== + var v1: []; // Error + ~~ +!!! Type argument list cannot be empty. + 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 + ~ +!!! Type '[]' is not assignable to type '[number, string]': +!!! Property '0' is missing in type '[]'. + t = [1]; // Error + ~ +!!! Type '[number]' is not assignable to type '[number, string]': +!!! Property '1' is missing in type '[number]'. + t = [1, "hello"]; // Ok + t = ["hello", 1]; // Error + ~ +!!! Type '[string, number]' is not assignable to type '[number, string]': +!!! Types of property '0' are incompatible: +!!! Type 'string' is not assignable to type 'number'. + 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 + ~~ +!!! Type '[]' is not assignable to type '[number, string]'. + + var a: number[]; + var a1: [number, string]; + var a2: [number, number]; + var a3: [number, {}]; + a = a1; // Error + ~ +!!! Type '[number, string]' is not assignable to type 'number[]': +!!! Types of property 'concat' are incompatible: +!!! Type '{ (...items: U[]): {}[]; (...items: {}[]): {}[]; }' is not assignable to type '{ (...items: U[]): number[]; (...items: number[]): number[]; }': +!!! Type '{}[]' is not assignable to type 'number[]': +!!! Type '{}' is not assignable to type 'number'. + a = a2; + a = a3; // Error + ~ +!!! Type '[number, {}]' is not assignable to type 'number[]': +!!! Types of property 'concat' are incompatible: +!!! Type '{ (...items: U[]): {}[]; (...items: {}[]): {}[]; }' is not assignable to type '{ (...items: U[]): number[]; (...items: number[]): number[]; }': +!!! Type '{}[]' is not assignable to type 'number[]': +!!! Type '{}' is not assignable to type 'number'. + a1 = a2; // Error + ~~ +!!! Type '[number, number]' is not assignable to type '[number, string]': +!!! Types of property '1' are incompatible: +!!! Type 'number' is not assignable to type 'string'. + a1 = a3; // Error + ~~ +!!! Type '[number, {}]' is not assignable to type '[number, string]': +!!! Types of property '1' are incompatible: +!!! Type '{}' is not assignable to type 'string'. + a3 = a1; + a3 = a2; + \ No newline at end of file diff --git a/tests/baselines/reference/typeName1.errors.txt b/tests/baselines/reference/typeName1.errors.txt index 26a744f7d2d18..ac729f4724ad5 100644 --- a/tests/baselines/reference/typeName1.errors.txt +++ b/tests/baselines/reference/typeName1.errors.txt @@ -1,4 +1,4 @@ -==== tests/cases/compiler/typeName1.ts (16 errors) ==== +==== tests/cases/compiler/typeName1.ts (17 errors) ==== interface I { k; } @@ -55,6 +55,8 @@ ~~~ !!! Type 'number' is not assignable to type '{ z: I; x: boolean; y: (s: string) => boolean; w: { (): boolean; [x: string]: { x: any; y: any; }; [x: number]: { x: any; y: any; }; z: I; }; }[][]': !!! Property 'concat' is missing in type 'Number'. + ~~~~ +!!! Property 'z' of type 'I' is not assignable to string index type '{ x: any; y: any; }'. var x13:{ new(): number; new(n:number):number; x: string; w: {y: number;}; (): {}; } = 3; ~~~ !!! Type 'number' is not assignable to type '{ (): {}; new (): number; new (n: number): number; x: string; w: { y: number; }; }': From ef52312644b4830ad4cb6d5bd7c5273f25e292fb Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 13 Aug 2014 07:15:13 -0700 Subject: [PATCH 3/7] Addressing CR feedback. --- src/compiler/checker.ts | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 8a1b9872411d7..440408d1f914d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3549,6 +3549,10 @@ module ts { return undefined; } + // In a variable, parameter or property declaration with a type annotation, the contextual type of an initializer + // expression is the type of the variable, parameter or property. In a parameter declaration of a contextually + // typed function expression, the contextual type of an initializer expression is the contextual type of the + // parameter. function getContextualTypeForInitializerExpression(node: Expression): Type { var declaration = node.parent; if (node === declaration.initializer) { @@ -3580,6 +3584,7 @@ module ts { return undefined; } + // In a typed function call, an argument expression is contextually typed by the type of the corresponding parameter. function getContextualTypeForArgument(node: Expression): Type { var callExpression = node.parent; var argIndex = indexOf(callExpression.arguments, node); @@ -3594,11 +3599,14 @@ module ts { var binaryExpression = node.parent; var operator = binaryExpression.operator; if (operator >= SyntaxKind.FirstAssignment && operator <= SyntaxKind.LastAssignment) { + // In an assignment expression, the right operand is contextually typed by the type of the left operand. if (node === binaryExpression.right) { return checkExpression(binaryExpression.left); } } else if (operator === SyntaxKind.BarBarToken) { + // When an || expression has a contextual type, the operands are contextually typed by that type. When an || + // expression has no contextual type, the right operand is contextually typed by the type of the left operand. var type = getContextualType(binaryExpression); if (!type && node === binaryExpression.right) { type = checkExpression(binaryExpression.left); @@ -3608,6 +3616,9 @@ module ts { return undefined; } + // In an object literal contextually typed by a type T, the contextual type of a property assignment is the type of + // the matching property in T, if one exists. Otherwise, it is the type of the numeric index signature in T, if one + // exists. Otherwise, it is the type of the string index signature in T, if one exists. function getContextualTypeForPropertyExpression(node: Expression): Type { var declaration = node.parent; var objectLiteral = declaration.parent; @@ -3623,17 +3634,18 @@ module ts { return undefined; } + // In an array literal contextually typed by a type T, the contextual type of an element expression is the corresponding + // tuple element type in T, if one exists and T is a tuple type. Otherwise, it is the type of the numeric index signature + // in T, if one exists. function getContextualTypeForElementExpression(node: Expression): Type { var arrayLiteral = node.parent; var type = getContextualType(arrayLiteral); 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); - } + var prop = getPropertyOfType(type, "" + index); + if (prop) { + return getTypeOfSymbol(prop); } } return getIndexTypeOfType(type, IndexKind.Number); @@ -3641,11 +3653,14 @@ module ts { return undefined; } + // In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type. function getContextualTypeForConditionalOperand(node: Expression): Type { var conditional = node.parent; return node === conditional.whenTrue || node === conditional.whenFalse ? getContextualType(conditional) : undefined; } + // Return the contextual type for a given expression node. During overload resolution, a contextual type may temporarily + // be "pushed" onto a node using the contextualType property. function getContextualType(node: Expression): Type { if (node.contextualType) { return node.contextualType; @@ -3780,6 +3795,8 @@ module ts { } } + // If a symbol is a synthesized symbol with no value declaration, we assume it is a property. Example of this are the synthesized + // '.prototype' property as well as synthesized tuple index properties. function getDeclarationKindFromSymbol(s: Symbol) { return s.valueDeclaration ? s.valueDeclaration.kind : SyntaxKind.Property; } From 92b367741b0085b4abb5f6eddc10dd3708a80ce4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 13 Aug 2014 11:13:24 -0700 Subject: [PATCH 4/7] Adding error message for empty tuple types. --- src/compiler/diagnosticInformationMap.generated.ts | 1 + src/compiler/diagnosticMessages.json | 10 +++++++--- src/compiler/parser.ts | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index cc789bcca1151..0f13f5e683883 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -106,6 +106,7 @@ module ts { An_object_literal_cannot_have_property_and_accessor_with_the_same_name: { code: 1119, category: DiagnosticCategory.Error, key: "An object literal cannot have property and accessor with the same name." }, An_export_assignment_cannot_have_modifiers: { code: 1120, category: DiagnosticCategory.Error, key: "An export assignment cannot have modifiers." }, Octal_literals_are_not_allowed_in_strict_mode: { code: 1121, category: DiagnosticCategory.Error, key: "Octal literals are not allowed in strict mode." }, + A_tuple_type_element_list_cannot_be_empty: { code: 1122, category: DiagnosticCategory.Error, key: "A tuple type element list cannot be empty." }, Duplicate_identifier_0: { code: 2000, category: DiagnosticCategory.Error, key: "Duplicate identifier '{0}'." }, Extends_clause_of_exported_class_0_has_or_is_using_private_name_1: { code: 2018, category: DiagnosticCategory.Error, key: "Extends clause of exported class '{0}' has or is using private name '{1}'." }, Implements_clause_of_exported_class_0_has_or_is_using_private_name_1: { code: 2019, category: DiagnosticCategory.Error, key: "Implements clause of exported class '{0}' has or is using private name '{1}'." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index d60fd2e5c4fc7..b913f76f58c5a 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -416,6 +416,10 @@ "category": "Error", "code": 1121 }, + "A tuple type element list cannot be empty.": { + "category": "Error", + "code": 1122 + }, "Duplicate identifier '{0}'.": { "category": "Error", "code": 2000 @@ -1042,7 +1046,7 @@ "File change detected. Compiling...": { "category": "Message", "code": 6032 - }, + }, "STRING": { "category": "Message", "code": 6033 @@ -1078,8 +1082,8 @@ "Additional locations:": { "category": "Message", "code": 6041 - }, - "Compilation complete. Watching for file changes.": { + }, + "Compilation complete. Watching for file changes.": { "category": "Message", "code": 6042 }, diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 95182051e587f..4a24d1c026586 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -1402,7 +1402,7 @@ module ts { 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); + grammarErrorAtPos(startTokenPos, scanner.getStartPos() - startTokenPos, Diagnostics.A_tuple_type_element_list_cannot_be_empty); } return finishNode(node); } From f0b33b345ba6d7aae5bc2168fe6b3796f2ff5556 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 13 Aug 2014 15:45:43 -0700 Subject: [PATCH 5/7] Accepting new baselines. --- tests/baselines/reference/tupleTypes.errors.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/baselines/reference/tupleTypes.errors.txt b/tests/baselines/reference/tupleTypes.errors.txt index b10a098b9a705..69c6a9073ab28 100644 --- a/tests/baselines/reference/tupleTypes.errors.txt +++ b/tests/baselines/reference/tupleTypes.errors.txt @@ -1,7 +1,7 @@ ==== tests/cases/compiler/tupleTypes.ts (9 errors) ==== var v1: []; // Error ~~ -!!! Type argument list cannot be empty. +!!! A tuple type element list cannot be empty. var v2: [number]; var v3: [number, string]; var v4: [number, [string, string]]; From c0e802deb5e83232d05978aa0995771641a034ef Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 16 Aug 2014 11:15:31 -0700 Subject: [PATCH 6/7] Accepting new baselines after merge. --- tests/baselines/reference/tupleTypes.errors.txt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/baselines/reference/tupleTypes.errors.txt b/tests/baselines/reference/tupleTypes.errors.txt index 69c6a9073ab28..44cb22daf0f54 100644 --- a/tests/baselines/reference/tupleTypes.errors.txt +++ b/tests/baselines/reference/tupleTypes.errors.txt @@ -62,18 +62,16 @@ a = a1; // Error ~ !!! Type '[number, string]' is not assignable to type 'number[]': -!!! Types of property 'concat' are incompatible: -!!! Type '{ (...items: U[]): {}[]; (...items: {}[]): {}[]; }' is not assignable to type '{ (...items: U[]): number[]; (...items: number[]): number[]; }': -!!! Type '{}[]' is not assignable to type 'number[]': -!!! Type '{}' is not assignable to type 'number'. +!!! Types of property 'pop' are incompatible: +!!! Type '() => {}' is not assignable to type '() => number': +!!! Type '{}' is not assignable to type 'number'. a = a2; a = a3; // Error ~ !!! Type '[number, {}]' is not assignable to type 'number[]': -!!! Types of property 'concat' are incompatible: -!!! Type '{ (...items: U[]): {}[]; (...items: {}[]): {}[]; }' is not assignable to type '{ (...items: U[]): number[]; (...items: number[]): number[]; }': -!!! Type '{}[]' is not assignable to type 'number[]': -!!! Type '{}' is not assignable to type 'number'. +!!! Types of property 'pop' are incompatible: +!!! Type '() => {}' is not assignable to type '() => number': +!!! Type '{}' is not assignable to type 'number'. a1 = a2; // Error ~~ !!! Type '[number, number]' is not assignable to type '[number, string]': From 63b83e7c3fdd673ecdbfb6a4a533b0c963e55773 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 16 Aug 2014 12:06:51 -0700 Subject: [PATCH 7/7] Contextual typing of array literals is now based on the presence or absence of numerically named properties and doesn't directly test for tuple types. --- src/compiler/checker.ts | 35 ++++++++++--------- src/compiler/core.ts | 26 ++++++++------ .../baselines/reference/tupleTypes.errors.txt | 6 ++-- 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c91489b3d88f1..6a2fab1312b2b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3758,19 +3758,17 @@ module ts { return undefined; } - // In an array literal contextually typed by a type T, the contextual type of an element expression is the corresponding - // tuple element type in T, if one exists and T is a tuple type. Otherwise, it is the type of the numeric index signature - // in T, if one exists. + // In an array literal contextually typed by a type T, the contextual type of an element expression at index N is + // the type of the property with the numeric name N in T, if one exists. Otherwise, it is the type of the numeric + // index signature in T, if one exists. function getContextualTypeForElementExpression(node: Expression): Type { var arrayLiteral = node.parent; var type = getContextualType(arrayLiteral); if (type) { - if (type.flags & TypeFlags.Tuple) { - var index = indexOf(arrayLiteral.elements, node); - var prop = getPropertyOfType(type, "" + index); - if (prop) { - return getTypeOfSymbol(prop); - } + var index = indexOf(arrayLiteral.elements, node); + var prop = getPropertyOfType(type, "" + index); + if (prop) { + return getTypeOfSymbol(prop); } return getIndexTypeOfType(type, IndexKind.Number); } @@ -3837,21 +3835,24 @@ module ts { function checkArrayLiteral(node: ArrayLiteral, contextualMapper?: TypeMapper): Type { var contextualType = getContextualType(node); - var isTupleLiteral = contextualType && (contextualType.flags & TypeFlags.Tuple) !== 0; + var elements = node.elements; var elementTypes: Type[] = []; - forEach(node.elements, element => { - var type = element.kind !== SyntaxKind.OmittedExpression ? checkExpression(element, contextualMapper) : undefinedType; - if (isTupleLiteral || !contains(elementTypes, type)) { - elementTypes.push(type); + var isTupleLiteral: boolean = false; + for (var i = 0; i < elements.length; i++) { + if (contextualType && getPropertyOfType(contextualType, "" + i)) { + isTupleLiteral = true; } - }); + var element = elements[i]; + var type = element.kind !== SyntaxKind.OmittedExpression ? checkExpression(element, contextualMapper) : undefinedType; + elementTypes.push(type); + } if (isTupleLiteral) { return createTupleType(elementTypes); } var contextualElementType = contextualType && !isInferentialContext(contextualMapper) ? getIndexTypeOfType(contextualType, IndexKind.Number) : undefined; - var elementType = getBestCommonType(elementTypes, contextualElementType, true); + var elementType = getBestCommonType(uniqueElements(elementTypes), contextualElementType, true); if (!elementType) { - elementType = elementTypes.length ? emptyObjectType : undefinedType; + elementType = elements.length ? emptyObjectType : undefinedType; } return createArrayType(elementType); } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 8f6ac1ddfc8f5..221b6016ce583 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -19,8 +19,7 @@ module ts { export function contains(array: T[], value: T): boolean { if (array) { - var len = array.length; - for (var i = 0; i < len; i++) { + for (var i = 0, len = array.length; i < len; i++) { if (array[i] === value) { return true; } @@ -31,8 +30,7 @@ module ts { export function indexOf(array: T[], value: T): number { if (array) { - var len = array.length; - for (var i = 0; i < len; i++) { + for (var i = 0, len = array.length; i < len; i++) { if (array[i] === value) { return i; } @@ -42,9 +40,8 @@ module ts { } export function filter(array: T[], f: (x: T) => boolean): T[] { - var result: T[]; if (array) { - result = []; + var result: T[] = []; for (var i = 0, len = array.length; i < len; i++) { var item = array[i]; if (f(item)) { @@ -56,11 +53,9 @@ module ts { } export function map(array: T[], f: (x: T) => U): U[] { - var result: U[]; if (array) { - result = []; - var len = array.length; - for (var i = 0; i < len; i++) { + var result: U[] = []; + for (var i = 0, len = array.length; i < len; i++) { result.push(f(array[i])); } } @@ -73,6 +68,17 @@ module ts { return array1.concat(array2); } + export function uniqueElements(array: T[]): T[] { + if (array) { + var result: T[] = []; + for (var i = 0, len = array.length; i < len; i++) { + var item = array[i]; + if (!contains(result, item)) result.push(item); + } + } + return result; + } + export function sum(array: any[], prop: string): number { var result = 0; for (var i = 0; i < array.length; i++) { diff --git a/tests/baselines/reference/tupleTypes.errors.txt b/tests/baselines/reference/tupleTypes.errors.txt index 44cb22daf0f54..1e1ebd07292ae 100644 --- a/tests/baselines/reference/tupleTypes.errors.txt +++ b/tests/baselines/reference/tupleTypes.errors.txt @@ -16,8 +16,8 @@ t = []; // Error ~ -!!! Type '[]' is not assignable to type '[number, string]': -!!! Property '0' is missing in type '[]'. +!!! Type '{}[]' is not assignable to type '[number, string]': +!!! Property '0' is missing in type '{}[]'. t = [1]; // Error ~ !!! Type '[number]' is not assignable to type '[number, string]': @@ -53,7 +53,7 @@ tt = [undefined, undefined]; tt = []; // Error ~~ -!!! Type '[]' is not assignable to type '[number, string]'. +!!! Type '{}[]' is not assignable to type '[number, string]'. var a: number[]; var a1: [number, string];