From c309c6f47b984a52ebcfad00fa1c26ab8996da8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 22 Mar 2024 21:06:18 +0100 Subject: [PATCH] Allow implicit `undefined` returns when the contextual union type contains it --- src/compiler/checker.ts | 2 +- ...sAndExpressionsStrictNullChecks.errors.txt | 25 ++++++++++----- ...tatementsAndExpressionsStrictNullChecks.js | 28 +++++++++++++++-- ...entsAndExpressionsStrictNullChecks.symbols | 30 +++++++++++++++++- ...ementsAndExpressionsStrictNullChecks.types | 31 +++++++++++++++++-- .../inferenceDoesNotAddUndefinedOrNull.types | 8 ++--- ...tatementsAndExpressionsStrictNullChecks.ts | 18 ++++++++++- 7 files changed, 124 insertions(+), 18 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ad13bd597db39..771b20bdc8160 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -37279,7 +37279,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 } diff --git a/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.errors.txt b/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.errors.txt index 54ce2296ef376..5cb8c8301543e 100644 --- a/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.errors.txt +++ b/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.errors.txt @@ -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; + + const fn1: FN = () => { + return; + }; + + const fn2: FN = async () => { + return; + }; + + const fn3: FN = () => {}; + + const fn4: FN = async () => {}; \ No newline at end of file diff --git a/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.js b/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.js index 0f552486f2c1e..83e3287f6331e 100644 --- a/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.js +++ b/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.js @@ -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; + +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 () => { }; diff --git a/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.symbols b/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.symbols index eded39d697a8a..814f7cca64573 100644 --- a/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.symbols +++ b/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.symbols @@ -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; +>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)) + diff --git a/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.types b/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.types index cd6f14f540428..7207cdedc9a89 100644 --- a/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.types +++ b/tests/baselines/reference/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.types @@ -28,9 +28,9 @@ 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 = () => { @@ -100,3 +100,30 @@ f(h2); >f : (a: () => undefined) => void >h2 : () => undefined +// https://github.com/microsoft/TypeScript/issues/57840 + +type FN = () => Promise | undefined; +>FN : () => Promise | undefined + +const fn1: FN = () => { +>fn1 : FN +>() => { return;} : () => undefined + + return; +}; + +const fn2: FN = async () => { +>fn2 : FN +>async () => { return;} : () => Promise + + return; +}; + +const fn3: FN = () => {}; +>fn3 : FN +>() => {} : () => undefined + +const fn4: FN = async () => {}; +>fn4 : FN +>async () => {} : () => Promise + diff --git a/tests/baselines/reference/inferenceDoesNotAddUndefinedOrNull.types b/tests/baselines/reference/inferenceDoesNotAddUndefinedOrNull.types index 123c4ed08a176..17ab794abdbfb 100644 --- a/tests/baselines/reference/inferenceDoesNotAddUndefinedOrNull.types +++ b/tests/baselines/reference/inferenceDoesNotAddUndefinedOrNull.types @@ -31,11 +31,11 @@ function flatMapChildren(node: Node, cb: (child: Node) => readonly T[] | T | >[] : never[] 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 : (cbNode: (node: Node) => T_1 | undefined, cbNodeArray?: ((nodes: NodeArray) => T_1 | undefined) | undefined) => T_1 | undefined >node : Node >forEachChild : (cbNode: (node: Node) => T_1 | undefined, cbNodeArray?: ((nodes: NodeArray) => T_1 | undefined) | 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 const value = cb(child); @@ -75,11 +75,11 @@ function flatMapChildren2(node: Node, cb: (child: Node) => readonly T[] | T | >[] : never[] 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 : (cbNode: (node: Node) => T_1 | undefined, cbNodeArray?: ((nodes: NodeArray) => T_1 | undefined) | undefined) => T_1 | undefined >node : Node >forEachChild : (cbNode: (node: Node) => T_1 | undefined, cbNodeArray?: ((nodes: NodeArray) => T_1 | undefined) | 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 const value = cb(child); diff --git a/tests/cases/compiler/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts b/tests/cases/compiler/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts index be6828cd35725..1ef1afd1a4fcb 100644 --- a/tests/cases/compiler/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts +++ b/tests/cases/compiler/functionsMissingReturnStatementsAndExpressionsStrictNullChecks.ts @@ -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; + +const fn1: FN = () => { + return; +}; + +const fn2: FN = async () => { + return; +}; + +const fn3: FN = () => {}; + +const fn4: FN = async () => {};