Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
@@ -37253,7 +37253,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
}
Original file line number Diff line number Diff line change
@@ -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
}
Comment on lines 32 to 34
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not the right person to review this, because I personally lean strongly toward the belief that distinctions between return, return undefined, and no return belong in a linter and not in TS, which I think is not the consensus view on the team. I can’t tell whether the design meeting notes on this were meant to be descriptive or prescriptive:

But no implicit returns when you have 4 | undefined

If it was intended to be prescriptive, this baseline change violates the intention that was captured in the notes. However, moving in this direction is my preference, so I’m going to approve in hopes that it moves the review along 🤷‍♂️

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that was descriptive, stating what the current behavior is and that it's inconsistent.


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 () => {};

Original file line number Diff line number Diff line change
@@ -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 () => { };
Original file line number Diff line number Diff line change
@@ -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))

Original file line number Diff line number Diff line change
@@ -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>
> : ^^^^^^^^^^^^^^^^^^^^^^^^

Original file line number Diff line number Diff line change
@@ -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
> : ^^^^

Original file line number Diff line number Diff line change
@@ -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 () => {};