From c6ee25d95ba8ca85ff77a16f14c61ac23d406b6e Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Sat, 5 Nov 2016 18:48:33 -0700 Subject: [PATCH] Type checking for async iterables and async generators. --- Jakefile.js | 3 +- src/compiler/binder.ts | 10 +- src/compiler/checker.ts | 795 ++++++++++++++++----------- src/compiler/diagnosticMessages.json | 20 +- src/compiler/transformers/es2017.ts | 8 +- src/compiler/transformers/ts.ts | 2 +- src/compiler/types.ts | 12 +- src/compiler/utilities.ts | 46 +- src/lib/es2017.asynciterable.d.ts | 23 + src/lib/es2017.d.ts | 3 +- 10 files changed, 595 insertions(+), 327 deletions(-) create mode 100644 src/lib/es2017.asynciterable.d.ts diff --git a/Jakefile.js b/Jakefile.js index 8ce70eb353f..4ec2ed6e4c2 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -299,7 +299,8 @@ var es2016LibrarySourceMap = es2016LibrarySource.map(function (source) { var es2017LibrarySource = [ "es2017.object.d.ts", - "es2017.sharedmemory.d.ts" + "es2017.sharedmemory.d.ts", + "es2017.asynciterable.d.ts" ]; var es2017LibrarySourceMap = es2017LibrarySource.map(function (source) { diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 1d721e2db90..a707fea9824 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -895,8 +895,8 @@ namespace ts { const enclosingLabeledStatement = node.parent.kind === SyntaxKind.LabeledStatement ? lastOrUndefined(activeLabels) : undefined; - // if do statement is wrapped in labeled statement then target labels for break/continue with or without - // label should be the same + // if do statement is wrapped in labeled statement then target labels for break/continue with or without + // label should be the same const preConditionLabel = enclosingLabeledStatement ? enclosingLabeledStatement.continueTarget : createBranchLabel(); const postDoLabel = enclosingLabeledStatement ? enclosingLabeledStatement.breakTarget : createBranchLabel(); addAntecedent(preDoLabel, currentFlow); @@ -2291,7 +2291,7 @@ namespace ts { function bindFunctionDeclaration(node: FunctionDeclaration) { if (!isDeclarationFile(file) && !isInAmbientContext(node)) { - if (isAsyncFunctionLike(node)) { + if (isAsyncFunction(node)) { emitFlags |= NodeFlags.HasAsyncFunctions; } } @@ -2308,7 +2308,7 @@ namespace ts { function bindFunctionExpression(node: FunctionExpression) { if (!isDeclarationFile(file) && !isInAmbientContext(node)) { - if (isAsyncFunctionLike(node)) { + if (isAsyncFunction(node)) { emitFlags |= NodeFlags.HasAsyncFunctions; } } @@ -2322,7 +2322,7 @@ namespace ts { function bindPropertyOrMethodOrAccessor(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags) { if (!isDeclarationFile(file) && !isInAmbientContext(node)) { - if (isAsyncFunctionLike(node)) { + if (isAsyncFunction(node)) { emitFlags |= NodeFlags.HasAsyncFunctions; } if (nodeIsDecorated(node)) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0f2c642124a..512a883e857 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -200,18 +200,14 @@ namespace ts { let getGlobalIterableType: () => GenericType; let getGlobalIteratorType: () => GenericType; let getGlobalIterableIteratorType: () => GenericType; + let getGlobalAsyncIterableType: () => GenericType; + let getGlobalAsyncIteratorType: () => GenericType; + let getGlobalAsyncIterableIteratorType: () => GenericType; - let getGlobalClassDecoratorType: () => ObjectType; - let getGlobalParameterDecoratorType: () => ObjectType; - let getGlobalPropertyDecoratorType: () => ObjectType; - let getGlobalMethodDecoratorType: () => ObjectType; - let getGlobalTypedPropertyDescriptorType: () => ObjectType; - let getGlobalPromiseType: () => ObjectType; - let tryGetGlobalPromiseType: () => ObjectType; - let getGlobalPromiseLikeType: () => ObjectType; - let getInstantiatedGlobalPromiseLikeType: () => ObjectType; + let getGlobalTypedPropertyDescriptorType: () => GenericType; + let getGlobalPromiseType: () => GenericType; + let tryGetGlobalPromiseType: () => GenericType; let getGlobalPromiseConstructorLikeType: () => ObjectType; - let getGlobalThenableType: () => ObjectType; let jsxElementClassType: Type; @@ -2967,10 +2963,6 @@ namespace ts { return type && (type.flags & TypeFlags.Any) !== 0; } - function isTypeNever(type: Type) { - return type && (type.flags & TypeFlags.Never) !== 0; - } - // Return the type of a binding element parent. We check SymbolLinks first to see if a type has been // assigned by contextual typing. function getTypeForBindingElementParent(node: VariableLikeDeclaration) { @@ -3564,6 +3556,11 @@ namespace ts { return unknownType; } + function isReferenceToType(type: Type, target: Type) { + return (getObjectFlags(type) & ObjectFlags.Reference) !== 0 + && (type).target === target; + } + function getTargetType(type: Type): Type { return getObjectFlags(type) & ObjectFlags.Reference ? (type).target : type; } @@ -5337,6 +5334,8 @@ namespace ts { return resolveName(undefined, name, meaning, diagnostic, name); } + function getGlobalType(name: string, arity?: 0): ObjectType; + function getGlobalType(name: string, arity: number): GenericType; function getGlobalType(name: string, arity = 0): ObjectType { return getTypeOfGlobalSymbol(getGlobalTypeSymbol(name), arity); } @@ -5368,12 +5367,20 @@ namespace ts { return genericGlobalType !== emptyGenericType ? createTypeReference(genericGlobalType, typeArguments) : emptyObjectType; } - function createIterableType(elementType: Type): Type { - return createTypeFromGenericGlobalType(getGlobalIterableType(), [elementType]); + function createAsyncIterableType(iteratedType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalAsyncIterableType(), [iteratedType]); } - function createIterableIteratorType(elementType: Type): Type { - return createTypeFromGenericGlobalType(getGlobalIterableIteratorType(), [elementType]); + function createAsyncIterableIteratorType(iteratedType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalAsyncIterableIteratorType(), [iteratedType]); + } + + function createIterableType(iteratedType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalIterableType(), [iteratedType]); + } + + function createIterableIteratorType(iteratedType: Type): Type { + return createTypeFromGenericGlobalType(getGlobalIterableIteratorType(), [iteratedType]); } function createArrayType(elementType: Type): ObjectType { @@ -10221,31 +10228,31 @@ namespace ts { function getContextualTypeForReturnExpression(node: Expression): Type { const func = getContainingFunction(node); - - if (isAsyncFunctionLike(func)) { - const contextualReturnType = getContextualReturnType(func); - if (contextualReturnType) { - return getPromisedType(contextualReturnType); + if (func) { + const functionFlags = getFunctionFlags(func); + if (functionFlags & FunctionFlags.Generator) { // AsyncGenerator function or Generator function + return undefined; } - return undefined; + const contextualReturnType = getContextualReturnType(func); + return functionFlags & FunctionFlags.Async + ? contextualReturnType && getAwaitedTypeOfPromise(contextualReturnType) // Async function + : contextualReturnType; // Regular function } - - if (func && !func.asteriskToken) { - return getContextualReturnType(func); - } - return undefined; } function getContextualTypeForYieldOperand(node: YieldExpression): Type { const func = getContainingFunction(node); if (func) { + const functionFlags = getFunctionFlags(func); const contextualReturnType = getContextualReturnType(func); if (contextualReturnType) { return node.asteriskToken ? contextualReturnType - : getElementTypeOfIterableIterator(contextualReturnType); + : functionFlags & FunctionFlags.Async + ? getIteratedTypeOfAsyncIterableIterator(contextualReturnType) // AsyncGenerator function + : getIteratedTypeOfIterableIterator(contextualReturnType); // Generator function } } @@ -10423,7 +10430,7 @@ namespace ts { const index = indexOf(arrayLiteral.elements, node); return getTypeOfPropertyOfContextualType(type, "" + index) || getIndexTypeOfContextualType(type, IndexKind.Number) - || (languageVersion >= ScriptTarget.ES2015 ? getElementTypeOfIterable(type, /*errorNode*/ undefined) : undefined); + || (languageVersion >= ScriptTarget.ES2015 ? getIteratedTypeOfIterable(type, /*errorNode*/ undefined) : undefined); } return undefined; } @@ -10672,7 +10679,7 @@ namespace ts { // if there is no index type / iterated type. const restArrayType = checkExpression((e).expression, contextualMapper); const restElementType = getIndexTypeOfType(restArrayType, IndexKind.Number) || - (languageVersion >= ScriptTarget.ES2015 ? getElementTypeOfIterable(restArrayType, /*errorNode*/ undefined) : undefined); + (languageVersion >= ScriptTarget.ES2015 ? getIteratedTypeOfIterable(restArrayType, /*errorNode*/ undefined) : undefined); if (restElementType) { elementTypes.push(restElementType); } @@ -13068,6 +13075,10 @@ namespace ts { pos < signature.parameters.length ? getTypeOfParameter(signature.parameters[pos]) : anyType; } + function getTypeOfFirstParameterOfSignature(signature: Signature) { + return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : neverType; + } + function assignContextualParameterTypes(signature: Signature, context: Signature, mapper: TypeMapper) { const len = signature.parameters.length - (signature.hasRestParameter ? 1 : 0); if (isInferentialContext(mapper)) { @@ -13178,7 +13189,7 @@ namespace ts { const globalPromiseType = getGlobalPromiseType(); if (globalPromiseType !== emptyGenericType) { // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type - promisedType = getAwaitedType(promisedType); + promisedType = getAwaitedType(promisedType) || emptyObjectType; return createTypeReference(globalPromiseType, [promisedType]); } @@ -13201,25 +13212,26 @@ namespace ts { return unknownType; } - const isAsync = isAsyncFunctionLike(func); + const functionFlags = getFunctionFlags(func); let type: Type; if (func.body.kind !== SyntaxKind.Block) { type = checkExpressionCached(func.body, contextualMapper); - if (isAsync) { + if (functionFlags & FunctionFlags.Async) { // From within an async function you can return either a non-promise value or a promise. Any // Promise/A+ compatible implementation will always assimilate any foreign promise, so the // return type of the body should be unwrapped to its awaited type, which we will wrap in // the native Promise type later in this function. - type = checkAwaitedType(type, func, Diagnostics.Return_expression_in_async_function_does_not_have_a_valid_callable_then_member); + type = checkAwaitedType(type, /*errorNode*/ func); } } else { let types: Type[]; - const funcIsGenerator = !!func.asteriskToken; - if (funcIsGenerator) { + if (functionFlags & FunctionFlags.Generator) { // Generator or AsyncGenerator function types = checkAndAggregateYieldOperandTypes(func, contextualMapper); if (types.length === 0) { - const iterableIteratorAny = createIterableIteratorType(anyType); + const iterableIteratorAny = functionFlags & FunctionFlags.Async + ? createAsyncIterableIteratorType(anyType) // AsyncGenerator function + : createIterableIteratorType(anyType); // Generator function if (compilerOptions.noImplicitAny) { error(func.asteriskToken, Diagnostics.Generator_implicitly_has_type_0_because_it_does_not_yield_any_values_Consider_supplying_a_return_type, typeToString(iterableIteratorAny)); @@ -13231,23 +13243,31 @@ namespace ts { types = checkAndAggregateReturnExpressionTypes(func, contextualMapper); if (!types) { // For an async function, the return type will not be never, but rather a Promise for never. - return isAsync ? createPromiseReturnType(func, neverType) : neverType; + return functionFlags & FunctionFlags.Async + ? createPromiseReturnType(func, neverType) // Async function + : neverType; // Normal function } if (types.length === 0) { // For an async function, the return type will not be void, but rather a Promise for void. - return isAsync ? createPromiseReturnType(func, voidType) : voidType; + return functionFlags & FunctionFlags.Async + ? createPromiseReturnType(func, voidType) // Async function + : voidType; // Normal function } } // Return a union of the return expression types. type = getUnionType(types, /*subtypeReduction*/ true); - if (funcIsGenerator) { - type = createIterableIteratorType(type); + if (functionFlags & FunctionFlags.Generator) { // AsyncGenerator function or Generator function + type = functionFlags & FunctionFlags.Async + ? createAsyncIterableIteratorType(type) // AsyncGenerator function + : createIterableIteratorType(type); // Generator function } } + if (!contextualSignature) { reportErrorsFromWidening(func, type); } + if (isUnitType(type) && !(contextualSignature && isLiteralContextualType( @@ -13259,22 +13279,24 @@ namespace ts { // From within an async function you can return either a non-promise value or a promise. Any // Promise/A+ compatible implementation will always assimilate any foreign promise, so the // return type of the body is awaited type of the body, wrapped in a native Promise type. - return isAsync ? createPromiseReturnType(func, widenedType) : widenedType; + return (functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async + ? createPromiseReturnType(func, widenedType) // Async function + : widenedType; // Generator function, AsyncGenerator function, or normal function } function checkAndAggregateYieldOperandTypes(func: FunctionLikeDeclaration, contextualMapper: TypeMapper): Type[] { const aggregatedTypes: Type[] = []; - + const functionFlags = getFunctionFlags(func); forEachYieldExpression(func.body, yieldExpression => { const expr = yieldExpression.expression; if (expr) { let type = checkExpressionCached(expr, contextualMapper); - if (yieldExpression.asteriskToken) { // A yield* expression effectively yields everything that its operand yields - type = checkElementTypeOfIterable(type, yieldExpression.expression); + type = functionFlags & FunctionFlags.Async + ? checkIteratedTypeOfIterableOrAsyncIterable(type, yieldExpression.expression) // AsyncGenerator function + : checkIteratedTypeOfIterable(type, yieldExpression.expression); // Generator function } - if (!contains(aggregatedTypes, type)) { aggregatedTypes.push(type); } @@ -13311,7 +13333,7 @@ namespace ts { } function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, contextualMapper: TypeMapper): Type[] { - const isAsync = isAsyncFunctionLike(func); + const functionFlags = getFunctionFlags(func); const aggregatedTypes: Type[] = []; let hasReturnWithNoExpression = functionHasImplicitReturn(func); let hasReturnOfTypeNever = false; @@ -13319,12 +13341,12 @@ namespace ts { const expr = returnStatement.expression; if (expr) { let type = checkExpressionCached(expr, contextualMapper); - if (isAsync) { + if (functionFlags & FunctionFlags.Async) { // From within an async function you can return either a non-promise value or a promise. Any // Promise/A+ compatible implementation will always assimilate any foreign promise, so the // return type of the body should be unwrapped to its awaited type, which should be wrapped in // the native Promise type by the caller. - type = checkAwaitedType(type, func, Diagnostics.Return_expression_in_async_function_does_not_have_a_valid_callable_then_member); + type = checkAwaitedType(type, func); } if (type.flags & TypeFlags.Never) { hasReturnOfTypeNever = true; @@ -13467,9 +13489,13 @@ namespace ts { function checkFunctionExpressionOrObjectLiteralMethodDeferred(node: ArrowFunction | FunctionExpression | MethodDeclaration) { Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); - const isAsync = isAsyncFunctionLike(node); - const returnOrPromisedType = node.type && (isAsync ? checkAsyncFunctionReturnType(node) : getTypeFromTypeNode(node.type)); - if (!node.asteriskToken) { + const functionFlags = getFunctionFlags(node); + const returnOrPromisedType = node.type && + ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async ? + checkAsyncFunctionReturnType(node) : // Async function + getTypeFromTypeNode(node.type)); // AsyncGenerator function, Generator function, or normal function + + if (functionFlags & FunctionFlags.Generator) { // AsyncGenerator function or Generator function // return is not necessary in the body of generators checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnOrPromisedType); } @@ -13495,11 +13521,11 @@ namespace ts { // its return type annotation. const exprType = checkExpression(node.body); if (returnOrPromisedType) { - if (isAsync) { - const awaitedType = checkAwaitedType(exprType, node.body, Diagnostics.Expression_body_for_async_arrow_function_does_not_have_a_valid_callable_then_member); + if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function + const awaitedType = checkAwaitedType(exprType, node.body); checkTypeAssignableTo(awaitedType, returnOrPromisedType, node.body); } - else { + else { // Normal function checkTypeAssignableTo(exprType, returnOrPromisedType, node.body); } } @@ -14203,18 +14229,24 @@ namespace ts { const func = getContainingFunction(node); // If the user's code is syntactically correct, the func should always have a star. After all, // we are in a yield context. - if (func && func.asteriskToken) { + const functionFlags = func && getFunctionFlags(func); + if (functionFlags & FunctionFlags.Generator) { const expressionType = checkExpressionCached(node.expression, /*contextualMapper*/ undefined); let expressionElementType: Type; const nodeIsYieldStar = !!node.asteriskToken; if (nodeIsYieldStar) { - expressionElementType = checkElementTypeOfIterable(expressionType, node.expression); + expressionElementType = functionFlags & FunctionFlags.Async + ? checkIteratedTypeOfIterableOrAsyncIterable(expressionType, node.expression) // AsyncGenerator function + : checkIteratedTypeOfIterable(expressionType, node.expression); // Generator function } + // There is no point in doing an assignability check if the function // has no explicit return type because the return type is directly computed // from the yield expressions. if (func.type) { - const signatureElementType = getElementTypeOfIterableIterator(getTypeFromTypeNode(func.type)) || anyType; + const signatureElementType = functionFlags & FunctionFlags.Async + ? getIteratedTypeOfAsyncIterableIterator(getTypeFromTypeNode(func.type)) || anyType // AsyncGenerator function + : getIteratedTypeOfIterableIterator(getTypeFromTypeNode(func.type)) || anyType; // Generator function if (nodeIsYieldStar) { checkTypeAssignableTo(expressionElementType, signatureElementType, node.expression, /*headMessage*/ undefined); } @@ -14529,16 +14561,6 @@ namespace ts { } } - function isSyntacticallyValidGenerator(node: SignatureDeclaration): boolean { - if (!(node).asteriskToken || !(node).body) { - return false; - } - - return node.kind === SyntaxKind.MethodDeclaration || - node.kind === SyntaxKind.FunctionDeclaration || - node.kind === SyntaxKind.FunctionExpression; - } - function getTypePredicateParameterIndex(parameterList: NodeArray, parameter: Identifier): number { if (parameterList) { for (let i = 0; i < parameterList.length; i++) { @@ -14680,13 +14702,14 @@ namespace ts { } if (node.type) { - if (languageVersion >= ScriptTarget.ES2015 && isSyntacticallyValidGenerator(node)) { + const functionFlags = getFunctionFlags(node); + if (languageVersion >= ScriptTarget.ES2015 && (functionFlags & FunctionFlags.InvalidGenerator) === FunctionFlags.Generator) { const returnType = getTypeFromTypeNode(node.type); if (returnType === voidType) { error(node.type, Diagnostics.A_generator_cannot_have_a_void_type_annotation); } else { - const generatorElementType = getElementTypeOfIterableIterator(returnType) || anyType; + const generatorElementType = getIteratedTypeOfIterableIterator(returnType) || anyType; const iterableIteratorInstantiation = createIterableIteratorType(generatorElementType); // Naively, one could check that IterableIterator is assignable to the return type annotation. @@ -14698,7 +14721,7 @@ namespace ts { checkTypeAssignableTo(iterableIteratorInstantiation, returnType, node.type); } } - else if (isAsyncFunctionLike(node)) { + else if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { checkAsyncFunctionReturnType(node); } } @@ -15426,21 +15449,29 @@ namespace ts { } } - function checkNonThenableType(type: Type, location?: Node, message?: DiagnosticMessage) { - type = getWidenedType(type); - if (!isTypeAny(type) && !isTypeNever(type) && isTypeAssignableTo(type, getGlobalThenableType())) { - if (location) { - if (!message) { - message = Diagnostics.Operand_for_await_does_not_have_a_valid_callable_then_member; - } + function getAwaitedTypeOfPromise(type: Type, errorNode?: Node): Type | undefined { + const promisedType = getPromisedTypeOfPromise(type, errorNode); + return promisedType && getAwaitedType(promisedType, errorNode); + } - error(location, message); - } + /** + * Determines whether a type has a callable 'then' method. + */ + function isThenableType(type: Type) { + // + // { + // then( // thenFunction + // ): any; + // } + // - return unknownType; + const thenFunction = !isTypeAny(type) && getTypeOfPropertyOfType(type, "then"); + const thenSignatures = thenFunction ? getSignaturesOfType(thenFunction, SignatureKind.Call) : emptyArray; + if (thenSignatures.length > 0) { + return true; } - return type; + return false; } /** @@ -15448,7 +15479,7 @@ namespace ts { * @param type The type of the promise. * @remarks The "promised type" of a type is the type of the "value" parameter of the "onfulfilled" callback. */ - function getPromisedType(promise: Type): Type { + function getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type { // // { // promise // then( // thenFunction @@ -15463,25 +15494,26 @@ namespace ts { return undefined; } - if (getObjectFlags(promise) & ObjectFlags.Reference) { - if ((promise).target === tryGetGlobalPromiseType() - || (promise).target === getGlobalPromiseLikeType()) { - return (promise).typeArguments[0]; - } + const typeAsPromise = promise; + if (typeAsPromise.promisedTypeOfPromise) { + return typeAsPromise.promisedTypeOfPromise; } - const globalPromiseLikeType = getInstantiatedGlobalPromiseLikeType(); - if (globalPromiseLikeType === emptyObjectType || !isTypeAssignableTo(promise, globalPromiseLikeType)) { - return undefined; + if (isReferenceToType(promise, tryGetGlobalPromiseType()) || + isReferenceToType(promise, getGlobalPromiseType())) { + return typeAsPromise.promisedTypeOfPromise = (promise).typeArguments[0]; } const thenFunction = getTypeOfPropertyOfType(promise, "then"); - if (!thenFunction || isTypeAny(thenFunction)) { + if (isTypeAny(thenFunction)) { return undefined; } - const thenSignatures = getSignaturesOfType(thenFunction, SignatureKind.Call); + const thenSignatures = thenFunction ? getSignaturesOfType(thenFunction, SignatureKind.Call) : emptyArray; if (thenSignatures.length === 0) { + if (errorNode) { + error(errorNode, Diagnostics.A_promise_must_have_a_then_method); + } return undefined; } @@ -15492,14 +15524,13 @@ namespace ts { const onfulfilledParameterSignatures = getSignaturesOfType(onfulfilledParameterType, SignatureKind.Call); if (onfulfilledParameterSignatures.length === 0) { + if (errorNode) { + error(errorNode, Diagnostics.The_first_parameter_of_the_then_method_of_a_promise_must_be_a_callback); + } return undefined; } - return getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), /*subtypeReduction*/ true); - } - - function getTypeOfFirstParameterOfSignature(signature: Signature) { - return signature.parameters.length > 0 ? getTypeAtPosition(signature, 0) : neverType; + return typeAsPromise.promisedTypeOfPromise = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature), /*subtypeReduction*/ true); } /** @@ -15509,96 +15540,111 @@ namespace ts { * Promise-like type; otherwise, it is the type of the expression. This is used to reflect * The runtime behavior of the `await` keyword. */ - function getAwaitedType(type: Type) { - return checkAwaitedType(type, /*location*/ undefined, /*message*/ undefined); + function checkAwaitedType(type: Type, errorNode: Node): Type { + return getAwaitedType(type, errorNode) || unknownType; } - function checkAwaitedType(type: Type, location?: Node, message?: DiagnosticMessage) { - return checkAwaitedTypeWorker(type); - - function checkAwaitedTypeWorker(type: Type): Type { - if (type.flags & TypeFlags.Union) { - const types: Type[] = []; - for (const constituentType of (type).types) { - types.push(checkAwaitedTypeWorker(constituentType)); - } - - return getUnionType(types, /*subtypeReduction*/ true); - } - else { - const promisedType = getPromisedType(type); - if (promisedType === undefined) { - // The type was not a PromiseLike, so it could not be unwrapped any further. - // As long as the type does not have a callable "then" property, it is - // safe to return the type; otherwise, an error will have been reported in - // the call to checkNonThenableType and we will return unknownType. - // - // An example of a non-promise "thenable" might be: - // - // await { then(): void {} } - // - // The "thenable" does not match the minimal definition for a PromiseLike. When - // a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise - // will never settle. We treat this as an error to help flag an early indicator - // of a runtime problem. If the user wants to return this value from an async - // function, they would need to wrap it in some other value. If they want it to - // be treated as a promise, they can cast to . - return checkNonThenableType(type, location, message); - } - else { - if (type.id === promisedType.id || indexOf(awaitedTypeStack, promisedType.id) >= 0) { - // We have a bad actor in the form of a promise whose promised type is - // the same promise type, or a mutually recursive promise. Return the - // unknown type as we cannot guess the shape. If this were the actual - // case in the JavaScript, this Promise would never resolve. - // - // An example of a bad actor with a singly-recursive promise type might - // be: - // - // interface BadPromise { - // then( - // onfulfilled: (value: BadPromise) => any, - // onrejected: (error: any) => any): BadPromise; - // } - // - // The above interface will pass the PromiseLike check, and return a - // promised type of `BadPromise`. Since this is a self reference, we - // don't want to keep recursing ad infinitum. - // - // An example of a bad actor in the form of a mutually-recursive - // promise type might be: - // - // interface BadPromiseA { - // then( - // onfulfilled: (value: BadPromiseB) => any, - // onrejected: (error: any) => any): BadPromiseB; - // } - // - // interface BadPromiseB { - // then( - // onfulfilled: (value: BadPromiseA) => any, - // onrejected: (error: any) => any): BadPromiseA; - // } - // - if (location) { - error( - location, - Diagnostics._0_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method, - symbolToString(type.symbol)); - } - - return unknownType; - } - - // Keep track of the type we're about to unwrap to avoid bad recursive promise types. - // See the comments above for more information. - awaitedTypeStack.push(type.id); - const awaitedType = checkAwaitedTypeWorker(promisedType); - awaitedTypeStack.pop(); - return awaitedType; - } - } + function getAwaitedType(type: Type, errorNode?: Node): Type | undefined { + const typeAsAwaitable = type; + if (typeAsAwaitable.awaitedTypeOfType) { + return typeAsAwaitable.awaitedTypeOfType; } + + if (isTypeAny(type)) { + return typeAsAwaitable.awaitedTypeOfType = type; + } + + if (type.flags & TypeFlags.Union) { + let types: Type[]; + for (const constituentType of (type).types) { + types = append(types, getAwaitedType(constituentType, errorNode)); + } + + if (!types) { + return undefined; + } + + return typeAsAwaitable.awaitedTypeOfType = getUnionType(types, /*subtypeReduction*/ true); + } + + const promisedType = getPromisedTypeOfPromise(type); + if (promisedType) { + if (type.id === promisedType.id || indexOf(awaitedTypeStack, promisedType.id) >= 0) { + // Verify that we don't have a bad actor in the form of a promise whose + // promised type is the same as the promise type, or a mutually recursive + // promise. If so, we returnundefined as we cannot guess the shape. If this + // were the actual case in the JavaScript, this Promise would never resolve. + // + // An example of a bad actor with a singly-recursive promise type might + // be: + // + // interface BadPromise { + // then( + // onfulfilled: (value: BadPromise) => any, + // onrejected: (error: any) => any): BadPromise; + // } + // The above interface will pass the PromiseLike check, and return a + // promised type of `BadPromise`. Since this is a self reference, we + // don't want to keep recursing ad infinitum. + // + // An example of a bad actor in the form of a mutually-recursive + // promise type might be: + // + // interface BadPromiseA { + // then( + // onfulfilled: (value: BadPromiseB) => any, + // onrejected: (error: any) => any): BadPromiseB; + // } + // + // interface BadPromiseB { + // then( + // onfulfilled: (value: BadPromiseA) => any, + // onrejected: (error: any) => any): BadPromiseA; + // } + // + if (errorNode) { + error(errorNode, Diagnostics.Type_is_referenced_directly_or_indirectly_in_the_fulfillment_callback_of_its_own_then_method); + } + return undefined; + } + + // Keep track of the type we're about to unwrap to avoid bad recursive promise types. + // See the comments above for more information. + awaitedTypeStack.push(type.id); + const awaitedType = getAwaitedType(promisedType, errorNode); + awaitedTypeStack.pop(); + + if (!awaitedType) { + return undefined; + } + + return typeAsAwaitable.awaitedTypeOfType = awaitedType; + } + + // The type was not a promise, so it could not be unwrapped any further. + // As long as the type does not have a callable "then" property, it is + // safe to return the type; otherwise, an error will be reported in + // the call to getNonThenableType and we will return undefined. + // + // An example of a non-promise "thenable" might be: + // + // await { then(): void {} } + // + // The "thenable" does not match the minimal definition for a promise. When + // a Promise/A+-compatible or ES6 promise tries to adopt this value, the promise + // will never settle. We treat this as an error to help flag an early indicator + // of a runtime problem. If the user wants to return this value from an async + // function, they would need to wrap it in some other value. If they want it to + // be treated as a promise, they can cast to . + const widenedType = getWidenedType(type); + if (isThenableType(widenedType)) { + if (errorNode) { + error(errorNode, Diagnostics.Type_used_as_operand_to_await_or_the_return_type_of_an_async_function_must_not_contain_a_callable_then_member_if_it_is_not_a_promise); + } + return undefined; + } + + return typeAsAwaitable.awaitedTypeOfType = widenedType; } /** @@ -15699,7 +15745,7 @@ namespace ts { } // Get and return the awaited type of the return type. - return checkAwaitedType(returnType, node, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type); + return checkAwaitedType(returnType, node); } /** Check a decorator */ @@ -15830,7 +15876,7 @@ namespace ts { function checkFunctionOrMethodDeclaration(node: FunctionDeclaration | MethodDeclaration): void { checkDecorators(node); checkSignatureDeclaration(node); - const isAsync = isAsyncFunctionLike(node); + const functionFlags = getFunctionFlags(node); // Do not use hasDynamicName here, because that returns false for well known symbols. // We want to perform checkComputedPropertyName for all computed properties, including @@ -15872,8 +15918,10 @@ namespace ts { checkSourceElement(node.body); - if (!node.asteriskToken) { - const returnOrPromisedType = node.type && (isAsync ? checkAsyncFunctionReturnType(node) : getTypeFromTypeNode(node.type)); + if ((functionFlags & FunctionFlags.Generator) === 0) { // Async function or normal function + const returnOrPromisedType = node.type && (functionFlags & FunctionFlags.Async + ? checkAsyncFunctionReturnType(node) // Async function + : getTypeFromTypeNode(node.type)); // normal function checkAllCodePathsInNonVoidFunctionReturnOrThrow(node, returnOrPromisedType); } @@ -15884,7 +15932,7 @@ namespace ts { reportImplicitAnyError(node, anyType); } - if (node.asteriskToken && nodeIsPresent(node.body)) { + if (functionFlags & FunctionFlags.Generator && nodeIsPresent(node.body)) { // A generator with a body and no type annotation can still cause errors. It can error if the // yielded values have no common supertype, or it can give an implicit any error if it has no // yielded values. The only way to trigger these errors is to try checking its return type. @@ -16458,10 +16506,10 @@ namespace ts { forEach(node.declarationList.declarations, checkSourceElement); } - function checkGrammarDisallowedModifiersOnObjectLiteralExpressionMethod(node: Node) { + function checkGrammarDisallowedModifiersOnObjectLiteralExpressionMethod(node: MethodDeclaration) { // We only disallow modifier on a method declaration if it is a property of object-literal-expression if (node.modifiers && node.parent.kind === SyntaxKind.ObjectLiteralExpression) { - if (isAsyncFunctionLike(node)) { + if (getFunctionFlags(node) & FunctionFlags.Async) { if (node.modifiers.length > 1) { return grammarErrorOnFirstToken(node, Diagnostics.Modifiers_cannot_appear_here); } @@ -16644,7 +16692,7 @@ namespace ts { return inputType; } if (languageVersion >= ScriptTarget.ES2015) { - return checkElementTypeOfIterable(inputType, errorNode); + return checkIteratedTypeOfIterable(inputType, errorNode); } if (allowStringInput) { return checkElementTypeOfArrayOrString(inputType, errorNode); @@ -16664,8 +16712,8 @@ namespace ts { /** * When errorNode is undefined, it means we should not report any errors. */ - function checkElementTypeOfIterable(iterable: Type, errorNode: Node): Type { - const elementType = getElementTypeOfIterable(iterable, errorNode); + function checkIteratedTypeOfIterable(iterable: Type, errorNode: Node): Type { + const elementType = getIteratedTypeOfIterable(iterable, errorNode); // Now even though we have extracted the iteratedType, we will have to validate that the type // passed in is actually an Iterable. if (errorNode && elementType) { @@ -16675,12 +16723,169 @@ namespace ts { return elementType || anyType; } + function checkIteratedTypeOfIterableOrAsyncIterable(asyncIterable: Type, errorNode: Node): Type { + // A yield* in an async generator function will first attempt to create an async + // iterator by calling `[Symbol.asyncIterator]()`. If the operand does not have a callable + // `Symbol.asyncIterator` method, it then tries to call `[Symbol.iterator]()` and wrap the + // result in an async iterator. + const elementType = getIteratedTypeOfAsyncIterable(asyncIterable, errorNode, /*allowIterables*/ true); + // Now even though we have extracted the iteratedType, we will have to validate that the type + // passed in is actually an Iterable. + if (errorNode && elementType) { + checkTypeAssignableTo(asyncIterable, createAsyncIterableType(elementType), errorNode); + } + + return elementType || anyType; + } + + + /** + * We want to treat type as an iterable, and get the type it is an async iterable of. The + * async iterable must have the following structure (annotated with the names of the + * variables below): + * + * { // asyncIterable + * [Symbol.asyncIterator]: { // asyncIteratorMethod + * (): AsyncIterator + * } + * } + * + * T is the type we are after. At every level that involves analyzing return types + * of signatures, we union the return types of all the signatures. + * + * Another thing to note is that at any step of this process, we could run into a dead end, + * meaning either the property is missing, or we run into the anyType. If either of these things + * happens, we return undefined to signal that we could not find the iterated type. If a property + * is missing, and the previous step did not result in 'any', then we also give an error if the + * caller requested it. Then the caller can decide what to do in the case where there is no iterated + * type. This is different from returning anyType, because that would signify that we have matched the + * whole pattern and that T (above) is 'any'. + */ + function getIteratedTypeOfAsyncIterable(type: Type, errorNode: Node, allowIterables: boolean): Type { + if (isTypeAny(type)) { + return undefined; + } + + const typeAsAsyncIterable = type; + if (typeAsAsyncIterable.iteratedTypeOfAsyncIterable) { + return typeAsAsyncIterable.iteratedTypeOfAsyncIterable; + } + + // As an optimization, if the type is instantiated directly using the + // globalAsyncIterableType (AsyncIterable) or globalAsyncIterableIteratorType + // (AsyncIterableIterator), then just grab its type argument. + if (isReferenceToType(type, getGlobalAsyncIterableType()) || + isReferenceToType(type, getGlobalAsyncIterableIteratorType())) { + return typeAsAsyncIterable.iteratedTypeOfAsyncIterable = (type).typeArguments[0]; + } + + if (allowIterables) { + if (isReferenceToType(type, getGlobalIterableType()) || + isReferenceToType(type, getGlobalIterableIteratorType())) { + return typeAsAsyncIterable.iteratedTypeOfAsyncIterable = (type).typeArguments[0]; + } + } + + const asyncIteratorMethod = getTypeOfPropertyOfType(type, getPropertyNameForKnownSymbolName("asyncIterator")); + if (isTypeAny(asyncIteratorMethod)) { + return undefined; + } + + const asyncIteratorMethodSignatures = asyncIteratorMethod ? getSignaturesOfType(asyncIteratorMethod, SignatureKind.Call) : emptyArray; + if (asyncIteratorMethodSignatures.length === 0) { + if (allowIterables) { + const iteratedType = getIteratedTypeOfIterable(type, /*errorNode*/ undefined); + if (iteratedType) { + return typeAsAsyncIterable.iteratedTypeOfAsyncIterable = iteratedType; + } + } + + if (errorNode) { + error(errorNode, Diagnostics.Type_must_have_a_Symbol_asyncIterator_method_that_returns_an_async_iterator); + } + return undefined; + } + + return typeAsAsyncIterable.iteratedTypeOfAsyncIterable = getIteratedTypeOfAsyncIterator(getUnionType(map(asyncIteratorMethodSignatures, getReturnTypeOfSignature), /*subtypeReduction*/ true), errorNode); + } + + /** + * This function has very similar logic as getElementTypeOfAsyncIterable, except that it operates on + * AsyncIterators instead of AsyncIterables. Here is the structure: + * + * { // asyncIterator + * next: { // nextMethod + * (): PromiseLike<{ // nextResult + * value: T // nextValue + * }> + * } + * } + * + */ + function getIteratedTypeOfAsyncIterator(type: Type, errorNode: Node): Type { + if (isTypeAny(type)) { + return undefined; + } + + const typeAsAsyncIterator = type; + if (typeAsAsyncIterator.iteratedTypeOfAsyncIterator) { + return typeAsAsyncIterator.iteratedTypeOfAsyncIterator; + } + + // As an optimization, if the type is instantiated directly using the + // globalAsyncIteratorType (AsyncIterator), then just grab its type argument. + if (isReferenceToType(type, getGlobalAsyncIteratorType())) { + return typeAsAsyncIterator.iteratedTypeOfAsyncIterator = (type).typeArguments[0]; + } + + const nextMethod = getTypeOfPropertyOfType(type, "next"); + if (isTypeAny(nextMethod)) { + return undefined; + } + + const nextMethodSignatures = nextMethod ? getSignaturesOfType(nextMethod, SignatureKind.Call) : emptyArray; + if (nextMethodSignatures.length === 0) { + if (errorNode) { + error(errorNode, Diagnostics.An_async_iterator_must_have_a_next_method); + } + return undefined; + } + + const nextResult = getUnionType(map(nextMethodSignatures, getReturnTypeOfSignature), /*subtypeReduction*/ true); + if (isTypeAny(nextResult)) { + return undefined; + } + + const awaitedResult = getAwaitedTypeOfPromise(nextResult, errorNode); + if (isTypeAny(awaitedResult)) { + return undefined; + } + + const nextValue = awaitedResult && getTypeOfPropertyOfType(awaitedResult, "value"); + if (!nextValue) { + if (errorNode) { + error(errorNode, Diagnostics.The_type_returned_by_the_next_method_of_an_async_iterator_must_be_a_promise_for_a_type_with_a_value_property); + } + return undefined; + } + + return typeAsAsyncIterator.iteratedTypeOfAsyncIterator = nextValue; + } + + function getIteratedTypeOfAsyncIterableIterator(type: Type): Type { + if (isTypeAny(type)) { + return undefined; + } + return getIteratedTypeOfAsyncIterable(type, /*errorNode*/ undefined, /*allowIterables*/ false) + || getIteratedTypeOfAsyncIterator(type, /*errorNode*/ undefined); + } + /** * We want to treat type as an iterable, and get the type it is an iterable of. The iterable * must have the following structure (annotated with the names of the variables below): * * { // iterable - * [Symbol.iterator]: { // iteratorFunction + * [Symbol.iterator]: { // iteratorMethod * (): Iterator * } * } @@ -16696,37 +16901,38 @@ namespace ts { * type. This is different from returning anyType, because that would signify that we have matched the * whole pattern and that T (above) is 'any'. */ - function getElementTypeOfIterable(type: Type, errorNode: Node): Type { + function getIteratedTypeOfIterable(type: Type, errorNode: Node): Type { if (isTypeAny(type)) { return undefined; } const typeAsIterable = type; - if (!typeAsIterable.iterableElementType) { - // As an optimization, if the type is instantiated directly using the globalIterableType (Iterable), - // then just grab its type argument. - if ((getObjectFlags(type) & ObjectFlags.Reference) && (type).target === getGlobalIterableType()) { - typeAsIterable.iterableElementType = (type).typeArguments[0]; - } - else { - const iteratorFunction = getTypeOfPropertyOfType(type, getPropertyNameForKnownSymbolName("iterator")); - if (isTypeAny(iteratorFunction)) { - return undefined; - } - - const iteratorFunctionSignatures = iteratorFunction ? getSignaturesOfType(iteratorFunction, SignatureKind.Call) : emptyArray; - if (iteratorFunctionSignatures.length === 0) { - if (errorNode) { - error(errorNode, Diagnostics.Type_must_have_a_Symbol_iterator_method_that_returns_an_iterator); - } - return undefined; - } - - typeAsIterable.iterableElementType = getElementTypeOfIterator(getUnionType(map(iteratorFunctionSignatures, getReturnTypeOfSignature), /*subtypeReduction*/ true), errorNode); - } + if (typeAsIterable.iteratedTypeOfIterable) { + return typeAsIterable.iteratedTypeOfIterable; } - return typeAsIterable.iterableElementType; + // As an optimization, if the type is instantiated directly using the + // globalIterableType (Iterable) or globalIterableIteratorType (IterableIterator), + // then just grab its type argument. + if (isReferenceToType(type, getGlobalIterableType()) || + isReferenceToType(type, getGlobalIterableIteratorType())) { + return typeAsIterable.iteratedTypeOfIterable = (type).typeArguments[0]; + } + + const iteratorMethod = getTypeOfPropertyOfType(type, getPropertyNameForKnownSymbolName("iterator")); + if (isTypeAny(iteratorMethod)) { + return undefined; + } + + const iteratorMethodSignatures = iteratorMethod ? getSignaturesOfType(iteratorMethod, SignatureKind.Call) : emptyArray; + if (iteratorMethodSignatures.length === 0) { + if (errorNode) { + error(errorNode, Diagnostics.Type_must_have_a_Symbol_iterator_method_that_returns_an_iterator); + } + return undefined; + } + + return typeAsIterable.iteratedTypeOfIterable = getIteratedTypeOfIterator(getUnionType(map(iteratorMethodSignatures, getReturnTypeOfSignature), /*subtypeReduction*/ true), errorNode); } /** @@ -16742,65 +16948,58 @@ namespace ts { * } * */ - function getElementTypeOfIterator(type: Type, errorNode: Node): Type { + function getIteratedTypeOfIterator(type: Type, errorNode: Node): Type { if (isTypeAny(type)) { return undefined; } const typeAsIterator = type; - if (!typeAsIterator.iteratorElementType) { - // As an optimization, if the type is instantiated directly using the globalIteratorType (Iterator), - // then just grab its type argument. - if ((getObjectFlags(type) & ObjectFlags.Reference) && (type).target === getGlobalIteratorType()) { - typeAsIterator.iteratorElementType = (type).typeArguments[0]; - } - else { - const iteratorNextFunction = getTypeOfPropertyOfType(type, "next"); - if (isTypeAny(iteratorNextFunction)) { - return undefined; - } - - const iteratorNextFunctionSignatures = iteratorNextFunction ? getSignaturesOfType(iteratorNextFunction, SignatureKind.Call) : emptyArray; - if (iteratorNextFunctionSignatures.length === 0) { - if (errorNode) { - error(errorNode, Diagnostics.An_iterator_must_have_a_next_method); - } - return undefined; - } - - const iteratorNextResult = getUnionType(map(iteratorNextFunctionSignatures, getReturnTypeOfSignature), /*subtypeReduction*/ true); - if (isTypeAny(iteratorNextResult)) { - return undefined; - } - - const iteratorNextValue = getTypeOfPropertyOfType(iteratorNextResult, "value"); - if (!iteratorNextValue) { - if (errorNode) { - error(errorNode, Diagnostics.The_type_returned_by_the_next_method_of_an_iterator_must_have_a_value_property); - } - return undefined; - } - - typeAsIterator.iteratorElementType = iteratorNextValue; - } + if (typeAsIterator.iteratedTypeOfIterator) { + return typeAsIterator.iteratedTypeOfIterator; } - return typeAsIterator.iteratorElementType; + // As an optimization, if the type is instantiated directly using the globalIteratorType (Iterator), + // then just grab its type argument. + if (isReferenceToType(type, getGlobalIteratorType())) { + return typeAsIterator.iteratedTypeOfIterator = (type).typeArguments[0]; + } + + const iteratorNextFunction = getTypeOfPropertyOfType(type, "next"); + if (isTypeAny(iteratorNextFunction)) { + return undefined; + } + + const iteratorNextFunctionSignatures = iteratorNextFunction ? getSignaturesOfType(iteratorNextFunction, SignatureKind.Call) : emptyArray; + if (iteratorNextFunctionSignatures.length === 0) { + if (errorNode) { + error(errorNode, Diagnostics.An_iterator_must_have_a_next_method); + } + return undefined; + } + + const iteratorNextResult = getUnionType(map(iteratorNextFunctionSignatures, getReturnTypeOfSignature), /*subtypeReduction*/ true); + if (isTypeAny(iteratorNextResult)) { + return undefined; + } + + const iteratorNextValue = getTypeOfPropertyOfType(iteratorNextResult, "value"); + if (!iteratorNextValue) { + if (errorNode) { + error(errorNode, Diagnostics.The_type_returned_by_the_next_method_of_an_iterator_must_have_a_value_property); + } + return undefined; + } + + return typeAsIterator.iteratedTypeOfIterator = iteratorNextValue; } - function getElementTypeOfIterableIterator(type: Type): Type { + function getIteratedTypeOfIterableIterator(type: Type): Type { if (isTypeAny(type)) { return undefined; } - // As an optimization, if the type is instantiated directly using the globalIterableIteratorType (IterableIterator), - // then just grab its type argument. - if ((getObjectFlags(type) & ObjectFlags.Reference) && (type).target === getGlobalIterableIteratorType()) { - return (type).typeArguments[0]; - } - - return getElementTypeOfIterable(type, /*errorNode*/ undefined) || - getElementTypeOfIterator(type, /*errorNode*/ undefined); + return getIteratedTypeOfIterable(type, /*errorNode*/ undefined) + || getIteratedTypeOfIterator(type, /*errorNode*/ undefined); } /** @@ -16886,7 +17085,9 @@ namespace ts { } function isUnwrappedReturnTypeVoidOrAny(func: FunctionLikeDeclaration, returnType: Type): boolean { - const unwrappedReturnType = isAsyncFunctionLike(func) ? getPromisedType(returnType) : returnType; + const unwrappedReturnType = (getFunctionFlags(func) & FunctionFlags.AsyncGenerator) === FunctionFlags.Async + ? getPromisedTypeOfPromise(returnType) // Async function + : returnType; // AsyncGenerator function, Generator function, or normal function return unwrappedReturnType && maybeTypeOfKind(unwrappedReturnType, TypeFlags.Void | TypeFlags.Any); } @@ -16905,8 +17106,8 @@ namespace ts { const returnType = getReturnTypeOfSignature(signature); if (strictNullChecks || node.expression || returnType.flags & TypeFlags.Never) { const exprType = node.expression ? checkExpressionCached(node.expression) : undefinedType; - - if (func.asteriskToken) { + const functionFlags = getFunctionFlags(func); + if (functionFlags & FunctionFlags.Generator) { // AsyncGenerator function or Generator function // A generator does not need its return expressions checked against its return type. // Instead, the yield expressions are checked against the element type. // TODO: Check return expressions of generators when return type tracking is added @@ -16925,9 +17126,10 @@ namespace ts { } } else if (func.type || isGetAccessorWithAnnotatedSetAccessor(func)) { - if (isAsyncFunctionLike(func)) { - const promisedType = getPromisedType(returnType); - const awaitedType = checkAwaitedType(exprType, node.expression || node, Diagnostics.Return_expression_in_async_function_does_not_have_a_valid_callable_then_member); + if (functionFlags & FunctionFlags.Async) { // Async function + // TODO(rbuckton): resume here + const promisedType = getPromisedTypeOfPromise(returnType); + const awaitedType = checkAwaitedType(exprType, node.expression || node); if (promisedType) { // If the function has a return type, but promisedType is // undefined, an error will be reported in checkAsyncFunctionReturnType @@ -19747,23 +19949,27 @@ namespace ts { globalRegExpType = getGlobalType("RegExp"); jsxElementType = getExportedTypeFromNamespace("JSX", JsxNames.Element); - getGlobalClassDecoratorType = memoize(() => getGlobalType("ClassDecorator")); - getGlobalPropertyDecoratorType = memoize(() => getGlobalType("PropertyDecorator")); - getGlobalMethodDecoratorType = memoize(() => getGlobalType("MethodDecorator")); - getGlobalParameterDecoratorType = memoize(() => getGlobalType("ParameterDecorator")); getGlobalTypedPropertyDescriptorType = memoize(() => getGlobalType("TypedPropertyDescriptor", /*arity*/ 1)); getGlobalESSymbolConstructorSymbol = memoize(() => getGlobalValueSymbol("Symbol")); getGlobalPromiseType = memoize(() => getGlobalType("Promise", /*arity*/ 1)); tryGetGlobalPromiseType = memoize(() => getGlobalSymbol("Promise", SymbolFlags.Type, /*diagnostic*/ undefined) && getGlobalPromiseType()); - getGlobalPromiseLikeType = memoize(() => getGlobalType("PromiseLike", /*arity*/ 1)); - getInstantiatedGlobalPromiseLikeType = memoize(createInstantiatedPromiseLikeType); getGlobalPromiseConstructorSymbol = memoize(() => getGlobalValueSymbol("Promise")); tryGetGlobalPromiseConstructorSymbol = memoize(() => getGlobalSymbol("Promise", SymbolFlags.Value, /*diagnostic*/ undefined) && getGlobalPromiseConstructorSymbol()); getGlobalPromiseConstructorLikeType = memoize(() => getGlobalType("PromiseConstructorLike")); - getGlobalThenableType = memoize(createThenableType); getGlobalTemplateStringsArrayType = memoize(() => getGlobalType("TemplateStringsArray")); + if (languageVersion >= ScriptTarget.ES2017) { + getGlobalAsyncIteratorType = memoize(() => getGlobalType("AsyncIterator", /*arity*/ 1)); + getGlobalAsyncIterableType = memoize(() => getGlobalType("AsyncIterable", /*arity*/ 1)); + getGlobalAsyncIterableIteratorType = memoize(() => getGlobalType("AsyncIterableIterator", /*arity*/ 1)); + } + else { + getGlobalAsyncIteratorType = memoize(() => emptyGenericType); + getGlobalAsyncIterableType = memoize(() => emptyGenericType); + getGlobalAsyncIterableIteratorType = memoize(() => emptyGenericType); + } + if (languageVersion >= ScriptTarget.ES2015) { getGlobalESSymbolType = memoize(() => getGlobalType("Symbol")); getGlobalIterableType = memoize(() => getGlobalType("Iterable", /*arity*/ 1)); @@ -19830,28 +20036,6 @@ namespace ts { } } - function createInstantiatedPromiseLikeType(): ObjectType { - const promiseLikeType = getGlobalPromiseLikeType(); - if (promiseLikeType !== emptyGenericType) { - return createTypeReference(promiseLikeType, [anyType]); - } - - return emptyObjectType; - } - - function createThenableType() { - // build the thenable type that is used to verify against a non-promise "thenable" operand to `await`. - const thenPropertySymbol = createSymbol(SymbolFlags.Transient | SymbolFlags.Property, "then"); - getSymbolLinks(thenPropertySymbol).type = globalFunctionType; - - const thenableType = createObjectType(ObjectFlags.Anonymous); - thenableType.properties = [thenPropertySymbol]; - thenableType.members = createSymbolTable(thenableType.properties); - thenableType.callSignatures = []; - thenableType.constructSignatures = []; - return thenableType; - } - // GRAMMAR CHECKING function checkGrammarDecorators(node: Node): boolean { if (!node.decorators) { @@ -20143,10 +20327,7 @@ namespace ts { case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: - if (!(node).asteriskToken) { - return false; - } - break; + return false; } return grammarErrorOnNode(asyncModifier, Diagnostics._0_modifier_cannot_be_used_here, "async"); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 9a353e6fb36..b5b1832a347 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -175,15 +175,15 @@ "category": "Error", "code": 1057 }, - "Operand for 'await' does not have a valid callable 'then' member.": { + "Type used as operand to 'await' or the return type of an async function must not contain a callable 'then' member if it is not a promise.": { "category": "Error", "code": 1058 }, - "Return expression in async function does not have a valid callable 'then' member.": { + "A promise must have a 'then' method.": { "category": "Error", "code": 1059 }, - "Expression body for async arrow function does not have a valid callable 'then' member.": { + "The first parameter of the 'then' method of a promise must be a callback.": { "category": "Error", "code": 1060 }, @@ -191,7 +191,7 @@ "category": "Error", "code": 1061 }, - "{0} is referenced directly or indirectly in the fulfillment callback of its own 'then' method.": { + "Type is referenced directly or indirectly in the fulfillment callback of its own 'then' method.": { "category": "Error", "code": 1062 }, @@ -1611,6 +1611,10 @@ "category": "Error", "code": 2503 }, + "Type must have a '[Symbol.asyncIterator]()' method that returns an async iterator.": { + "category": "Error", + "code": 2504 + }, "A generator cannot have a 'void' type annotation.": { "category": "Error", "code": 2505 @@ -1667,6 +1671,10 @@ "category": "Error", "code": 2518 }, + "An async iterator must have a 'next()' method.": { + "category": "Error", + "code": 2519 + }, "Duplicate identifier '{0}'. Compiler uses declaration '{1}' to support async functions.": { "category": "Error", "code": 2520 @@ -1759,6 +1767,10 @@ "category": "Error", "code": 2542 }, + "The type returned by the 'next()' method of an async iterator must be a promise for a type with a 'value' property.": { + "category": "Error", + "code": 2543 + }, "JSX element attributes type '{0}' may not be a union type.": { "category": "Error", "code": 2600 diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 548609d676c..1c401aa55fc 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -133,7 +133,7 @@ namespace ts { * @param node The method node. */ function visitMethodDeclaration(node: MethodDeclaration) { - if (!isAsyncFunctionLike(node)) { + if (!isAsyncFunction(node)) { return node; } const method = createMethod( @@ -166,7 +166,7 @@ namespace ts { * @param node The function node. */ function visitFunctionDeclaration(node: FunctionDeclaration): VisitResult { - if (!isAsyncFunctionLike(node)) { + if (!isAsyncFunction(node)) { return node; } const func = createFunctionDeclaration( @@ -194,7 +194,7 @@ namespace ts { * @param node The function expression node. */ function visitFunctionExpression(node: FunctionExpression): Expression { - if (!isAsyncFunctionLike(node)) { + if (!isAsyncFunction(node)) { return node; } if (nodeIsMissing(node.body)) { @@ -223,7 +223,7 @@ namespace ts { * - The node is marked async */ function visitArrowFunction(node: ArrowFunction) { - if (!isAsyncFunctionLike(node)) { + if (!isAsyncFunction(node)) { return node; } const func = createArrowFunction( diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 1362746ba57..5ca16cd9e21 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -1670,7 +1670,7 @@ namespace ts { if (isFunctionLike(node) && node.type) { return serializeTypeNode(node.type); } - else if (isAsyncFunctionLike(node)) { + else if (isAsyncFunction(node)) { return createIdentifier("Promise"); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5536a818df5..85ecbd2a6c3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2862,8 +2862,16 @@ namespace ts { // Just a place to cache element types of iterables and iterators /* @internal */ export interface IterableOrIteratorType extends ObjectType, UnionType { - iterableElementType?: Type; - iteratorElementType?: Type; + iteratedTypeOfIterable?: Type; + iteratedTypeOfIterator?: Type; + iteratedTypeOfAsyncIterable?: Type; + iteratedTypeOfAsyncIterator?: Type; + } + + /* @internal */ + export interface PromiseOrAwaitableType extends ObjectType, UnionType { + promisedTypeOfPromise?: Type; + awaitedTypeOfType?: Type; } // Type parameters (TypeFlags.TypeParameter) diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 299f91be662..9d827ae83c2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1855,8 +1855,50 @@ namespace ts { return SyntaxKind.FirstTriviaToken <= token && token <= SyntaxKind.LastTriviaToken; } - export function isAsyncFunctionLike(node: Node): boolean { - return isFunctionLike(node) && hasModifier(node, ModifierFlags.Async) && !isAccessor(node); + export const enum FunctionFlags { + Normal = 0, + Generator = 1 << 0, + Async = 1 << 1, + AsyncGenerator = Async | Generator, + Invalid = 1 << 2, + InvalidGenerator = Generator | Invalid, + } + + export function getFunctionFlags(node: FunctionLikeDeclaration) { + let flags = FunctionFlags.Normal; + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + if (node.asteriskToken) { + flags |= FunctionFlags.Generator; + } + // fall through + case SyntaxKind.ArrowFunction: + if (hasModifier(node, ModifierFlags.Async)) { + flags |= FunctionFlags.Async; + } + break; + } + + if (!node.body) { + flags |= FunctionFlags.Invalid; + } + + return flags; + } + + export function isAsyncFunction(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.MethodDeclaration: + return (node).body !== undefined + && (node).asteriskToken === undefined + && hasModifier(node, ModifierFlags.Async); + } + return false; } export function isStringOrNumericLiteral(kind: SyntaxKind): boolean { diff --git a/src/lib/es2017.asynciterable.d.ts b/src/lib/es2017.asynciterable.d.ts new file mode 100644 index 00000000000..623f6850682 --- /dev/null +++ b/src/lib/es2017.asynciterable.d.ts @@ -0,0 +1,23 @@ +/// + +interface SymbolConstructor { + /** + * A method that returns the default async iterator for an object. Called by the semantics of + * the for-await-of statement. + */ + readonly asyncIterator: symbol; +} + +interface AsyncIterator { + next(value?: any): Promise>; + return?(value?: any): Promise>; + throw?(e?: any): Promise>; +} + +interface AsyncIterable { + [Symbol.asyncIterator](): AsyncIterator; +} + +interface AsyncIterableIterator extends AsyncIterator { + [Symbol.asyncIterator](): AsyncIterableIterator; +} \ No newline at end of file diff --git a/src/lib/es2017.d.ts b/src/lib/es2017.d.ts index 13f9d93f444..a4145fb5cbf 100644 --- a/src/lib/es2017.d.ts +++ b/src/lib/es2017.d.ts @@ -1,3 +1,4 @@ /// /// -/// \ No newline at end of file +/// +/// \ No newline at end of file