Skip to content

Commit 3cf26e4

Browse files
authored
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
1 parent 3b06ef1 commit 3cf26e4

File tree

5 files changed

+303
-5
lines changed

5 files changed

+303
-5
lines changed

src/compiler/checker.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30574,18 +30574,18 @@ namespace ts {
3057430574
}
3057530575

3057630576
const hasExplicitReturn = func.flags & NodeFlags.HasExplicitReturn;
30577+
const errorNode = getEffectiveReturnTypeNode(func) || func;
3057730578

3057830579
if (type && type.flags & TypeFlags.Never) {
30579-
error(getEffectiveReturnTypeNode(func), Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point);
30580+
error(errorNode, Diagnostics.A_function_returning_never_cannot_have_a_reachable_end_point);
3058030581
}
3058130582
else if (type && !hasExplicitReturn) {
3058230583
// minimal check: function has syntactic return type annotation and no explicit return statements in the body
3058330584
// this function does not conform to the specification.
30584-
// NOTE: having returnType !== undefined is a precondition for entering this branch so func.type will always be present
30585-
error(getEffectiveReturnTypeNode(func), Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value);
30585+
error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value);
3058630586
}
3058730587
else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) {
30588-
error(getEffectiveReturnTypeNode(func) || func, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined);
30588+
error(errorNode, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined);
3058930589
}
3059030590
else if (compilerOptions.noImplicitReturns) {
3059130591
if (!type) {
@@ -30600,7 +30600,7 @@ namespace ts {
3060030600
return;
3060130601
}
3060230602
}
30603-
error(getEffectiveReturnTypeNode(func) || func, Diagnostics.Not_all_code_paths_return_a_value);
30603+
error(errorNode, Diagnostics.Not_all_code_paths_return_a_value);
3060430604
}
3060530605
}
3060630606

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
tests/cases/conformance/jsdoc/foo.js(7,10): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
2+
tests/cases/conformance/jsdoc/foo.js(13,5): error TS2322: Type 'string' is not assignable to type 'number'.
3+
tests/cases/conformance/jsdoc/foo.js(16,60): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
4+
tests/cases/conformance/jsdoc/foo.js(21,20): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
5+
tests/cases/conformance/jsdoc/foo.js(31,10): error TS2534: A function returning 'never' cannot have a reachable end point.
6+
tests/cases/conformance/jsdoc/foo.js(37,5): error TS2322: Type 'string' is not assignable to type 'never'.
7+
tests/cases/conformance/jsdoc/foo.js(40,56): error TS2534: A function returning 'never' cannot have a reachable end point.
8+
tests/cases/conformance/jsdoc/foo.js(45,18): error TS2534: A function returning 'never' cannot have a reachable end point.
9+
10+
11+
==== tests/cases/conformance/jsdoc/foo.js (8 errors) ====
12+
/**
13+
* @callback FunctionReturningPromise
14+
* @returns {Promise<number>}
15+
*/
16+
17+
/** @type {FunctionReturningPromise} */
18+
function testPromise1() {
19+
~~~~~~~~~~~~
20+
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
21+
console.log("Nope");
22+
}
23+
24+
/** @type {FunctionReturningPromise} */
25+
async function testPromise2() {
26+
return "asd";
27+
~~~~~~~~~~~~~
28+
!!! error TS2322: Type 'string' is not assignable to type 'number'.
29+
}
30+
31+
var testPromise3 = /** @type {FunctionReturningPromise} */ function() {
32+
~~~~~~~~
33+
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
34+
console.log("test")
35+
}
36+
37+
/** @type {FunctionReturningPromise} */
38+
var testPromise4 = function() {
39+
~~~~~~~~
40+
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
41+
console.log("test")
42+
}
43+
44+
/**
45+
* @callback FunctionReturningNever
46+
* @returns {never}
47+
*/
48+
49+
/** @type {FunctionReturningNever} */
50+
function testNever1() {
51+
~~~~~~~~~~
52+
!!! error TS2534: A function returning 'never' cannot have a reachable end point.
53+
54+
}
55+
56+
/** @type {FunctionReturningNever} */
57+
async function testNever2() {
58+
return "asd";
59+
~~~~~~~~~~~~~
60+
!!! error TS2322: Type 'string' is not assignable to type 'never'.
61+
}
62+
63+
var testNever3 = /** @type {FunctionReturningNever} */ function() {
64+
~~~~~~~~
65+
!!! error TS2534: A function returning 'never' cannot have a reachable end point.
66+
console.log("test")
67+
}
68+
69+
/** @type {FunctionReturningNever} */
70+
var testNever4 = function() {
71+
~~~~~~~~
72+
!!! error TS2534: A function returning 'never' cannot have a reachable end point.
73+
console.log("test")
74+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
=== tests/cases/conformance/jsdoc/foo.js ===
2+
/**
3+
* @callback FunctionReturningPromise
4+
* @returns {Promise<number>}
5+
*/
6+
7+
/** @type {FunctionReturningPromise} */
8+
function testPromise1() {
9+
>testPromise1 : Symbol(testPromise1, Decl(foo.js, 0, 0))
10+
11+
console.log("Nope");
12+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
13+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
14+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
15+
}
16+
17+
/** @type {FunctionReturningPromise} */
18+
async function testPromise2() {
19+
>testPromise2 : Symbol(testPromise2, Decl(foo.js, 8, 1))
20+
21+
return "asd";
22+
}
23+
24+
var testPromise3 = /** @type {FunctionReturningPromise} */ function() {
25+
>testPromise3 : Symbol(testPromise3, Decl(foo.js, 15, 3))
26+
27+
console.log("test")
28+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
29+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
30+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
31+
}
32+
33+
/** @type {FunctionReturningPromise} */
34+
var testPromise4 = function() {
35+
>testPromise4 : Symbol(testPromise4, Decl(foo.js, 20, 3))
36+
37+
console.log("test")
38+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
39+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
40+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
41+
}
42+
43+
/**
44+
* @callback FunctionReturningNever
45+
* @returns {never}
46+
*/
47+
48+
/** @type {FunctionReturningNever} */
49+
function testNever1() {
50+
>testNever1 : Symbol(testNever1, Decl(foo.js, 22, 1))
51+
52+
}
53+
54+
/** @type {FunctionReturningNever} */
55+
async function testNever2() {
56+
>testNever2 : Symbol(testNever2, Decl(foo.js, 32, 1))
57+
58+
return "asd";
59+
}
60+
61+
var testNever3 = /** @type {FunctionReturningNever} */ function() {
62+
>testNever3 : Symbol(testNever3, Decl(foo.js, 39, 3))
63+
64+
console.log("test")
65+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
66+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
67+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
68+
}
69+
70+
/** @type {FunctionReturningNever} */
71+
var testNever4 = function() {
72+
>testNever4 : Symbol(testNever4, Decl(foo.js, 44, 3))
73+
74+
console.log("test")
75+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
76+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
77+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
78+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
=== tests/cases/conformance/jsdoc/foo.js ===
2+
/**
3+
* @callback FunctionReturningPromise
4+
* @returns {Promise<number>}
5+
*/
6+
7+
/** @type {FunctionReturningPromise} */
8+
function testPromise1() {
9+
>testPromise1 : () => Promise<number>
10+
11+
console.log("Nope");
12+
>console.log("Nope") : void
13+
>console.log : (...data: any[]) => void
14+
>console : Console
15+
>log : (...data: any[]) => void
16+
>"Nope" : "Nope"
17+
}
18+
19+
/** @type {FunctionReturningPromise} */
20+
async function testPromise2() {
21+
>testPromise2 : () => Promise<number>
22+
23+
return "asd";
24+
>"asd" : "asd"
25+
}
26+
27+
var testPromise3 = /** @type {FunctionReturningPromise} */ function() {
28+
>testPromise3 : FunctionReturningPromise
29+
>function() { console.log("test")} : () => Promise<number>
30+
31+
console.log("test")
32+
>console.log("test") : void
33+
>console.log : (...data: any[]) => void
34+
>console : Console
35+
>log : (...data: any[]) => void
36+
>"test" : "test"
37+
}
38+
39+
/** @type {FunctionReturningPromise} */
40+
var testPromise4 = function() {
41+
>testPromise4 : FunctionReturningPromise
42+
>function() { console.log("test")} : () => Promise<number>
43+
44+
console.log("test")
45+
>console.log("test") : void
46+
>console.log : (...data: any[]) => void
47+
>console : Console
48+
>log : (...data: any[]) => void
49+
>"test" : "test"
50+
}
51+
52+
/**
53+
* @callback FunctionReturningNever
54+
* @returns {never}
55+
*/
56+
57+
/** @type {FunctionReturningNever} */
58+
function testNever1() {
59+
>testNever1 : () => never
60+
61+
}
62+
63+
/** @type {FunctionReturningNever} */
64+
async function testNever2() {
65+
>testNever2 : () => never
66+
67+
return "asd";
68+
>"asd" : "asd"
69+
}
70+
71+
var testNever3 = /** @type {FunctionReturningNever} */ function() {
72+
>testNever3 : FunctionReturningNever
73+
>function() { console.log("test")} : () => never
74+
75+
console.log("test")
76+
>console.log("test") : void
77+
>console.log : (...data: any[]) => void
78+
>console : Console
79+
>log : (...data: any[]) => void
80+
>"test" : "test"
81+
}
82+
83+
/** @type {FunctionReturningNever} */
84+
var testNever4 = function() {
85+
>testNever4 : FunctionReturningNever
86+
>function() { console.log("test")} : () => never
87+
88+
console.log("test")
89+
>console.log("test") : void
90+
>console.log : (...data: any[]) => void
91+
>console : Console
92+
>log : (...data: any[]) => void
93+
>"test" : "test"
94+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// @noEmit: true
2+
// @allowJs: true
3+
// @checkJs: true
4+
// @Filename: foo.js
5+
6+
/**
7+
* @callback FunctionReturningPromise
8+
* @returns {Promise<number>}
9+
*/
10+
11+
/** @type {FunctionReturningPromise} */
12+
function testPromise1() {
13+
console.log("Nope");
14+
}
15+
16+
/** @type {FunctionReturningPromise} */
17+
async function testPromise2() {
18+
return "asd";
19+
}
20+
21+
var testPromise3 = /** @type {FunctionReturningPromise} */ function() {
22+
console.log("test")
23+
}
24+
25+
/** @type {FunctionReturningPromise} */
26+
var testPromise4 = function() {
27+
console.log("test")
28+
}
29+
30+
/**
31+
* @callback FunctionReturningNever
32+
* @returns {never}
33+
*/
34+
35+
/** @type {FunctionReturningNever} */
36+
function testNever1() {
37+
38+
}
39+
40+
/** @type {FunctionReturningNever} */
41+
async function testNever2() {
42+
return "asd";
43+
}
44+
45+
var testNever3 = /** @type {FunctionReturningNever} */ function() {
46+
console.log("test")
47+
}
48+
49+
/** @type {FunctionReturningNever} */
50+
var testNever4 = function() {
51+
console.log("test")
52+
}

0 commit comments

Comments
 (0)