Skip to content
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

Reduce void | undefined only in conjunction with subtype reduction #42846

Merged
merged 3 commits into from Feb 18, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/compiler/checker.ts
Expand Up @@ -13361,7 +13361,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--;
Expand All @@ -13372,7 +13372,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, (<LiteralType>t).regularType);
if (remove) {
orderedRemoveItemAt(types, i);
Expand Down Expand Up @@ -13440,7 +13440,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);
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/callChain.types
Expand Up @@ -260,7 +260,7 @@ declare const o5: <T>() => undefined | (() => void);
>o5 : <T>() => undefined | (() => void)

o5<number>()?.();
>o5<number>()?.() : void
>o5<number>()?.() : void | undefined
>o5<number>() : (() => void) | undefined
>o5 : <T>() => (() => void) | undefined

Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/callChainInference.types
Expand Up @@ -29,7 +29,7 @@ if (value) {
}

value?.foo("a");
>value?.foo("a") : void
>value?.foo("a") : void | undefined
>value?.foo : (<T>(this: T, arg: keyof T) => void) | undefined
>value : Y | undefined
>foo : (<T>(this: T, arg: keyof T) => void) | undefined
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/controlFlowOptionalChain.types
Expand Up @@ -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

Expand Down
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/discriminantPropertyCheck.types
Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/promiseTypeStrictNull.types
Expand Up @@ -888,8 +888,8 @@ const p75 = p.then(() => undefined, () => null);
>null : null

const p76 = p.then(() => undefined, () => {});
>p76 : Promise<void>
>p.then(() => undefined, () => {}) : Promise<void>
>p76 : Promise<void | undefined>
>p.then(() => undefined, () => {}) : Promise<void | undefined>
>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>
>p : Promise<boolean>
>then : <TResult1 = boolean, TResult2 = never>(onfulfilled?: ((value: boolean) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>
Expand Down Expand Up @@ -1092,8 +1092,8 @@ const p93 = p.then(() => {}, () => x);
>x : any

const p94 = p.then(() => {}, () => undefined);
>p94 : Promise<void>
>p.then(() => {}, () => undefined) : Promise<void>
>p94 : Promise<void | undefined>
>p.then(() => {}, () => undefined) : Promise<void | undefined>
>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>
>p : Promise<boolean>
>then : <TResult1 = boolean, TResult2 = never>(onfulfilled?: ((value: boolean) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/superMethodCall.types
Expand Up @@ -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<void>
>asyncMethod : () => Promise<void | undefined>

return super.method?.();
>super.method?.() : void
>super.method?.() : void | undefined
>super.method : (() => void) | undefined
>super : Base
>method : (() => void) | undefined
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/thisMethodCall.types
Expand Up @@ -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
Expand Down
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/typeVariableTypeGuards.types
Expand Up @@ -16,7 +16,7 @@ class A<P extends Partial<Foo>> {
>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<P>
>this : this
Expand Down
Expand Up @@ -51,7 +51,7 @@ function bad<P extends Props>(props: Readonly<P>) {
// 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 : <A1, R>(func: ((arg1: A1) => R) | null | undefined, arg1: A1) => R | undefined
>props.onBar : P["onBar"] | undefined
>props : Readonly<P>
Expand Down
23 changes: 23 additions & 0 deletions tests/baselines/reference/voidUndefinedReduction.js
@@ -0,0 +1,23 @@
//// [voidUndefinedReduction.ts]
// Repro from #42786

function isDefined<T>(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());
}
33 changes: 33 additions & 0 deletions tests/baselines/reference/voidUndefinedReduction.symbols
@@ -0,0 +1,33 @@
=== tests/cases/compiler/voidUndefinedReduction.ts ===
// Repro from #42786

function isDefined<T>(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, --, --))
}

37 changes: 37 additions & 0 deletions tests/baselines/reference/voidUndefinedReduction.types
@@ -0,0 +1,37 @@
=== tests/cases/compiler/voidUndefinedReduction.ts ===
// Repro from #42786

function isDefined<T>(value: T | undefined | null | void): value is T {
>isDefined : <T>(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 : <T>(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
}

13 changes: 13 additions & 0 deletions tests/cases/compiler/voidUndefinedReduction.ts
@@ -0,0 +1,13 @@
// @strict: true

// Repro from #42786

function isDefined<T>(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());
}