From f3543b718f389cb6678bfc7b192b40c99ee0966e Mon Sep 17 00:00:00 2001 From: Jason Freeman Date: Wed, 25 Feb 2015 18:15:48 -0800 Subject: [PATCH] In ES6, an Iterable should contextually type an array literal --- src/compiler/checker.ts | 52 +++++++++++++------ tests/baselines/reference/for-of36.js | 11 ++++ tests/baselines/reference/for-of36.types | 12 +++++ tests/baselines/reference/for-of37.js | 11 ++++ tests/baselines/reference/for-of37.types | 15 ++++++ .../reference/iterableContextualTyping1.js | 5 ++ .../reference/iterableContextualTyping1.types | 12 +++++ .../es6/for-ofStatements/for-of36.ts | 5 ++ .../es6/for-ofStatements/for-of37.ts | 5 ++ .../iterableContextualTyping1.ts | 2 + 10 files changed, 115 insertions(+), 15 deletions(-) create mode 100644 tests/baselines/reference/for-of36.js create mode 100644 tests/baselines/reference/for-of36.types create mode 100644 tests/baselines/reference/for-of37.js create mode 100644 tests/baselines/reference/for-of37.types create mode 100644 tests/baselines/reference/iterableContextualTyping1.js create mode 100644 tests/baselines/reference/iterableContextualTyping1.types create mode 100644 tests/cases/conformance/es6/for-ofStatements/for-of36.ts create mode 100644 tests/cases/conformance/es6/for-ofStatements/for-of37.ts create mode 100644 tests/cases/conformance/expressions/contextualTyping/iterableContextualTyping1.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6df535bd322..2e5d61f860a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5461,7 +5461,9 @@ module ts { var type = getContextualType(arrayLiteral); if (type) { var index = indexOf(arrayLiteral.elements, node); - return getTypeOfPropertyOfContextualType(type, "" + index) || getIndexTypeOfContextualType(type, IndexKind.Number); + return getTypeOfPropertyOfContextualType(type, "" + index) + || getIndexTypeOfContextualType(type, IndexKind.Number) + || (languageVersion >= ScriptTarget.ES6 ? getIteratedType(type, /*expressionForError*/ undefined) : undefined); } return undefined; } @@ -8770,7 +8772,14 @@ module ts { checkReferenceExpression(varExpr, Diagnostics.Invalid_left_hand_side_in_for_of_statement, Diagnostics.The_left_hand_side_of_a_for_of_statement_cannot_be_a_previously_defined_constant); var rightType = checkExpression(node.expression); var iteratedType = getIteratedType(rightType, node.expression); - checkTypeAssignableTo(iteratedType, leftType, varExpr, /*headMessage*/ undefined); + + // iteratedType will be undefined if the rightType was missing properties/signatures + // required to get it's iteratedType (like [Symbol.iterator] or next). This may be + // because we accessed properties from anyType, or it may have led to an error inside + // getIteratedType. + if (iteratedType) { + checkTypeAssignableTo(iteratedType, leftType, varExpr, /*headMessage*/ undefined); + } } checkSourceElement(node.statement); @@ -8828,15 +8837,22 @@ module ts { return anyType; } - return getIteratedType(getTypeOfExpression(forOfStatement.expression), forOfStatement.expression); + // iteratedType will be undefined if the for-of expression type was missing properties/signatures + // required to get it's iteratedType (like [Symbol.iterator] or next). This may be + // because we accessed properties from anyType, or it may have led to an error inside + // getIteratedType. + return getIteratedType(getTypeOfExpression(forOfStatement.expression), forOfStatement.expression) || anyType; } + /** + * When expressionForError is undefined, it means we should not report any errors. + */ function getIteratedType(iterable: Type, expressionForError: Expression): Type { Debug.assert(languageVersion >= ScriptTarget.ES6); var iteratedType = getIteratedTypeSubroutine(iterable, expressionForError); // Now even though we have extracted the iteratedType, we will have to validate that the type // passed in is actually an Iterable. - if (iteratedType !== unknownType) { + if (expressionForError && iteratedType) { var completeIterableType = globalIterableType !== emptyObjectType ? createTypeReference(globalIterableType, [iteratedType]) : emptyObjectType; checkTypeAssignableTo(iterable, completeIterableType, expressionForError); } @@ -8845,7 +8861,7 @@ module ts { function getIteratedTypeSubroutine(iterable: Type, expressionForError: Expression) { if (allConstituentTypesHaveKind(iterable, TypeFlags.Any)) { - return iterable; // any or unknown + return undefined; } // We want to treat type as an iterable, and get the type it is an iterable of. The iterable @@ -8867,40 +8883,46 @@ module ts { // of signatures, we union the return types of all the signatures. var iteratorFunction = getTypeOfPropertyOfType(iterable, getPropertyNameForKnownSymbolName("iterator")); if (iteratorFunction && allConstituentTypesHaveKind(iteratorFunction, TypeFlags.Any)) { - return iteratorFunction; // any or unknown + return undefined; } var iteratorFunctionSignatures = iteratorFunction ? getSignaturesOfType(iteratorFunction, SignatureKind.Call) : emptyArray; if (iteratorFunctionSignatures.length === 0) { - error(expressionForError, Diagnostics.The_right_hand_side_of_a_for_of_statement_must_have_a_Symbol_iterator_method_that_returns_an_iterator); - return unknownType; + if (expressionForError) { + error(expressionForError, Diagnostics.The_right_hand_side_of_a_for_of_statement_must_have_a_Symbol_iterator_method_that_returns_an_iterator); + } + return undefined; } var iterator = getUnionType(map(iteratorFunctionSignatures, getReturnTypeOfSignature)); if (allConstituentTypesHaveKind(iterator, TypeFlags.Any)) { - return iterator; // any or unknown + return undefined; } var iteratorNextFunction = getTypeOfPropertyOfType(iterator, "next"); if (iteratorNextFunction && allConstituentTypesHaveKind(iteratorNextFunction, TypeFlags.Any)) { - return iteratorNextFunction; // any or unknown + return undefined; } var iteratorNextFunctionSignatures = iteratorNextFunction ? getSignaturesOfType(iteratorNextFunction, SignatureKind.Call) : emptyArray; if (iteratorNextFunctionSignatures.length === 0) { - error(expressionForError, Diagnostics.The_iterator_returned_by_the_right_hand_side_of_a_for_of_statement_must_have_a_next_method); - return unknownType; + if (expressionForError) { + error(expressionForError, Diagnostics.The_iterator_returned_by_the_right_hand_side_of_a_for_of_statement_must_have_a_next_method); + } + return undefined; } var iteratorNextResult = getUnionType(map(iteratorNextFunctionSignatures, getReturnTypeOfSignature)); if (allConstituentTypesHaveKind(iteratorNextResult, TypeFlags.Any)) { - return iteratorNextResult; // any or unknown + return undefined; } var iteratorNextValue = getTypeOfPropertyOfType(iteratorNextResult, "value"); if (!iteratorNextValue) { - error(expressionForError, Diagnostics.The_object_returned_by_the_next_method_of_the_iterator_must_have_a_value_property); - return unknownType; + if (expressionForError) { + error(expressionForError, Diagnostics.The_object_returned_by_the_next_method_of_the_iterator_must_have_a_value_property); + } + return undefined; } return iteratorNextValue; diff --git a/tests/baselines/reference/for-of36.js b/tests/baselines/reference/for-of36.js new file mode 100644 index 00000000000..14523695151 --- /dev/null +++ b/tests/baselines/reference/for-of36.js @@ -0,0 +1,11 @@ +//// [for-of36.ts] +var tuple: [string, boolean] = ["", true]; +for (var v of tuple) { + v; +} + +//// [for-of36.js] +var tuple = ["", true]; +for (var v of tuple) { + v; +} diff --git a/tests/baselines/reference/for-of36.types b/tests/baselines/reference/for-of36.types new file mode 100644 index 00000000000..da03367ba5d --- /dev/null +++ b/tests/baselines/reference/for-of36.types @@ -0,0 +1,12 @@ +=== tests/cases/conformance/es6/for-ofStatements/for-of36.ts === +var tuple: [string, boolean] = ["", true]; +>tuple : [string, boolean] +>["", true] : [string, boolean] + +for (var v of tuple) { +>v : string | boolean +>tuple : [string, boolean] + + v; +>v : string | boolean +} diff --git a/tests/baselines/reference/for-of37.js b/tests/baselines/reference/for-of37.js new file mode 100644 index 00000000000..472193e6cb5 --- /dev/null +++ b/tests/baselines/reference/for-of37.js @@ -0,0 +1,11 @@ +//// [for-of37.ts] +var map = new Map([["", true]]); +for (var v of map) { + v; +} + +//// [for-of37.js] +var map = new Map([["", true]]); +for (var v of map) { + v; +} diff --git a/tests/baselines/reference/for-of37.types b/tests/baselines/reference/for-of37.types new file mode 100644 index 00000000000..f137db79af0 --- /dev/null +++ b/tests/baselines/reference/for-of37.types @@ -0,0 +1,15 @@ +=== tests/cases/conformance/es6/for-ofStatements/for-of37.ts === +var map = new Map([["", true]]); +>map : Map +>new Map([["", true]]) : Map +>Map : MapConstructor +>[["", true]] : [string, boolean][] +>["", true] : [string, boolean] + +for (var v of map) { +>v : [string, boolean] +>map : Map + + v; +>v : [string, boolean] +} diff --git a/tests/baselines/reference/iterableContextualTyping1.js b/tests/baselines/reference/iterableContextualTyping1.js new file mode 100644 index 00000000000..8621bbd8417 --- /dev/null +++ b/tests/baselines/reference/iterableContextualTyping1.js @@ -0,0 +1,5 @@ +//// [iterableContextualTyping1.ts] +var iter: Iterable<(x: string) => number> = [s => s.length]; + +//// [iterableContextualTyping1.js] +var iter = [s => s.length]; diff --git a/tests/baselines/reference/iterableContextualTyping1.types b/tests/baselines/reference/iterableContextualTyping1.types new file mode 100644 index 00000000000..386d64312fc --- /dev/null +++ b/tests/baselines/reference/iterableContextualTyping1.types @@ -0,0 +1,12 @@ +=== tests/cases/conformance/expressions/contextualTyping/iterableContextualTyping1.ts === +var iter: Iterable<(x: string) => number> = [s => s.length]; +>iter : Iterable<(x: string) => number> +>Iterable : Iterable +>x : string +>[s => s.length] : ((s: string) => number)[] +>s => s.length : (s: string) => number +>s : string +>s.length : number +>s : string +>length : number + diff --git a/tests/cases/conformance/es6/for-ofStatements/for-of36.ts b/tests/cases/conformance/es6/for-ofStatements/for-of36.ts new file mode 100644 index 00000000000..e67806fef64 --- /dev/null +++ b/tests/cases/conformance/es6/for-ofStatements/for-of36.ts @@ -0,0 +1,5 @@ +//@target: ES6 +var tuple: [string, boolean] = ["", true]; +for (var v of tuple) { + v; +} \ No newline at end of file diff --git a/tests/cases/conformance/es6/for-ofStatements/for-of37.ts b/tests/cases/conformance/es6/for-ofStatements/for-of37.ts new file mode 100644 index 00000000000..561e3bf2e52 --- /dev/null +++ b/tests/cases/conformance/es6/for-ofStatements/for-of37.ts @@ -0,0 +1,5 @@ +//@target: ES6 +var map = new Map([["", true]]); +for (var v of map) { + v; +} \ No newline at end of file diff --git a/tests/cases/conformance/expressions/contextualTyping/iterableContextualTyping1.ts b/tests/cases/conformance/expressions/contextualTyping/iterableContextualTyping1.ts new file mode 100644 index 00000000000..542d1928011 --- /dev/null +++ b/tests/cases/conformance/expressions/contextualTyping/iterableContextualTyping1.ts @@ -0,0 +1,2 @@ +//@target: ES6 +var iter: Iterable<(x: string) => number> = [s => s.length]; \ No newline at end of file