Skip to content

Commit f473058

Browse files
Allow implicit return with explicit undefined return type (#53092)
Co-authored-by: Daniel Rosenwasser <[email protected]>
1 parent a70c409 commit f473058

18 files changed

+240
-43
lines changed

src/compiler/checker.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35540,8 +35540,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3554035540
const functionFlags = getFunctionFlags(func);
3554135541
const type = returnType && unwrapReturnType(returnType, functionFlags);
3554235542

35543-
// Functions with with an explicitly specified 'void' or 'any' return type don't need any return expressions.
35544-
if (type && maybeTypeOfKind(type, TypeFlags.Any | TypeFlags.Void)) {
35543+
// Functions with an explicitly specified 'undefined, 'void' or 'any' return type don't need any return expressions.
35544+
if (type && maybeTypeOfKind(type, TypeFlags.Undefined | TypeFlags.Void | TypeFlags.Any)) {
3554535545
return;
3554635546
}
3554735547

@@ -35560,9 +35560,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3556035560
else if (type && !hasExplicitReturn) {
3556135561
// minimal check: function has syntactic return type annotation and no explicit return statements in the body
3556235562
// this function does not conform to the specification.
35563-
error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value);
35563+
if (strictNullChecks) {
35564+
error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_undefined_void_nor_any_must_return_a_value);
35565+
}
35566+
else {
35567+
error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value);
35568+
}
3556435569
}
35565-
else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) {
35570+
else if (type && strictNullChecks) {
3556635571
error(errorNode, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined);
3556735572
}
3556835573
else if (compilerOptions.noImplicitReturns) {

src/compiler/diagnosticMessages.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3611,6 +3611,10 @@
36113611
"category": "Error",
36123612
"code": 2846
36133613
},
3614+
"A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.": {
3615+
"category": "Error",
3616+
"code": 2847
3617+
},
36143618

36153619
"Import declaration '{0}' is using private name '{1}'.": {
36163620
"category": "Error",
@@ -6718,7 +6722,7 @@
67186722
"category": "Suggestion",
67196723
"code": 80010
67206724
},
6721-
6725+
67226726
"Add missing 'super()' call": {
67236727
"category": "Message",
67246728
"code": 90001

src/services/codefixes/returnValueCorrect.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const fixRemoveBracesFromArrowFunctionBody = "fixRemoveBracesFromArrowFunctionBo
5252
const fixIdWrapTheBlockWithParen = "fixWrapTheBlockWithParen";
5353
const errorCodes = [
5454
Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value.code,
55+
Diagnostics.A_function_whose_declared_type_is_neither_undefined_void_nor_any_must_return_a_value.code,
5556
Diagnostics.Type_0_is_not_assignable_to_type_1.code,
5657
Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code
5758
];
@@ -214,6 +215,7 @@ function getInfo(checker: TypeChecker, sourceFile: SourceFile, position: number,
214215
const declaration = findAncestor(node.parent, isFunctionLikeDeclaration);
215216
switch (errorCode) {
216217
case Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value.code:
218+
case Diagnostics.A_function_whose_declared_type_is_neither_undefined_void_nor_any_must_return_a_value.code:
217219
if (!declaration || !declaration.body || !declaration.type || !rangeContainsRange(declaration.type, node)) return undefined;
218220
return getFixInfo(checker, declaration, checker.getTypeFromTypeNode(declaration.type), /* isFunctionType */ false);
219221
case Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code:

tests/baselines/reference/controlFlowGenericTypes.errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(49,15): error TS2
22
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(55,15): error TS2345: Argument of type 'undefined' is not assignable to parameter of type 'Box<unknown>'.
33
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(81,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'.
44
Property 'foo' does not exist on type 'AA'.
5-
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(90,44): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
5+
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(90,44): error TS2847: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
66
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(91,11): error TS2339: Property 'foo' does not exist on type 'MyUnion'.
77
Property 'foo' does not exist on type 'AA'.
88
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(156,16): error TS18048: 'obj' is possibly 'undefined'.
@@ -109,7 +109,7 @@ tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(168,9): error TS1
109109

110110
const fn2 = <T extends MyUnion>(value: T): MyUnion => {
111111
~~~~~~~
112-
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
112+
!!! error TS2847: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
113113
value.foo; // Error
114114
~~~
115115
!!! error TS2339: Property 'foo' does not exist on type 'MyUnion'.

tests/baselines/reference/functionsMissingReturnStatementsAndExpressions.errors.txt

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
tests/cases/compiler/functionsMissingReturnStatementsAndExpressions.ts(1,16): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
22
tests/cases/compiler/functionsMissingReturnStatementsAndExpressions.ts(99,17): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
3-
tests/cases/compiler/functionsMissingReturnStatementsAndExpressions.ts(104,16): error TS2378: A 'get' accessor must return a value.
4-
tests/cases/compiler/functionsMissingReturnStatementsAndExpressions.ts(126,15): error TS18050: The value 'undefined' cannot be used here.
5-
tests/cases/compiler/functionsMissingReturnStatementsAndExpressions.ts(127,5): error TS1003: Identifier expected.
3+
tests/cases/compiler/functionsMissingReturnStatementsAndExpressions.ts(107,17): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
4+
tests/cases/compiler/functionsMissingReturnStatementsAndExpressions.ts(112,16): error TS2378: A 'get' accessor must return a value.
5+
tests/cases/compiler/functionsMissingReturnStatementsAndExpressions.ts(134,15): error TS18050: The value 'undefined' cannot be used here.
6+
tests/cases/compiler/functionsMissingReturnStatementsAndExpressions.ts(135,5): error TS1003: Identifier expected.
67

78

8-
==== tests/cases/compiler/functionsMissingReturnStatementsAndExpressions.ts (5 errors) ====
9+
==== tests/cases/compiler/functionsMissingReturnStatementsAndExpressions.ts (6 errors) ====
910
function f1(): string {
1011
~~~~~~
1112
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
@@ -40,12 +41,12 @@ tests/cases/compiler/functionsMissingReturnStatementsAndExpressions.ts(127,5): e
4041
return null;
4142
}
4243

43-
function f8(): void {
44+
function f8(): any {
4445
// Fine since are typed any.
4546
return;
4647
}
4748

48-
function f9(): void {
49+
function f9(): any {
4950
// Fine since we are typed any and return undefined
5051
return undefined;
5152
}
@@ -112,6 +113,16 @@ tests/cases/compiler/functionsMissingReturnStatementsAndExpressions.ts(127,5): e
112113
// Not okay; union does not contain void or any
113114
}
114115

116+
function f22(): undefined {
117+
// Okay; return type allows implicit return of undefined
118+
}
119+
120+
function f23(): undefined | number {
121+
~~~~~~~~~~~~~~~~~~
122+
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
123+
// Error; because `undefined | number` becomes `number` without strictNullChecks.
124+
}
125+
115126
class C {
116127
public get m1() {
117128
~~
@@ -143,4 +154,5 @@ tests/cases/compiler/functionsMissingReturnStatementsAndExpressions.ts(127,5): e
143154
}
144155
~
145156
!!! error TS1003: Identifier expected.
146-
}
157+
}
158+

tests/baselines/reference/functionsMissingReturnStatementsAndExpressions.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ function f7(): void {
3131
return null;
3232
}
3333

34-
function f8(): void {
34+
function f8(): any {
3535
// Fine since are typed any.
3636
return;
3737
}
3838

39-
function f9(): void {
39+
function f9(): any {
4040
// Fine since we are typed any and return undefined
4141
return undefined;
4242
}
@@ -101,6 +101,14 @@ function f21(): number | string {
101101
// Not okay; union does not contain void or any
102102
}
103103

104+
function f22(): undefined {
105+
// Okay; return type allows implicit return of undefined
106+
}
107+
108+
function f23(): undefined | number {
109+
// Error; because `undefined | number` becomes `number` without strictNullChecks.
110+
}
111+
104112
class C {
105113
public get m1() {
106114
// Errors; get accessors must return a value.
@@ -126,7 +134,8 @@ class C {
126134
throw null;
127135
throw undefined.
128136
}
129-
}
137+
}
138+
130139

131140
//// [functionsMissingReturnStatementsAndExpressions.js]
132141
function f1() {
@@ -209,6 +218,12 @@ function f20() {
209218
function f21() {
210219
// Not okay; union does not contain void or any
211220
}
221+
function f22() {
222+
// Okay; return type allows implicit return of undefined
223+
}
224+
function f23() {
225+
// Error; because `undefined | number` becomes `number` without strictNullChecks.
226+
}
212227
var C = /** @class */ (function () {
213228
function C() {
214229
}

tests/baselines/reference/functionsMissingReturnStatementsAndExpressions.symbols

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ function f7(): void {
4646
return null;
4747
}
4848

49-
function f8(): void {
49+
function f8(): any {
5050
>f8 : Symbol(f8, Decl(functionsMissingReturnStatementsAndExpressions.ts, 30, 1))
5151

5252
// Fine since are typed any.
5353
return;
5454
}
5555

56-
function f9(): void {
56+
function f9(): any {
5757
>f9 : Symbol(f9, Decl(functionsMissingReturnStatementsAndExpressions.ts, 35, 1))
5858

5959
// Fine since we are typed any and return undefined
@@ -152,37 +152,49 @@ function f21(): number | string {
152152
// Not okay; union does not contain void or any
153153
}
154154

155+
function f22(): undefined {
156+
>f22 : Symbol(f22, Decl(functionsMissingReturnStatementsAndExpressions.ts, 100, 1))
157+
158+
// Okay; return type allows implicit return of undefined
159+
}
160+
161+
function f23(): undefined | number {
162+
>f23 : Symbol(f23, Decl(functionsMissingReturnStatementsAndExpressions.ts, 104, 1))
163+
164+
// Error; because `undefined | number` becomes `number` without strictNullChecks.
165+
}
166+
155167
class C {
156-
>C : Symbol(C, Decl(functionsMissingReturnStatementsAndExpressions.ts, 100, 1))
168+
>C : Symbol(C, Decl(functionsMissingReturnStatementsAndExpressions.ts, 108, 1))
157169

158170
public get m1() {
159-
>m1 : Symbol(C.m1, Decl(functionsMissingReturnStatementsAndExpressions.ts, 102, 9))
171+
>m1 : Symbol(C.m1, Decl(functionsMissingReturnStatementsAndExpressions.ts, 110, 9))
160172

161173
// Errors; get accessors must return a value.
162174
}
163175

164176
public get m2() {
165-
>m2 : Symbol(C.m2, Decl(functionsMissingReturnStatementsAndExpressions.ts, 105, 5))
177+
>m2 : Symbol(C.m2, Decl(functionsMissingReturnStatementsAndExpressions.ts, 113, 5))
166178

167179
// Permissible; returns undefined.
168180
return;
169181
}
170182

171183
public get m3() {
172-
>m3 : Symbol(C.m3, Decl(functionsMissingReturnStatementsAndExpressions.ts, 110, 5))
184+
>m3 : Symbol(C.m3, Decl(functionsMissingReturnStatementsAndExpressions.ts, 118, 5))
173185

174186
return "Okay, because this is a return expression.";
175187
}
176188

177189
public get m4() {
178-
>m4 : Symbol(C.m4, Decl(functionsMissingReturnStatementsAndExpressions.ts, 114, 5))
190+
>m4 : Symbol(C.m4, Decl(functionsMissingReturnStatementsAndExpressions.ts, 122, 5))
179191

180192
// Fine since this consists of a single throw statement.
181193
throw null;
182194
}
183195

184196
public get m5() {
185-
>m5 : Symbol(C.m5, Decl(functionsMissingReturnStatementsAndExpressions.ts, 119, 5))
197+
>m5 : Symbol(C.m5, Decl(functionsMissingReturnStatementsAndExpressions.ts, 127, 5))
186198

187199
// Not fine, since we can *only* consist of a single throw statement
188200
// if no return statements are present but we are a get accessor.
@@ -191,3 +203,4 @@ class C {
191203
>undefined : Symbol(undefined)
192204
}
193205
}
206+

tests/baselines/reference/functionsMissingReturnStatementsAndExpressions.types

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ function f7(): void {
4747
return null;
4848
}
4949

50-
function f8(): void {
51-
>f8 : () => void
50+
function f8(): any {
51+
>f8 : () => any
5252

5353
// Fine since are typed any.
5454
return;
5555
}
5656

57-
function f9(): void {
58-
>f9 : () => void
57+
function f9(): any {
58+
>f9 : () => any
5959

6060
// Fine since we are typed any and return undefined
6161
return undefined;
@@ -154,6 +154,18 @@ function f21(): number | string {
154154
// Not okay; union does not contain void or any
155155
}
156156

157+
function f22(): undefined {
158+
>f22 : () => undefined
159+
160+
// Okay; return type allows implicit return of undefined
161+
}
162+
163+
function f23(): undefined | number {
164+
>f23 : () => undefined | number
165+
166+
// Error; because `undefined | number` becomes `number` without strictNullChecks.
167+
}
168+
157169
class C {
158170
>C : C
159171

@@ -196,3 +208,4 @@ class C {
196208
}
197209
> : any
198210
}
211+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
tests/cases/compiler/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts(5,16): error TS2847: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
2+
tests/cases/compiler/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts(13,22): error TS2847: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
3+
4+
5+
==== tests/cases/compiler/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts (2 errors) ====
6+
function f1(): undefined | number {
7+
// Okay; return type allows implicit return of undefined
8+
}
9+
10+
function f2(): number {
11+
~~~~~~
12+
!!! error TS2847: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
13+
// Error; return type does not include undefined
14+
}
15+
16+
async function f3(): Promise<undefined | number> {
17+
// Okay; return type allows implicit return of undefined
18+
}
19+
20+
async function f4(): Promise<number> {
21+
~~~~~~~~~~~~~~~
22+
!!! error TS2847: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
23+
// Error; return type does not include undefined
24+
}
25+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//// [functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts]
2+
function f1(): undefined | number {
3+
// Okay; return type allows implicit return of undefined
4+
}
5+
6+
function f2(): number {
7+
// Error; return type does not include undefined
8+
}
9+
10+
async function f3(): Promise<undefined | number> {
11+
// Okay; return type allows implicit return of undefined
12+
}
13+
14+
async function f4(): Promise<number> {
15+
// Error; return type does not include undefined
16+
}
17+
18+
19+
//// [functionsMissingReturnStatementsAndExpressionsStrictNullChecks.js]
20+
function f1() {
21+
// Okay; return type allows implicit return of undefined
22+
}
23+
function f2() {
24+
// Error; return type does not include undefined
25+
}
26+
async function f3() {
27+
// Okay; return type allows implicit return of undefined
28+
}
29+
async function f4() {
30+
// Error; return type does not include undefined
31+
}

0 commit comments

Comments
 (0)