Skip to content

Commit b40385b

Browse files
authored
Explicit undefined return type checked similar to explicit void return type (#53607)
1 parent 27d3454 commit b40385b

File tree

40 files changed

+1002
-171
lines changed

40 files changed

+1002
-171
lines changed

src/compiler/checker.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35426,10 +35426,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3542635426
: neverType; // Normal function
3542735427
}
3542835428
if (types.length === 0) {
35429-
// For an async function, the return type will not be void, but rather a Promise for void.
35430-
return functionFlags & FunctionFlags.Async
35431-
? createPromiseReturnType(func, voidType) // Async function
35432-
: voidType; // Normal function
35429+
// For an async function, the return type will not be void/undefined, but rather a Promise for void/undefined.
35430+
const contextualReturnType = getContextualReturnType(func, /*contextFlags*/ undefined);
35431+
const returnType = contextualReturnType && (unwrapReturnType(contextualReturnType, functionFlags) || voidType).flags & TypeFlags.Undefined ? undefinedType : voidType;
35432+
return functionFlags & FunctionFlags.Async ? createPromiseReturnType(func, returnType) : // Async function
35433+
returnType; // Normal function
3543335434
}
3543435435

3543535436
// Return a union of the return expression types.
@@ -35665,8 +35666,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3566535666
const functionFlags = getFunctionFlags(func);
3566635667
const type = returnType && unwrapReturnType(returnType, functionFlags);
3566735668

35668-
// Functions with an explicitly specified 'undefined, 'void', 'any' or 'unknown' return type don't need any return expressions.
35669-
if (type && maybeTypeOfKind(type, TypeFlags.Undefined | TypeFlags.Void | TypeFlags.Any | TypeFlags.Unknown)) {
35669+
// Functions with an explicitly specified return type that includes `void` or is exactly `any` or `undefined` don't
35670+
// need any return statements.
35671+
if (type && (maybeTypeOfKind(type, TypeFlags.Void) || type.flags & (TypeFlags.Any | TypeFlags.Undefined))) {
3567035672
return;
3567135673
}
3567235674

@@ -35685,14 +35687,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3568535687
else if (type && !hasExplicitReturn) {
3568635688
// minimal check: function has syntactic return type annotation and no explicit return statements in the body
3568735689
// this function does not conform to the specification.
35688-
if (strictNullChecks) {
35689-
error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_undefined_void_nor_any_must_return_a_value);
35690-
}
35691-
else {
35692-
error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value);
35693-
}
35690+
error(errorNode, Diagnostics.A_function_whose_declared_type_is_neither_undefined_void_nor_any_must_return_a_value);
3569435691
}
35695-
else if (type && strictNullChecks) {
35692+
else if (type && strictNullChecks && !isTypeAssignableTo(undefinedType, type)) {
3569635693
error(errorNode, Diagnostics.Function_lacks_ending_return_statement_and_return_type_does_not_include_undefined);
3569735694
}
3569835695
else if (compilerOptions.noImplicitReturns) {
@@ -35704,7 +35701,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3570435701
return;
3570535702
}
3570635703
const inferredReturnType = getReturnTypeOfSignature(getSignatureFromDeclaration(func));
35707-
if (isUnwrappedReturnTypeVoidOrAny(func, inferredReturnType)) {
35704+
if (isUnwrappedReturnTypeUndefinedVoidOrAny(func, inferredReturnType)) {
3570835705
return;
3570935706
}
3571035707
}
@@ -42182,9 +42179,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4218242179
return isAsync ? getAwaitedTypeNoAlias(returnType) || errorType : returnType;
4218342180
}
4218442181

42185-
function isUnwrappedReturnTypeVoidOrAny(func: SignatureDeclaration, returnType: Type): boolean {
42186-
const unwrappedReturnType = unwrapReturnType(returnType, getFunctionFlags(func));
42187-
return !!unwrappedReturnType && maybeTypeOfKind(unwrappedReturnType, TypeFlags.Void | TypeFlags.AnyOrUnknown);
42182+
function isUnwrappedReturnTypeUndefinedVoidOrAny(func: SignatureDeclaration, returnType: Type): boolean {
42183+
const type = unwrapReturnType(returnType, getFunctionFlags(func));
42184+
return !!(type && (maybeTypeOfKind(type, TypeFlags.Void) || type.flags & (TypeFlags.Any | TypeFlags.Undefined)));
4218842185
}
4218942186

4219042187
function checkReturnStatement(node: ReturnStatement) {
@@ -42232,7 +42229,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4223242229
}
4223342230
}
4223442231
}
42235-
else if (container.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeVoidOrAny(container, returnType)) {
42232+
else if (container.kind !== SyntaxKind.Constructor && compilerOptions.noImplicitReturns && !isUnwrappedReturnTypeUndefinedVoidOrAny(container, returnType)) {
4223642233
// The function has a return type, but the return statement doesn't have an expression.
4223742234
error(node, Diagnostics.Not_all_code_paths_return_a_value);
4223842235
}

src/compiler/diagnosticMessages.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1880,7 +1880,7 @@
18801880
"category": "Error",
18811881
"code": 2354
18821882
},
1883-
"A function whose declared type is neither 'void' nor 'any' must return a value.": {
1883+
"A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.": {
18841884
"category": "Error",
18851885
"code": 2355
18861886
},
@@ -3615,10 +3615,6 @@
36153615
"category": "Error",
36163616
"code": 2846
36173617
},
3618-
"A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.": {
3619-
"category": "Error",
3620-
"code": 2847
3621-
},
36223618
"The right-hand side of an 'instanceof' expression must not be an instantiation expression.": {
36233619
"category": "Error",
36243620
"code": 2848

src/services/codefixes/returnValueCorrect.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ const fixIdAddReturnStatement = "fixAddReturnStatement";
5151
const fixRemoveBracesFromArrowFunctionBody = "fixRemoveBracesFromArrowFunctionBody";
5252
const fixIdWrapTheBlockWithParen = "fixWrapTheBlockWithParen";
5353
const errorCodes = [
54-
Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value.code,
5554
Diagnostics.A_function_whose_declared_type_is_neither_undefined_void_nor_any_must_return_a_value.code,
5655
Diagnostics.Type_0_is_not_assignable_to_type_1.code,
5756
Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1.code
@@ -214,7 +213,6 @@ function getInfo(checker: TypeChecker, sourceFile: SourceFile, position: number,
214213

215214
const declaration = findAncestor(node.parent, isFunctionLikeDeclaration);
216215
switch (errorCode) {
217-
case Diagnostics.A_function_whose_declared_type_is_neither_void_nor_any_must_return_a_value.code:
218216
case Diagnostics.A_function_whose_declared_type_is_neither_undefined_void_nor_any_must_return_a_value.code:
219217
if (!declaration || !declaration.body || !declaration.type || !rangeContainsRange(declaration.type, node)) return undefined;
220218
return getFixInfo(checker, declaration, checker.getTypeFromTypeNode(declaration.type), /*isFunctionType*/ false);

tests/baselines/reference/ParameterList5.errors.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
tests/cases/compiler/ParameterList5.ts(1,15): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
1+
tests/cases/compiler/ParameterList5.ts(1,15): error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
22
tests/cases/compiler/ParameterList5.ts(1,16): error TS2369: A parameter property is only allowed in a constructor implementation.
33
tests/cases/compiler/ParameterList5.ts(1,29): error TS2304: Cannot find name 'C'.
44

55

66
==== tests/cases/compiler/ParameterList5.ts (3 errors) ====
77
function A(): (public B) => C {
88
~~~~~~~~~~~~~~~
9-
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
9+
!!! error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
1010
~~~~~~~~
1111
!!! error TS2369: A parameter property is only allowed in a constructor implementation.
1212
~

tests/baselines/reference/asyncFunctionDeclaration15_es5.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(6,23): error TS1055: Type '{}' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
2-
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(6,23): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
2+
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(6,23): error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
33
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(7,23): error TS1055: Type 'any' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
44
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(8,23): error TS1055: Type 'number' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
5-
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(8,23): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
5+
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(8,23): error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
66
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(9,23): error TS1055: Type 'PromiseLike' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
77
tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration15_es5.ts(10,23): error TS1055: Type 'typeof Thenable' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
88
Construct signature return types 'Thenable' and 'PromiseLike<T>' are incompatible.
@@ -22,15 +22,15 @@ tests/cases/conformance/async/es5/functionDeclarations/asyncFunctionDeclaration1
2222
~~~
2323
!!! error TS1055: Type '{}' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
2424
~~~
25-
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
25+
!!! error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
2626
async function fn3(): any { } // error
2727
~~~
2828
!!! error TS1055: Type 'any' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
2929
async function fn4(): number { } // error
3030
~~~~~~
3131
!!! error TS1055: Type 'number' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.
3232
~~~~~~
33-
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
33+
!!! error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
3434
async function fn5(): PromiseLike<void> { } // error
3535
~~~~~~~~~~~~~~~~~
3636
!!! error TS1055: Type 'PromiseLike' is not a valid async function return type in ES5/ES3 because it does not refer to a Promise-compatible constructor value.

tests/baselines/reference/asyncFunctionDeclaration15_es6.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(6,23): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<{}>'?
2-
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(6,23): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
2+
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(6,23): error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
33
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(7,23): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<any>'?
44
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(8,23): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'?
5-
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(8,23): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
5+
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(8,23): error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
66
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(9,23): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<void>'?
77
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(10,23): error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<void>'?
88
tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration15_es6.ts(17,16): error TS1058: The return type of an async function must either be a valid promise or must not contain a callable 'then' member.
@@ -19,15 +19,15 @@ tests/cases/conformance/async/es6/functionDeclarations/asyncFunctionDeclaration1
1919
~~~
2020
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<{}>'?
2121
~~~
22-
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
22+
!!! error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
2323
async function fn3(): any { } // error
2424
~~~
2525
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<any>'?
2626
async function fn4(): number { } // error
2727
~~~~~~
2828
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'?
2929
~~~~~~
30-
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
30+
!!! error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
3131
async function fn5(): PromiseLike<void> { } // error
3232
~~~~~~~~~~~~~~~~~
3333
!!! error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<void>'?

tests/baselines/reference/conflictingTypeAnnotatedVar.errors.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
tests/cases/compiler/conflictingTypeAnnotatedVar.ts(1,5): error TS2300: Duplicate identifier 'foo'.
22
tests/cases/compiler/conflictingTypeAnnotatedVar.ts(2,10): error TS2300: Duplicate identifier 'foo'.
33
tests/cases/compiler/conflictingTypeAnnotatedVar.ts(2,10): error TS2393: Duplicate function implementation.
4-
tests/cases/compiler/conflictingTypeAnnotatedVar.ts(2,17): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
4+
tests/cases/compiler/conflictingTypeAnnotatedVar.ts(2,17): error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
55
tests/cases/compiler/conflictingTypeAnnotatedVar.ts(3,10): error TS2300: Duplicate identifier 'foo'.
66
tests/cases/compiler/conflictingTypeAnnotatedVar.ts(3,10): error TS2393: Duplicate function implementation.
7-
tests/cases/compiler/conflictingTypeAnnotatedVar.ts(3,17): error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
7+
tests/cases/compiler/conflictingTypeAnnotatedVar.ts(3,17): error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
88

99

1010
==== tests/cases/compiler/conflictingTypeAnnotatedVar.ts (7 errors) ====
@@ -17,11 +17,11 @@ tests/cases/compiler/conflictingTypeAnnotatedVar.ts(3,17): error TS2355: A funct
1717
~~~
1818
!!! error TS2393: Duplicate function implementation.
1919
~~~~~~
20-
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
20+
!!! error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
2121
function foo(): number { }
2222
~~~
2323
!!! error TS2300: Duplicate identifier 'foo'.
2424
~~~
2525
!!! error TS2393: Duplicate function implementation.
2626
~~~~~~
27-
!!! error TS2355: A function whose declared type is neither 'void' nor 'any' must return a value.
27+
!!! error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.

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 TS2847: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
5+
tests/cases/conformance/controlFlow/controlFlowGenericTypes.ts(90,44): error TS2355: 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 TS2847: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
112+
!!! error TS2355: 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'.

0 commit comments

Comments
 (0)