Skip to content

Commit 57ba6dd

Browse files
TypeScript Botahejlsberg
TypeScript Bot
andauthored
Cherry-pick PR microsoft#42846 into release-4.2 (microsoft#42852)
Component commits: 0edae12 Reduce void | undefined only in conjunction with subtype reduction 6b487a6 Accept new baselines e7b6601 Add regression test Co-authored-by: Anders Hejlsberg <[email protected]>
1 parent 16f2556 commit 57ba6dd

16 files changed

+129
-23
lines changed

src/compiler/checker.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -13358,7 +13358,7 @@ namespace ts {
1335813358
return true;
1335913359
}
1336013360

13361-
function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags) {
13361+
function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags, reduceVoidUndefined: boolean) {
1336213362
let i = types.length;
1336313363
while (i > 0) {
1336413364
i--;
@@ -13369,7 +13369,7 @@ namespace ts {
1336913369
flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number ||
1337013370
flags & TypeFlags.BigIntLiteral && includes & TypeFlags.BigInt ||
1337113371
flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol ||
13372-
flags & TypeFlags.Undefined && includes & TypeFlags.Void ||
13372+
reduceVoidUndefined && flags & TypeFlags.Undefined && includes & TypeFlags.Void ||
1337313373
isFreshLiteralType(t) && containsType(types, (<LiteralType>t).regularType);
1337413374
if (remove) {
1337513375
orderedRemoveItemAt(types, i);
@@ -13437,7 +13437,7 @@ namespace ts {
1343713437
}
1343813438
if (unionReduction & (UnionReduction.Literal | UnionReduction.Subtype)) {
1343913439
if (includes & (TypeFlags.Literal | TypeFlags.UniqueESSymbol) || includes & TypeFlags.Void && includes & TypeFlags.Undefined) {
13440-
removeRedundantLiteralTypes(typeSet, includes);
13440+
removeRedundantLiteralTypes(typeSet, includes, !!(unionReduction & UnionReduction.Subtype));
1344113441
}
1344213442
if (includes & TypeFlags.StringLiteral && includes & TypeFlags.TemplateLiteral) {
1344313443
removeStringLiteralsMatchedByTemplateLiterals(typeSet);

tests/baselines/reference/callChain.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ declare const o5: <T>() => undefined | (() => void);
260260
>o5 : <T>() => undefined | (() => void)
261261

262262
o5<number>()?.();
263-
>o5<number>()?.() : void
263+
>o5<number>()?.() : void | undefined
264264
>o5<number>() : (() => void) | undefined
265265
>o5 : <T>() => (() => void) | undefined
266266

tests/baselines/reference/callChainInference.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ if (value) {
2929
}
3030

3131
value?.foo("a");
32-
>value?.foo("a") : void
32+
>value?.foo("a") : void | undefined
3333
>value?.foo : (<T>(this: T, arg: keyof T) => void) | undefined
3434
>value : Y | undefined
3535
>foo : (<T>(this: T, arg: keyof T) => void) | undefined

tests/baselines/reference/controlFlowOptionalChain.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ function f01(x: unknown) {
595595
>true : true
596596

597597
maybeIsString?.(x);
598-
>maybeIsString?.(x) : void
598+
>maybeIsString?.(x) : void | undefined
599599
>maybeIsString : ((value: unknown) => asserts value is string) | undefined
600600
>x : unknown
601601

tests/baselines/reference/controlFlowSuperPropertyAccess.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class C extends B {
1313
>body : () => void
1414

1515
super.m && super.m();
16-
>super.m && super.m() : void
16+
>super.m && super.m() : void | undefined
1717
>super.m : (() => void) | undefined
1818
>super : B
1919
>m : (() => void) | undefined

tests/baselines/reference/discriminantPropertyCheck.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ const u: U = {} as any;
343343
>{} : {}
344344

345345
u.a && u.b && f(u.a, u.b);
346-
>u.a && u.b && f(u.a, u.b) : void | ""
346+
>u.a && u.b && f(u.a, u.b) : void | "" | undefined
347347
>u.a && u.b : string | undefined
348348
>u.a : string | undefined
349349
>u : U
@@ -361,7 +361,7 @@ u.a && u.b && f(u.a, u.b);
361361
>b : string
362362

363363
u.b && u.a && f(u.a, u.b);
364-
>u.b && u.a && f(u.a, u.b) : void | ""
364+
>u.b && u.a && f(u.a, u.b) : void | "" | undefined
365365
>u.b && u.a : string | undefined
366366
>u.b : string | undefined
367367
>u : U

tests/baselines/reference/promiseTypeStrictNull.types

+4-4
Original file line numberDiff line numberDiff line change
@@ -888,8 +888,8 @@ const p75 = p.then(() => undefined, () => null);
888888
>null : null
889889

890890
const p76 = p.then(() => undefined, () => {});
891-
>p76 : Promise<void>
892-
>p.then(() => undefined, () => {}) : Promise<void>
891+
>p76 : Promise<void | undefined>
892+
>p.then(() => undefined, () => {}) : Promise<void | undefined>
893893
>p.then : <TResult1 = boolean, TResult2 = never>(onfulfilled?: ((value: boolean) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>
894894
>p : Promise<boolean>
895895
>then : <TResult1 = boolean, TResult2 = never>(onfulfilled?: ((value: boolean) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>
@@ -1092,8 +1092,8 @@ const p93 = p.then(() => {}, () => x);
10921092
>x : any
10931093

10941094
const p94 = p.then(() => {}, () => undefined);
1095-
>p94 : Promise<void>
1096-
>p.then(() => {}, () => undefined) : Promise<void>
1095+
>p94 : Promise<void | undefined>
1096+
>p.then(() => {}, () => undefined) : Promise<void | undefined>
10971097
>p.then : <TResult1 = boolean, TResult2 = never>(onfulfilled?: ((value: boolean) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>
10981098
>p : Promise<boolean>
10991099
>then : <TResult1 = boolean, TResult2 = never>(onfulfilled?: ((value: boolean) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>

tests/baselines/reference/superMethodCall.types

+4-4
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@ class Derived extends Base {
1111
>Base : Base
1212

1313
method() {
14-
>method : () => void
14+
>method : () => void | undefined
1515

1616
return super.method?.();
17-
>super.method?.() : void
17+
>super.method?.() : void | undefined
1818
>super.method : (() => void) | undefined
1919
>super : Base
2020
>method : (() => void) | undefined
2121
}
2222

2323
async asyncMethod() {
24-
>asyncMethod : () => Promise<void>
24+
>asyncMethod : () => Promise<void | undefined>
2525

2626
return super.method?.();
27-
>super.method?.() : void
27+
>super.method?.() : void | undefined
2828
>super.method : (() => void) | undefined
2929
>super : Base
3030
>method : (() => void) | undefined

tests/baselines/reference/thisMethodCall.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class C {
99
>other : () => void
1010

1111
this.method?.();
12-
>this.method?.() : void
12+
>this.method?.() : void | undefined
1313
>this.method : (() => void) | undefined
1414
>this : this
1515
>method : (() => void) | undefined

tests/baselines/reference/truthinessCallExpressionCoercion2.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function test(required1: () => boolean, required2: () => boolean, b: boolean, op
6060

6161
// ok
6262
optional && console.log('optional');
63-
>optional && console.log('optional') : void
63+
>optional && console.log('optional') : void | undefined
6464
>optional : (() => boolean) | undefined
6565
>console.log('optional') : void
6666
>console.log : (...data: any[]) => void
@@ -70,7 +70,7 @@ function test(required1: () => boolean, required2: () => boolean, b: boolean, op
7070

7171
// ok
7272
1 && optional && console.log('optional');
73-
>1 && optional && console.log('optional') : void
73+
>1 && optional && console.log('optional') : void | undefined
7474
>1 && optional : (() => boolean) | undefined
7575
>1 : 1
7676
>optional : (() => boolean) | undefined
@@ -441,7 +441,7 @@ class Foo {
441441

442442
// ok
443443
1 && this.optional && console.log('optional');
444-
>1 && this.optional && console.log('optional') : void
444+
>1 && this.optional && console.log('optional') : void | undefined
445445
>1 && this.optional : (() => boolean) | undefined
446446
>1 : 1
447447
>this.optional : (() => boolean) | undefined

tests/baselines/reference/typeVariableTypeGuards.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class A<P extends Partial<Foo>> {
1616
>doSomething : () => void
1717

1818
this.props.foo && this.props.foo()
19-
>this.props.foo && this.props.foo() : void
19+
>this.props.foo && this.props.foo() : void | undefined
2020
>this.props.foo : P["foo"] | undefined
2121
>this.props : Readonly<P>
2222
>this : this

tests/baselines/reference/voidReturnIndexUnionInference.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function bad<P extends Props>(props: Readonly<P>) {
5151
// ERROR HERE!!!
5252
// Type R in signature of safeInvoke incorrectly inferred as {} instead of void!
5353
safeInvoke(props.onBar, "blah");
54-
>safeInvoke(props.onBar, "blah") : void
54+
>safeInvoke(props.onBar, "blah") : void | undefined
5555
>safeInvoke : <A1, R>(func: ((arg1: A1) => R) | null | undefined, arg1: A1) => R | undefined
5656
>props.onBar : P["onBar"] | undefined
5757
>props : Readonly<P>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//// [voidUndefinedReduction.ts]
2+
// Repro from #42786
3+
4+
function isDefined<T>(value: T | undefined | null | void): value is T {
5+
return value !== undefined && value !== null;
6+
}
7+
8+
declare const foo: string | undefined;
9+
10+
if (isDefined(foo)) {
11+
console.log(foo.toUpperCase());
12+
}
13+
14+
15+
//// [voidUndefinedReduction.js]
16+
"use strict";
17+
// Repro from #42786
18+
function isDefined(value) {
19+
return value !== undefined && value !== null;
20+
}
21+
if (isDefined(foo)) {
22+
console.log(foo.toUpperCase());
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
=== tests/cases/compiler/voidUndefinedReduction.ts ===
2+
// Repro from #42786
3+
4+
function isDefined<T>(value: T | undefined | null | void): value is T {
5+
>isDefined : Symbol(isDefined, Decl(voidUndefinedReduction.ts, 0, 0))
6+
>T : Symbol(T, Decl(voidUndefinedReduction.ts, 2, 19))
7+
>value : Symbol(value, Decl(voidUndefinedReduction.ts, 2, 22))
8+
>T : Symbol(T, Decl(voidUndefinedReduction.ts, 2, 19))
9+
>value : Symbol(value, Decl(voidUndefinedReduction.ts, 2, 22))
10+
>T : Symbol(T, Decl(voidUndefinedReduction.ts, 2, 19))
11+
12+
return value !== undefined && value !== null;
13+
>value : Symbol(value, Decl(voidUndefinedReduction.ts, 2, 22))
14+
>undefined : Symbol(undefined)
15+
>value : Symbol(value, Decl(voidUndefinedReduction.ts, 2, 22))
16+
}
17+
18+
declare const foo: string | undefined;
19+
>foo : Symbol(foo, Decl(voidUndefinedReduction.ts, 6, 13))
20+
21+
if (isDefined(foo)) {
22+
>isDefined : Symbol(isDefined, Decl(voidUndefinedReduction.ts, 0, 0))
23+
>foo : Symbol(foo, Decl(voidUndefinedReduction.ts, 6, 13))
24+
25+
console.log(foo.toUpperCase());
26+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
27+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
28+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
29+
>foo.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
30+
>foo : Symbol(foo, Decl(voidUndefinedReduction.ts, 6, 13))
31+
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
32+
}
33+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
=== tests/cases/compiler/voidUndefinedReduction.ts ===
2+
// Repro from #42786
3+
4+
function isDefined<T>(value: T | undefined | null | void): value is T {
5+
>isDefined : <T>(value: T | undefined | null | void) => value is T
6+
>value : void | T | null | undefined
7+
>null : null
8+
9+
return value !== undefined && value !== null;
10+
>value !== undefined && value !== null : boolean
11+
>value !== undefined : boolean
12+
>value : void | T | null | undefined
13+
>undefined : undefined
14+
>value !== null : boolean
15+
>value : T | null
16+
>null : null
17+
}
18+
19+
declare const foo: string | undefined;
20+
>foo : string | undefined
21+
22+
if (isDefined(foo)) {
23+
>isDefined(foo) : boolean
24+
>isDefined : <T>(value: void | T | null | undefined) => value is T
25+
>foo : string | undefined
26+
27+
console.log(foo.toUpperCase());
28+
>console.log(foo.toUpperCase()) : void
29+
>console.log : (...data: any[]) => void
30+
>console : Console
31+
>log : (...data: any[]) => void
32+
>foo.toUpperCase() : string
33+
>foo.toUpperCase : () => string
34+
>foo : string
35+
>toUpperCase : () => string
36+
}
37+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// @strict: true
2+
3+
// Repro from #42786
4+
5+
function isDefined<T>(value: T | undefined | null | void): value is T {
6+
return value !== undefined && value !== null;
7+
}
8+
9+
declare const foo: string | undefined;
10+
11+
if (isDefined(foo)) {
12+
console.log(foo.toUpperCase());
13+
}

0 commit comments

Comments
 (0)