From 64be1f2da2df62dae5dfefef822b08dd72a56745 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 2 Feb 2017 14:45:58 -0800 Subject: [PATCH] merge getIteratedTypeOfIterableOrElementTypeOf* --- src/compiler/checker.ts | 202 ++++++++---------- .../reference/ES5For-of36.errors.txt | 4 +- 2 files changed, 86 insertions(+), 120 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e96f55c22fe..ba5b46d2f03 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17980,16 +17980,92 @@ namespace ts { * of a iterable (if defined globally) or element type of an array like for ES2015 or earlier. */ function getIteratedTypeOrElementType(inputType: Type, errorNode: Node, allowStringInput: boolean, checkAssignability: boolean): Type { - if (languageVersion >= ScriptTarget.ES2015) { - const iteratedType = getIteratedTypeOfIterable(inputType, errorNode); - if (checkAssignability && errorNode && iteratedType) { - checkTypeAssignableTo(inputType, createIterableType(iteratedType), errorNode); + const uplevelIteration = languageVersion >= ScriptTarget.ES2015; + const downlevelIteration = !uplevelIteration && compilerOptions.downlevelIteration; + + // Get the iterated type of an `Iterable` or `IterableIterator` only in ES2015 + // or higher, or when downlevelIteration is supplied. + if (uplevelIteration || downlevelIteration) { + // We only report errors for an invalid iterable type in ES2015 or higher. + const iteratedType = getIteratedTypeOfIterable(inputType, uplevelIteration ? errorNode : undefined); + if (iteratedType || uplevelIteration) { + // Normally we would perform this check in `checkIteratedTypeOrElementType`, + // but we need to perform it here as `checkIteratedTypeOrElementType` won't + // have enough context to know whether the input type actually conforms + // to `Iterable`. + if (checkAssignability && errorNode && iteratedType) { + checkTypeAssignableTo(inputType, createIterableType(iteratedType), errorNode); + } + return iteratedType; } - return iteratedType; } - return allowStringInput - ? getIteratedTypeOfIterableOrElementTypeOfArrayOrString(inputType, errorNode, checkAssignability) - : getIteratedTypeOfIterableOrElementTypeOfArray(inputType, errorNode, checkAssignability); + + let arrayType = inputType; + let reportedError = false; + let hasStringConstituent = false; + + // If strings are permitted, remove any string-like constituents from the array type. + // This allows us to find other non-string element types from an array unioned with + // a string. + if (allowStringInput) { + if (arrayType.flags & TypeFlags.Union) { + // After we remove all types that are StringLike, we will know if there was a string constituent + // based on whether the result of filter is a new array. + const arrayTypes = (inputType).types; + const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike)); + if (filteredTypes !== arrayTypes) { + arrayType = getUnionType(filteredTypes, /*subtypeReduction*/ true); + } + } + else if (arrayType.flags & TypeFlags.StringLike) { + arrayType = neverType; + } + hasStringConstituent = arrayType !== inputType; + if (hasStringConstituent) { + if (languageVersion < ScriptTarget.ES5) { + if (errorNode) { + error(errorNode, Diagnostics.Using_a_string_in_a_for_of_statement_is_only_supported_in_ECMAScript_5_and_higher); + reportedError = true; + } + } + + // Now that we've removed all the StringLike types, if no constituents remain, then the entire + // arrayOrStringType was a string. + if (arrayType.flags & TypeFlags.Never) { + return stringType; + } + } + } + + if (!isArrayLikeType(arrayType)) { + if (errorNode && !reportedError) { + // Which error we report depends on whether we allow strings or if there was a + // string constituent. For example, if the input type is number | string, we + // want to say that number is not an array type. But if the input was just + // number and string input is allowed, we want to say that number is not an + // array type or a string type. + const diagnostic = !allowStringInput || hasStringConstituent + ? downlevelIteration + ? Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator + : Diagnostics.Type_0_is_not_an_array_type + : downlevelIteration + ? Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator + : Diagnostics.Type_0_is_not_an_array_type_or_a_string_type; + error(errorNode, diagnostic, typeToString(arrayType)); + } + return hasStringConstituent ? stringType : undefined; + } + + const arrayElementType = getIndexTypeOfType(arrayType, IndexKind.Number); + if (hasStringConstituent && arrayElementType) { + // This is just an optimization for the case where arrayOrStringType is string | string[] + if (arrayElementType.flags & TypeFlags.StringLike) { + return stringType; + } + + return getUnionType([arrayElementType, stringType], /*subtypeReduction*/ true); + } + return arrayElementType; } function checkIteratedTypeOfIterableOrAsyncIterable(asyncIterable: Type, errorNode: Node): Type { @@ -18279,116 +18355,6 @@ namespace ts { getIteratedTypeOfIterator(type, /*errorNode*/ undefined); } - /** - * This function does the following steps: - * 1. Break up arrayOrStringType (possibly a union) into its string constituents and array constituents. - * 2. Take the element types of the array constituents. - * 3. Return the union of the element types, and string if there was a string constituent. - * - * For example: - * string -> string - * number[] -> number - * string[] | number[] -> string | number - * string | number[] -> string | number - * string | string[] | number[] -> string | number - * - * It also errors if: - * 1. Some constituent is neither a string nor an array. - * 2. Some constituent is a string and target is less than ES5 (because in ES3 string is not indexable). - */ - function getIteratedTypeOfIterableOrElementTypeOfArrayOrString(arrayOrStringType: Type, errorNode: Node, checkAssignability: boolean): Type { - const iteratedType = compilerOptions.downlevelIteration && getIteratedTypeOfIterable(arrayOrStringType, /*errorNode*/ undefined); - if (iteratedType) { - if (checkAssignability && errorNode) { - checkTypeAssignableTo(arrayOrStringType, createIterableType(iteratedType), errorNode); - } - return iteratedType; - } - - let arrayType = arrayOrStringType; - if (arrayOrStringType.flags & TypeFlags.Union) { - // After we remove all types that are StringLike, we will know if there was a string constituent - // based on whether the result of filter is a new array. - const arrayTypes = (arrayOrStringType as UnionType).types; - const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike)); - if (filteredTypes !== arrayTypes) { - arrayType = getUnionType(filteredTypes, /*subtypeReduction*/ true); - } - } - else if (arrayOrStringType.flags & TypeFlags.StringLike) { - arrayType = neverType; - } - - const hasStringConstituent = arrayOrStringType !== arrayType; - let reportedError = false; - if (hasStringConstituent) { - if (languageVersion < ScriptTarget.ES5) { - if (errorNode) { - error(errorNode, Diagnostics.Using_a_string_in_a_for_of_statement_is_only_supported_in_ECMAScript_5_and_higher); - reportedError = true; - } - } - - // Now that we've removed all the StringLike types, if no constituents remain, then the entire - // arrayOrStringType was a string. - if (arrayType.flags & TypeFlags.Never) { - return stringType; - } - } - - if (!isArrayLikeType(arrayType)) { - if (errorNode) { - if (!reportedError) { - // Which error we report depends on whether there was a string constituent. For example, - // if the input type is number | string, we want to say that number is not an array type. - // But if the input was just number, we want to say that number is not an array type - // or a string type. - const diagnostic = hasStringConstituent - ? compilerOptions.downlevelIteration - ? Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator - : Diagnostics.Type_0_is_not_an_array_type - : compilerOptions.downlevelIteration - ? Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator - : Diagnostics.Type_0_is_not_an_array_type_or_a_string_type; - error(errorNode, diagnostic, typeToString(arrayType)); - } - } - return hasStringConstituent ? stringType : undefined; - } - - const arrayElementType = getIndexTypeOfType(arrayType, IndexKind.Number); - if (arrayElementType && hasStringConstituent) { - // This is just an optimization for the case where arrayOrStringType is string | string[] - if (arrayElementType.flags & TypeFlags.StringLike) { - return stringType; - } - - return getUnionType([arrayElementType, stringType], /*subtypeReduction*/ true); - } - - return arrayElementType; - } - - function getIteratedTypeOfIterableOrElementTypeOfArray(inputType: Type, errorNode: Node, checkAssignability: boolean): Type { - const iteratedType = compilerOptions.downlevelIteration && getIteratedTypeOfIterable(inputType, /*errorNode*/ undefined); - if (iteratedType) { - if (checkAssignability && errorNode) { - checkTypeAssignableTo(inputType, createIterableType(iteratedType), errorNode); - } - return iteratedType; - } - if (isArrayLikeType(inputType)) { - return getIndexTypeOfType(inputType, IndexKind.Number); - } - if (errorNode) { - const diagnostic = compilerOptions.downlevelIteration - ? Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator - : Diagnostics.Type_0_is_not_an_array_type; - error(errorNode, diagnostic, typeToString(inputType)); - } - return undefined; - } - function checkBreakOrContinueStatement(node: BreakOrContinueStatement) { // Grammar checking checkGrammarStatementInAmbientContext(node) || checkGrammarBreakOrContinueStatement(node); diff --git a/tests/baselines/reference/ES5For-of36.errors.txt b/tests/baselines/reference/ES5For-of36.errors.txt index 635396c09f8..b1cf3e1e3b2 100644 --- a/tests/baselines/reference/ES5For-of36.errors.txt +++ b/tests/baselines/reference/ES5For-of36.errors.txt @@ -1,10 +1,10 @@ -tests/cases/conformance/statements/for-ofStatements/ES5For-of36.ts(1,10): error TS2546: Type 'number' is not an array type or does not have a '[Symbol.iterator]()' method that returns an iterator. +tests/cases/conformance/statements/for-ofStatements/ES5For-of36.ts(1,10): error TS2547: Type 'number' is not an array type or does not have a '[Symbol.iterator]()' method that returns an iterator. ==== tests/cases/conformance/statements/for-ofStatements/ES5For-of36.ts (1 errors) ==== for (let [a = 0, b = 1] of [2, 3]) { ~~~~~~~~~~~~~~ -!!! error TS2546: Type 'number' is not an array type or does not have a '[Symbol.iterator]()' method that returns an iterator. +!!! error TS2547: Type 'number' is not an array type or does not have a '[Symbol.iterator]()' method that returns an iterator. a; b; } \ No newline at end of file