diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1a256809f10..85b63ad0d7a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -120,6 +120,7 @@ module ts { let getGlobalPropertyDecoratorType: () => ObjectType; let getGlobalMethodDecoratorType: () => ObjectType; let getGlobalPromiseType: () => ObjectType; + let tryGetGlobalPromiseType: () => ObjectType; let getGlobalPromiseLikeType: () => ObjectType; let getInstantiatedGlobalPromiseLikeType: () => ObjectType; let getGlobalPromiseConstructorLikeType: () => ObjectType; @@ -3501,6 +3502,10 @@ module ts { function getGlobalType(name: string, arity = 0): ObjectType { return getTypeOfGlobalSymbol(getGlobalTypeSymbol(name), arity); } + + function tryGetGlobalType(name: string, arity = 0): ObjectType { + return getTypeOfGlobalSymbol(getGlobalSymbol(name, SymbolFlags.Type, /*diagnostic*/ undefined), arity); + } function getGlobalESSymbolConstructorSymbol() { return globalESSymbolConstructorSymbol || (globalESSymbolConstructorSymbol = getGlobalValueSymbol("Symbol")); @@ -7296,7 +7301,7 @@ module ts { } } - function createPromiseType(promisedType: Type, location: Node): Type { + function createPromiseType(promisedType: Type): Type { // creates a `Promise` type where `T` is the promisedType argument let globalPromiseType = getGlobalPromiseType(); if (globalPromiseType !== emptyObjectType) { @@ -7307,8 +7312,7 @@ module ts { } } - error(location, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type); - return unknownType; + return undefined; } function getReturnTypeFromBody(func: FunctionLikeDeclaration, contextualMapper?: TypeMapper): Type { @@ -7321,6 +7325,11 @@ module ts { let type: Type; if (func.body.kind !== SyntaxKind.Block) { if (isAsync) { + // 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 not the return type of the consise body, rather it + // is the awaited type of the consise body, which we will wrap in the native Promise type + // later in this function. type = checkAwaitedExpressionCached(func.body, contextualMapper); } else { @@ -7332,7 +7341,14 @@ module ts { let types = checkAndAggregateReturnExpressionTypes(func.body, contextualMapper); if (types.length === 0) { if (isAsync) { - return createPromiseType(voidType, func); + // For an async function, the return type will not be void, but rather a Promise for void. + let promiseType = createPromiseType(voidType); + if (!promiseType) { + error(func, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type); + return unknownType; + } + + return promiseType; } else { return voidType; @@ -7354,7 +7370,16 @@ module ts { let widenedType = getWidenedType(type); if (isAsync) { - return createPromiseType(widenedType, func); + // 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. + let promiseType = createPromiseType(widenedType); + if (!promiseType) { + error(func, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type); + return unknownType; + } + + return promiseType; } else { return widenedType; @@ -7492,11 +7517,6 @@ module ts { let returnType = node.type && getTypeFromTypeNode(node.type); let promisedType: Type; if (returnType && isAsync) { - // 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 we - // should not be checking assignability of a promise to the return type. Instead, we need to - // check assignability of the awaited type of the concise body against the promised type of - // its return type annotation. promisedType = checkAsyncFunctionReturnType(node, returnType); } @@ -7509,6 +7529,11 @@ module ts { checkSourceElement(node.body); } else { + // 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 we + // should not be checking assignability of a promise to the return type. Instead, we need to + // check assignability of the awaited type of the concise body against the promised type of + // its return type annotation. let exprType = checkExpression(node.body); if (returnType) { if (isAsync) { @@ -8340,23 +8365,6 @@ module ts { break; } } - - if (isAsyncFunctionLike(node)) { - var promiseConstructor = getPromiseConstructor(node); - if (promiseConstructor) { - var promiseIdentifier = getFirstIdentifier(promiseConstructor); - var promiseName = promiseIdentifier.text; - var typeSymbol = resolveName(node, promiseName, SymbolFlags.Type | SymbolFlags.Module, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined); - var valueSymbol = resolveName(node, promiseName, SymbolFlags.Value, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined); - if (typeSymbol !== valueSymbol) { - var valueLinks = getNodeLinks(valueSymbol.valueDeclaration); - if (!(valueLinks.flags & NodeCheckFlags.PromiseCollision)) { - valueLinks.flags |= NodeCheckFlags.PromiseCollision; - error(valueSymbol.valueDeclaration, Diagnostics.Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions, promiseName, getTextOfNode(promiseConstructor)); - } - } - } - } } checkSpecializedSignatureDeclaration(node); @@ -8952,7 +8960,7 @@ module ts { } function checkNonThenableType(type: Type, location?: Node, message?: DiagnosticMessage) { - if (!(type.flags & TypeFlags.Any) && isTypeAssignableTo(type, getGlobalThenableType())) { + if (!allConstituentTypesHaveKind(type, TypeFlags.Any) && isTypeAssignableTo(type, getGlobalThenableType())) { if (location) { if (!message) { message = Diagnostics.Operand_for_await_does_not_have_a_valid_callable_then_member; @@ -8970,33 +8978,58 @@ module ts { /** * Gets the "promised type" of a promise. * @param type The type of the promise. - * @remarks The "promised type" of a type is the type of the "value" argument of the "onfulfilled" callback. + * @remarks The "promised type" of a type is the type of the "value" parameter of the "onfulfilled" callback. */ - function getPromisedType(type: Type): Type { + function getPromisedType(promise: Type): Type { + // + // { // promise + // then( // thenFunction + // onfulfilled: ( // onfulfilledParameterType + // value: T // valueParameterType + // ) => any + // ): any; + // } + // + + if (allConstituentTypesHaveKind(promise, TypeFlags.Any)) { + return undefined; + } + + if ((promise.flags & TypeFlags.Reference) && (promise).target === tryGetGlobalPromiseType()) { + return (promise).typeArguments[0]; + } + let globalPromiseLikeType = getInstantiatedGlobalPromiseLikeType(); - if (globalPromiseLikeType !== emptyObjectType && isTypeAssignableTo(type, globalPromiseLikeType)) { - let thenProp = getPropertyOfType(type, "then"); - if (thenProp) { - let awaitedTypes: Type[] = []; - let thenType = getTypeOfSymbol(thenProp); - let thenSignatures = getSignaturesOfType(thenType, SignatureKind.Call); - for (let thenSignature of thenSignatures) { - thenSignature = getErasedSignature(thenSignature); - let onfulfilledParameterType = getTypeAtPosition(thenSignature, 0); - let onfulfilledParameterSignatures = getSignaturesOfType(onfulfilledParameterType, SignatureKind.Call); - for (let onfulfilledParameterSignature of onfulfilledParameterSignatures) { - let valueParameterType = getTypeAtPosition(onfulfilledParameterSignature, 0); - if (valueParameterType !== type) { - awaitedTypes.push(valueParameterType); - } - } - } - - return getUnionType(awaitedTypes); - } + if (globalPromiseLikeType === emptyObjectType || !isTypeAssignableTo(promise, globalPromiseLikeType)) { + return undefined; } - return unknownType; + let thenFunction = getTypeOfPropertyOfType(promise, "then"); + if (thenFunction && allConstituentTypesHaveKind(thenFunction, TypeFlags.Any)) { + return undefined; + } + + let thenSignatures = thenFunction ? getSignaturesOfType(thenFunction, SignatureKind.Call) : emptyArray; + if (thenSignatures.length === 0) { + return undefined; + } + + let onfulfilledParameterType = getUnionType(map(thenSignatures, getTypeOfFirstParameterOfSignature)); + if (allConstituentTypesHaveKind(onfulfilledParameterType, TypeFlags.Any)) { + return undefined; + } + + let onfulfilledParameterSignatures = getSignaturesOfType(onfulfilledParameterType, SignatureKind.Call); + if (onfulfilledParameterSignatures.length === 0) { + return undefined; + } + + let valueParameterType = getUnionType(map(onfulfilledParameterSignatures, getTypeOfFirstParameterOfSignature)); + return valueParameterType; + } + + function getTypeOfFirstParameterOfSignature(signature: Signature) { + return getTypeAtPosition(signature, 0); } /** @@ -9006,28 +9039,7 @@ module 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, location?: Node): Type { - let promisedType = getPromisedType(type); - if (promisedType === unknownType) { - // if we got the unknown type, the type wasn't a promise. We need to check to - // ensure it is not a thenable. - if (checkNonThenableType(type, location)) { - return type; - } - - return unknownType; - } - else if (promisedType === type) { - // if we have a bad actor in the form of a promise whose promised type is the same - // 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. - if (location) { - error(location, Diagnostics.Operand_for_await_does_not_have_a_valid_callable_then_member); - } - - return unknownType; - } - + function getAwaitedType(type: Type, location?: Node): Type { // `seen` keeps track of types we've tried to await to avoid cycles. // This is to protect against a bad actor with a mutually recursive promised type: // @@ -9039,26 +9051,34 @@ module ts { // } // let seen: boolean[]; - - // unwrap any nested promises while (true) { - let nestedPromisedType = getPromisedType(promisedType); - if (nestedPromisedType === unknownType) { + let promisedType = getPromisedType(type); + if (!promisedType) { // this type could not be unwrapped further. We need to check to // ensure it is not a thenable - if (checkNonThenableType(promisedType, location)) { - return promisedType; + if (checkNonThenableType(type, location)) { + return type; } return unknownType; } if (!seen) { + if (promisedType === type) { + // if we have a bad actor in the form of a promise whose promised type is the same + // 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. + if (location) { + error(location, Diagnostics.Operand_for_await_does_not_have_a_valid_callable_then_member); + } + + return unknownType; + } + seen = []; seen[type.id] = true; - seen[promisedType.id] = true; } - else if (seen[nestedPromisedType.id]) { + else if (seen[promisedType.id]) { // if we've already seen this type, this is a promise that // would never resolve. As above, we return the unknown type. if (location) { @@ -9068,8 +9088,8 @@ module ts { return unknownType; } - seen[nestedPromisedType.id] = true; - promisedType = nestedPromisedType; + seen[promisedType.id] = true; + type = promisedType; } } @@ -9089,15 +9109,11 @@ module ts { function checkAsyncFunctionReturnType(node: SignatureDeclaration, returnType: Type): Type { let globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(); if (globalPromiseConstructorLikeType !== emptyObjectType) { - if (!returnType) { - returnType = getReturnTypeOfSignature(getSignatureFromDeclaration(node)); - } - - // get the constructor type of the return type + // get the constructor type of the return type let declaredType = returnType.symbol ? getTypeOfSymbol(returnType.symbol) : emptyObjectType; if (isTypeAssignableTo(declaredType, globalPromiseConstructorLikeType)) { let promisedType = getPromisedType(returnType); - if (promisedType !== unknownType) { + if (promisedType) { // unwrap the promised type let promiseConstructor = getPromiseConstructor(node); if (promiseConstructor) { @@ -10045,7 +10061,7 @@ module ts { } } else if (func.type && !isAccessor(func.kind) && isAsyncFunctionLike(func)) { - checkTypeAssignableTo(getAwaitedType(exprType), getPromisedType(returnType), node.expression); + checkTypeAssignableTo(getAwaitedType(exprType), getAwaitedType(returnType), node.expression); } else if (func.type || isGetAccessorWithAnnotatatedSetAccessor(func)) { checkTypeAssignableTo(exprType, returnType, node.expression); @@ -12379,6 +12395,7 @@ module ts { getGlobalMethodDecoratorType = memoize(() => getGlobalType("MethodDecorator")); getGlobalParameterDecoratorType = memoize(() => getGlobalType("ParameterDecorator")); 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")); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 770f23b53b8..74f32488e5b 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -570,6 +570,10 @@ module ts { function doOutsideOfContext(context: ParserContextFlags, func: () => T): T { // contextFlagsToClear will contain only the context flags that are // currently set that we need to temporarily clear + // We don't just blindly reset to the previous flags to ensure + // that we do not mutate cached flags for the incremental + // parser (ThisNodeHasError, ThisNodeOrAnySubNodesHasError, and + // HasAggregatedChildData). let contextFlagsToClear = context & contextFlags; if (contextFlagsToClear) { // clear the requested context flags @@ -586,7 +590,11 @@ module ts { function doInsideOfContext(context: ParserContextFlags, func: () => T): T { // contextFlagsToSet will contain only the context flags that - // are not currently set that we need to temporarily enable + // are not currently set that we need to temporarily enable. + // We don't just blindly reset to the previous flags to ensure + // that we do not mutate cached flags for the incremental + // parser (ThisNodeHasError, ThisNodeOrAnySubNodesHasError, and + // HasAggregatedChildData). let contextFlagsToSet = context & ~contextFlags; if (contextFlagsToSet) { // set the requested context flags