Allow implicit undefined returns when the contextual union type contains it (#57912)

This commit is contained in:
Mateusz Burzyński
2025-10-30 19:56:21 +01:00
committed by GitHub
parent 2b2d6cebcb
commit f6f4eab587
7 changed files with 138 additions and 23 deletions

View File

@@ -39125,7 +39125,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (types.length === 0) {
// For an async function, the return type will not be void/undefined, but rather a Promise for void/undefined.
const contextualReturnType = getContextualReturnType(func, /*contextFlags*/ undefined);
const returnType = contextualReturnType && (unwrapReturnType(contextualReturnType, functionFlags) || voidType).flags & TypeFlags.Undefined ? undefinedType : voidType;
const returnType = contextualReturnType && someType(unwrapReturnType(contextualReturnType, functionFlags) || voidType, t => !!(t.flags & TypeFlags.Undefined)) ? undefinedType : voidType;
return functionFlags & FunctionFlags.Async ? createPromiseReturnType(func, returnType) : // Async function
returnType; // Normal function
}

View File

@@ -1,7 +1,5 @@
functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts(5,17): error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts(9,17): error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts(17,7): error TS2322: Type '() => void' is not assignable to type '() => number | undefined'.
Type 'void' is not assignable to type 'number | undefined'.
functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts(21,7): error TS2322: Type '() => void' is not assignable to type '() => number'.
Type 'void' is not assignable to type 'number'.
functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts(29,23): error TS2355: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
@@ -10,7 +8,7 @@ functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts(52,3): error T
Type 'void' is not assignable to type 'undefined'.
==== functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts (7 errors) ====
==== functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts (6 errors) ====
function f10(): undefined {
// Ok, return type allows implicit return of undefined
}
@@ -32,10 +30,7 @@ functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts(52,3): error T
}
const f21: () => undefined | number = () => {
~~~
!!! error TS2322: Type '() => void' is not assignable to type '() => number | undefined'.
!!! error TS2322: Type 'void' is not assignable to type 'number | undefined'.
// Error, regular void function because contextual type for implicit return isn't just undefined
// Ok, contextual type for implicit return contains undefined
}
const f22: () => number = () => {
@@ -85,4 +80,20 @@ functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts(52,3): error T
}
f(h2);
// https://github.com/microsoft/TypeScript/issues/57840
type FN = () => Promise<undefined> | undefined;
const fn1: FN = () => {
return;
};
const fn2: FN = async () => {
return;
};
const fn3: FN = () => {};
const fn4: FN = async () => {};

View File

@@ -18,7 +18,7 @@ const f20: () => undefined = () => {
}
const f21: () => undefined | number = () => {
// Error, regular void function because contextual type for implicit return isn't just undefined
// Ok, contextual type for implicit return contains undefined
}
const f22: () => number = () => {
@@ -58,6 +58,22 @@ function h2(): undefined {
}
f(h2);
// https://github.com/microsoft/TypeScript/issues/57840
type FN = () => Promise<undefined> | undefined;
const fn1: FN = () => {
return;
};
const fn2: FN = async () => {
return;
};
const fn3: FN = () => {};
const fn4: FN = async () => {};
//// [functionsMissingReturnStatementsAndExpressionsStrictNullChecks.js]
@@ -74,7 +90,7 @@ const f20 = () => {
// Ok, contextual type for implicit return is undefined
};
const f21 = () => {
// Error, regular void function because contextual type for implicit return isn't just undefined
// Ok, contextual type for implicit return contains undefined
};
const f22 = () => {
// Error, regular void function because contextual type for implicit return isn't just undefined
@@ -98,3 +114,11 @@ f(h1); // Error
function h2() {
}
f(h2);
const fn1 = () => {
return;
};
const fn2 = async () => {
return;
};
const fn3 = () => { };
const fn4 = async () => { };

View File

@@ -28,7 +28,7 @@ const f20: () => undefined = () => {
const f21: () => undefined | number = () => {
>f21 : Symbol(f21, Decl(functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts, 16, 5))
// Error, regular void function because contextual type for implicit return isn't just undefined
// Ok, contextual type for implicit return contains undefined
}
const f22: () => number = () => {
@@ -92,3 +92,31 @@ f(h2);
>f : Symbol(f, Decl(functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts, 34, 1))
>h2 : Symbol(h2, Decl(functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts, 51, 6))
// https://github.com/microsoft/TypeScript/issues/57840
type FN = () => Promise<undefined> | undefined;
>FN : Symbol(FN, Decl(functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts, 56, 6))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --))
const fn1: FN = () => {
>fn1 : Symbol(fn1, Decl(functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts, 62, 5))
>FN : Symbol(FN, Decl(functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts, 56, 6))
return;
};
const fn2: FN = async () => {
>fn2 : Symbol(fn2, Decl(functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts, 66, 5))
>FN : Symbol(FN, Decl(functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts, 56, 6))
return;
};
const fn3: FN = () => {};
>fn3 : Symbol(fn3, Decl(functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts, 70, 5))
>FN : Symbol(FN, Decl(functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts, 56, 6))
const fn4: FN = async () => {};
>fn4 : Symbol(fn4, Decl(functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts, 72, 5))
>FN : Symbol(FN, Decl(functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts, 56, 6))

View File

@@ -34,10 +34,10 @@ const f20: () => undefined = () => {
const f21: () => undefined | number = () => {
>f21 : () => undefined | number
> : ^^^^^^
>() => { // Error, regular void function because contextual type for implicit return isn't just undefined} : () => void
> : ^^^^^^^^^^
>() => { // Ok, contextual type for implicit return contains undefined} : () => undefined
> : ^^^^^^^^^^^^^^^
// Error, regular void function because contextual type for implicit return isn't just undefined
// Ok, contextual type for implicit return contains undefined
}
const f22: () => number = () => {
@@ -132,3 +132,39 @@ f(h2);
>h2 : () => undefined
> : ^^^^^^
// https://github.com/microsoft/TypeScript/issues/57840
type FN = () => Promise<undefined> | undefined;
>FN : FN
> : ^^
const fn1: FN = () => {
>fn1 : FN
> : ^^
>() => { return;} : () => undefined
> : ^^^^^^^^^^^^^^^
return;
};
const fn2: FN = async () => {
>fn2 : FN
> : ^^
>async () => { return;} : () => Promise<undefined>
> : ^^^^^^^^^^^^^^^^^^^^^^^^
return;
};
const fn3: FN = () => {};
>fn3 : FN
> : ^^
>() => {} : () => undefined
> : ^^^^^^^^^^^^^^^
const fn4: FN = async () => {};
>fn4 : FN
> : ^^
>async () => {} : () => Promise<undefined>
> : ^^^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -46,16 +46,16 @@ function flatMapChildren<T>(node: Node, cb: (child: Node) => readonly T[] | T |
> : ^^^^^^^
node.forEachChild(child => {
>node.forEachChild(child => { const value = cb(child); if (value !== undefined) { result.push(...toArray(value)); } }) : void | undefined
> : ^^^^^^^^^^^^^^^^
>node.forEachChild(child => { const value = cb(child); if (value !== undefined) { result.push(...toArray(value)); } }) : undefined
> : ^^^^^^^^^
>node.forEachChild : <T_1>(cbNode: (node: Node) => T_1 | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T_1 | undefined) => T_1 | undefined
> : ^^^^^^ ^^ ^^ ^^^ ^^^^^
>node : Node
> : ^^^^
>forEachChild : <T_1>(cbNode: (node: Node) => T_1 | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T_1 | undefined) => T_1 | undefined
> : ^^^^^^ ^^ ^^ ^^^ ^^^^^
>child => { const value = cb(child); if (value !== undefined) { result.push(...toArray(value)); } } : (child: Node) => void
> : ^ ^^^^^^^^^^^^^^^
>child => { const value = cb(child); if (value !== undefined) { result.push(...toArray(value)); } } : (child: Node) => undefined
> : ^ ^^^^^^^^^^^^^^^^^^^^
>child : Node
> : ^^^^
@@ -118,16 +118,16 @@ function flatMapChildren2<T>(node: Node, cb: (child: Node) => readonly T[] | T |
> : ^^^^^^^
node.forEachChild(child => {
>node.forEachChild(child => { const value = cb(child); if (value !== null) { result.push(...toArray(value)); } }) : void | undefined
> : ^^^^^^^^^^^^^^^^
>node.forEachChild(child => { const value = cb(child); if (value !== null) { result.push(...toArray(value)); } }) : undefined
> : ^^^^^^^^^
>node.forEachChild : <T_1>(cbNode: (node: Node) => T_1 | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T_1 | undefined) => T_1 | undefined
> : ^^^^^^ ^^ ^^ ^^^ ^^^^^
>node : Node
> : ^^^^
>forEachChild : <T_1>(cbNode: (node: Node) => T_1 | undefined, cbNodeArray?: (nodes: NodeArray<Node>) => T_1 | undefined) => T_1 | undefined
> : ^^^^^^ ^^ ^^ ^^^ ^^^^^
>child => { const value = cb(child); if (value !== null) { result.push(...toArray(value)); } } : (child: Node) => void
> : ^ ^^^^^^^^^^^^^^^
>child => { const value = cb(child); if (value !== null) { result.push(...toArray(value)); } } : (child: Node) => undefined
> : ^ ^^^^^^^^^^^^^^^^^^^^
>child : Node
> : ^^^^

View File

@@ -19,7 +19,7 @@ const f20: () => undefined = () => {
}
const f21: () => undefined | number = () => {
// Error, regular void function because contextual type for implicit return isn't just undefined
// Ok, contextual type for implicit return contains undefined
}
const f22: () => number = () => {
@@ -59,3 +59,19 @@ function h2(): undefined {
}
f(h2);
// https://github.com/microsoft/TypeScript/issues/57840
type FN = () => Promise<undefined> | undefined;
const fn1: FN = () => {
return;
};
const fn2: FN = async () => {
return;
};
const fn3: FN = () => {};
const fn4: FN = async () => {};