Skip to content

Fix issue with 'for await' over a union type #16157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 60 additions & 64 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4117,7 +4117,7 @@ namespace ts {
// This elementType will be used if the specific property corresponding to this index is not
// present (aka the tuple element property). This call also checks that the parentType is in
// fact an iterable or array (depending on target language).
const elementType = checkIteratedTypeOrElementType(parentType, pattern, /*allowStringInput*/ false, /*allowAsyncIterable*/ false);
const elementType = checkIteratedTypeOrElementType(parentType, pattern, /*allowStringInput*/ false, /*allowAsyncIterables*/ false);
if (declaration.dotDotDotToken) {
// Rest element has an array type with the same element type as the parent type
type = createArrayType(elementType);
Expand Down Expand Up @@ -10888,12 +10888,12 @@ namespace ts {

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

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

function getAssignedTypeOfBinaryExpression(node: BinaryExpression): Type {
Expand Down Expand Up @@ -12867,7 +12867,7 @@ namespace ts {
const index = indexOf(arrayLiteral.elements, node);
return getTypeOfPropertyOfContextualType(type, "" + index)
|| getIndexTypeOfContextualType(type, IndexKind.Number)
|| getIteratedTypeOrElementType(type, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterable*/ false, /*checkAssignability*/ false);
|| getIteratedTypeOrElementType(type, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterables*/ false, /*checkAssignability*/ false);
}
return undefined;
}
Expand Down Expand Up @@ -13105,7 +13105,7 @@ namespace ts {
}

const arrayOrIterableType = checkExpression(node.expression, checkMode);
return checkIteratedTypeOrElementType(arrayOrIterableType, node.expression, /*allowStringInput*/ false, /*allowAsyncIterable*/ false);
return checkIteratedTypeOrElementType(arrayOrIterableType, node.expression, /*allowStringInput*/ false, /*allowAsyncIterables*/ false);
}

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

function checkIteratedTypeOrElementType(inputType: Type, errorNode: Node, allowStringInput: boolean, allowAsyncIterable: boolean): Type {
function checkIteratedTypeOrElementType(inputType: Type, errorNode: Node, allowStringInput: boolean, allowAsyncIterables: boolean): Type {
if (isTypeAny(inputType)) {
return inputType;
}

return getIteratedTypeOrElementType(inputType, errorNode, allowStringInput, allowAsyncIterable, /*checkAssignability*/ true) || anyType;
return getIteratedTypeOrElementType(inputType, errorNode, allowStringInput, allowAsyncIterables, /*checkAssignability*/ true) || anyType;
}

/**
* When consuming an iterable type in a for..of, spread, or iterator destructuring assignment
* we want to get the iterated type of an iterable for ES2015 or later, or the iterated type
* 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, allowAsyncIterable: boolean, checkAssignability: boolean): Type {
function getIteratedTypeOrElementType(inputType: Type, errorNode: Node, allowStringInput: boolean, allowAsyncIterables: boolean, checkAssignability: boolean): Type {
const uplevelIteration = languageVersion >= ScriptTarget.ES2015;
const downlevelIteration = !uplevelIteration && compilerOptions.downlevelIteration;

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

const typeAsIterable = <IterableOrIteratorType>type;
if (isAsyncIterable ? typeAsIterable.iteratedTypeOfAsyncIterable : typeAsIterable.iteratedTypeOfIterable) {
return isAsyncIterable ? typeAsIterable.iteratedTypeOfAsyncIterable : typeAsIterable.iteratedTypeOfIterable;
}
return mapType(type, getIteratedType);

function getIteratedType(type: Type) {
const typeAsIterable = <IterableOrIteratorType>type;
if (allowAsyncIterables) {
if (typeAsIterable.iteratedTypeOfAsyncIterable) {
return typeAsIterable.iteratedTypeOfAsyncIterable;
}

if (isAsyncIterable) {
// As an optimization, if the type is an instantiation of the global `AsyncIterable<T>`
// or the global `AsyncIterableIterator<T>` then just grab its type argument.
if (isReferenceToType(type, getGlobalAsyncIterableType(/*reportErrors*/ false)) ||
isReferenceToType(type, getGlobalAsyncIterableIteratorType(/*reportErrors*/ false))) {
return typeAsIterable.iteratedTypeOfAsyncIterable = (<GenericType>type).typeArguments[0];
// As an optimization, if the type is an instantiation of the global `AsyncIterable<T>`
// or the global `AsyncIterableIterator<T>` then just grab its type argument.
if (isReferenceToType(type, getGlobalAsyncIterableType(/*reportErrors*/ false)) ||
isReferenceToType(type, getGlobalAsyncIterableIteratorType(/*reportErrors*/ false))) {
return typeAsIterable.iteratedTypeOfAsyncIterable = (<GenericType>type).typeArguments[0];
}
}
}

if (!isAsyncIterable || allowNonAsyncIterables) {
// As an optimization, if the type is an instantiation of the global `Iterable<T>` or
// `IterableIterator<T>` then just grab its type argument.
if (isReferenceToType(type, getGlobalIterableType(/*reportErrors*/ false)) ||
isReferenceToType(type, getGlobalIterableIteratorType(/*reportErrors*/ false))) {
return isAsyncIterable
? typeAsIterable.iteratedTypeOfAsyncIterable = (<GenericType>type).typeArguments[0]
: typeAsIterable.iteratedTypeOfIterable = (<GenericType>type).typeArguments[0];
if (allowSyncIterables) {
if (typeAsIterable.iteratedTypeOfIterable) {
return typeAsIterable.iteratedTypeOfIterable;
}

// As an optimization, if the type is an instantiation of the global `Iterable<T>` or
// `IterableIterator<T>` then just grab its type argument.
if (isReferenceToType(type, getGlobalIterableType(/*reportErrors*/ false)) ||
isReferenceToType(type, getGlobalIterableIteratorType(/*reportErrors*/ false))) {
return typeAsIterable.iteratedTypeOfIterable = (<GenericType>type).typeArguments[0];
}
}
}

let iteratorMethodSignatures: Signature[];
let isNonAsyncIterable = false;
if (isAsyncIterable) {
const iteratorMethod = getTypeOfPropertyOfType(type, getPropertyNameForKnownSymbolName("asyncIterator"));
if (isTypeAny(iteratorMethod)) {
const asyncMethodType = allowAsyncIterables && getTypeOfPropertyOfType(type, getPropertyNameForKnownSymbolName("asyncIterator"));
const methodType = asyncMethodType || (allowSyncIterables && getTypeOfPropertyOfType(type, getPropertyNameForKnownSymbolName("iterator")));
if (isTypeAny(methodType)) {
return undefined;
}
iteratorMethodSignatures = iteratorMethod && getSignaturesOfType(iteratorMethod, SignatureKind.Call);
}

if (!isAsyncIterable || (allowNonAsyncIterables && !some(iteratorMethodSignatures))) {
const iteratorMethod = getTypeOfPropertyOfType(type, getPropertyNameForKnownSymbolName("iterator"));
if (isTypeAny(iteratorMethod)) {
const signatures = methodType && getSignaturesOfType(methodType, SignatureKind.Call);
if (!some(signatures)) {
if (errorNode) {
error(errorNode,
allowAsyncIterables
? Diagnostics.Type_must_have_a_Symbol_asyncIterator_method_that_returns_an_async_iterator
: Diagnostics.Type_must_have_a_Symbol_iterator_method_that_returns_an_iterator);
// only report on the first error
errorNode = undefined;
}
return undefined;
}
iteratorMethodSignatures = iteratorMethod && getSignaturesOfType(iteratorMethod, SignatureKind.Call);
isNonAsyncIterable = true;
}

if (some(iteratorMethodSignatures)) {
const iteratorMethodReturnType = getUnionType(map(iteratorMethodSignatures, getReturnTypeOfSignature), /*subtypeReduction*/ true);
const iteratedType = getIteratedTypeOfIterator(iteratorMethodReturnType, errorNode, /*isAsyncIterator*/ !isNonAsyncIterable);
const returnType = getUnionType(map(signatures, getReturnTypeOfSignature), /*subtypeReduction*/ true);
const iteratedType = getIteratedTypeOfIterator(returnType, errorNode, /*isAsyncIterator*/ !!asyncMethodType);
if (checkAssignability && errorNode && iteratedType) {
// If `checkAssignability` was specified, we were called from
// `checkIteratedTypeOrElementType`. As such, we need to validate that
// the type passed in is actually an Iterable.
checkTypeAssignableTo(type, isNonAsyncIterable
? createIterableType(iteratedType)
: createAsyncIterableType(iteratedType), errorNode);
checkTypeAssignableTo(type, asyncMethodType
? createAsyncIterableType(iteratedType)
: createIterableType(iteratedType), errorNode);
}
return isAsyncIterable

return asyncMethodType
? typeAsIterable.iteratedTypeOfAsyncIterable = iteratedType
: typeAsIterable.iteratedTypeOfIterable = iteratedType;
}

if (errorNode) {
error(errorNode,
isAsyncIterable
? Diagnostics.Type_must_have_a_Symbol_asyncIterator_method_that_returns_an_async_iterator
: Diagnostics.Type_must_have_a_Symbol_iterator_method_that_returns_an_iterator);
}

return undefined;
}

/**
Expand Down Expand Up @@ -20439,7 +20435,7 @@ namespace ts {
return undefined;
}

return getIteratedTypeOfIterable(returnType, /*errorNode*/ undefined, isAsyncGenerator, /*allowNonAsyncIterables*/ false, /*checkAssignability*/ false)
return getIteratedTypeOfIterable(returnType, /*errorNode*/ undefined, /*allowAsyncIterables*/ isAsyncGenerator, /*allowSyncIterables*/ !isAsyncGenerator, /*checkAssignability*/ false)
|| getIteratedTypeOfIterator(returnType, /*errorNode*/ undefined, isAsyncGenerator);
}

Expand Down Expand Up @@ -22649,7 +22645,7 @@ namespace ts {
Debug.assert(expr.parent.kind === SyntaxKind.ArrayLiteralExpression);
// [{ property1: p1, property2 }] = elems;
const typeOfArrayLiteral = getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment(<Expression>expr.parent);
const elementType = checkIteratedTypeOrElementType(typeOfArrayLiteral || unknownType, expr.parent, /*allowStringInput*/ false, /*allowAsyncIterable*/ false) || unknownType;
const elementType = checkIteratedTypeOrElementType(typeOfArrayLiteral || unknownType, expr.parent, /*allowStringInput*/ false, /*allowAsyncIterables*/ false) || unknownType;
return checkArrayLiteralDestructuringElementAssignment(<ArrayLiteralExpression>expr.parent, typeOfArrayLiteral,
indexOf((<ArrayLiteralExpression>expr.parent).elements, expr), elementType || unknownType);
}
Expand Down
11 changes: 11 additions & 0 deletions tests/baselines/reference/forAwaitForUnion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//// [forAwaitForUnion.ts]
async function f<T>(source: Iterable<T> | AsyncIterable<T>) {
for await (const x of source) {
}
}

//// [forAwaitForUnion.js]
async function f(source) {
for await (const x of source) {
}
}
15 changes: 15 additions & 0 deletions tests/baselines/reference/forAwaitForUnion.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
=== tests/cases/compiler/forAwaitForUnion.ts ===
async function f<T>(source: Iterable<T> | AsyncIterable<T>) {
>f : Symbol(f, Decl(forAwaitForUnion.ts, 0, 0))
>T : Symbol(T, Decl(forAwaitForUnion.ts, 0, 17))
>source : Symbol(source, Decl(forAwaitForUnion.ts, 0, 20))
>Iterable : Symbol(Iterable, Decl(lib.es2015.iterable.d.ts, --, --))
>T : Symbol(T, Decl(forAwaitForUnion.ts, 0, 17))
>AsyncIterable : Symbol(AsyncIterable, Decl(lib.esnext.asynciterable.d.ts, --, --))
>T : Symbol(T, Decl(forAwaitForUnion.ts, 0, 17))

for await (const x of source) {
>x : Symbol(x, Decl(forAwaitForUnion.ts, 1, 20))
>source : Symbol(source, Decl(forAwaitForUnion.ts, 0, 20))
}
}
15 changes: 15 additions & 0 deletions tests/baselines/reference/forAwaitForUnion.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
=== tests/cases/compiler/forAwaitForUnion.ts ===
async function f<T>(source: Iterable<T> | AsyncIterable<T>) {
>f : <T>(source: Iterable<T> | AsyncIterable<T>) => Promise<void>
>T : T
>source : Iterable<T> | AsyncIterable<T>
>Iterable : Iterable<T>
>T : T
>AsyncIterable : AsyncIterable<T>
>T : T

for await (const x of source) {
>x : T
>source : Iterable<T> | AsyncIterable<T>
}
}
6 changes: 6 additions & 0 deletions tests/cases/compiler/forAwaitForUnion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// @target: esnext
// @lib: esnext
async function f<T>(source: Iterable<T> | AsyncIterable<T>) {
for await (const x of source) {
}
}