Skip to content

Commit 1cc860d

Browse files
authored
Merge pull request #16157 from Microsoft/fix16153
Fix issue with 'for await' over a union type
2 parents 411cb45 + bd8d8b3 commit 1cc860d

File tree

5 files changed

+107
-64
lines changed

5 files changed

+107
-64
lines changed

src/compiler/checker.ts

Lines changed: 60 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4117,7 +4117,7 @@ namespace ts {
41174117
// This elementType will be used if the specific property corresponding to this index is not
41184118
// present (aka the tuple element property). This call also checks that the parentType is in
41194119
// fact an iterable or array (depending on target language).
4120-
const elementType = checkIteratedTypeOrElementType(parentType, pattern, /*allowStringInput*/ false, /*allowAsyncIterable*/ false);
4120+
const elementType = checkIteratedTypeOrElementType(parentType, pattern, /*allowStringInput*/ false, /*allowAsyncIterables*/ false);
41214121
if (declaration.dotDotDotToken) {
41224122
// Rest element has an array type with the same element type as the parent type
41234123
type = createArrayType(elementType);
@@ -10888,12 +10888,12 @@ namespace ts {
1088810888

1088910889
function getTypeOfDestructuredArrayElement(type: Type, index: number) {
1089010890
return isTupleLikeType(type) && getTypeOfPropertyOfType(type, "" + index) ||
10891-
checkIteratedTypeOrElementType(type, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterable*/ false) ||
10891+
checkIteratedTypeOrElementType(type, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) ||
1089210892
unknownType;
1089310893
}
1089410894

1089510895
function getTypeOfDestructuredSpreadExpression(type: Type) {
10896-
return createArrayType(checkIteratedTypeOrElementType(type, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterable*/ false) || unknownType);
10896+
return createArrayType(checkIteratedTypeOrElementType(type, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) || unknownType);
1089710897
}
1089810898

1089910899
function getAssignedTypeOfBinaryExpression(node: BinaryExpression): Type {
@@ -12867,7 +12867,7 @@ namespace ts {
1286712867
const index = indexOf(arrayLiteral.elements, node);
1286812868
return getTypeOfPropertyOfContextualType(type, "" + index)
1286912869
|| getIndexTypeOfContextualType(type, IndexKind.Number)
12870-
|| getIteratedTypeOrElementType(type, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterable*/ false, /*checkAssignability*/ false);
12870+
|| getIteratedTypeOrElementType(type, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterables*/ false, /*checkAssignability*/ false);
1287112871
}
1287212872
return undefined;
1287312873
}
@@ -13105,7 +13105,7 @@ namespace ts {
1310513105
}
1310613106

1310713107
const arrayOrIterableType = checkExpression(node.expression, checkMode);
13108-
return checkIteratedTypeOrElementType(arrayOrIterableType, node.expression, /*allowStringInput*/ false, /*allowAsyncIterable*/ false);
13108+
return checkIteratedTypeOrElementType(arrayOrIterableType, node.expression, /*allowStringInput*/ false, /*allowAsyncIterables*/ false);
1310913109
}
1311013110

1311113111
function hasDefaultValue(node: BindingElement | Expression): boolean {
@@ -13134,7 +13134,7 @@ namespace ts {
1313413134
// if there is no index type / iterated type.
1313513135
const restArrayType = checkExpression((<SpreadElement>e).expression, checkMode);
1313613136
const restElementType = getIndexTypeOfType(restArrayType, IndexKind.Number) ||
13137-
getIteratedTypeOrElementType(restArrayType, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterable*/ false, /*checkAssignability*/ false);
13137+
getIteratedTypeOrElementType(restArrayType, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterables*/ false, /*checkAssignability*/ false);
1313813138
if (restElementType) {
1313913139
elementTypes.push(restElementType);
1314013140
}
@@ -16987,7 +16987,7 @@ namespace ts {
1698716987
// This elementType will be used if the specific property corresponding to this index is not
1698816988
// present (aka the tuple element property). This call also checks that the parentType is in
1698916989
// fact an iterable or array (depending on target language).
16990-
const elementType = checkIteratedTypeOrElementType(sourceType, node, /*allowStringInput*/ false, /*allowAsyncIterable*/ false) || unknownType;
16990+
const elementType = checkIteratedTypeOrElementType(sourceType, node, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) || unknownType;
1699116991
const elements = node.elements;
1699216992
for (let i = 0; i < elements.length; i++) {
1699316993
checkArrayLiteralDestructuringElementAssignment(node, sourceType, i, elementType, checkMode);
@@ -20131,29 +20131,29 @@ namespace ts {
2013120131
return checkIteratedTypeOrElementType(expressionType, rhsExpression, /*allowStringInput*/ true, awaitModifier !== undefined);
2013220132
}
2013320133

20134-
function checkIteratedTypeOrElementType(inputType: Type, errorNode: Node, allowStringInput: boolean, allowAsyncIterable: boolean): Type {
20134+
function checkIteratedTypeOrElementType(inputType: Type, errorNode: Node, allowStringInput: boolean, allowAsyncIterables: boolean): Type {
2013520135
if (isTypeAny(inputType)) {
2013620136
return inputType;
2013720137
}
2013820138

20139-
return getIteratedTypeOrElementType(inputType, errorNode, allowStringInput, allowAsyncIterable, /*checkAssignability*/ true) || anyType;
20139+
return getIteratedTypeOrElementType(inputType, errorNode, allowStringInput, allowAsyncIterables, /*checkAssignability*/ true) || anyType;
2014020140
}
2014120141

2014220142
/**
2014320143
* When consuming an iterable type in a for..of, spread, or iterator destructuring assignment
2014420144
* we want to get the iterated type of an iterable for ES2015 or later, or the iterated type
2014520145
* of a iterable (if defined globally) or element type of an array like for ES2015 or earlier.
2014620146
*/
20147-
function getIteratedTypeOrElementType(inputType: Type, errorNode: Node, allowStringInput: boolean, allowAsyncIterable: boolean, checkAssignability: boolean): Type {
20147+
function getIteratedTypeOrElementType(inputType: Type, errorNode: Node, allowStringInput: boolean, allowAsyncIterables: boolean, checkAssignability: boolean): Type {
2014820148
const uplevelIteration = languageVersion >= ScriptTarget.ES2015;
2014920149
const downlevelIteration = !uplevelIteration && compilerOptions.downlevelIteration;
2015020150

2015120151
// Get the iterated type of an `Iterable<T>` or `IterableIterator<T>` only in ES2015
2015220152
// or higher, when inside of an async generator or for-await-if, or when
2015320153
// downlevelIteration is requested.
20154-
if (uplevelIteration || downlevelIteration || allowAsyncIterable) {
20154+
if (uplevelIteration || downlevelIteration || allowAsyncIterables) {
2015520155
// We only report errors for an invalid iterable type in ES2015 or higher.
20156-
const iteratedType = getIteratedTypeOfIterable(inputType, uplevelIteration ? errorNode : undefined, allowAsyncIterable, allowAsyncIterable, checkAssignability);
20156+
const iteratedType = getIteratedTypeOfIterable(inputType, uplevelIteration ? errorNode : undefined, allowAsyncIterables, /*allowSyncIterables*/ true, checkAssignability);
2015720157
if (iteratedType || uplevelIteration) {
2015820158
return iteratedType;
2015920159
}
@@ -20267,79 +20267,75 @@ namespace ts {
2026720267
* For a **for-await-of** statement or a `yield*` in an async generator we will look for
2026820268
* the `[Symbol.asyncIterator]()` method first, and then the `[Symbol.iterator]()` method.
2026920269
*/
20270-
function getIteratedTypeOfIterable(type: Type, errorNode: Node | undefined, isAsyncIterable: boolean, allowNonAsyncIterables: boolean, checkAssignability: boolean): Type | undefined {
20270+
function getIteratedTypeOfIterable(type: Type, errorNode: Node | undefined, allowAsyncIterables: boolean, allowSyncIterables: boolean, checkAssignability: boolean): Type | undefined {
2027120271
if (isTypeAny(type)) {
2027220272
return undefined;
2027320273
}
2027420274

20275-
const typeAsIterable = <IterableOrIteratorType>type;
20276-
if (isAsyncIterable ? typeAsIterable.iteratedTypeOfAsyncIterable : typeAsIterable.iteratedTypeOfIterable) {
20277-
return isAsyncIterable ? typeAsIterable.iteratedTypeOfAsyncIterable : typeAsIterable.iteratedTypeOfIterable;
20278-
}
20275+
return mapType(type, getIteratedType);
20276+
20277+
function getIteratedType(type: Type) {
20278+
const typeAsIterable = <IterableOrIteratorType>type;
20279+
if (allowAsyncIterables) {
20280+
if (typeAsIterable.iteratedTypeOfAsyncIterable) {
20281+
return typeAsIterable.iteratedTypeOfAsyncIterable;
20282+
}
2027920283

20280-
if (isAsyncIterable) {
20281-
// As an optimization, if the type is an instantiation of the global `AsyncIterable<T>`
20282-
// or the global `AsyncIterableIterator<T>` then just grab its type argument.
20283-
if (isReferenceToType(type, getGlobalAsyncIterableType(/*reportErrors*/ false)) ||
20284-
isReferenceToType(type, getGlobalAsyncIterableIteratorType(/*reportErrors*/ false))) {
20285-
return typeAsIterable.iteratedTypeOfAsyncIterable = (<GenericType>type).typeArguments[0];
20284+
// As an optimization, if the type is an instantiation of the global `AsyncIterable<T>`
20285+
// or the global `AsyncIterableIterator<T>` then just grab its type argument.
20286+
if (isReferenceToType(type, getGlobalAsyncIterableType(/*reportErrors*/ false)) ||
20287+
isReferenceToType(type, getGlobalAsyncIterableIteratorType(/*reportErrors*/ false))) {
20288+
return typeAsIterable.iteratedTypeOfAsyncIterable = (<GenericType>type).typeArguments[0];
20289+
}
2028620290
}
20287-
}
2028820291

20289-
if (!isAsyncIterable || allowNonAsyncIterables) {
20290-
// As an optimization, if the type is an instantiation of the global `Iterable<T>` or
20291-
// `IterableIterator<T>` then just grab its type argument.
20292-
if (isReferenceToType(type, getGlobalIterableType(/*reportErrors*/ false)) ||
20293-
isReferenceToType(type, getGlobalIterableIteratorType(/*reportErrors*/ false))) {
20294-
return isAsyncIterable
20295-
? typeAsIterable.iteratedTypeOfAsyncIterable = (<GenericType>type).typeArguments[0]
20296-
: typeAsIterable.iteratedTypeOfIterable = (<GenericType>type).typeArguments[0];
20292+
if (allowSyncIterables) {
20293+
if (typeAsIterable.iteratedTypeOfIterable) {
20294+
return typeAsIterable.iteratedTypeOfIterable;
20295+
}
20296+
20297+
// As an optimization, if the type is an instantiation of the global `Iterable<T>` or
20298+
// `IterableIterator<T>` then just grab its type argument.
20299+
if (isReferenceToType(type, getGlobalIterableType(/*reportErrors*/ false)) ||
20300+
isReferenceToType(type, getGlobalIterableIteratorType(/*reportErrors*/ false))) {
20301+
return typeAsIterable.iteratedTypeOfIterable = (<GenericType>type).typeArguments[0];
20302+
}
2029720303
}
20298-
}
2029920304

20300-
let iteratorMethodSignatures: Signature[];
20301-
let isNonAsyncIterable = false;
20302-
if (isAsyncIterable) {
20303-
const iteratorMethod = getTypeOfPropertyOfType(type, getPropertyNameForKnownSymbolName("asyncIterator"));
20304-
if (isTypeAny(iteratorMethod)) {
20305+
const asyncMethodType = allowAsyncIterables && getTypeOfPropertyOfType(type, getPropertyNameForKnownSymbolName("asyncIterator"));
20306+
const methodType = asyncMethodType || (allowSyncIterables && getTypeOfPropertyOfType(type, getPropertyNameForKnownSymbolName("iterator")));
20307+
if (isTypeAny(methodType)) {
2030520308
return undefined;
2030620309
}
20307-
iteratorMethodSignatures = iteratorMethod && getSignaturesOfType(iteratorMethod, SignatureKind.Call);
20308-
}
2030920310

20310-
if (!isAsyncIterable || (allowNonAsyncIterables && !some(iteratorMethodSignatures))) {
20311-
const iteratorMethod = getTypeOfPropertyOfType(type, getPropertyNameForKnownSymbolName("iterator"));
20312-
if (isTypeAny(iteratorMethod)) {
20311+
const signatures = methodType && getSignaturesOfType(methodType, SignatureKind.Call);
20312+
if (!some(signatures)) {
20313+
if (errorNode) {
20314+
error(errorNode,
20315+
allowAsyncIterables
20316+
? Diagnostics.Type_must_have_a_Symbol_asyncIterator_method_that_returns_an_async_iterator
20317+
: Diagnostics.Type_must_have_a_Symbol_iterator_method_that_returns_an_iterator);
20318+
// only report on the first error
20319+
errorNode = undefined;
20320+
}
2031320321
return undefined;
2031420322
}
20315-
iteratorMethodSignatures = iteratorMethod && getSignaturesOfType(iteratorMethod, SignatureKind.Call);
20316-
isNonAsyncIterable = true;
20317-
}
2031820323

20319-
if (some(iteratorMethodSignatures)) {
20320-
const iteratorMethodReturnType = getUnionType(map(iteratorMethodSignatures, getReturnTypeOfSignature), /*subtypeReduction*/ true);
20321-
const iteratedType = getIteratedTypeOfIterator(iteratorMethodReturnType, errorNode, /*isAsyncIterator*/ !isNonAsyncIterable);
20324+
const returnType = getUnionType(map(signatures, getReturnTypeOfSignature), /*subtypeReduction*/ true);
20325+
const iteratedType = getIteratedTypeOfIterator(returnType, errorNode, /*isAsyncIterator*/ !!asyncMethodType);
2032220326
if (checkAssignability && errorNode && iteratedType) {
2032320327
// If `checkAssignability` was specified, we were called from
2032420328
// `checkIteratedTypeOrElementType`. As such, we need to validate that
2032520329
// the type passed in is actually an Iterable.
20326-
checkTypeAssignableTo(type, isNonAsyncIterable
20327-
? createIterableType(iteratedType)
20328-
: createAsyncIterableType(iteratedType), errorNode);
20330+
checkTypeAssignableTo(type, asyncMethodType
20331+
? createAsyncIterableType(iteratedType)
20332+
: createIterableType(iteratedType), errorNode);
2032920333
}
20330-
return isAsyncIterable
20334+
20335+
return asyncMethodType
2033120336
? typeAsIterable.iteratedTypeOfAsyncIterable = iteratedType
2033220337
: typeAsIterable.iteratedTypeOfIterable = iteratedType;
2033320338
}
20334-
20335-
if (errorNode) {
20336-
error(errorNode,
20337-
isAsyncIterable
20338-
? Diagnostics.Type_must_have_a_Symbol_asyncIterator_method_that_returns_an_async_iterator
20339-
: Diagnostics.Type_must_have_a_Symbol_iterator_method_that_returns_an_iterator);
20340-
}
20341-
20342-
return undefined;
2034320339
}
2034420340

2034520341
/**
@@ -20439,7 +20435,7 @@ namespace ts {
2043920435
return undefined;
2044020436
}
2044120437

20442-
return getIteratedTypeOfIterable(returnType, /*errorNode*/ undefined, isAsyncGenerator, /*allowNonAsyncIterables*/ false, /*checkAssignability*/ false)
20438+
return getIteratedTypeOfIterable(returnType, /*errorNode*/ undefined, /*allowAsyncIterables*/ isAsyncGenerator, /*allowSyncIterables*/ !isAsyncGenerator, /*checkAssignability*/ false)
2044320439
|| getIteratedTypeOfIterator(returnType, /*errorNode*/ undefined, isAsyncGenerator);
2044420440
}
2044520441

@@ -22649,7 +22645,7 @@ namespace ts {
2264922645
Debug.assert(expr.parent.kind === SyntaxKind.ArrayLiteralExpression);
2265022646
// [{ property1: p1, property2 }] = elems;
2265122647
const typeOfArrayLiteral = getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment(<Expression>expr.parent);
22652-
const elementType = checkIteratedTypeOrElementType(typeOfArrayLiteral || unknownType, expr.parent, /*allowStringInput*/ false, /*allowAsyncIterable*/ false) || unknownType;
22648+
const elementType = checkIteratedTypeOrElementType(typeOfArrayLiteral || unknownType, expr.parent, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) || unknownType;
2265322649
return checkArrayLiteralDestructuringElementAssignment(<ArrayLiteralExpression>expr.parent, typeOfArrayLiteral,
2265422650
indexOf((<ArrayLiteralExpression>expr.parent).elements, expr), elementType || unknownType);
2265522651
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//// [forAwaitForUnion.ts]
2+
async function f<T>(source: Iterable<T> | AsyncIterable<T>) {
3+
for await (const x of source) {
4+
}
5+
}
6+
7+
//// [forAwaitForUnion.js]
8+
async function f(source) {
9+
for await (const x of source) {
10+
}
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
=== tests/cases/compiler/forAwaitForUnion.ts ===
2+
async function f<T>(source: Iterable<T> | AsyncIterable<T>) {
3+
>f : Symbol(f, Decl(forAwaitForUnion.ts, 0, 0))
4+
>T : Symbol(T, Decl(forAwaitForUnion.ts, 0, 17))
5+
>source : Symbol(source, Decl(forAwaitForUnion.ts, 0, 20))
6+
>Iterable : Symbol(Iterable, Decl(lib.es2015.iterable.d.ts, --, --))
7+
>T : Symbol(T, Decl(forAwaitForUnion.ts, 0, 17))
8+
>AsyncIterable : Symbol(AsyncIterable, Decl(lib.esnext.asynciterable.d.ts, --, --))
9+
>T : Symbol(T, Decl(forAwaitForUnion.ts, 0, 17))
10+
11+
for await (const x of source) {
12+
>x : Symbol(x, Decl(forAwaitForUnion.ts, 1, 20))
13+
>source : Symbol(source, Decl(forAwaitForUnion.ts, 0, 20))
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
=== tests/cases/compiler/forAwaitForUnion.ts ===
2+
async function f<T>(source: Iterable<T> | AsyncIterable<T>) {
3+
>f : <T>(source: Iterable<T> | AsyncIterable<T>) => Promise<void>
4+
>T : T
5+
>source : Iterable<T> | AsyncIterable<T>
6+
>Iterable : Iterable<T>
7+
>T : T
8+
>AsyncIterable : AsyncIterable<T>
9+
>T : T
10+
11+
for await (const x of source) {
12+
>x : T
13+
>source : Iterable<T> | AsyncIterable<T>
14+
}
15+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// @target: esnext
2+
// @lib: esnext
3+
async function f<T>(source: Iterable<T> | AsyncIterable<T>) {
4+
for await (const x of source) {
5+
}
6+
}

0 commit comments

Comments
 (0)