From 5db4e7add37892ee27eb24677c713ffdd8bc6dae Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 8 Apr 2020 15:26:56 -0700 Subject: [PATCH] Fix async function block return expr error in js (#37845) --- src/compiler/checker.ts | 18 +++----- .../asyncArrowFunction_allowJs.errors.txt | 38 +++++++++++++++++ .../asyncArrowFunction_allowJs.symbols | 30 ++++++++++++++ .../asyncArrowFunction_allowJs.types | 41 +++++++++++++++++++ .../asyncArrowFunction_allowJs.ts | 27 ++++++++++++ 5 files changed, 141 insertions(+), 13 deletions(-) create mode 100644 tests/baselines/reference/asyncArrowFunction_allowJs.errors.txt create mode 100644 tests/baselines/reference/asyncArrowFunction_allowJs.symbols create mode 100644 tests/baselines/reference/asyncArrowFunction_allowJs.types create mode 100644 tests/cases/conformance/async/es2017/asyncArrowFunction/asyncArrowFunction_allowJs.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f8d1f82ac05..98c059ca306 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27244,7 +27244,7 @@ namespace ts { } const functionFlags = getFunctionFlags(func); - const type = returnType && getReturnOrPromisedType(returnType, functionFlags); + const type = returnType && unwrapReturnType(returnType, functionFlags); // Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions. if (type && maybeTypeOfKind(type, TypeFlags.Any | TypeFlags.Void)) { @@ -27364,14 +27364,6 @@ namespace ts { } } - function getReturnOrPromisedType(type: Type | undefined, functionFlags: FunctionFlags) { - const isGenerator = !!(functionFlags & FunctionFlags.Generator); - const isAsync = !!(functionFlags & FunctionFlags.Async); - return type && isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, type, isAsync) || errorType : - type && isAsync ? getAwaitedType(type) || errorType : - type; - } - function checkFunctionExpressionOrObjectLiteralMethodDeferred(node: ArrowFunction | FunctionExpression | MethodDeclaration) { Debug.assert(node.kind !== SyntaxKind.MethodDeclaration || isObjectLiteralMethod(node)); @@ -27399,7 +27391,7 @@ namespace ts { // check assignability of the awaited type of the expression body against the promised type of // its return type annotation. const exprType = checkExpression(node.body); - const returnOrPromisedType = getReturnOrPromisedType(returnType, functionFlags); + const returnOrPromisedType = returnType && unwrapReturnType(returnType, functionFlags); if (returnOrPromisedType) { if ((functionFlags & FunctionFlags.AsyncGenerator) === FunctionFlags.Async) { // Async function const awaitedType = checkAwaitedType(exprType, node.body, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member); @@ -32687,8 +32679,8 @@ namespace ts { function unwrapReturnType(returnType: Type, functionFlags: FunctionFlags) { const isGenerator = !!(functionFlags & FunctionFlags.Generator); const isAsync = !!(functionFlags & FunctionFlags.Async); - return isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync) || errorType : - isAsync ? getPromisedTypeOfPromise(returnType) || errorType : + return isGenerator ? getIterationTypeOfGeneratorFunctionReturnType(IterationTypeKind.Return, returnType, isAsync) ?? errorType : + isAsync ? getAwaitedType(returnType) ?? errorType : returnType; } @@ -32725,7 +32717,7 @@ namespace ts { } } else if (getReturnTypeFromAnnotation(func)) { - const unwrappedReturnType = unwrapReturnType(returnType, functionFlags); + const unwrappedReturnType = unwrapReturnType(returnType, functionFlags) ?? returnType; const unwrappedExprType = functionFlags & FunctionFlags.Async ? checkAwaitedType(exprType, node, Diagnostics.The_return_type_of_an_async_function_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member) : exprType; diff --git a/tests/baselines/reference/asyncArrowFunction_allowJs.errors.txt b/tests/baselines/reference/asyncArrowFunction_allowJs.errors.txt new file mode 100644 index 00000000000..6c8d0c370d2 --- /dev/null +++ b/tests/baselines/reference/asyncArrowFunction_allowJs.errors.txt @@ -0,0 +1,38 @@ +tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(3,17): error TS2322: Type '0' is not assignable to type 'string'. +tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(7,23): error TS2322: Type '0' is not assignable to type 'string'. +tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(12,2): error TS2322: Type '0' is not assignable to type 'string'. +tests/cases/conformance/async/es2017/asyncArrowFunction/file.js(19,3): error TS2345: Argument of type '() => Promise' is not assignable to parameter of type '() => string'. + Type 'Promise' is not assignable to type 'string'. + + +==== tests/cases/conformance/async/es2017/asyncArrowFunction/file.js (4 errors) ==== + // Error (good) + /** @type {function(): string} */ + const a = () => 0 + ~ +!!! error TS2322: Type '0' is not assignable to type 'string'. + + // Error (good) + /** @type {function(): string} */ + const b = async () => 0 + ~ +!!! error TS2322: Type '0' is not assignable to type 'string'. + + // No error (bad) + /** @type {function(): string} */ + const c = async () => { + return 0 + ~~~~~~~~ +!!! error TS2322: Type '0' is not assignable to type 'string'. + } + + /** @type {function(function(): string): void} */ + const f = (p) => {} + + // Error (good) + f(async () => { + ~~~~~~~~~~~~~ +!!! error TS2345: Argument of type '() => Promise' is not assignable to parameter of type '() => string'. +!!! error TS2345: Type 'Promise' is not assignable to type 'string'. + return 0 + }) \ No newline at end of file diff --git a/tests/baselines/reference/asyncArrowFunction_allowJs.symbols b/tests/baselines/reference/asyncArrowFunction_allowJs.symbols new file mode 100644 index 00000000000..929831b6f8a --- /dev/null +++ b/tests/baselines/reference/asyncArrowFunction_allowJs.symbols @@ -0,0 +1,30 @@ +=== tests/cases/conformance/async/es2017/asyncArrowFunction/file.js === +// Error (good) +/** @type {function(): string} */ +const a = () => 0 +>a : Symbol(a, Decl(file.js, 2, 5)) + +// Error (good) +/** @type {function(): string} */ +const b = async () => 0 +>b : Symbol(b, Decl(file.js, 6, 5)) + +// No error (bad) +/** @type {function(): string} */ +const c = async () => { +>c : Symbol(c, Decl(file.js, 10, 5)) + + return 0 +} + +/** @type {function(function(): string): void} */ +const f = (p) => {} +>f : Symbol(f, Decl(file.js, 15, 5)) +>p : Symbol(p, Decl(file.js, 15, 11)) + +// Error (good) +f(async () => { +>f : Symbol(f, Decl(file.js, 15, 5)) + + return 0 +}) diff --git a/tests/baselines/reference/asyncArrowFunction_allowJs.types b/tests/baselines/reference/asyncArrowFunction_allowJs.types new file mode 100644 index 00000000000..f0c6059cf21 --- /dev/null +++ b/tests/baselines/reference/asyncArrowFunction_allowJs.types @@ -0,0 +1,41 @@ +=== tests/cases/conformance/async/es2017/asyncArrowFunction/file.js === +// Error (good) +/** @type {function(): string} */ +const a = () => 0 +>a : () => string +>() => 0 : () => string +>0 : 0 + +// Error (good) +/** @type {function(): string} */ +const b = async () => 0 +>b : () => string +>async () => 0 : () => string +>0 : 0 + +// No error (bad) +/** @type {function(): string} */ +const c = async () => { +>c : () => string +>async () => { return 0} : () => string + + return 0 +>0 : 0 +} + +/** @type {function(function(): string): void} */ +const f = (p) => {} +>f : (arg0: () => string) => void +>(p) => {} : (p: () => string) => void +>p : () => string + +// Error (good) +f(async () => { +>f(async () => { return 0}) : void +>f : (arg0: () => string) => void +>async () => { return 0} : () => Promise + + return 0 +>0 : 0 + +}) diff --git a/tests/cases/conformance/async/es2017/asyncArrowFunction/asyncArrowFunction_allowJs.ts b/tests/cases/conformance/async/es2017/asyncArrowFunction/asyncArrowFunction_allowJs.ts new file mode 100644 index 00000000000..9c21ae85576 --- /dev/null +++ b/tests/cases/conformance/async/es2017/asyncArrowFunction/asyncArrowFunction_allowJs.ts @@ -0,0 +1,27 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @target: es2017 +// @filename: file.js + +// Error (good) +/** @type {function(): string} */ +const a = () => 0 + +// Error (good) +/** @type {function(): string} */ +const b = async () => 0 + +// No error (bad) +/** @type {function(): string} */ +const c = async () => { + return 0 +} + +/** @type {function(function(): string): void} */ +const f = (p) => {} + +// Error (good) +f(async () => { + return 0 +}) \ No newline at end of file