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

This commit is contained in:
Maria José Solano 2023-03-27 13:21:07 -07:00 committed by GitHub
parent 437fd059be
commit c5b288487a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 663 additions and 6 deletions

View File

@ -35658,8 +35658,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;
}

View File

@ -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.
}

View File

@ -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.
}

View File

@ -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.
}

View File

@ -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.
}

View File

@ -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.
}

View File

@ -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.
}

View File

@ -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.
}

View File

@ -20,7 +20,6 @@ tests/cases/conformance/types/unknown/unknownType1.ts(128,5): error TS2322: Type
tests/cases/conformance/types/unknown/unknownType1.ts(129,5): error TS2322: Type 'number' is not assignable to type '{ [x: string]: unknown; }'.
tests/cases/conformance/types/unknown/unknownType1.ts(143,29): error TS2698: Spread types may only be created from object types.
tests/cases/conformance/types/unknown/unknownType1.ts(144,29): error TS2698: Spread types may only be created from object types.
tests/cases/conformance/types/unknown/unknownType1.ts(150,17): error TS2847: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
tests/cases/conformance/types/unknown/unknownType1.ts(156,14): error TS2700: Rest types may only be created from object types.
tests/cases/conformance/types/unknown/unknownType1.ts(162,5): error TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor.
tests/cases/conformance/types/unknown/unknownType1.ts(170,9): error TS2322: Type 'T' is not assignable to type '{}'.
@ -28,7 +27,7 @@ tests/cases/conformance/types/unknown/unknownType1.ts(171,9): error TS2322: Type
tests/cases/conformance/types/unknown/unknownType1.ts(181,5): error TS2322: Type 'T' is not assignable to type '{}'.
==== tests/cases/conformance/types/unknown/unknownType1.ts (27 errors) ====
==== tests/cases/conformance/types/unknown/unknownType1.ts (26 errors) ====
// In an intersection everything absorbs unknown
type T00 = unknown & null; // null
@ -222,8 +221,6 @@ tests/cases/conformance/types/unknown/unknownType1.ts(181,5): error TS2322: Type
// Functions with unknown return type don't need return expressions
function f27(): unknown {
~~~~~~~
!!! error TS2847: A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
}
// Rest type cannot be created from unknown

View File

@ -0,0 +1,48 @@
// @noImplicitReturns: false
// @strictNullChecks: true, false
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.
}