From 3029313f3292823a39b39abb57d598cc6c6267e8 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 30 May 2017 15:14:52 -0700 Subject: [PATCH] Fix issue with 'for await' over a union type --- src/compiler/checker.ts | 124 +++++++++--------- tests/baselines/reference/forAwaitForUnion.js | 11 ++ .../reference/forAwaitForUnion.symbols | 15 +++ .../reference/forAwaitForUnion.types | 15 +++ tests/cases/compiler/forAwaitForUnion.ts | 6 + 5 files changed, 107 insertions(+), 64 deletions(-) create mode 100644 tests/baselines/reference/forAwaitForUnion.js create mode 100644 tests/baselines/reference/forAwaitForUnion.symbols create mode 100644 tests/baselines/reference/forAwaitForUnion.types create mode 100644 tests/cases/compiler/forAwaitForUnion.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9ff1c4b41b..7ef6aa6217 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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); @@ -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 { @@ -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; } @@ -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 { @@ -13134,7 +13134,7 @@ namespace ts { // if there is no index type / iterated type. const restArrayType = checkExpression((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); } @@ -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); @@ -20131,12 +20131,12 @@ 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; } /** @@ -20144,16 +20144,16 @@ namespace ts { * 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` or `IterableIterator` 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; } @@ -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 = type; - if (isAsyncIterable ? typeAsIterable.iteratedTypeOfAsyncIterable : typeAsIterable.iteratedTypeOfIterable) { - return isAsyncIterable ? typeAsIterable.iteratedTypeOfAsyncIterable : typeAsIterable.iteratedTypeOfIterable; - } + return mapType(type, getIteratedType); - if (isAsyncIterable) { - // As an optimization, if the type is an instantiation of the global `AsyncIterable` - // or the global `AsyncIterableIterator` then just grab its type argument. - if (isReferenceToType(type, getGlobalAsyncIterableType(/*reportErrors*/ false)) || - isReferenceToType(type, getGlobalAsyncIterableIteratorType(/*reportErrors*/ false))) { - return typeAsIterable.iteratedTypeOfAsyncIterable = (type).typeArguments[0]; + function getIteratedType(type: Type) { + const typeAsIterable = type; + if (allowAsyncIterables) { + if (typeAsIterable.iteratedTypeOfAsyncIterable) { + return typeAsIterable.iteratedTypeOfAsyncIterable; + } + + // As an optimization, if the type is an instantiation of the global `AsyncIterable` + // or the global `AsyncIterableIterator` then just grab its type argument. + if (isReferenceToType(type, getGlobalAsyncIterableType(/*reportErrors*/ false)) || + isReferenceToType(type, getGlobalAsyncIterableIteratorType(/*reportErrors*/ false))) { + return typeAsIterable.iteratedTypeOfAsyncIterable = (type).typeArguments[0]; + } } - } - if (!isAsyncIterable || allowNonAsyncIterables) { - // As an optimization, if the type is an instantiation of the global `Iterable` or - // `IterableIterator` then just grab its type argument. - if (isReferenceToType(type, getGlobalIterableType(/*reportErrors*/ false)) || - isReferenceToType(type, getGlobalIterableIteratorType(/*reportErrors*/ false))) { - return isAsyncIterable - ? typeAsIterable.iteratedTypeOfAsyncIterable = (type).typeArguments[0] - : typeAsIterable.iteratedTypeOfIterable = (type).typeArguments[0]; + if (allowSyncIterables) { + if (typeAsIterable.iteratedTypeOfIterable) { + return typeAsIterable.iteratedTypeOfIterable; + } + + // As an optimization, if the type is an instantiation of the global `Iterable` or + // `IterableIterator` then just grab its type argument. + if (isReferenceToType(type, getGlobalIterableType(/*reportErrors*/ false)) || + isReferenceToType(type, getGlobalIterableIteratorType(/*reportErrors*/ false))) { + return typeAsIterable.iteratedTypeOfIterable = (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; } /** @@ -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); } @@ -22649,7 +22645,7 @@ namespace ts { Debug.assert(expr.parent.kind === SyntaxKind.ArrayLiteralExpression); // [{ property1: p1, property2 }] = elems; const typeOfArrayLiteral = getTypeOfArrayLiteralOrObjectLiteralDestructuringAssignment(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(expr.parent, typeOfArrayLiteral, indexOf((expr.parent).elements, expr), elementType || unknownType); } diff --git a/tests/baselines/reference/forAwaitForUnion.js b/tests/baselines/reference/forAwaitForUnion.js new file mode 100644 index 0000000000..e0a4b9ebf8 --- /dev/null +++ b/tests/baselines/reference/forAwaitForUnion.js @@ -0,0 +1,11 @@ +//// [forAwaitForUnion.ts] +async function f(source: Iterable | AsyncIterable) { + for await (const x of source) { + } +} + +//// [forAwaitForUnion.js] +async function f(source) { + for await (const x of source) { + } +} diff --git a/tests/baselines/reference/forAwaitForUnion.symbols b/tests/baselines/reference/forAwaitForUnion.symbols new file mode 100644 index 0000000000..8b31ea4868 --- /dev/null +++ b/tests/baselines/reference/forAwaitForUnion.symbols @@ -0,0 +1,15 @@ +=== tests/cases/compiler/forAwaitForUnion.ts === +async function f(source: Iterable | AsyncIterable) { +>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)) + } +} diff --git a/tests/baselines/reference/forAwaitForUnion.types b/tests/baselines/reference/forAwaitForUnion.types new file mode 100644 index 0000000000..715fd120ca --- /dev/null +++ b/tests/baselines/reference/forAwaitForUnion.types @@ -0,0 +1,15 @@ +=== tests/cases/compiler/forAwaitForUnion.ts === +async function f(source: Iterable | AsyncIterable) { +>f : (source: Iterable | AsyncIterable) => Promise +>T : T +>source : Iterable | AsyncIterable +>Iterable : Iterable +>T : T +>AsyncIterable : AsyncIterable +>T : T + + for await (const x of source) { +>x : T +>source : Iterable | AsyncIterable + } +} diff --git a/tests/cases/compiler/forAwaitForUnion.ts b/tests/cases/compiler/forAwaitForUnion.ts new file mode 100644 index 0000000000..65e08ab77a --- /dev/null +++ b/tests/cases/compiler/forAwaitForUnion.ts @@ -0,0 +1,6 @@ +// @target: esnext +// @lib: esnext +async function f(source: Iterable | AsyncIterable) { + for await (const x of source) { + } +} \ No newline at end of file