Skip to content

Commit 64be1f2

Browse files
committed
merge getIteratedTypeOfIterableOrElementTypeOf*
1 parent 5d415ca commit 64be1f2

File tree

2 files changed

+86
-120
lines changed

2 files changed

+86
-120
lines changed

src/compiler/checker.ts

Lines changed: 84 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -17980,16 +17980,92 @@ namespace ts {
1798017980
* of a iterable (if defined globally) or element type of an array like for ES2015 or earlier.
1798117981
*/
1798217982
function getIteratedTypeOrElementType(inputType: Type, errorNode: Node, allowStringInput: boolean, checkAssignability: boolean): Type {
17983-
if (languageVersion >= ScriptTarget.ES2015) {
17984-
const iteratedType = getIteratedTypeOfIterable(inputType, errorNode);
17985-
if (checkAssignability && errorNode && iteratedType) {
17986-
checkTypeAssignableTo(inputType, createIterableType(iteratedType), errorNode);
17983+
const uplevelIteration = languageVersion >= ScriptTarget.ES2015;
17984+
const downlevelIteration = !uplevelIteration && compilerOptions.downlevelIteration;
17985+
17986+
// Get the iterated type of an `Iterable<T>` or `IterableIterator<T>` only in ES2015
17987+
// or higher, or when downlevelIteration is supplied.
17988+
if (uplevelIteration || downlevelIteration) {
17989+
// We only report errors for an invalid iterable type in ES2015 or higher.
17990+
const iteratedType = getIteratedTypeOfIterable(inputType, uplevelIteration ? errorNode : undefined);
17991+
if (iteratedType || uplevelIteration) {
17992+
// Normally we would perform this check in `checkIteratedTypeOrElementType`,
17993+
// but we need to perform it here as `checkIteratedTypeOrElementType` won't
17994+
// have enough context to know whether the input type actually conforms
17995+
// to `Iterable<T>`.
17996+
if (checkAssignability && errorNode && iteratedType) {
17997+
checkTypeAssignableTo(inputType, createIterableType(iteratedType), errorNode);
17998+
}
17999+
return iteratedType;
18000+
}
18001+
}
18002+
18003+
let arrayType = inputType;
18004+
let reportedError = false;
18005+
let hasStringConstituent = false;
18006+
18007+
// If strings are permitted, remove any string-like constituents from the array type.
18008+
// This allows us to find other non-string element types from an array unioned with
18009+
// a string.
18010+
if (allowStringInput) {
18011+
if (arrayType.flags & TypeFlags.Union) {
18012+
// After we remove all types that are StringLike, we will know if there was a string constituent
18013+
// based on whether the result of filter is a new array.
18014+
const arrayTypes = (<UnionType>inputType).types;
18015+
const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike));
18016+
if (filteredTypes !== arrayTypes) {
18017+
arrayType = getUnionType(filteredTypes, /*subtypeReduction*/ true);
18018+
}
18019+
}
18020+
else if (arrayType.flags & TypeFlags.StringLike) {
18021+
arrayType = neverType;
18022+
}
18023+
hasStringConstituent = arrayType !== inputType;
18024+
if (hasStringConstituent) {
18025+
if (languageVersion < ScriptTarget.ES5) {
18026+
if (errorNode) {
18027+
error(errorNode, Diagnostics.Using_a_string_in_a_for_of_statement_is_only_supported_in_ECMAScript_5_and_higher);
18028+
reportedError = true;
18029+
}
18030+
}
18031+
18032+
// Now that we've removed all the StringLike types, if no constituents remain, then the entire
18033+
// arrayOrStringType was a string.
18034+
if (arrayType.flags & TypeFlags.Never) {
18035+
return stringType;
18036+
}
18037+
}
18038+
}
18039+
18040+
if (!isArrayLikeType(arrayType)) {
18041+
if (errorNode && !reportedError) {
18042+
// Which error we report depends on whether we allow strings or if there was a
18043+
// string constituent. For example, if the input type is number | string, we
18044+
// want to say that number is not an array type. But if the input was just
18045+
// number and string input is allowed, we want to say that number is not an
18046+
// array type or a string type.
18047+
const diagnostic = !allowStringInput || hasStringConstituent
18048+
? downlevelIteration
18049+
? Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator
18050+
: Diagnostics.Type_0_is_not_an_array_type
18051+
: downlevelIteration
18052+
? Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator
18053+
: Diagnostics.Type_0_is_not_an_array_type_or_a_string_type;
18054+
error(errorNode, diagnostic, typeToString(arrayType));
1798718055
}
17988-
return iteratedType;
18056+
return hasStringConstituent ? stringType : undefined;
18057+
}
18058+
18059+
const arrayElementType = getIndexTypeOfType(arrayType, IndexKind.Number);
18060+
if (hasStringConstituent && arrayElementType) {
18061+
// This is just an optimization for the case where arrayOrStringType is string | string[]
18062+
if (arrayElementType.flags & TypeFlags.StringLike) {
18063+
return stringType;
18064+
}
18065+
18066+
return getUnionType([arrayElementType, stringType], /*subtypeReduction*/ true);
1798918067
}
17990-
return allowStringInput
17991-
? getIteratedTypeOfIterableOrElementTypeOfArrayOrString(inputType, errorNode, checkAssignability)
17992-
: getIteratedTypeOfIterableOrElementTypeOfArray(inputType, errorNode, checkAssignability);
18068+
return arrayElementType;
1799318069
}
1799418070

1799518071
function checkIteratedTypeOfIterableOrAsyncIterable(asyncIterable: Type, errorNode: Node): Type {
@@ -18279,116 +18355,6 @@ namespace ts {
1827918355
getIteratedTypeOfIterator(type, /*errorNode*/ undefined);
1828018356
}
1828118357

18282-
/**
18283-
* This function does the following steps:
18284-
* 1. Break up arrayOrStringType (possibly a union) into its string constituents and array constituents.
18285-
* 2. Take the element types of the array constituents.
18286-
* 3. Return the union of the element types, and string if there was a string constituent.
18287-
*
18288-
* For example:
18289-
* string -> string
18290-
* number[] -> number
18291-
* string[] | number[] -> string | number
18292-
* string | number[] -> string | number
18293-
* string | string[] | number[] -> string | number
18294-
*
18295-
* It also errors if:
18296-
* 1. Some constituent is neither a string nor an array.
18297-
* 2. Some constituent is a string and target is less than ES5 (because in ES3 string is not indexable).
18298-
*/
18299-
function getIteratedTypeOfIterableOrElementTypeOfArrayOrString(arrayOrStringType: Type, errorNode: Node, checkAssignability: boolean): Type {
18300-
const iteratedType = compilerOptions.downlevelIteration && getIteratedTypeOfIterable(arrayOrStringType, /*errorNode*/ undefined);
18301-
if (iteratedType) {
18302-
if (checkAssignability && errorNode) {
18303-
checkTypeAssignableTo(arrayOrStringType, createIterableType(iteratedType), errorNode);
18304-
}
18305-
return iteratedType;
18306-
}
18307-
18308-
let arrayType = arrayOrStringType;
18309-
if (arrayOrStringType.flags & TypeFlags.Union) {
18310-
// After we remove all types that are StringLike, we will know if there was a string constituent
18311-
// based on whether the result of filter is a new array.
18312-
const arrayTypes = (arrayOrStringType as UnionType).types;
18313-
const filteredTypes = filter(arrayTypes, t => !(t.flags & TypeFlags.StringLike));
18314-
if (filteredTypes !== arrayTypes) {
18315-
arrayType = getUnionType(filteredTypes, /*subtypeReduction*/ true);
18316-
}
18317-
}
18318-
else if (arrayOrStringType.flags & TypeFlags.StringLike) {
18319-
arrayType = neverType;
18320-
}
18321-
18322-
const hasStringConstituent = arrayOrStringType !== arrayType;
18323-
let reportedError = false;
18324-
if (hasStringConstituent) {
18325-
if (languageVersion < ScriptTarget.ES5) {
18326-
if (errorNode) {
18327-
error(errorNode, Diagnostics.Using_a_string_in_a_for_of_statement_is_only_supported_in_ECMAScript_5_and_higher);
18328-
reportedError = true;
18329-
}
18330-
}
18331-
18332-
// Now that we've removed all the StringLike types, if no constituents remain, then the entire
18333-
// arrayOrStringType was a string.
18334-
if (arrayType.flags & TypeFlags.Never) {
18335-
return stringType;
18336-
}
18337-
}
18338-
18339-
if (!isArrayLikeType(arrayType)) {
18340-
if (errorNode) {
18341-
if (!reportedError) {
18342-
// Which error we report depends on whether there was a string constituent. For example,
18343-
// if the input type is number | string, we want to say that number is not an array type.
18344-
// But if the input was just number, we want to say that number is not an array type
18345-
// or a string type.
18346-
const diagnostic = hasStringConstituent
18347-
? compilerOptions.downlevelIteration
18348-
? Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator
18349-
: Diagnostics.Type_0_is_not_an_array_type
18350-
: compilerOptions.downlevelIteration
18351-
? Diagnostics.Type_0_is_not_an_array_type_or_a_string_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator
18352-
: Diagnostics.Type_0_is_not_an_array_type_or_a_string_type;
18353-
error(errorNode, diagnostic, typeToString(arrayType));
18354-
}
18355-
}
18356-
return hasStringConstituent ? stringType : undefined;
18357-
}
18358-
18359-
const arrayElementType = getIndexTypeOfType(arrayType, IndexKind.Number);
18360-
if (arrayElementType && hasStringConstituent) {
18361-
// This is just an optimization for the case where arrayOrStringType is string | string[]
18362-
if (arrayElementType.flags & TypeFlags.StringLike) {
18363-
return stringType;
18364-
}
18365-
18366-
return getUnionType([arrayElementType, stringType], /*subtypeReduction*/ true);
18367-
}
18368-
18369-
return arrayElementType;
18370-
}
18371-
18372-
function getIteratedTypeOfIterableOrElementTypeOfArray(inputType: Type, errorNode: Node, checkAssignability: boolean): Type {
18373-
const iteratedType = compilerOptions.downlevelIteration && getIteratedTypeOfIterable(inputType, /*errorNode*/ undefined);
18374-
if (iteratedType) {
18375-
if (checkAssignability && errorNode) {
18376-
checkTypeAssignableTo(inputType, createIterableType(iteratedType), errorNode);
18377-
}
18378-
return iteratedType;
18379-
}
18380-
if (isArrayLikeType(inputType)) {
18381-
return getIndexTypeOfType(inputType, IndexKind.Number);
18382-
}
18383-
if (errorNode) {
18384-
const diagnostic = compilerOptions.downlevelIteration
18385-
? Diagnostics.Type_0_is_not_an_array_type_or_does_not_have_a_Symbol_iterator_method_that_returns_an_iterator
18386-
: Diagnostics.Type_0_is_not_an_array_type;
18387-
error(errorNode, diagnostic, typeToString(inputType));
18388-
}
18389-
return undefined;
18390-
}
18391-
1839218358
function checkBreakOrContinueStatement(node: BreakOrContinueStatement) {
1839318359
// Grammar checking
1839418360
checkGrammarStatementInAmbientContext(node) || checkGrammarBreakOrContinueStatement(node);
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
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.
1+
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.
22

33

44
==== tests/cases/conformance/statements/for-ofStatements/ES5For-of36.ts (1 errors) ====
55
for (let [a = 0, b = 1] of [2, 3]) {
66
~~~~~~~~~~~~~~
7-
!!! error TS2546: Type 'number' is not an array type or does not have a '[Symbol.iterator]()' method that returns an iterator.
7+
!!! error TS2547: Type 'number' is not an array type or does not have a '[Symbol.iterator]()' method that returns an iterator.
88
a;
99
b;
1010
}

0 commit comments

Comments
 (0)