From 655f2ec74eb07cd3a5e3f1717da521b27a71a879 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 8 May 2015 16:51:35 -0700 Subject: [PATCH] Cleanup and PR feedback --- src/compiler/checker.ts | 210 ++++++++++++------ .../diagnosticInformationMap.generated.ts | 6 +- src/compiler/diagnosticMessages.json | 2 +- src/compiler/parser.ts | 50 +++-- src/compiler/types.ts | 14 +- 5 files changed, 187 insertions(+), 95 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0529b8012bfc6..d759eb1fc1aac 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7303,7 +7303,9 @@ module ts { if (globalPromiseType !== emptyObjectType) { // if the promised type is itself a promise, get the underlying type; otherwise, fallback to the promised type promisedType = getAwaitedType(promisedType); - return createTypeReference(globalPromiseType, [promisedType]); + if (promisedType !== unknownType) { + return createTypeReference(globalPromiseType, [promisedType]); + } } error(location, Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type); @@ -7319,17 +7321,23 @@ module ts { let isAsync = isAsyncFunctionLike(func); let type: Type; if (func.body.kind !== SyntaxKind.Block) { - type = isAsync - ? checkAwaitedExpressionCached(func.body, contextualMapper) - : checkExpressionCached(func.body, contextualMapper); + if (isAsync) { + type = checkAwaitedExpressionCached(func.body, contextualMapper); + } + else { + type = checkExpressionCached(func.body, contextualMapper); + } } else { // Aggregate the types of expressions within all the return statements. let types = checkAndAggregateReturnExpressionTypes(func.body, contextualMapper); if (types.length === 0) { - return isAsync - ? createPromiseType(voidType, func) - : voidType; + if (isAsync) { + return createPromiseType(voidType, func); + } + else { + return voidType; + } } // When return statements are contextually typed we allow the return type to be a union type. Otherwise we require the @@ -7340,14 +7348,18 @@ module ts { return unknownType; } } + if (!contextualSignature) { reportErrorsFromWidening(func, type); } let widenedType = getWidenedType(type); - return isAsync - ? createPromiseType(widenedType, func) - : widenedType; + if (isAsync) { + return createPromiseType(widenedType, func); + } + else { + return widenedType; + } } /// Returns a set of types relating to every return expression relating to a function block. @@ -7357,9 +7369,14 @@ module ts { forEachReturnStatement(body, returnStatement => { let expr = returnStatement.expression; if (expr) { - let type = isAsync - ? checkAwaitedExpressionCached(expr, contextualMapper) - : checkExpressionCached(expr, contextualMapper); + let type: Type; + if (isAsync) { + type = checkAwaitedExpressionCached(expr, contextualMapper); + } + else { + type = checkExpressionCached(expr, contextualMapper); + } + if (!contains(aggregatedTypes, type)) { aggregatedTypes.push(type); } @@ -7495,10 +7512,12 @@ module ts { else { let exprType = checkExpression(node.body); if (returnType) { - checkTypeAssignableTo( - isAsync ? getAwaitedType(exprType) : exprType, - isAsync ? promisedType : returnType, - node.body); + if (isAsync) { + checkTypeAssignableTo(getAwaitedType(exprType, node.body), promisedType, node.body); + } + else { + checkTypeAssignableTo(exprType, returnType, node.body); + } } checkFunctionExpressionBodies(node.body); } @@ -7619,8 +7638,8 @@ module ts { grammarErrorOnFirstToken(node, Diagnostics.await_expression_must_be_contained_within_an_async_function); } - var operandType = checkExpression(node.expression); - return getAwaitedType(operandType); + let operandType = checkExpression(node.expression); + return getAwaitedType(operandType, node); } function checkPrefixUnaryExpression(node: PrefixUnaryExpression): Type { @@ -8322,13 +8341,14 @@ 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, undefined, undefined); - var valueSymbol = resolveName(node, promiseName, SymbolFlags.Value, undefined, undefined); + 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)) { @@ -8932,8 +8952,28 @@ module ts { } } + function checkNonThenableType(type: Type, location?: Node, message?: DiagnosticMessage) { + if (!(type.flags & TypeFlags.Any) && isTypeAssignableTo(type, getGlobalThenableType())) { + if (location) { + if (!message) { + message = Diagnostics.Operand_for_await_does_not_have_a_valid_callable_then_member; + } + + error(location, message); + } + + return false; + } + + return true; + } + + /** + * 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. + */ function getPromisedType(type: Type): Type { - // the "promised type" of a type is the type of the "value" argument of the "onfulfilled" callback. let globalPromiseLikeType = getInstantiatedGlobalPromiseLikeType(); if (globalPromiseLikeType !== emptyObjectType && isTypeAssignableTo(type, globalPromiseLikeType)) { let thenProp = getPropertyOfType(type, "then"); @@ -8954,67 +8994,100 @@ module ts { } return getUnionType(awaitedTypes); - } + } } - - return emptyObjectType; - } - function getAwaitedType(type: Type): Type { - // The "awaited type" of an expression is its "promised type" if the expression is a `Promise`; otherwise, it is the type of the expression. - + return unknownType; + } + + /** + * Gets the "awaited type" of a type. + * @param type The type to await. + * @remarks The "awaited type" of an expression is its "promised type" if the expression is a + * 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 === emptyObjectType) { - return 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; } - - // if we have a bad actor in the form of a promise whose promised type is the same promise, return the empty type. - // if this were the actual case in the JavaScript, this Promise would never resolve. - if (promisedType === type) { - return emptyObjectType; + 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; } - // unwrap any nested promises + // `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: + // + // declare class PromiseA { + // then(onfulfilled: (value: PromiseB) => any, onrejected?); + // } + // declare class PromiseB { + // then(onfulfilled: (value: PromiseA) => any, onrejected?); + // } + // let seen: boolean[]; + + // unwrap any nested promises while (true) { let nestedPromisedType = getPromisedType(promisedType); - if (nestedPromisedType === emptyObjectType) { - // if this could not be unwrapped further, return the promised type - return promisedType; + if (nestedPromisedType === unknownType) { + // this type could not be unwrapped further. We need to check to + // ensure it is not a thenable + if (checkNonThenableType(promisedType, location)) { + return promisedType; + } + + return unknownType; } if (!seen) { - // `seen` keeps track of types we've tried to await to avoid cycles seen = []; seen[type.id] = true; seen[promisedType.id] = true; } else if (seen[nestedPromisedType.id]) { - // if we've already seen this type, this is a promise that would never resolve. As above, we return the empty type. - return emptyObjectType; + // if we've already seen this type, this is a promise that + // would never resolve. As above, we return the unknown type. + if (location) { + error(location, Diagnostics.Operand_for_await_does_not_have_a_valid_callable_then_member); + } + + return unknownType; } seen[nestedPromisedType.id] = true; promisedType = nestedPromisedType; } - - return promisedType; - - // if we didn't get a promised type, check the type does not have a callable then member. - if (isTypeAssignableTo(type, getGlobalThenableType())) { - error(null, Diagnostics.Type_for_await_does_not_have_a_valid_callable_then_member); - return emptyObjectType; - } - - // if the type was not a "promise" or a "thenable", return the type. - return type; } - function checkAsyncFunctionReturnType(node: SignatureDeclaration, returnType: Type): Type { - // This checks that an async function has a valid Promise-compatible return type, and returns the *awaited type* of the promise. - // An async function has a valid Promise-compatible return type if the resolved value of the return type has a construct - // signature that takes in an `initializer` function that in turn supplies a `resolve` function as one of its arguments - // and results in an object with a callable `then` signature. + /** + * Checks the return type of an async function to ensure it is a compatible + * Promise implementation. + * @param node The signature to check + * @param returnType The return type for the function + * @remarks + * This checks that an async function has a valid Promise-compatible return type, + * and returns the *awaited type* of the promise. An async function has a valid + * Promise-compatible return type if the resolved value of the return type has a + * construct signature that takes in an `initializer` function that in turn supplies + * a `resolve` function as one of its arguments and results in an object with a + * callable `then` signature. + */ + function checkAsyncFunctionReturnType(node: SignatureDeclaration, returnType: Type): Type { let globalPromiseConstructorLikeType = getGlobalPromiseConstructorLikeType(); if (globalPromiseConstructorLikeType !== emptyObjectType) { if (!returnType) { @@ -9022,23 +9095,24 @@ module ts { } // get the constructor type of the return type - var declaredType = returnType.symbol ? getTypeOfSymbol(returnType.symbol) : emptyObjectType; + let declaredType = returnType.symbol ? getTypeOfSymbol(returnType.symbol) : emptyObjectType; if (isTypeAssignableTo(declaredType, globalPromiseConstructorLikeType)) { - var promisedType = getPromisedType(returnType); - if (promisedType !== emptyObjectType) { + let promisedType = getPromisedType(returnType); + if (promisedType !== unknownType) { // unwrap the promised type - var promiseConstructor = getPromiseConstructor(node); + let promiseConstructor = getPromiseConstructor(node); if (promiseConstructor) { - emitAwaiter = true; checkExpressionOrQualifiedName(promiseConstructor); - return getAwaitedType(promisedType); } + + emitAwaiter = true; + return getAwaitedType(promisedType, node); } } } error(node, ts.Diagnostics.An_async_function_or_method_must_have_a_valid_awaitable_return_type); - return emptyObjectType; + return unknownType; } /** Check a decorator */ @@ -12351,9 +12425,11 @@ module ts { let thenPropertySymbol = createSymbol(SymbolFlags.Transient | SymbolFlags.Property, "then"); getSymbolLinks(thenPropertySymbol).type = globalFunctionType; - let thenableType = createObjectType(TypeFlags.ObjectType); + let thenableType = createObjectType(TypeFlags.Anonymous); thenableType.properties = [thenPropertySymbol]; thenableType.members = createSymbolTable(thenableType.properties); + thenableType.callSignatures = []; + thenableType.constructSignatures = []; return thenableType; } diff --git a/src/compiler/diagnosticInformationMap.generated.ts b/src/compiler/diagnosticInformationMap.generated.ts index ec2bca8a7ce24..4a4ae96110907 100644 --- a/src/compiler/diagnosticInformationMap.generated.ts +++ b/src/compiler/diagnosticInformationMap.generated.ts @@ -50,7 +50,7 @@ module ts { A_get_accessor_cannot_have_parameters: { code: 1054, category: DiagnosticCategory.Error, key: "A 'get' accessor cannot have parameters." }, Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher: { code: 1056, category: DiagnosticCategory.Error, key: "Accessors are only available when targeting ECMAScript 5 and higher." }, An_async_function_or_method_must_have_a_valid_awaitable_return_type: { code: 1057, category: DiagnosticCategory.Error, key: "An async function or method must have a valid awaitable return type." }, - Type_for_await_does_not_have_a_valid_callable_then_member: { code: 1058, category: DiagnosticCategory.Error, key: "Type for 'await' does not have a valid callable 'then' member." }, + Operand_for_await_does_not_have_a_valid_callable_then_member: { code: 1058, category: DiagnosticCategory.Error, key: "Operand for 'await' does not have a valid callable 'then' member." }, Enum_member_must_have_initializer: { code: 1061, category: DiagnosticCategory.Error, key: "Enum member must have initializer." }, An_export_assignment_cannot_be_used_in_a_namespace: { code: 1063, category: DiagnosticCategory.Error, key: "An export assignment cannot be used in a namespace." }, Ambient_enum_elements_can_only_have_integer_literal_initializers: { code: 1066, category: DiagnosticCategory.Error, key: "Ambient enum elements can only have integer literal initializers." }, @@ -381,11 +381,11 @@ module ts { An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments: { code: 2499, category: DiagnosticCategory.Error, key: "An interface can only extend an identifier/qualified-name with optional type arguments." }, A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments: { code: 2500, category: DiagnosticCategory.Error, key: "A class can only implement an identifier/qualified-name with optional type arguments." }, A_rest_element_cannot_contain_a_binding_pattern: { code: 2501, category: DiagnosticCategory.Error, key: "A rest element cannot contain a binding pattern." }, + _0_is_referenced_directly_or_indirectly_in_its_own_type_annotation: { code: 2502, category: DiagnosticCategory.Error, key: "'{0}' is referenced directly or indirectly in its own type annotation." }, + Cannot_find_namespace_0: { code: 2503, category: DiagnosticCategory.Error, key: "Cannot find namespace '{0}'." }, Duplicate_identifier_0_Compiler_uses_declaration_1_to_support_async_functions: { code: 2520, category: DiagnosticCategory.Error, key: "Duplicate identifier '{0}'. Compiler uses declaration '{1}' to support async functions." }, Expression_resolves_to_variable_declaration_0_that_compiler_uses_to_support_async_functions: { code: 2521, category: DiagnosticCategory.Error, key: "Expression resolves to variable declaration '{0}' that compiler uses to support async functions." }, The_arguments_object_cannot_be_referenced_in_an_async_arrow_function_Consider_using_a_standard_async_function_expression: { code: 2522, category: DiagnosticCategory.Error, key: "The 'arguments' object cannot be referenced in an async arrow function Consider using a standard async function expression." }, - _0_is_referenced_directly_or_indirectly_in_its_own_type_annotation: { code: 2502, category: DiagnosticCategory.Error, key: "'{0}' is referenced directly or indirectly in its own type annotation." }, - Cannot_find_namespace_0: { code: 2503, category: DiagnosticCategory.Error, key: "Cannot find namespace '{0}'." }, Import_declaration_0_is_using_private_name_1: { code: 4000, category: DiagnosticCategory.Error, key: "Import declaration '{0}' is using private name '{1}'." }, Type_parameter_0_of_exported_class_has_or_is_using_private_name_1: { code: 4002, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported class has or is using private name '{1}'." }, Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1: { code: 4004, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported interface has or is using private name '{1}'." }, diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 600c928a0ddf5..feb7730a8a317 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -187,7 +187,7 @@ "category": "Error", "code": 1057 }, - "Type for 'await' does not have a valid callable 'then' member.": { + "Operand for 'await' does not have a valid callable 'then' member.": { "category": "Error", "code": 1058 }, diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index db6a00ec9aa81..f4ac24c5fc821 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -568,11 +568,15 @@ module ts { } function doOutsideOfContext(context: ParserContextFlags, func: () => T): T { - let setContextFlags = context & contextFlags; - if (setContextFlags) { - setContextFlag(false, setContextFlags); + // contextFlagsToClear will contain only the context flags that are + // currently set that we need to temporarily clear + let contextFlagsToClear = context & contextFlags; + if (contextFlagsToClear) { + // clear the requested context flags + setContextFlag(false, contextFlagsToClear); let result = func(); - setContextFlag(true, setContextFlags); + // restore the context flags we just cleared + setContextFlag(true, contextFlagsToClear); return result; } @@ -581,11 +585,15 @@ module ts { } function doInsideOfContext(context: ParserContextFlags, func: () => T): T { - let unsetContextFlags = context & ~contextFlags; - if (unsetContextFlags) { - setContextFlag(true, unsetContextFlags); + // contextFlagsToSet will contain only the context flags that + // are not currently set that we need to temporarily enable + let contextFlagsToSet = context & ~contextFlags; + if (contextFlagsToSet) { + // set the requested context flags + setContextFlag(true, contextFlagsToSet); let result = func(); - setContextFlag(false, unsetContextFlags); + // reset the context flags we just set + setContextFlag(false, contextFlagsToSet); return result; } @@ -1013,6 +1021,11 @@ module ts { } function canFollowModifier(isArrowFunction?: boolean): boolean { + // Arrow functions can have an `async` modifier, but the rules for what can follow that modifier + // differ from the rules for any other declaration. + // The `async` modifier on an async function can only be followed by an open parenthesis, + // or a less than token (in the case of a generic arrow function). + // In addition, the `async` modifier must appear on the same line as the following token. if (isArrowFunction) { if (scanner.hasPrecedingLineBreak()) { return false; @@ -1898,12 +1911,12 @@ module ts { function parseBindingElementInitializer(inParameter: boolean) { // BindingElement[Yield,GeneratorParameter,Await,AsyncParameter] : - // [+GeneratorParameter] BindingPattern[?Yield,?Await,GeneratorParameter] Initializer[In]opt - // [+AsyncParameter] BindingPattern[?Yield,?Await,AsyncParameter] Initializer[In]opt + // [+GeneratorParameter] BindingPattern[?Yield,?Await,?AsyncParameter,GeneratorParameter] Initializer[In]opt + // [+AsyncParameter] BindingPattern[?Yield,?GeneratorParameter,?Await,AsyncParameter] Initializer[In]opt // [~GeneratorParameter,~AsyncParameter] BindingPattern[?Yield,?Await] Initializer[In,?Yield,?Await]opt // SingleNameBinding[Yield,GeneratorParameter,Await,AsyncParameter] : - // [+GeneratorParameter] BindingIdentifier[Yield] Initializer[In]opt - // [+AsyncParameter] BindingIdentifier[Await] Initializer[In]opt + // [+GeneratorParameter] BindingIdentifier[Yield, ?Await] Initializer[In]opt + // [+AsyncParameter] BindingIdentifier[Await, ?Yield] Initializer[In]opt // [~GeneratorParameter,~AsyncParameter] BindingIdentifier[?Yield,?Await] Initializer[In,?Yield,?Await]opt let parseInitializer = inParameter ? parseParameterInitializer : parseNonParameterInitializer; return inGeneratorParameterOrAsyncParameterContext() @@ -2425,9 +2438,9 @@ module ts { case SyntaxKind.LessThanToken: case SyntaxKind.AwaitKeyword: case SyntaxKind.YieldKeyword: - // Yield always starts an expression. Either it is an identifier (in which case + // Yield/await always starts an expression. Either it is an identifier (in which case // it is definitely an expression). Or it's a keyword (either because we're in - // a generator, or in strict mode (or both)) and it started a yield expression. + // a generator or async function, or in strict mode (or both)) and it started a yield or await expression. return true; default: // Error tolerance. If we see the start of some binary operator, we consider @@ -3317,6 +3330,9 @@ module ts { case SyntaxKind.OpenBraceToken: return parseObjectLiteralExpression(); case SyntaxKind.AsyncKeyword: + // Async arrow functions are parsed earlier in parseAssignmentExpressionOrHigher. + // If we encounter `async [no LineTerminator here] function` then this is an async + // function; otherwise, its an identifier. if (!lookAhead(nextTokenIsFunctionKeywordOnSameLine)) { break; } @@ -3450,7 +3466,7 @@ module ts { parseExpected(SyntaxKind.FunctionKeyword); node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); - let isGenerator = node.asteriskToken != undefined; + let isGenerator = !!node.asteriskToken; let isAsync = isAsyncFunctionLike(node); node.name = isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalIdentifier) : @@ -4092,7 +4108,7 @@ module ts { parseExpected(SyntaxKind.FunctionKeyword); node.asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken); node.name = node.flags & NodeFlags.Default ? parseOptionalIdentifier() : parseIdentifier(); - let isGenerator = node.asteriskToken != undefined; + let isGenerator = !!node.asteriskToken; let isAsync = isAsyncFunctionLike(node); fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext*/ isGenerator, /*awaitAndAsyncParameterContext*/ isAsync, /*requireCompleteParameterList:*/ false, node); node.body = parseFunctionBlockOrSemicolon(isGenerator, isAsync, Diagnostics.or_expected); @@ -4116,7 +4132,7 @@ module ts { method.asteriskToken = asteriskToken; method.name = name; method.questionToken = questionToken; - let isGenerator = asteriskToken != undefined; + let isGenerator = !!asteriskToken; let isAsync = isAsyncFunctionLike(method); fillSignature(SyntaxKind.ColonToken, /*yieldAndGeneratorParameterContext:*/ isGenerator, /*awaitAndAsyncParameterContext*/ isAsync, /*requireCompleteParameterList:*/ false, method); method.body = parseFunctionBlockOrSemicolon(isGenerator, isAsync, diagnosticMessage); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 86b4113eecc9c..29206c53f39a5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -344,16 +344,16 @@ module ts { // If this node was parsed as part of a decorator Decorator = 1 << 4, - // If the parser encountered an error when parsing the code that created this node. Note - // the parser only sets this directly on the node it creates right after encountering the - // error. - ThisNodeHasError = 1 << 5, - // If this node was parsed in the parameters of an async function. - AsyncParameter = 1 << 6, + AsyncParameter = 1 << 5, // If this node was parsed in the 'await' context created when parsing an async function. - Await = 1 << 7, + Await = 1 << 6, + + // If the parser encountered an error when parsing the code that created this node. Note + // the parser only sets this directly on the node it creates right after encountering the + // error. + ThisNodeHasError = 1 << 7, // Context flags set directly by the parser. ParserGeneratedFlags = StrictMode | DisallowIn | Yield | GeneratorParameter | Decorator | ThisNodeHasError | AsyncParameter | Await,