From 57ba6ddaee5d4e5e70504ead2e41826e4914ec37 Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Wed, 17 Feb 2021 17:48:43 -0800 Subject: [PATCH] Cherry-pick PR #42846 into release-4.2 (#42852) Component commits: 0edae127ae Reduce void | undefined only in conjunction with subtype reduction 6b487a6db5 Accept new baselines e7b6601de7 Add regression test Co-authored-by: Anders Hejlsberg --- src/compiler/checker.ts | 6 +-- tests/baselines/reference/callChain.types | 2 +- .../reference/callChainInference.types | 2 +- .../reference/controlFlowOptionalChain.types | 2 +- .../controlFlowSuperPropertyAccess.types | 2 +- .../reference/discriminantPropertyCheck.types | 4 +- .../reference/promiseTypeStrictNull.types | 8 ++-- .../baselines/reference/superMethodCall.types | 8 ++-- .../baselines/reference/thisMethodCall.types | 2 +- .../truthinessCallExpressionCoercion2.types | 6 +-- .../reference/typeVariableTypeGuards.types | 2 +- .../voidReturnIndexUnionInference.types | 2 +- .../reference/voidUndefinedReduction.js | 23 ++++++++++++ .../reference/voidUndefinedReduction.symbols | 33 +++++++++++++++++ .../reference/voidUndefinedReduction.types | 37 +++++++++++++++++++ .../cases/compiler/voidUndefinedReduction.ts | 13 +++++++ 16 files changed, 129 insertions(+), 23 deletions(-) create mode 100644 tests/baselines/reference/voidUndefinedReduction.js create mode 100644 tests/baselines/reference/voidUndefinedReduction.symbols create mode 100644 tests/baselines/reference/voidUndefinedReduction.types create mode 100644 tests/cases/compiler/voidUndefinedReduction.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6ea2bc0dcefb0..e8693edb294db 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13358,7 +13358,7 @@ namespace ts { return true; } - function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags) { + function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags, reduceVoidUndefined: boolean) { let i = types.length; while (i > 0) { i--; @@ -13369,7 +13369,7 @@ namespace ts { flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number || flags & TypeFlags.BigIntLiteral && includes & TypeFlags.BigInt || flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol || - flags & TypeFlags.Undefined && includes & TypeFlags.Void || + reduceVoidUndefined && flags & TypeFlags.Undefined && includes & TypeFlags.Void || isFreshLiteralType(t) && containsType(types, (t).regularType); if (remove) { orderedRemoveItemAt(types, i); @@ -13437,7 +13437,7 @@ namespace ts { } if (unionReduction & (UnionReduction.Literal | UnionReduction.Subtype)) { if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol) || includes & TypeFlags.Void && includes & TypeFlags.Undefined) { - removeRedundantLiteralTypes(typeSet, includes); + removeRedundantLiteralTypes(typeSet, includes, !!(unionReduction & UnionReduction.Subtype)); } if (includes & TypeFlags.StringLiteral && includes & TypeFlags.TemplateLiteral) { removeStringLiteralsMatchedByTemplateLiterals(typeSet); diff --git a/tests/baselines/reference/callChain.types b/tests/baselines/reference/callChain.types index 1f1a4f1b7c918..ea4700aa9575d 100644 --- a/tests/baselines/reference/callChain.types +++ b/tests/baselines/reference/callChain.types @@ -260,7 +260,7 @@ declare const o5: () => undefined | (() => void); >o5 : () => undefined | (() => void) o5()?.(); ->o5()?.() : void +>o5()?.() : void | undefined >o5() : (() => void) | undefined >o5 : () => (() => void) | undefined diff --git a/tests/baselines/reference/callChainInference.types b/tests/baselines/reference/callChainInference.types index 3f19f66091e63..4ed4f5acf6e90 100644 --- a/tests/baselines/reference/callChainInference.types +++ b/tests/baselines/reference/callChainInference.types @@ -29,7 +29,7 @@ if (value) { } value?.foo("a"); ->value?.foo("a") : void +>value?.foo("a") : void | undefined >value?.foo : ((this: T, arg: keyof T) => void) | undefined >value : Y | undefined >foo : ((this: T, arg: keyof T) => void) | undefined diff --git a/tests/baselines/reference/controlFlowOptionalChain.types b/tests/baselines/reference/controlFlowOptionalChain.types index 78be0e6f295a6..4e335fca6d287 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.types +++ b/tests/baselines/reference/controlFlowOptionalChain.types @@ -595,7 +595,7 @@ function f01(x: unknown) { >true : true maybeIsString?.(x); ->maybeIsString?.(x) : void +>maybeIsString?.(x) : void | undefined >maybeIsString : ((value: unknown) => asserts value is string) | undefined >x : unknown diff --git a/tests/baselines/reference/controlFlowSuperPropertyAccess.types b/tests/baselines/reference/controlFlowSuperPropertyAccess.types index 82ba6ccd34e4a..87e7b3f00d0bc 100644 --- a/tests/baselines/reference/controlFlowSuperPropertyAccess.types +++ b/tests/baselines/reference/controlFlowSuperPropertyAccess.types @@ -13,7 +13,7 @@ class C extends B { >body : () => void super.m && super.m(); ->super.m && super.m() : void +>super.m && super.m() : void | undefined >super.m : (() => void) | undefined >super : B >m : (() => void) | undefined diff --git a/tests/baselines/reference/discriminantPropertyCheck.types b/tests/baselines/reference/discriminantPropertyCheck.types index 38e04bf6a04da..10d3b5f117c47 100644 --- a/tests/baselines/reference/discriminantPropertyCheck.types +++ b/tests/baselines/reference/discriminantPropertyCheck.types @@ -343,7 +343,7 @@ const u: U = {} as any; >{} : {} u.a && u.b && f(u.a, u.b); ->u.a && u.b && f(u.a, u.b) : void | "" +>u.a && u.b && f(u.a, u.b) : void | "" | undefined >u.a && u.b : string | undefined >u.a : string | undefined >u : U @@ -361,7 +361,7 @@ u.a && u.b && f(u.a, u.b); >b : string u.b && u.a && f(u.a, u.b); ->u.b && u.a && f(u.a, u.b) : void | "" +>u.b && u.a && f(u.a, u.b) : void | "" | undefined >u.b && u.a : string | undefined >u.b : string | undefined >u : U diff --git a/tests/baselines/reference/promiseTypeStrictNull.types b/tests/baselines/reference/promiseTypeStrictNull.types index 5e27b4ccd7646..97038aeb4e533 100644 --- a/tests/baselines/reference/promiseTypeStrictNull.types +++ b/tests/baselines/reference/promiseTypeStrictNull.types @@ -888,8 +888,8 @@ const p75 = p.then(() => undefined, () => null); >null : null const p76 = p.then(() => undefined, () => {}); ->p76 : Promise ->p.then(() => undefined, () => {}) : Promise +>p76 : Promise +>p.then(() => undefined, () => {}) : Promise >p.then : (onfulfilled?: ((value: boolean) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined) => Promise >p : Promise >then : (onfulfilled?: ((value: boolean) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined) => Promise @@ -1092,8 +1092,8 @@ const p93 = p.then(() => {}, () => x); >x : any const p94 = p.then(() => {}, () => undefined); ->p94 : Promise ->p.then(() => {}, () => undefined) : Promise +>p94 : Promise +>p.then(() => {}, () => undefined) : Promise >p.then : (onfulfilled?: ((value: boolean) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined) => Promise >p : Promise >then : (onfulfilled?: ((value: boolean) => TResult1 | PromiseLike) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike) | null | undefined) => Promise diff --git a/tests/baselines/reference/superMethodCall.types b/tests/baselines/reference/superMethodCall.types index 7f81a8d7adf32..16d40d19dedf4 100644 --- a/tests/baselines/reference/superMethodCall.types +++ b/tests/baselines/reference/superMethodCall.types @@ -11,20 +11,20 @@ class Derived extends Base { >Base : Base method() { ->method : () => void +>method : () => void | undefined return super.method?.(); ->super.method?.() : void +>super.method?.() : void | undefined >super.method : (() => void) | undefined >super : Base >method : (() => void) | undefined } async asyncMethod() { ->asyncMethod : () => Promise +>asyncMethod : () => Promise return super.method?.(); ->super.method?.() : void +>super.method?.() : void | undefined >super.method : (() => void) | undefined >super : Base >method : (() => void) | undefined diff --git a/tests/baselines/reference/thisMethodCall.types b/tests/baselines/reference/thisMethodCall.types index b00d50fe4f4b7..08e023a44d223 100644 --- a/tests/baselines/reference/thisMethodCall.types +++ b/tests/baselines/reference/thisMethodCall.types @@ -9,7 +9,7 @@ class C { >other : () => void this.method?.(); ->this.method?.() : void +>this.method?.() : void | undefined >this.method : (() => void) | undefined >this : this >method : (() => void) | undefined diff --git a/tests/baselines/reference/truthinessCallExpressionCoercion2.types b/tests/baselines/reference/truthinessCallExpressionCoercion2.types index d8b79c8318fea..83c867e6f8e56 100644 --- a/tests/baselines/reference/truthinessCallExpressionCoercion2.types +++ b/tests/baselines/reference/truthinessCallExpressionCoercion2.types @@ -60,7 +60,7 @@ function test(required1: () => boolean, required2: () => boolean, b: boolean, op // ok optional && console.log('optional'); ->optional && console.log('optional') : void +>optional && console.log('optional') : void | undefined >optional : (() => boolean) | undefined >console.log('optional') : void >console.log : (...data: any[]) => void @@ -70,7 +70,7 @@ function test(required1: () => boolean, required2: () => boolean, b: boolean, op // ok 1 && optional && console.log('optional'); ->1 && optional && console.log('optional') : void +>1 && optional && console.log('optional') : void | undefined >1 && optional : (() => boolean) | undefined >1 : 1 >optional : (() => boolean) | undefined @@ -441,7 +441,7 @@ class Foo { // ok 1 && this.optional && console.log('optional'); ->1 && this.optional && console.log('optional') : void +>1 && this.optional && console.log('optional') : void | undefined >1 && this.optional : (() => boolean) | undefined >1 : 1 >this.optional : (() => boolean) | undefined diff --git a/tests/baselines/reference/typeVariableTypeGuards.types b/tests/baselines/reference/typeVariableTypeGuards.types index 35656ad71d6e7..063f6bbc96d55 100644 --- a/tests/baselines/reference/typeVariableTypeGuards.types +++ b/tests/baselines/reference/typeVariableTypeGuards.types @@ -16,7 +16,7 @@ class A

> { >doSomething : () => void this.props.foo && this.props.foo() ->this.props.foo && this.props.foo() : void +>this.props.foo && this.props.foo() : void | undefined >this.props.foo : P["foo"] | undefined >this.props : Readonly

>this : this diff --git a/tests/baselines/reference/voidReturnIndexUnionInference.types b/tests/baselines/reference/voidReturnIndexUnionInference.types index 149861164330b..fa7d87b8f63af 100644 --- a/tests/baselines/reference/voidReturnIndexUnionInference.types +++ b/tests/baselines/reference/voidReturnIndexUnionInference.types @@ -51,7 +51,7 @@ function bad

(props: Readonly

) { // ERROR HERE!!! // Type R in signature of safeInvoke incorrectly inferred as {} instead of void! safeInvoke(props.onBar, "blah"); ->safeInvoke(props.onBar, "blah") : void +>safeInvoke(props.onBar, "blah") : void | undefined >safeInvoke : (func: ((arg1: A1) => R) | null | undefined, arg1: A1) => R | undefined >props.onBar : P["onBar"] | undefined >props : Readonly

diff --git a/tests/baselines/reference/voidUndefinedReduction.js b/tests/baselines/reference/voidUndefinedReduction.js new file mode 100644 index 0000000000000..f077531051253 --- /dev/null +++ b/tests/baselines/reference/voidUndefinedReduction.js @@ -0,0 +1,23 @@ +//// [voidUndefinedReduction.ts] +// Repro from #42786 + +function isDefined(value: T | undefined | null | void): value is T { + return value !== undefined && value !== null; +} + +declare const foo: string | undefined; + +if (isDefined(foo)) { + console.log(foo.toUpperCase()); +} + + +//// [voidUndefinedReduction.js] +"use strict"; +// Repro from #42786 +function isDefined(value) { + return value !== undefined && value !== null; +} +if (isDefined(foo)) { + console.log(foo.toUpperCase()); +} diff --git a/tests/baselines/reference/voidUndefinedReduction.symbols b/tests/baselines/reference/voidUndefinedReduction.symbols new file mode 100644 index 0000000000000..92430f8425065 --- /dev/null +++ b/tests/baselines/reference/voidUndefinedReduction.symbols @@ -0,0 +1,33 @@ +=== tests/cases/compiler/voidUndefinedReduction.ts === +// Repro from #42786 + +function isDefined(value: T | undefined | null | void): value is T { +>isDefined : Symbol(isDefined, Decl(voidUndefinedReduction.ts, 0, 0)) +>T : Symbol(T, Decl(voidUndefinedReduction.ts, 2, 19)) +>value : Symbol(value, Decl(voidUndefinedReduction.ts, 2, 22)) +>T : Symbol(T, Decl(voidUndefinedReduction.ts, 2, 19)) +>value : Symbol(value, Decl(voidUndefinedReduction.ts, 2, 22)) +>T : Symbol(T, Decl(voidUndefinedReduction.ts, 2, 19)) + + return value !== undefined && value !== null; +>value : Symbol(value, Decl(voidUndefinedReduction.ts, 2, 22)) +>undefined : Symbol(undefined) +>value : Symbol(value, Decl(voidUndefinedReduction.ts, 2, 22)) +} + +declare const foo: string | undefined; +>foo : Symbol(foo, Decl(voidUndefinedReduction.ts, 6, 13)) + +if (isDefined(foo)) { +>isDefined : Symbol(isDefined, Decl(voidUndefinedReduction.ts, 0, 0)) +>foo : Symbol(foo, Decl(voidUndefinedReduction.ts, 6, 13)) + + console.log(foo.toUpperCase()); +>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>console : Symbol(console, Decl(lib.dom.d.ts, --, --)) +>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --)) +>foo.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +>foo : Symbol(foo, Decl(voidUndefinedReduction.ts, 6, 13)) +>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --)) +} + diff --git a/tests/baselines/reference/voidUndefinedReduction.types b/tests/baselines/reference/voidUndefinedReduction.types new file mode 100644 index 0000000000000..a11667bf47655 --- /dev/null +++ b/tests/baselines/reference/voidUndefinedReduction.types @@ -0,0 +1,37 @@ +=== tests/cases/compiler/voidUndefinedReduction.ts === +// Repro from #42786 + +function isDefined(value: T | undefined | null | void): value is T { +>isDefined : (value: T | undefined | null | void) => value is T +>value : void | T | null | undefined +>null : null + + return value !== undefined && value !== null; +>value !== undefined && value !== null : boolean +>value !== undefined : boolean +>value : void | T | null | undefined +>undefined : undefined +>value !== null : boolean +>value : T | null +>null : null +} + +declare const foo: string | undefined; +>foo : string | undefined + +if (isDefined(foo)) { +>isDefined(foo) : boolean +>isDefined : (value: void | T | null | undefined) => value is T +>foo : string | undefined + + console.log(foo.toUpperCase()); +>console.log(foo.toUpperCase()) : void +>console.log : (...data: any[]) => void +>console : Console +>log : (...data: any[]) => void +>foo.toUpperCase() : string +>foo.toUpperCase : () => string +>foo : string +>toUpperCase : () => string +} + diff --git a/tests/cases/compiler/voidUndefinedReduction.ts b/tests/cases/compiler/voidUndefinedReduction.ts new file mode 100644 index 0000000000000..8973f307cfe89 --- /dev/null +++ b/tests/cases/compiler/voidUndefinedReduction.ts @@ -0,0 +1,13 @@ +// @strict: true + +// Repro from #42786 + +function isDefined(value: T | undefined | null | void): value is T { + return value !== undefined && value !== null; +} + +declare const foo: string | undefined; + +if (isDefined(foo)) { + console.log(foo.toUpperCase()); +}