From 3cf26e44ee1205258cd256eca124e02fd5c9770d Mon Sep 17 00:00:00 2001 From: Zen <843968788@qq.com> Date: Tue, 6 Apr 2021 22:21:02 +0800 Subject: [PATCH] fix(43160): improve error location for functions without explicit return (#43367) * fix(43160): improve error location for functions without explicit return * handle functions returning never --- src/compiler/checker.ts | 10 +- .../errorOnFunctionReturnType.errors.txt | 74 +++++++++++++++ .../errorOnFunctionReturnType.symbols | 78 +++++++++++++++ .../reference/errorOnFunctionReturnType.types | 94 +++++++++++++++++++ .../jsdoc/errorOnFunctionReturnType.ts | 52 ++++++++++ 5 files changed, 303 insertions(+), 5 deletions(-) create mode 100644 tests/baselines/reference/errorOnFunctionReturnType.errors.txt create mode 100644 tests/baselines/reference/errorOnFunctionReturnType.symbols create mode 100644 tests/baselines/reference/errorOnFunctionReturnType.types create mode 100644 tests/cases/conformance/jsdoc/errorOnFunctionReturnType.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f5fc9912692..50d0c929504 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -30574,18 +30574,18 @@ namespace ts { } const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn; + const errorNode = getEffectiveReturnTypeNode(func) || func; if (type && type.flags & TypeFlags.Never) { - error(getEffectiveReturnTypeNode(func), Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point); + error(errorNode, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point); } else if (type && !hasExplicitReturn) { // minimal check: function has syntactic return type annotation and no explicit return statements in the body // this function does not conform to the specification. - // NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present - error(getEffectiveReturnTypeNode(func), Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); + error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value); } else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) { - error(getEffectiveReturnTypeNode(func) || func, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); + error(errorNode, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined); } else if (compilerOptions.noImplicitReturns) { if (!type) { @@ -30600,7 +30600,7 @@ namespace ts { return; } } - error(getEffectiveReturnTypeNode(func) || func, Diagnostics.Not_all_code_paths_return_a_value); + error(errorNode, Diagnostics.Not_all_code_paths_return_a_value); } } diff --git a/tests/baselines/reference/errorOnFunctionReturnType.errors.txt b/tests/baselines/reference/errorOnFunctionReturnType.errors.txt new file mode 100644 index 00000000000..90666bc7f53 --- /dev/null +++ b/tests/baselines/reference/errorOnFunctionReturnType.errors.txt @@ -0,0 +1,74 @@ +tests/cases/conformance/jsdoc/foo.js(7,10): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. +tests/cases/conformance/jsdoc/foo.js(13,5): error TS2322: Type 'string' is not assignable to type 'number'. +tests/cases/conformance/jsdoc/foo.js(16,60): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. +tests/cases/conformance/jsdoc/foo.js(21,20): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. +tests/cases/conformance/jsdoc/foo.js(31,10): error TS2534: A function returning 'never' cannot have a reachable end point. +tests/cases/conformance/jsdoc/foo.js(37,5): error TS2322: Type 'string' is not assignable to type 'never'. +tests/cases/conformance/jsdoc/foo.js(40,56): error TS2534: A function returning 'never' cannot have a reachable end point. +tests/cases/conformance/jsdoc/foo.js(45,18): error TS2534: A function returning 'never' cannot have a reachable end point. + + +==== tests/cases/conformance/jsdoc/foo.js (8 errors) ==== + /** + * @callback FunctionReturningPromise + * @returns {Promise} + */ + + /** @type {FunctionReturningPromise} */ + function testPromise1() { + ~~~~~~~~~~~~ +!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. + console.log("Nope"); + } + + /** @type {FunctionReturningPromise} */ + async function testPromise2() { + return "asd"; + ~~~~~~~~~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'number'. + } + + var testPromise3 = /** @type {FunctionReturningPromise} */ function() { + ~~~~~~~~ +!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. + console.log("test") + } + + /** @type {FunctionReturningPromise} */ + var testPromise4 = function() { + ~~~~~~~~ +!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value. + console.log("test") + } + + /** + * @callback FunctionReturningNever + * @returns {never} + */ + + /** @type {FunctionReturningNever} */ + function testNever1() { + ~~~~~~~~~~ +!!! error TS2534: A function returning 'never' cannot have a reachable end point. + + } + + /** @type {FunctionReturningNever} */ + async function testNever2() { + return "asd"; + ~~~~~~~~~~~~~ +!!! error TS2322: Type 'string' is not assignable to type 'never'. + } + + var testNever3 = /** @type {FunctionReturningNever} */ function() { + ~~~~~~~~ +!!! error TS2534: A function returning 'never' cannot have a reachable end point. + console.log("test") + } + + /** @type {FunctionReturningNever} */ + var testNever4 = function() { + ~~~~~~~~ +!!! error TS2534: A function returning 'never' cannot have a reachable end point. + console.log("test") + } \ No newline at end of file diff --git a/tests/baselines/reference/errorOnFunctionReturnType.symbols b/tests/baselines/reference/errorOnFunctionReturnType.symbols new file mode 100644 index 00000000000..8475308d659 --- /dev/null +++ b/tests/baselines/reference/errorOnFunctionReturnType.symbols @@ -0,0 +1,78 @@ +=== tests/cases/conformance/jsdoc/foo.js === +/** + * @callback FunctionReturningPromise + * @returns {Promise} + */ + +/** @type {FunctionReturningPromise} */ +function testPromise1() { +>testPromise1 : Symbol(testPromise1, Decl(foo.js, 0, 0)) + + console.log("Nope"); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +} + +/** @type {FunctionReturningPromise} */ +async function testPromise2() { +>testPromise2 : Symbol(testPromise2, Decl(foo.js, 8, 1)) + + return "asd"; +} + +var testPromise3 = /** @type {FunctionReturningPromise} */ function() { +>testPromise3 : Symbol(testPromise3, Decl(foo.js, 15, 3)) + + console.log("test") +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +} + +/** @type {FunctionReturningPromise} */ +var testPromise4 = function() { +>testPromise4 : Symbol(testPromise4, Decl(foo.js, 20, 3)) + + console.log("test") +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +} + +/** + * @callback FunctionReturningNever + * @returns {never} + */ + +/** @type {FunctionReturningNever} */ +function testNever1() { +>testNever1 : Symbol(testNever1, Decl(foo.js, 22, 1)) + +} + +/** @type {FunctionReturningNever} */ +async function testNever2() { +>testNever2 : Symbol(testNever2, Decl(foo.js, 32, 1)) + + return "asd"; +} + +var testNever3 = /** @type {FunctionReturningNever} */ function() { +>testNever3 : Symbol(testNever3, Decl(foo.js, 39, 3)) + + console.log("test") +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +} + +/** @type {FunctionReturningNever} */ +var testNever4 = function() { +>testNever4 : Symbol(testNever4, Decl(foo.js, 44, 3)) + + console.log("test") +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +} diff --git a/tests/baselines/reference/errorOnFunctionReturnType.types b/tests/baselines/reference/errorOnFunctionReturnType.types new file mode 100644 index 00000000000..afb819db0e0 --- /dev/null +++ b/tests/baselines/reference/errorOnFunctionReturnType.types @@ -0,0 +1,94 @@ +=== tests/cases/conformance/jsdoc/foo.js === +/** + * @callback FunctionReturningPromise + * @returns {Promise} + */ + +/** @type {FunctionReturningPromise} */ +function testPromise1() { +>testPromise1 : () => Promise + + console.log("Nope"); +>console.log("Nope") : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>"Nope" : "Nope" +} + +/** @type {FunctionReturningPromise} */ +async function testPromise2() { +>testPromise2 : () => Promise + + return "asd"; +>"asd" : "asd" +} + +var testPromise3 = /** @type {FunctionReturningPromise} */ function() { +>testPromise3 : FunctionReturningPromise +>function() { console.log("test")} : () => Promise + + console.log("test") +>console.log("test") : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>"test" : "test" +} + +/** @type {FunctionReturningPromise} */ +var testPromise4 = function() { +>testPromise4 : FunctionReturningPromise +>function() { console.log("test")} : () => Promise + + console.log("test") +>console.log("test") : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>"test" : "test" +} + +/** + * @callback FunctionReturningNever + * @returns {never} + */ + +/** @type {FunctionReturningNever} */ +function testNever1() { +>testNever1 : () => never + +} + +/** @type {FunctionReturningNever} */ +async function testNever2() { +>testNever2 : () => never + + return "asd"; +>"asd" : "asd" +} + +var testNever3 = /** @type {FunctionReturningNever} */ function() { +>testNever3 : FunctionReturningNever +>function() { console.log("test")} : () => never + + console.log("test") +>console.log("test") : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>"test" : "test" +} + +/** @type {FunctionReturningNever} */ +var testNever4 = function() { +>testNever4 : FunctionReturningNever +>function() { console.log("test")} : () => never + + console.log("test") +>console.log("test") : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>"test" : "test" +} diff --git a/tests/cases/conformance/jsdoc/errorOnFunctionReturnType.ts b/tests/cases/conformance/jsdoc/errorOnFunctionReturnType.ts new file mode 100644 index 00000000000..6434d0a77c2 --- /dev/null +++ b/tests/cases/conformance/jsdoc/errorOnFunctionReturnType.ts @@ -0,0 +1,52 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: foo.js + +/** + * @callback FunctionReturningPromise + * @returns {Promise} + */ + +/** @type {FunctionReturningPromise} */ +function testPromise1() { + console.log("Nope"); +} + +/** @type {FunctionReturningPromise} */ +async function testPromise2() { + return "asd"; +} + +var testPromise3 = /** @type {FunctionReturningPromise} */ function() { + console.log("test") +} + +/** @type {FunctionReturningPromise} */ +var testPromise4 = function() { + console.log("test") +} + +/** + * @callback FunctionReturningNever + * @returns {never} + */ + +/** @type {FunctionReturningNever} */ +function testNever1() { + +} + +/** @type {FunctionReturningNever} */ +async function testNever2() { + return "asd"; +} + +var testNever3 = /** @type {FunctionReturningNever} */ function() { + console.log("test") +} + +/** @type {FunctionReturningNever} */ +var testNever4 = function() { + console.log("test") +} \ No newline at end of file