Skip to content

Don't error when function has an implicit return but its return type is assignable to undefined #53490

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

Merged
merged 10 commits into from
Mar 27, 2023
Merged
Show file tree
Hide file tree
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
4 changes: 2 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35659,8 +35659,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const functionFlags = getFunctionFlags(func);
const type = returnType && unwrapReturnType(returnType, functionFlags);

// Functions with an explicitly specified 'undefined, 'void' or 'any' return type don't need any return expressions.
if (type && maybeTypeOfKind(type, TypeFlags.Undefined | TypeFlags.Void | TypeFlags.Any)) {
// Functions with an explicitly specified 'undefined, 'void', 'any' or 'unknown' return type don't need any return expressions.
if (type && maybeTypeOfKind(type, TypeFlags.Undefined | TypeFlags.Void | TypeFlags.Any | TypeFlags.Unknown)) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//// [functionsWithImplicitReturnTypeAssignableToUndefined.ts]
function f1(): unknown {
if (Math.random() < 0.5) return true;

// Implicit return, but undefined is always assignable to unknown.
}

type MyUnknown = unknown;
function f2(): unknown {
if (Math.random() < 0.5) return true;

// Implicit return, but undefined is always assignable to unknown.
}

function f3(): any {
// Implicit return, but undefined is always assignable to any.
}

function f4(): void {
// Implicit return, but undefined is always assignable to void.
}

function f5(): {} {
if (Math.random() < 0.5) return {};

// Implicit return, but undefined is assignable to object when strictNullChecks is off.
}

function f6(): Record<string, any> {
if (Math.random() < 0.5) return { "foo": true };

// Implicit return, but undefined is assignable to records (which are just fancy objects)
// when strictNullChecks is off.
}

function f7(): null {
if (Math.random() < 0.5) return null;

// Implicit return, but undefined is assignable to null when strictNullChecks is off.
}

function f8(): string | null {
if (Math.random() < 0.5) return "foo";

// Implicit return, but undefined is assignable to null when strictNullChecks is off.
}

//// [functionsWithImplicitReturnTypeAssignableToUndefined.js]
function f1() {
if (Math.random() < 0.5)
return true;
// Implicit return, but undefined is always assignable to unknown.
}
function f2() {
if (Math.random() < 0.5)
return true;
// Implicit return, but undefined is always assignable to unknown.
}
function f3() {
// Implicit return, but undefined is always assignable to any.
}
function f4() {
// Implicit return, but undefined is always assignable to void.
}
function f5() {
if (Math.random() < 0.5)
return {};
// Implicit return, but undefined is assignable to object when strictNullChecks is off.
}
function f6() {
if (Math.random() < 0.5)
return { "foo": true };
// Implicit return, but undefined is assignable to records (which are just fancy objects)
// when strictNullChecks is off.
}
function f7() {
if (Math.random() < 0.5)
return null;
// Implicit return, but undefined is assignable to null when strictNullChecks is off.
}
function f8() {
if (Math.random() < 0.5)
return "foo";
// Implicit return, but undefined is assignable to null when strictNullChecks is off.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
=== tests/cases/compiler/functionsWithImplicitReturnTypeAssignableToUndefined.ts ===
function f1(): unknown {
>f1 : Symbol(f1, Decl(functionsWithImplicitReturnTypeAssignableToUndefined.ts, 0, 0))

if (Math.random() < 0.5) return true;
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))

// Implicit return, but undefined is always assignable to unknown.
}

type MyUnknown = unknown;
>MyUnknown : Symbol(MyUnknown, Decl(functionsWithImplicitReturnTypeAssignableToUndefined.ts, 4, 1))

function f2(): unknown {
>f2 : Symbol(f2, Decl(functionsWithImplicitReturnTypeAssignableToUndefined.ts, 6, 25))

if (Math.random() < 0.5) return true;
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))

// Implicit return, but undefined is always assignable to unknown.
}

function f3(): any {
>f3 : Symbol(f3, Decl(functionsWithImplicitReturnTypeAssignableToUndefined.ts, 11, 1))

// Implicit return, but undefined is always assignable to any.
}

function f4(): void {
>f4 : Symbol(f4, Decl(functionsWithImplicitReturnTypeAssignableToUndefined.ts, 15, 1))

// Implicit return, but undefined is always assignable to void.
}

function f5(): {} {
>f5 : Symbol(f5, Decl(functionsWithImplicitReturnTypeAssignableToUndefined.ts, 19, 1))

if (Math.random() < 0.5) return {};
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))

// Implicit return, but undefined is assignable to object when strictNullChecks is off.
}

function f6(): Record<string, any> {
>f6 : Symbol(f6, Decl(functionsWithImplicitReturnTypeAssignableToUndefined.ts, 25, 1))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))

if (Math.random() < 0.5) return { "foo": true };
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>"foo" : Symbol("foo", Decl(functionsWithImplicitReturnTypeAssignableToUndefined.ts, 28, 37))

// Implicit return, but undefined is assignable to records (which are just fancy objects)
// when strictNullChecks is off.
}

function f7(): null {
>f7 : Symbol(f7, Decl(functionsWithImplicitReturnTypeAssignableToUndefined.ts, 32, 1))

if (Math.random() < 0.5) return null;
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))

// Implicit return, but undefined is assignable to null when strictNullChecks is off.
}

function f8(): string | null {
>f8 : Symbol(f8, Decl(functionsWithImplicitReturnTypeAssignableToUndefined.ts, 38, 1))

if (Math.random() < 0.5) return "foo";
>Math.random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))
>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>random : Symbol(Math.random, Decl(lib.es5.d.ts, --, --))

// Implicit return, but undefined is assignable to null when strictNullChecks is off.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
=== tests/cases/compiler/functionsWithImplicitReturnTypeAssignableToUndefined.ts ===
function f1(): unknown {
>f1 : () => unknown

if (Math.random() < 0.5) return true;
>Math.random() < 0.5 : boolean
>Math.random() : number
>Math.random : () => number
>Math : Math
>random : () => number
>0.5 : 0.5
>true : true

// Implicit return, but undefined is always assignable to unknown.
}

type MyUnknown = unknown;
>MyUnknown : unknown

function f2(): unknown {
>f2 : () => unknown

if (Math.random() < 0.5) return true;
>Math.random() < 0.5 : boolean
>Math.random() : number
>Math.random : () => number
>Math : Math
>random : () => number
>0.5 : 0.5
>true : true

// Implicit return, but undefined is always assignable to unknown.
}

function f3(): any {
>f3 : () => any

// Implicit return, but undefined is always assignable to any.
}

function f4(): void {
>f4 : () => void

// Implicit return, but undefined is always assignable to void.
}

function f5(): {} {
>f5 : () => {}

if (Math.random() < 0.5) return {};
>Math.random() < 0.5 : boolean
>Math.random() : number
>Math.random : () => number
>Math : Math
>random : () => number
>0.5 : 0.5
>{} : {}

// Implicit return, but undefined is assignable to object when strictNullChecks is off.
}

function f6(): Record<string, any> {
>f6 : () => Record<string, any>

if (Math.random() < 0.5) return { "foo": true };
>Math.random() < 0.5 : boolean
>Math.random() : number
>Math.random : () => number
>Math : Math
>random : () => number
>0.5 : 0.5
>{ "foo": true } : { foo: boolean; }
>"foo" : boolean
>true : true

// Implicit return, but undefined is assignable to records (which are just fancy objects)
// when strictNullChecks is off.
}

function f7(): null {
>f7 : () => null

if (Math.random() < 0.5) return null;
>Math.random() < 0.5 : boolean
>Math.random() : number
>Math.random : () => number
>Math : Math
>random : () => number
>0.5 : 0.5

// Implicit return, but undefined is assignable to null when strictNullChecks is off.
}

function f8(): string | null {
>f8 : () => string | null

if (Math.random() < 0.5) return "foo";
>Math.random() < 0.5 : boolean
>Math.random() : number
>Math.random : () => number
>Math : Math
>random : () => number
>0.5 : 0.5
>"foo" : "foo"

// Implicit return, but undefined is assignable to null when strictNullChecks is off.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
tests/cases/compiler/functionsWithImplicitReturnTypeAssignableToUndefined.ts(22,16): error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
tests/cases/compiler/functionsWithImplicitReturnTypeAssignableToUndefined.ts(28,16): error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
tests/cases/compiler/functionsWithImplicitReturnTypeAssignableToUndefined.ts(35,16): error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
tests/cases/compiler/functionsWithImplicitReturnTypeAssignableToUndefined.ts(41,16): error TS2366: Function lacks ending return statement and return type does not include 'undefined'.


==== tests/cases/compiler/functionsWithImplicitReturnTypeAssignableToUndefined.ts (4 errors) ====
function f1(): unknown {
if (Math.random() < 0.5) return true;

// Implicit return, but undefined is always assignable to unknown.
}

type MyUnknown = unknown;
function f2(): unknown {
if (Math.random() < 0.5) return true;

// Implicit return, but undefined is always assignable to unknown.
}

function f3(): any {
// Implicit return, but undefined is always assignable to any.
}

function f4(): void {
// Implicit return, but undefined is always assignable to void.
}

function f5(): {} {
~~
!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
if (Math.random() < 0.5) return {};

// Implicit return, but undefined is assignable to object when strictNullChecks is off.
}

function f6(): Record<string, any> {
~~~~~~~~~~~~~~~~~~~
!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
if (Math.random() < 0.5) return { "foo": true };

// Implicit return, but undefined is assignable to records (which are just fancy objects)
// when strictNullChecks is off.
}

function f7(): null {
~~~~
!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
if (Math.random() < 0.5) return null;

// Implicit return, but undefined is assignable to null when strictNullChecks is off.
}

function f8(): string | null {
~~~~~~~~~~~~~
!!! error TS2366: Function lacks ending return statement and return type does not include 'undefined'.
if (Math.random() < 0.5) return "foo";

// Implicit return, but undefined is assignable to null when strictNullChecks is off.
}
Loading