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

Adding support for tuple types (e.g. [number, string]) #428

Merged
merged 10 commits into from
Sep 15, 2014
156 changes: 125 additions & 31 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

26 changes: 16 additions & 10 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ module ts {

export function contains<T>(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;
}
Expand All @@ -31,8 +30,7 @@ module ts {

export function indexOf<T>(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;
}
Expand All @@ -42,9 +40,8 @@ module ts {
}

export function filter<T>(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)) {
Expand All @@ -56,11 +53,9 @@ module ts {
}

export function map<T, U>(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]));
}
}
Expand All @@ -73,6 +68,17 @@ module ts {
return array1.concat(array2);
}

export function uniqueElements<T>(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++) {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}'." },
Expand Down
10 changes: 7 additions & 3 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1270,7 +1274,7 @@
"File change detected. Compiling...": {
"category": "Message",
"code": 6032
},
},
"STRING": {
"category": "Message",
"code": 6033
Expand Down Expand Up @@ -1306,8 +1310,8 @@
"Additional locations:": {
"category": "Message",
"code": 6041
},
"Compilation complete. Watching for file changes.": {
},
"Compilation complete. Watching for file changes.": {
"category": "Message",
"code": 6042
},
Expand Down
20 changes: 20 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ module ts {
return children((<TypeLiteralNode>node).members);
case SyntaxKind.ArrayType:
return child((<ArrayTypeNode>node).elementType);
case SyntaxKind.TupleType:
return children((<TupleTypeNode>node).elementTypes);
case SyntaxKind.ArrayLiteral:
return children((<ArrayLiteral>node).elements);
case SyntaxKind.ObjectLiteral:
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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;
}
};

Expand Down Expand Up @@ -837,6 +841,7 @@ module ts {
case ParsingContext.Parameters:
return isParameter();
case ParsingContext.TypeArguments:
case ParsingContext.TupleElementTypes:
return isType();
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1390,6 +1396,17 @@ module ts {
return finishNode(node);
}

function parseTupleType(): TupleTypeNode {
var node = <TupleTypeNode>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.A_tuple_type_element_list_cannot_be_empty);
}
return finishNode(node);
}

function parseFunctionType(signatureKind: SyntaxKind): TypeLiteralNode {
var node = <TypeLiteralNode>createNode(SyntaxKind.TypeLiteral);
var member = <SignatureDeclaration>createNode(signatureKind);
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down
17 changes: 14 additions & 3 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ module ts {
TypeQuery,
TypeLiteral,
ArrayType,
TupleType,
Copy link
Contributor

Choose a reason for hiding this comment

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

After you merge the changes from typeWriter (now in master), be sure to modify SyntaxKind.LastTypeNode accordingly.

// Expression
ArrayLiteral,
ObjectLiteral,
Expand Down Expand Up @@ -316,6 +317,10 @@ module ts {
elementType: TypeNode;
}

export interface TupleTypeNode extends TypeNode {
elementTypes: NodeArray<TypeNode>;
}

export interface StringLiteralTypeNode extends TypeNode {
text: string;
}
Expand Down Expand Up @@ -795,13 +800,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
Expand Down Expand Up @@ -854,6 +860,11 @@ module ts {
openReferenceChecks: Map<boolean>; // Open type reference check cache
}

export interface TupleType extends ObjectType {
elementTypes: Type[]; // Element types
baseArrayType: TypeReference; // Array<T> where T is best common type of element types
}

// Resolved object type
export interface ResolvedObjectType extends ObjectType {
members: SymbolTable; // Properties by name
Expand Down
87 changes: 87 additions & 0 deletions tests/baselines/reference/tupleTypes.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
==== tests/cases/compiler/tupleTypes.ts (9 errors) ====
var v1: []; // Error
~~
!!! A tuple type element 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<T, U>(a: T, b: [T, (x: T) => U]): U;
var ff1 = ff("hello", ["foo", x => x.length]);
var ff1: number;

function tuple2<T0, T1>(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 '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 '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]':
!!! 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;

4 changes: 3 additions & 1 deletion tests/baselines/reference/typeName1.errors.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
==== tests/cases/compiler/typeName1.ts (16 errors) ====
==== tests/cases/compiler/typeName1.ts (17 errors) ====
interface I {
k;
}
Expand Down Expand Up @@ -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 'length' 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; }; }':
Expand Down
53 changes: 53 additions & 0 deletions tests/cases/compiler/tupleTypes.ts
Original file line number Diff line number Diff line change
@@ -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<T, U>(a: T, b: [T, (x: T) => U]): U;
var ff1 = ff("hello", ["foo", x => x.length]);
var ff1: number;

function tuple2<T0, T1>(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;
Copy link
Member

Choose a reason for hiding this comment

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

Would be worth breaking this up into a few different files by concept (ex tupleTypeInference, etc).

We also need a lot more coverage here. We should have tests for things like

var x = 0;
var y = 3;
var tt = tuple2(1, "a");
var r1 = tt[x];
var r2 = tt[y];

Tuples crossing module boundaries, usages in class hierarchies with array types, assignability with nested tuples and nested arrays, etc.