Skip to content

Commit 61cd2a7

Browse files
committed
Introduce checkElementTypeOfArrayOrString for downlevel for..of type checking
1 parent 5b46f5f commit 61cd2a7

39 files changed

Lines changed: 685 additions & 954 deletions

src/compiler/checker.ts

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1869,7 +1869,11 @@ module ts {
18691869
return anyType;
18701870
}
18711871
if (declaration.parent.parent.kind === SyntaxKind.ForOfStatement) {
1872-
return getTypeForVariableDeclarationInForOfStatement(<ForOfStatement>declaration.parent.parent);
1872+
// checkRightHandSideOfForOf will return undefined if the for-of expression type was
1873+
// missing properties/signatures required to get its iteratedType (like
1874+
// [Symbol.iterator] or next). This may be because we accessed properties from anyType,
1875+
// or it may have led to an error inside getIteratedType.
1876+
return checkRightHandSideOfForOf((<ForOfStatement>declaration.parent.parent).expression) || anyType;
18731877
}
18741878
if (isBindingPattern(declaration.parent)) {
18751879
return getTypeForBindingElement(<BindingElement>declaration);
@@ -8781,16 +8785,15 @@ module ts {
87818785

87828786
// Check the LHS and RHS
87838787
// If the LHS is a declaration, just check it as a variable declaration, which will in turn check the RHS
8784-
// via getTypeForVariableDeclarationInForOfStatement.
8788+
// via checkRightHandSideOfForOf.
87858789
// If the LHS is an expression, check the LHS, as a destructuring assignment or as a reference.
87868790
// Then check that the RHS is assignable to it.
87878791
if (node.initializer.kind === SyntaxKind.VariableDeclarationList) {
87888792
checkForInOrForOfVariableDeclaration(node);
87898793
}
87908794
else {
87918795
var varExpr = <Expression>node.initializer;
8792-
var rightType = checkExpression(node.expression);
8793-
var iteratedType = checkIteratedType(rightType, node.expression);
8796+
var iteratedType = checkRightHandSideOfForOf(node.expression);
87948797

87958798
// There may be a destructuring assignment on the left side
87968799
if (varExpr.kind === SyntaxKind.ArrayLiteralExpression || varExpr.kind === SyntaxKind.ObjectLiteralExpression) {
@@ -8872,18 +8875,11 @@ module ts {
88728875
}
88738876
}
88748877

8875-
function getTypeForVariableDeclarationInForOfStatement(forOfStatement: ForOfStatement): Type {
8876-
// Temporarily return 'any' below ES6
8877-
if (languageVersion < ScriptTarget.ES6) {
8878-
return anyType;
8879-
}
8880-
8881-
// iteratedType will be undefined if the for-of expression type was missing properties/signatures
8882-
// required to get its iteratedType (like [Symbol.iterator] or next). This may be
8883-
// because we accessed properties from anyType, or it may have led to an error inside
8884-
// getIteratedType.
8885-
var expressionType = getTypeOfExpression(forOfStatement.expression);
8886-
return checkIteratedType(expressionType, forOfStatement.expression) || anyType;
8878+
function checkRightHandSideOfForOf(rhsExpression: Expression): Type {
8879+
var expressionType = getTypeOfExpression(rhsExpression);
8880+
return languageVersion >= ScriptTarget.ES6
8881+
? checkIteratedType(expressionType, rhsExpression)
8882+
: checkElementTypeOfArrayOrString(expressionType, rhsExpression);
88878883
}
88888884

88898885
/**
@@ -8982,6 +8978,45 @@ module ts {
89828978
}
89838979
}
89848980

8981+
function checkElementTypeOfArrayOrString(arrayOrStringType: Type, expressionForError: Expression): Type {
8982+
Debug.assert(languageVersion < ScriptTarget.ES6);
8983+
var isJustString = allConstituentTypesHaveKind(arrayOrStringType, TypeFlags.StringLike);
8984+
8985+
// Check isJustString because removeTypesFromUnionType will only remove types if it doesn't result
8986+
// in an emptyObjectType. In this case, we actually do want the emptyObjectType.
8987+
var arrayType = isJustString ? emptyObjectType : removeTypesFromUnionType(arrayOrStringType, TypeFlags.StringLike, /*isTypeOfKind*/ true);
8988+
var hasStringConstituent = arrayOrStringType !== emptyObjectType && arrayOrStringType !== arrayType;
8989+
8990+
var reportedError = false;
8991+
if (hasStringConstituent && languageVersion < ScriptTarget.ES5) {
8992+
error(expressionForError, Diagnostics.Using_a_string_in_a_for_of_statement_is_only_supported_in_ECMAScript_5_and_higher);
8993+
reportedError = true;
8994+
}
8995+
8996+
if (isJustString) {
8997+
return stringType;
8998+
}
8999+
9000+
if (!isArrayLikeType(arrayType)) {
9001+
if (!reportedError) {
9002+
error(expressionForError, Diagnostics.Type_0_is_not_an_array_type, typeToString(arrayType));
9003+
}
9004+
return hasStringConstituent ? stringType : unknownType;
9005+
}
9006+
9007+
var arrayElementType = getIndexTypeOfType(arrayType, IndexKind.Number) || unknownType;
9008+
if (hasStringConstituent) {
9009+
// This is just an optimization for the case where arrayOrStringType is string | string[]
9010+
if (arrayElementType.flags & TypeFlags.StringLike) {
9011+
return stringType;
9012+
}
9013+
9014+
return getUnionType([arrayElementType, stringType]);
9015+
}
9016+
9017+
return arrayElementType;
9018+
}
9019+
89859020
function checkBreakOrContinueStatement(node: BreakOrContinueStatement) {
89869021
// Grammar checking
89879022
checkGrammarStatementInAmbientContext(node) || checkGrammarBreakOrContinueStatement(node);

src/compiler/diagnosticInformationMap.generated.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ module ts {
338338
The_left_hand_side_of_a_for_in_statement_cannot_be_a_destructuring_pattern: { code: 2491, category: DiagnosticCategory.Error, key: "The left-hand side of a 'for...in' statement cannot be a destructuring pattern." },
339339
Cannot_redeclare_identifier_0_in_catch_clause: { code: 2492, category: DiagnosticCategory.Error, key: "Cannot redeclare identifier '{0}' in catch clause" },
340340
Tuple_type_0_with_length_1_cannot_be_assigned_to_tuple_with_length_2: { code: 2493, category: DiagnosticCategory.Error, key: "Tuple type '{0}' with length '{1}' cannot be assigned to tuple with length '{2}'." },
341+
Using_a_string_in_a_for_of_statement_is_only_supported_in_ECMAScript_5_and_higher: { code: 2494, category: DiagnosticCategory.Error, key: "Using a string in a 'for...of' statement is only supported in ECMAScript 5 and higher." },
341342
Import_declaration_0_is_using_private_name_1: { code: 4000, category: DiagnosticCategory.Error, key: "Import declaration '{0}' is using private name '{1}'." },
342343
Type_parameter_0_of_exported_class_has_or_is_using_private_name_1: { code: 4002, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported class has or is using private name '{1}'." },
343344
Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1: { code: 4004, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported interface has or is using private name '{1}'." },

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,6 +1343,10 @@
13431343
"category": "Error",
13441344
"code": 2493
13451345
},
1346+
"Using a string in a 'for...of' statement is only supported in ECMAScript 5 and higher.": {
1347+
"category": "Error",
1348+
"code": 2494
1349+
},
13461350

13471351
"Import declaration '{0}' is using private name '{1}'.": {
13481352
"category": "Error",
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== tests/cases/conformance/statements/for-ofStatements/ES5For-of10.ts ===
2+
function foo() {
3+
>foo : () => { x: number; }
4+
5+
return { x: 0 };
6+
>{ x: 0 } : { x: number; }
7+
>x : number
8+
}
9+
for (foo().x of []) {
10+
>foo().x : number
11+
>foo() : { x: number; }
12+
>foo : () => { x: number; }
13+
>x : number
14+
>[] : undefined[]
15+
16+
for (foo().x of [])
17+
>foo().x : number
18+
>foo() : { x: number; }
19+
>foo : () => { x: number; }
20+
>x : number
21+
>[] : undefined[]
22+
23+
var p = foo().x;
24+
>p : number
25+
>foo().x : number
26+
>foo() : { x: number; }
27+
>foo : () => { x: number; }
28+
>x : number
29+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
=== tests/cases/conformance/statements/for-ofStatements/ES5For-of11.ts ===
2+
var v;
3+
>v : any
4+
5+
for (v of []) { }
6+
>v : any
7+
>[] : undefined[]
8+
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
tests/cases/conformance/statements/for-ofStatements/ES5For-of12.ts(1,1): error TS2482: 'for...of' statements are only available when targeting ECMAScript 6 or higher.
1+
tests/cases/conformance/statements/for-ofStatements/ES5For-of12.ts(1,6): error TS2461: Type 'undefined' is not an array type.
22

33

44
==== tests/cases/conformance/statements/for-ofStatements/ES5For-of12.ts (1 errors) ====
55
for ([""] of []) { }
6-
~~~
7-
!!! error TS2482: 'for...of' statements are only available when targeting ECMAScript 6 or higher.
6+
~~~~
7+
!!! error TS2461: Type 'undefined' is not an array type.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
=== tests/cases/conformance/statements/for-ofStatements/ES5For-of13.ts ===
2+
for (let v of ['a', 'b', 'c']) {
3+
>v : string
4+
>['a', 'b', 'c'] : string[]
5+
6+
var x = v;
7+
>x : string
8+
>v : string
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
=== tests/cases/conformance/statements/for-ofStatements/ES5For-of24.ts ===
2+
var a = [1, 2, 3];
3+
>a : number[]
4+
>[1, 2, 3] : number[]
5+
6+
for (var v of a) {
7+
>v : number
8+
>a : number[]
9+
10+
let a = 0;
11+
>a : number
12+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
=== tests/cases/conformance/statements/for-ofStatements/ES5For-of25.ts ===
2+
var a = [1, 2, 3];
3+
>a : number[]
4+
>[1, 2, 3] : number[]
5+
6+
for (var v of a) {
7+
>v : number
8+
>a : number[]
9+
10+
v;
11+
>v : number
12+
13+
a;
14+
>a : number[]
15+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
tests/cases/conformance/statements/for-ofStatements/ES5For-of26.ts(1,10): error TS2461: Type 'number' is not an array type.
2+
3+
4+
==== tests/cases/conformance/statements/for-ofStatements/ES5For-of26.ts (1 errors) ====
5+
for (var [a = 0, b = 1] of [2, 3]) {
6+
~~~~~~~~~~~~~~
7+
!!! error TS2461: Type 'number' is not an array type.
8+
a;
9+
b;
10+
}

0 commit comments

Comments
 (0)