diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d5c80f9ca4076..2cdbc68a3782d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25388,23 +25388,32 @@ m2: ${(this.mapper2 as unknown as DebugTypeMapper).__debugToString().split("\n") if (!assumeTrue) { return filterType(type, t => !isRelated(t, candidate)); } - // If the current type is a union type, remove all constituents that couldn't be instances of - // the candidate type. If one or more constituents remain, return a union of those. - if (type.flags & TypeFlags.Union) { - const assignableType = filterType(type, t => isRelated(t, candidate)); - if (!(assignableType.flags & TypeFlags.Never)) { - return assignableType; - } - } - // If the candidate type is a subtype of the target type, narrow to the candidate type. - // Otherwise, if the target type is assignable to the candidate type, keep the target type. - // Otherwise, if the candidate type is assignable to the target type, narrow to the candidate - // type. Otherwise, the types are completely unrelated, so narrow to an intersection of the - // two types. - return isTypeSubtypeOf(candidate, type) ? candidate : - isTypeAssignableTo(type, candidate) ? type : - isTypeAssignableTo(candidate, type) ? candidate : - getIntersectionType([type, candidate]); + if (type.flags & TypeFlags.AnyOrUnknown) { + return candidate; + } + // We first attempt to filter the current type, narrowing constituents as appropriate and removing + // constituents that are unrelated to the candidate. + const keyPropertyName = type.flags & TypeFlags.Union ? getKeyPropertyName(type as UnionType) : undefined; + const narrowedType = mapType(candidate, c => { + // If a discriminant property is available, use that to reduce the type. + const discriminant = keyPropertyName && getTypeOfPropertyOfType(c, keyPropertyName); + const matching = discriminant && getConstituentTypeForKeyType(type as UnionType, discriminant); + // For each constituent t in the current type, if t and and c are directly related, pick the most + // specific of the two. + const directlyRelated = mapType(matching || type, t => isRelated(t, c) ? t : isRelated(c, t) ? c : neverType); + // If no constituents are directly related, create intersections for any generic constituents that + // are related by constraint. + return directlyRelated.flags & TypeFlags.Never ? + mapType(type, t => maybeTypeOfKind(t, TypeFlags.Instantiable) && isRelated(c, getBaseConstraintOfType(t) || unknownType) ? getIntersectionType([t, c]) : neverType) : + directlyRelated; + }); + // If filtering produced a non-empty type, return that. Otherwise, pick the most specific of the two + // based on assignability, or as a last resort produce an intersection. + return !(narrowedType.flags & TypeFlags.Never) ? narrowedType : + isTypeSubtypeOf(candidate, type) ? candidate : + isTypeAssignableTo(type, candidate) ? type : + isTypeAssignableTo(candidate, type) ? candidate : + getIntersectionType([type, candidate]); } function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { diff --git a/tests/baselines/reference/controlFlowOptionalChain.types b/tests/baselines/reference/controlFlowOptionalChain.types index 8eb843fb0600b..b16f6972ef4e2 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.types +++ b/tests/baselines/reference/controlFlowOptionalChain.types @@ -1768,9 +1768,9 @@ function f30(o: Thing | undefined) { >foo : string | number | undefined o.foo; ->o.foo : string | number +>o.foo : NonNullable >o : Thing ->foo : string | number +>foo : NonNullable } } diff --git a/tests/baselines/reference/narrowingUnionToUnion.js b/tests/baselines/reference/narrowingUnionToUnion.js new file mode 100644 index 0000000000000..84d9b0a022723 --- /dev/null +++ b/tests/baselines/reference/narrowingUnionToUnion.js @@ -0,0 +1,423 @@ +//// [narrowingUnionToUnion.ts] +type Falsy = false | 0 | 0n | '' | null | undefined; + +declare function isFalsy(value: unknown): value is Falsy; + +function fx1(x: string | number | undefined) { + if (isFalsy(x)) { + x; // "" | 0 | undefined + } +} + +function fx2(x: T | undefined) { + if (isFalsy(x)) { + x; // T & Falsy | undefined + } +} + +function fx3(x: T) { + if (isFalsy(x)) { + x; // T & "" | T & 0 + } +} + +declare function isA(obj: unknown): obj is { a: false } | { b: 0 }; + +function fx4(obj: { b: number }) { + if (isA(obj)) { + obj; // { b: 0 } + } +} + +declare class X { x: string } +declare class XS extends X { xs: string } + +declare class Y { y: string } +declare class YS extends Y { ys: string } + +declare function isXSorY(obj: unknown): obj is XS | Y; + +function fx5(obj: X | YS, c: typeof XS | typeof Y) { + if (obj instanceof c) { + obj; // XS | YS + } + if (isXSorY(obj)) { + obj; // XS | YS + } +} + +// Repro from #31156 + +declare function isEmptyStrOrUndefined(mixed: any): mixed is "" | undefined; + +function fx10(s: string | undefined) { + if (isEmptyStrOrUndefined(s)) { + s; // "" | undefined + if (s == undefined) { + s; // undefined + } + else { + s; // "" + } + } +} + +// Repro from #37807 + +function f1(x: any): asserts x is number | undefined { } +let v1: number | string | undefined; +f1(v1); +v1; // number | undefined + +function f2(x: any): asserts x is 6 | undefined { } +let v2: number | string | undefined; +f2(v2); +v2; // 6 | undefined + +// #39105 + +declare function isEmptyString(value: string): value is ''; +declare function isMaybeEmptyString(value: string | null | undefined): value is '' | null | undefined; + +declare function isZero(value: number): value is 0; +declare function isMaybeZero(value: number | null | undefined): value is 0 | null | undefined; + +declare function isEmptyArray(value: T[]): value is []; +declare function isMaybeEmptyArray(value: T[] | null | undefined): value is [] | null | undefined; + +const TEST_CASES = [ + (value: string) => { + if (isEmptyString(value)) { + value; // "" + } + else { + value; // string + } + if (isMaybeEmptyString(value)) { + value; // "" + } + else { + value; // string + } + }, + (value?: string) => { + if (isMaybeEmptyString(value)) { + value; // "" | undefined + } + else { + value; // string + } + }, + (value: number) => { + if (isZero(value)) { + value; // 0 + } + else { + value; // number + } + if (isMaybeZero(value)) { + value; // 0 + } + else { + value; // number + } + }, + (value?: number) => { + if (isMaybeZero(value)) { + value; // 0 | undefined + } + else { + value; // number + } + }, + (value: string[]) => { + if (isEmptyArray(value)) { + value; // [] + } + else { + value; // string[] + } + if (isMaybeEmptyArray(value)) { + value; // [] + } + else { + value; // string[] + } + }, + (value?: string[]) => { + if (isMaybeEmptyArray(value)) { + value; // [] | undefined + } + else { + value; // string[] + } + }, +]; + +// Repro from #42101 + +type EmptyString = '' | null | undefined; + +function isEmpty(value: string | EmptyString): value is EmptyString { + return value === '' || value === null || value === undefined; +} + +let test: string | null | undefined; + +if (isEmpty(test)) { + test; // EmptyString +} + +// Repro from #43825 + +declare function assert(value: any): asserts value is T + +function test1(foo: number | string | boolean) { + assert<1 | string>(foo); + foo; // string | 1 +} + +// Repro from #46909 + +function check1(x: unknown): x is (string | 0) { + return typeof x === "string" || x === 0; +} + +function check2(x: unknown): x is ("hello" | 0) { + return x === "hello" || x === 0; +} + +function test3(x: unknown) { + if (typeof x === "string" || x === 0) { + x; // string | 0 + if (x === "hello" || x === 0) { + x; // 0 | "hello" + } + } + if (check1(x)) { + x; // string | 0 + if (check2(x)) { + x; // 0 | "hello" + } + } +} + +// Repro from #49588 + +function assertRelationIsNullOrStringArray(v: (string | number)[] | null): asserts v is string[] | null {} + +function f1x(obj: (string | number)[] | null) { + assertRelationIsNullOrStringArray(obj); + obj; // string[] | null +} + + +//// [narrowingUnionToUnion.js] +"use strict"; +function fx1(x) { + if (isFalsy(x)) { + x; // "" | 0 | undefined + } +} +function fx2(x) { + if (isFalsy(x)) { + x; // T & Falsy | undefined + } +} +function fx3(x) { + if (isFalsy(x)) { + x; // T & "" | T & 0 + } +} +function fx4(obj) { + if (isA(obj)) { + obj; // { b: 0 } + } +} +function fx5(obj, c) { + if (obj instanceof c) { + obj; // XS | YS + } + if (isXSorY(obj)) { + obj; // XS | YS + } +} +function fx10(s) { + if (isEmptyStrOrUndefined(s)) { + s; // "" | undefined + if (s == undefined) { + s; // undefined + } + else { + s; // "" + } + } +} +// Repro from #37807 +function f1(x) { } +var v1; +f1(v1); +v1; // number | undefined +function f2(x) { } +var v2; +f2(v2); +v2; // 6 | undefined +var TEST_CASES = [ + function (value) { + if (isEmptyString(value)) { + value; // "" + } + else { + value; // string + } + if (isMaybeEmptyString(value)) { + value; // "" + } + else { + value; // string + } + }, + function (value) { + if (isMaybeEmptyString(value)) { + value; // "" | undefined + } + else { + value; // string + } + }, + function (value) { + if (isZero(value)) { + value; // 0 + } + else { + value; // number + } + if (isMaybeZero(value)) { + value; // 0 + } + else { + value; // number + } + }, + function (value) { + if (isMaybeZero(value)) { + value; // 0 | undefined + } + else { + value; // number + } + }, + function (value) { + if (isEmptyArray(value)) { + value; // [] + } + else { + value; // string[] + } + if (isMaybeEmptyArray(value)) { + value; // [] + } + else { + value; // string[] + } + }, + function (value) { + if (isMaybeEmptyArray(value)) { + value; // [] | undefined + } + else { + value; // string[] + } + }, +]; +function isEmpty(value) { + return value === '' || value === null || value === undefined; +} +var test; +if (isEmpty(test)) { + test; // EmptyString +} +function test1(foo) { + assert(foo); + foo; // string | 1 +} +// Repro from #46909 +function check1(x) { + return typeof x === "string" || x === 0; +} +function check2(x) { + return x === "hello" || x === 0; +} +function test3(x) { + if (typeof x === "string" || x === 0) { + x; // string | 0 + if (x === "hello" || x === 0) { + x; // 0 | "hello" + } + } + if (check1(x)) { + x; // string | 0 + if (check2(x)) { + x; // 0 | "hello" + } + } +} +// Repro from #49588 +function assertRelationIsNullOrStringArray(v) { } +function f1x(obj) { + assertRelationIsNullOrStringArray(obj); + obj; // string[] | null +} + + +//// [narrowingUnionToUnion.d.ts] +declare type Falsy = false | 0 | 0n | '' | null | undefined; +declare function isFalsy(value: unknown): value is Falsy; +declare function fx1(x: string | number | undefined): void; +declare function fx2(x: T | undefined): void; +declare function fx3(x: T): void; +declare function isA(obj: unknown): obj is { + a: false; +} | { + b: 0; +}; +declare function fx4(obj: { + b: number; +}): void; +declare class X { + x: string; +} +declare class XS extends X { + xs: string; +} +declare class Y { + y: string; +} +declare class YS extends Y { + ys: string; +} +declare function isXSorY(obj: unknown): obj is XS | Y; +declare function fx5(obj: X | YS, c: typeof XS | typeof Y): void; +declare function isEmptyStrOrUndefined(mixed: any): mixed is "" | undefined; +declare function fx10(s: string | undefined): void; +declare function f1(x: any): asserts x is number | undefined; +declare let v1: number | string | undefined; +declare function f2(x: any): asserts x is 6 | undefined; +declare let v2: number | string | undefined; +declare function isEmptyString(value: string): value is ''; +declare function isMaybeEmptyString(value: string | null | undefined): value is '' | null | undefined; +declare function isZero(value: number): value is 0; +declare function isMaybeZero(value: number | null | undefined): value is 0 | null | undefined; +declare function isEmptyArray(value: T[]): value is []; +declare function isMaybeEmptyArray(value: T[] | null | undefined): value is [] | null | undefined; +declare const TEST_CASES: (((value: string) => void) | ((value: number) => void) | ((value: string[]) => void))[]; +declare type EmptyString = '' | null | undefined; +declare function isEmpty(value: string | EmptyString): value is EmptyString; +declare let test: string | null | undefined; +declare function assert(value: any): asserts value is T; +declare function test1(foo: number | string | boolean): void; +declare function check1(x: unknown): x is (string | 0); +declare function check2(x: unknown): x is ("hello" | 0); +declare function test3(x: unknown): void; +declare function assertRelationIsNullOrStringArray(v: (string | number)[] | null): asserts v is string[] | null; +declare function f1x(obj: (string | number)[] | null): void; diff --git a/tests/baselines/reference/narrowingUnionToUnion.symbols b/tests/baselines/reference/narrowingUnionToUnion.symbols new file mode 100644 index 0000000000000..5c7dfcb41de4f --- /dev/null +++ b/tests/baselines/reference/narrowingUnionToUnion.symbols @@ -0,0 +1,482 @@ +=== tests/cases/compiler/narrowingUnionToUnion.ts === +type Falsy = false | 0 | 0n | '' | null | undefined; +>Falsy : Symbol(Falsy, Decl(narrowingUnionToUnion.ts, 0, 0)) + +declare function isFalsy(value: unknown): value is Falsy; +>isFalsy : Symbol(isFalsy, Decl(narrowingUnionToUnion.ts, 0, 52)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 2, 25)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 2, 25)) +>Falsy : Symbol(Falsy, Decl(narrowingUnionToUnion.ts, 0, 0)) + +function fx1(x: string | number | undefined) { +>fx1 : Symbol(fx1, Decl(narrowingUnionToUnion.ts, 2, 57)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 4, 13)) + + if (isFalsy(x)) { +>isFalsy : Symbol(isFalsy, Decl(narrowingUnionToUnion.ts, 0, 52)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 4, 13)) + + x; // "" | 0 | undefined +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 4, 13)) + } +} + +function fx2(x: T | undefined) { +>fx2 : Symbol(fx2, Decl(narrowingUnionToUnion.ts, 8, 1)) +>T : Symbol(T, Decl(narrowingUnionToUnion.ts, 10, 13)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 10, 16)) +>T : Symbol(T, Decl(narrowingUnionToUnion.ts, 10, 13)) + + if (isFalsy(x)) { +>isFalsy : Symbol(isFalsy, Decl(narrowingUnionToUnion.ts, 0, 52)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 10, 16)) + + x; // T & Falsy | undefined +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 10, 16)) + } +} + +function fx3(x: T) { +>fx3 : Symbol(fx3, Decl(narrowingUnionToUnion.ts, 14, 1)) +>T : Symbol(T, Decl(narrowingUnionToUnion.ts, 16, 13)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 16, 40)) +>T : Symbol(T, Decl(narrowingUnionToUnion.ts, 16, 13)) + + if (isFalsy(x)) { +>isFalsy : Symbol(isFalsy, Decl(narrowingUnionToUnion.ts, 0, 52)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 16, 40)) + + x; // T & "" | T & 0 +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 16, 40)) + } +} + +declare function isA(obj: unknown): obj is { a: false } | { b: 0 }; +>isA : Symbol(isA, Decl(narrowingUnionToUnion.ts, 20, 1)) +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 22, 21)) +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 22, 21)) +>a : Symbol(a, Decl(narrowingUnionToUnion.ts, 22, 44)) +>b : Symbol(b, Decl(narrowingUnionToUnion.ts, 22, 59)) + +function fx4(obj: { b: number }) { +>fx4 : Symbol(fx4, Decl(narrowingUnionToUnion.ts, 22, 67)) +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 24, 13)) +>b : Symbol(b, Decl(narrowingUnionToUnion.ts, 24, 19)) + + if (isA(obj)) { +>isA : Symbol(isA, Decl(narrowingUnionToUnion.ts, 20, 1)) +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 24, 13)) + + obj; // { b: 0 } +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 24, 13)) + } +} + +declare class X { x: string } +>X : Symbol(X, Decl(narrowingUnionToUnion.ts, 28, 1)) +>x : Symbol(X.x, Decl(narrowingUnionToUnion.ts, 30, 17)) + +declare class XS extends X { xs: string } +>XS : Symbol(XS, Decl(narrowingUnionToUnion.ts, 30, 29)) +>X : Symbol(X, Decl(narrowingUnionToUnion.ts, 28, 1)) +>xs : Symbol(XS.xs, Decl(narrowingUnionToUnion.ts, 31, 28)) + +declare class Y { y: string } +>Y : Symbol(Y, Decl(narrowingUnionToUnion.ts, 31, 41)) +>y : Symbol(Y.y, Decl(narrowingUnionToUnion.ts, 33, 17)) + +declare class YS extends Y { ys: string } +>YS : Symbol(YS, Decl(narrowingUnionToUnion.ts, 33, 29)) +>Y : Symbol(Y, Decl(narrowingUnionToUnion.ts, 31, 41)) +>ys : Symbol(YS.ys, Decl(narrowingUnionToUnion.ts, 34, 28)) + +declare function isXSorY(obj: unknown): obj is XS | Y; +>isXSorY : Symbol(isXSorY, Decl(narrowingUnionToUnion.ts, 34, 41)) +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 36, 25)) +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 36, 25)) +>XS : Symbol(XS, Decl(narrowingUnionToUnion.ts, 30, 29)) +>Y : Symbol(Y, Decl(narrowingUnionToUnion.ts, 31, 41)) + +function fx5(obj: X | YS, c: typeof XS | typeof Y) { +>fx5 : Symbol(fx5, Decl(narrowingUnionToUnion.ts, 36, 54)) +>T : Symbol(T, Decl(narrowingUnionToUnion.ts, 38, 13)) +>X : Symbol(X, Decl(narrowingUnionToUnion.ts, 28, 1)) +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 38, 26)) +>X : Symbol(X, Decl(narrowingUnionToUnion.ts, 28, 1)) +>YS : Symbol(YS, Decl(narrowingUnionToUnion.ts, 33, 29)) +>c : Symbol(c, Decl(narrowingUnionToUnion.ts, 38, 38)) +>XS : Symbol(XS, Decl(narrowingUnionToUnion.ts, 30, 29)) +>Y : Symbol(Y, Decl(narrowingUnionToUnion.ts, 31, 41)) + + if (obj instanceof c) { +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 38, 26)) +>c : Symbol(c, Decl(narrowingUnionToUnion.ts, 38, 38)) + + obj; // XS | YS +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 38, 26)) + } + if (isXSorY(obj)) { +>isXSorY : Symbol(isXSorY, Decl(narrowingUnionToUnion.ts, 34, 41)) +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 38, 26)) + + obj; // XS | YS +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 38, 26)) + } +} + +// Repro from #31156 + +declare function isEmptyStrOrUndefined(mixed: any): mixed is "" | undefined; +>isEmptyStrOrUndefined : Symbol(isEmptyStrOrUndefined, Decl(narrowingUnionToUnion.ts, 45, 1)) +>mixed : Symbol(mixed, Decl(narrowingUnionToUnion.ts, 49, 39)) +>mixed : Symbol(mixed, Decl(narrowingUnionToUnion.ts, 49, 39)) + +function fx10(s: string | undefined) { +>fx10 : Symbol(fx10, Decl(narrowingUnionToUnion.ts, 49, 76)) +>s : Symbol(s, Decl(narrowingUnionToUnion.ts, 51, 14)) + + if (isEmptyStrOrUndefined(s)) { +>isEmptyStrOrUndefined : Symbol(isEmptyStrOrUndefined, Decl(narrowingUnionToUnion.ts, 45, 1)) +>s : Symbol(s, Decl(narrowingUnionToUnion.ts, 51, 14)) + + s; // "" | undefined +>s : Symbol(s, Decl(narrowingUnionToUnion.ts, 51, 14)) + + if (s == undefined) { +>s : Symbol(s, Decl(narrowingUnionToUnion.ts, 51, 14)) +>undefined : Symbol(undefined) + + s; // undefined +>s : Symbol(s, Decl(narrowingUnionToUnion.ts, 51, 14)) + } + else { + s; // "" +>s : Symbol(s, Decl(narrowingUnionToUnion.ts, 51, 14)) + } + } +} + +// Repro from #37807 + +function f1(x: any): asserts x is number | undefined { } +>f1 : Symbol(f1, Decl(narrowingUnionToUnion.ts, 61, 1)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 65, 12)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 65, 12)) + +let v1: number | string | undefined; +>v1 : Symbol(v1, Decl(narrowingUnionToUnion.ts, 66, 3)) + +f1(v1); +>f1 : Symbol(f1, Decl(narrowingUnionToUnion.ts, 61, 1)) +>v1 : Symbol(v1, Decl(narrowingUnionToUnion.ts, 66, 3)) + +v1; // number | undefined +>v1 : Symbol(v1, Decl(narrowingUnionToUnion.ts, 66, 3)) + +function f2(x: any): asserts x is 6 | undefined { } +>f2 : Symbol(f2, Decl(narrowingUnionToUnion.ts, 68, 3)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 70, 12)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 70, 12)) + +let v2: number | string | undefined; +>v2 : Symbol(v2, Decl(narrowingUnionToUnion.ts, 71, 3)) + +f2(v2); +>f2 : Symbol(f2, Decl(narrowingUnionToUnion.ts, 68, 3)) +>v2 : Symbol(v2, Decl(narrowingUnionToUnion.ts, 71, 3)) + +v2; // 6 | undefined +>v2 : Symbol(v2, Decl(narrowingUnionToUnion.ts, 71, 3)) + +// #39105 + +declare function isEmptyString(value: string): value is ''; +>isEmptyString : Symbol(isEmptyString, Decl(narrowingUnionToUnion.ts, 73, 3)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 77, 31)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 77, 31)) + +declare function isMaybeEmptyString(value: string | null | undefined): value is '' | null | undefined; +>isMaybeEmptyString : Symbol(isMaybeEmptyString, Decl(narrowingUnionToUnion.ts, 77, 59)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 78, 36)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 78, 36)) + +declare function isZero(value: number): value is 0; +>isZero : Symbol(isZero, Decl(narrowingUnionToUnion.ts, 78, 102)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 80, 24)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 80, 24)) + +declare function isMaybeZero(value: number | null | undefined): value is 0 | null | undefined; +>isMaybeZero : Symbol(isMaybeZero, Decl(narrowingUnionToUnion.ts, 80, 51)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 81, 29)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 81, 29)) + +declare function isEmptyArray(value: T[]): value is []; +>isEmptyArray : Symbol(isEmptyArray, Decl(narrowingUnionToUnion.ts, 81, 94)) +>T : Symbol(T, Decl(narrowingUnionToUnion.ts, 83, 30)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 83, 33)) +>T : Symbol(T, Decl(narrowingUnionToUnion.ts, 83, 30)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 83, 33)) + +declare function isMaybeEmptyArray(value: T[] | null | undefined): value is [] | null | undefined; +>isMaybeEmptyArray : Symbol(isMaybeEmptyArray, Decl(narrowingUnionToUnion.ts, 83, 58)) +>T : Symbol(T, Decl(narrowingUnionToUnion.ts, 84, 35)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 84, 38)) +>T : Symbol(T, Decl(narrowingUnionToUnion.ts, 84, 35)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 84, 38)) + +const TEST_CASES = [ +>TEST_CASES : Symbol(TEST_CASES, Decl(narrowingUnionToUnion.ts, 86, 5)) + + (value: string) => { +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 87, 5)) + + if (isEmptyString(value)) { +>isEmptyString : Symbol(isEmptyString, Decl(narrowingUnionToUnion.ts, 73, 3)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 87, 5)) + + value; // "" +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 87, 5)) + } + else { + value; // string +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 87, 5)) + } + if (isMaybeEmptyString(value)) { +>isMaybeEmptyString : Symbol(isMaybeEmptyString, Decl(narrowingUnionToUnion.ts, 77, 59)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 87, 5)) + + value; // "" +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 87, 5)) + } + else { + value; // string +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 87, 5)) + } + }, + (value?: string) => { +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 101, 5)) + + if (isMaybeEmptyString(value)) { +>isMaybeEmptyString : Symbol(isMaybeEmptyString, Decl(narrowingUnionToUnion.ts, 77, 59)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 101, 5)) + + value; // "" | undefined +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 101, 5)) + } + else { + value; // string +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 101, 5)) + } + }, + (value: number) => { +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 109, 5)) + + if (isZero(value)) { +>isZero : Symbol(isZero, Decl(narrowingUnionToUnion.ts, 78, 102)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 109, 5)) + + value; // 0 +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 109, 5)) + } + else { + value; // number +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 109, 5)) + } + if (isMaybeZero(value)) { +>isMaybeZero : Symbol(isMaybeZero, Decl(narrowingUnionToUnion.ts, 80, 51)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 109, 5)) + + value; // 0 +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 109, 5)) + } + else { + value; // number +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 109, 5)) + } + }, + (value?: number) => { +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 123, 5)) + + if (isMaybeZero(value)) { +>isMaybeZero : Symbol(isMaybeZero, Decl(narrowingUnionToUnion.ts, 80, 51)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 123, 5)) + + value; // 0 | undefined +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 123, 5)) + } + else { + value; // number +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 123, 5)) + } + }, + (value: string[]) => { +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 131, 5)) + + if (isEmptyArray(value)) { +>isEmptyArray : Symbol(isEmptyArray, Decl(narrowingUnionToUnion.ts, 81, 94)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 131, 5)) + + value; // [] +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 131, 5)) + } + else { + value; // string[] +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 131, 5)) + } + if (isMaybeEmptyArray(value)) { +>isMaybeEmptyArray : Symbol(isMaybeEmptyArray, Decl(narrowingUnionToUnion.ts, 83, 58)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 131, 5)) + + value; // [] +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 131, 5)) + } + else { + value; // string[] +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 131, 5)) + } + }, + (value?: string[]) => { +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 145, 5)) + + if (isMaybeEmptyArray(value)) { +>isMaybeEmptyArray : Symbol(isMaybeEmptyArray, Decl(narrowingUnionToUnion.ts, 83, 58)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 145, 5)) + + value; // [] | undefined +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 145, 5)) + } + else { + value; // string[] +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 145, 5)) + } + }, +]; + +// Repro from #42101 + +type EmptyString = '' | null | undefined; +>EmptyString : Symbol(EmptyString, Decl(narrowingUnionToUnion.ts, 153, 2)) + +function isEmpty(value: string | EmptyString): value is EmptyString { +>isEmpty : Symbol(isEmpty, Decl(narrowingUnionToUnion.ts, 157, 41)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 159, 17)) +>EmptyString : Symbol(EmptyString, Decl(narrowingUnionToUnion.ts, 153, 2)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 159, 17)) +>EmptyString : Symbol(EmptyString, Decl(narrowingUnionToUnion.ts, 153, 2)) + + return value === '' || value === null || value === undefined; +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 159, 17)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 159, 17)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 159, 17)) +>undefined : Symbol(undefined) +} + +let test: string | null | undefined; +>test : Symbol(test, Decl(narrowingUnionToUnion.ts, 163, 3)) + +if (isEmpty(test)) { +>isEmpty : Symbol(isEmpty, Decl(narrowingUnionToUnion.ts, 157, 41)) +>test : Symbol(test, Decl(narrowingUnionToUnion.ts, 163, 3)) + + test; // EmptyString +>test : Symbol(test, Decl(narrowingUnionToUnion.ts, 163, 3)) +} + +// Repro from #43825 + +declare function assert(value: any): asserts value is T +>assert : Symbol(assert, Decl(narrowingUnionToUnion.ts, 167, 1)) +>T : Symbol(T, Decl(narrowingUnionToUnion.ts, 171, 24)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 171, 27)) +>value : Symbol(value, Decl(narrowingUnionToUnion.ts, 171, 27)) +>T : Symbol(T, Decl(narrowingUnionToUnion.ts, 171, 24)) + +function test1(foo: number | string | boolean) { +>test1 : Symbol(test1, Decl(narrowingUnionToUnion.ts, 171, 58)) +>foo : Symbol(foo, Decl(narrowingUnionToUnion.ts, 173, 15)) + + assert<1 | string>(foo); +>assert : Symbol(assert, Decl(narrowingUnionToUnion.ts, 167, 1)) +>foo : Symbol(foo, Decl(narrowingUnionToUnion.ts, 173, 15)) + + foo; // string | 1 +>foo : Symbol(foo, Decl(narrowingUnionToUnion.ts, 173, 15)) +} + +// Repro from #46909 + +function check1(x: unknown): x is (string | 0) { +>check1 : Symbol(check1, Decl(narrowingUnionToUnion.ts, 176, 1)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 180, 16)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 180, 16)) + + return typeof x === "string" || x === 0; +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 180, 16)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 180, 16)) +} + +function check2(x: unknown): x is ("hello" | 0) { +>check2 : Symbol(check2, Decl(narrowingUnionToUnion.ts, 182, 1)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 184, 16)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 184, 16)) + + return x === "hello" || x === 0; +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 184, 16)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 184, 16)) +} + +function test3(x: unknown) { +>test3 : Symbol(test3, Decl(narrowingUnionToUnion.ts, 186, 1)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 188, 15)) + + if (typeof x === "string" || x === 0) { +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 188, 15)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 188, 15)) + + x; // string | 0 +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 188, 15)) + + if (x === "hello" || x === 0) { +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 188, 15)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 188, 15)) + + x; // 0 | "hello" +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 188, 15)) + } + } + if (check1(x)) { +>check1 : Symbol(check1, Decl(narrowingUnionToUnion.ts, 176, 1)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 188, 15)) + + x; // string | 0 +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 188, 15)) + + if (check2(x)) { +>check2 : Symbol(check2, Decl(narrowingUnionToUnion.ts, 182, 1)) +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 188, 15)) + + x; // 0 | "hello" +>x : Symbol(x, Decl(narrowingUnionToUnion.ts, 188, 15)) + } + } +} + +// Repro from #49588 + +function assertRelationIsNullOrStringArray(v: (string | number)[] | null): asserts v is string[] | null {} +>assertRelationIsNullOrStringArray : Symbol(assertRelationIsNullOrStringArray, Decl(narrowingUnionToUnion.ts, 201, 1)) +>v : Symbol(v, Decl(narrowingUnionToUnion.ts, 205, 43)) +>v : Symbol(v, Decl(narrowingUnionToUnion.ts, 205, 43)) + +function f1x(obj: (string | number)[] | null) { +>f1x : Symbol(f1x, Decl(narrowingUnionToUnion.ts, 205, 106)) +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 207, 13)) + + assertRelationIsNullOrStringArray(obj); +>assertRelationIsNullOrStringArray : Symbol(assertRelationIsNullOrStringArray, Decl(narrowingUnionToUnion.ts, 201, 1)) +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 207, 13)) + + obj; // string[] | null +>obj : Symbol(obj, Decl(narrowingUnionToUnion.ts, 207, 13)) +} + diff --git a/tests/baselines/reference/narrowingUnionToUnion.types b/tests/baselines/reference/narrowingUnionToUnion.types new file mode 100644 index 0000000000000..4b373711bd927 --- /dev/null +++ b/tests/baselines/reference/narrowingUnionToUnion.types @@ -0,0 +1,520 @@ +=== tests/cases/compiler/narrowingUnionToUnion.ts === +type Falsy = false | 0 | 0n | '' | null | undefined; +>Falsy : false | "" | 0 | 0n | null | undefined +>false : false +>null : null + +declare function isFalsy(value: unknown): value is Falsy; +>isFalsy : (value: unknown) => value is Falsy +>value : unknown + +function fx1(x: string | number | undefined) { +>fx1 : (x: string | number | undefined) => void +>x : string | number | undefined + + if (isFalsy(x)) { +>isFalsy(x) : boolean +>isFalsy : (value: unknown) => value is Falsy +>x : string | number | undefined + + x; // "" | 0 | undefined +>x : "" | 0 | undefined + } +} + +function fx2(x: T | undefined) { +>fx2 : (x: T | undefined) => void +>x : T | undefined + + if (isFalsy(x)) { +>isFalsy(x) : boolean +>isFalsy : (value: unknown) => value is Falsy +>x : T | undefined + + x; // T & Falsy | undefined +>x : (T & null) | (T & false) | (T & "") | (T & 0) | (T & 0n) | undefined + } +} + +function fx3(x: T) { +>fx3 : (x: T) => void +>x : T + + if (isFalsy(x)) { +>isFalsy(x) : boolean +>isFalsy : (value: unknown) => value is Falsy +>x : string | number + + x; // T & "" | T & 0 +>x : (T & "") | (T & 0) + } +} + +declare function isA(obj: unknown): obj is { a: false } | { b: 0 }; +>isA : (obj: unknown) => obj is { a: false; } | { b: 0; } +>obj : unknown +>a : false +>false : false +>b : 0 + +function fx4(obj: { b: number }) { +>fx4 : (obj: { b: number;}) => void +>obj : { b: number; } +>b : number + + if (isA(obj)) { +>isA(obj) : boolean +>isA : (obj: unknown) => obj is { a: false; } | { b: 0; } +>obj : { b: number; } + + obj; // { b: 0 } +>obj : { b: 0; } + } +} + +declare class X { x: string } +>X : X +>x : string + +declare class XS extends X { xs: string } +>XS : XS +>X : X +>xs : string + +declare class Y { y: string } +>Y : Y +>y : string + +declare class YS extends Y { ys: string } +>YS : YS +>Y : Y +>ys : string + +declare function isXSorY(obj: unknown): obj is XS | Y; +>isXSorY : (obj: unknown) => obj is XS | Y +>obj : unknown + +function fx5(obj: X | YS, c: typeof XS | typeof Y) { +>fx5 : (obj: X | YS, c: typeof XS | typeof Y) => void +>obj : X | YS +>c : typeof XS | typeof Y +>XS : typeof XS +>Y : typeof Y + + if (obj instanceof c) { +>obj instanceof c : boolean +>obj : X | YS +>c : typeof XS | typeof Y + + obj; // XS | YS +>obj : XS | YS + } + if (isXSorY(obj)) { +>isXSorY(obj) : boolean +>isXSorY : (obj: unknown) => obj is XS | Y +>obj : X | YS + + obj; // XS | YS +>obj : XS | YS + } +} + +// Repro from #31156 + +declare function isEmptyStrOrUndefined(mixed: any): mixed is "" | undefined; +>isEmptyStrOrUndefined : (mixed: any) => mixed is "" | undefined +>mixed : any + +function fx10(s: string | undefined) { +>fx10 : (s: string | undefined) => void +>s : string | undefined + + if (isEmptyStrOrUndefined(s)) { +>isEmptyStrOrUndefined(s) : boolean +>isEmptyStrOrUndefined : (mixed: any) => mixed is "" | undefined +>s : string | undefined + + s; // "" | undefined +>s : "" | undefined + + if (s == undefined) { +>s == undefined : boolean +>s : "" | undefined +>undefined : undefined + + s; // undefined +>s : undefined + } + else { + s; // "" +>s : "" + } + } +} + +// Repro from #37807 + +function f1(x: any): asserts x is number | undefined { } +>f1 : (x: any) => asserts x is number | undefined +>x : any + +let v1: number | string | undefined; +>v1 : string | number | undefined + +f1(v1); +>f1(v1) : void +>f1 : (x: any) => asserts x is number | undefined +>v1 : string | number | undefined + +v1; // number | undefined +>v1 : number | undefined + +function f2(x: any): asserts x is 6 | undefined { } +>f2 : (x: any) => asserts x is 6 | undefined +>x : any + +let v2: number | string | undefined; +>v2 : string | number | undefined + +f2(v2); +>f2(v2) : void +>f2 : (x: any) => asserts x is 6 | undefined +>v2 : string | number | undefined + +v2; // 6 | undefined +>v2 : 6 | undefined + +// #39105 + +declare function isEmptyString(value: string): value is ''; +>isEmptyString : (value: string) => value is "" +>value : string + +declare function isMaybeEmptyString(value: string | null | undefined): value is '' | null | undefined; +>isMaybeEmptyString : (value: string | null | undefined) => value is "" | null | undefined +>value : string | null | undefined +>null : null +>null : null + +declare function isZero(value: number): value is 0; +>isZero : (value: number) => value is 0 +>value : number + +declare function isMaybeZero(value: number | null | undefined): value is 0 | null | undefined; +>isMaybeZero : (value: number | null | undefined) => value is 0 | null | undefined +>value : number | null | undefined +>null : null +>null : null + +declare function isEmptyArray(value: T[]): value is []; +>isEmptyArray : (value: T[]) => value is [] +>value : T[] + +declare function isMaybeEmptyArray(value: T[] | null | undefined): value is [] | null | undefined; +>isMaybeEmptyArray : (value: T[] | null | undefined) => value is [] | null | undefined +>value : T[] | null | undefined +>null : null +>null : null + +const TEST_CASES = [ +>TEST_CASES : (((value: string) => void) | ((value: number) => void) | ((value: string[]) => void))[] +>[ (value: string) => { if (isEmptyString(value)) { value; // "" } else { value; // string } if (isMaybeEmptyString(value)) { value; // "" } else { value; // string } }, (value?: string) => { if (isMaybeEmptyString(value)) { value; // "" | undefined } else { value; // string } }, (value: number) => { if (isZero(value)) { value; // 0 } else { value; // number } if (isMaybeZero(value)) { value; // 0 } else { value; // number } }, (value?: number) => { if (isMaybeZero(value)) { value; // 0 | undefined } else { value; // number } }, (value: string[]) => { if (isEmptyArray(value)) { value; // [] } else { value; // string[] } if (isMaybeEmptyArray(value)) { value; // [] } else { value; // string[] } }, (value?: string[]) => { if (isMaybeEmptyArray(value)) { value; // [] | undefined } else { value; // string[] } },] : (((value: string) => void) | ((value: number) => void) | ((value: string[]) => void))[] + + (value: string) => { +>(value: string) => { if (isEmptyString(value)) { value; // "" } else { value; // string } if (isMaybeEmptyString(value)) { value; // "" } else { value; // string } } : (value: string) => void +>value : string + + if (isEmptyString(value)) { +>isEmptyString(value) : boolean +>isEmptyString : (value: string) => value is "" +>value : string + + value; // "" +>value : "" + } + else { + value; // string +>value : string + } + if (isMaybeEmptyString(value)) { +>isMaybeEmptyString(value) : boolean +>isMaybeEmptyString : (value: string | null | undefined) => value is "" | null | undefined +>value : string + + value; // "" +>value : "" + } + else { + value; // string +>value : string + } + }, + (value?: string) => { +>(value?: string) => { if (isMaybeEmptyString(value)) { value; // "" | undefined } else { value; // string } } : (value?: string) => void +>value : string | undefined + + if (isMaybeEmptyString(value)) { +>isMaybeEmptyString(value) : boolean +>isMaybeEmptyString : (value: string | null | undefined) => value is "" | null | undefined +>value : string | undefined + + value; // "" | undefined +>value : "" | undefined + } + else { + value; // string +>value : string + } + }, + (value: number) => { +>(value: number) => { if (isZero(value)) { value; // 0 } else { value; // number } if (isMaybeZero(value)) { value; // 0 } else { value; // number } } : (value: number) => void +>value : number + + if (isZero(value)) { +>isZero(value) : boolean +>isZero : (value: number) => value is 0 +>value : number + + value; // 0 +>value : 0 + } + else { + value; // number +>value : number + } + if (isMaybeZero(value)) { +>isMaybeZero(value) : boolean +>isMaybeZero : (value: number | null | undefined) => value is 0 | null | undefined +>value : number + + value; // 0 +>value : 0 + } + else { + value; // number +>value : number + } + }, + (value?: number) => { +>(value?: number) => { if (isMaybeZero(value)) { value; // 0 | undefined } else { value; // number } } : (value?: number) => void +>value : number | undefined + + if (isMaybeZero(value)) { +>isMaybeZero(value) : boolean +>isMaybeZero : (value: number | null | undefined) => value is 0 | null | undefined +>value : number | undefined + + value; // 0 | undefined +>value : 0 | undefined + } + else { + value; // number +>value : number + } + }, + (value: string[]) => { +>(value: string[]) => { if (isEmptyArray(value)) { value; // [] } else { value; // string[] } if (isMaybeEmptyArray(value)) { value; // [] } else { value; // string[] } } : (value: string[]) => void +>value : string[] + + if (isEmptyArray(value)) { +>isEmptyArray(value) : boolean +>isEmptyArray : (value: T[]) => value is [] +>value : string[] + + value; // [] +>value : [] + } + else { + value; // string[] +>value : string[] + } + if (isMaybeEmptyArray(value)) { +>isMaybeEmptyArray(value) : boolean +>isMaybeEmptyArray : (value: T[] | null | undefined) => value is [] | null | undefined +>value : string[] + + value; // [] +>value : [] + } + else { + value; // string[] +>value : string[] + } + }, + (value?: string[]) => { +>(value?: string[]) => { if (isMaybeEmptyArray(value)) { value; // [] | undefined } else { value; // string[] } } : (value?: string[]) => void +>value : string[] | undefined + + if (isMaybeEmptyArray(value)) { +>isMaybeEmptyArray(value) : boolean +>isMaybeEmptyArray : (value: T[] | null | undefined) => value is [] | null | undefined +>value : string[] | undefined + + value; // [] | undefined +>value : [] | undefined + } + else { + value; // string[] +>value : string[] + } + }, +]; + +// Repro from #42101 + +type EmptyString = '' | null | undefined; +>EmptyString : "" | null | undefined +>null : null + +function isEmpty(value: string | EmptyString): value is EmptyString { +>isEmpty : (value: string | EmptyString) => value is EmptyString +>value : string | null | undefined + + return value === '' || value === null || value === undefined; +>value === '' || value === null || value === undefined : boolean +>value === '' || value === null : boolean +>value === '' : boolean +>value : string | null | undefined +>'' : "" +>value === null : boolean +>value : string | null | undefined +>null : null +>value === undefined : boolean +>value : string | undefined +>undefined : undefined +} + +let test: string | null | undefined; +>test : string | null | undefined +>null : null + +if (isEmpty(test)) { +>isEmpty(test) : boolean +>isEmpty : (value: string | null | undefined) => value is EmptyString +>test : string | null | undefined + + test; // EmptyString +>test : EmptyString +} + +// Repro from #43825 + +declare function assert(value: any): asserts value is T +>assert : (value: any) => asserts value is T +>value : any + +function test1(foo: number | string | boolean) { +>test1 : (foo: number | string | boolean) => void +>foo : string | number | boolean + + assert<1 | string>(foo); +>assert<1 | string>(foo) : void +>assert : (value: any) => asserts value is T +>foo : string | number | boolean + + foo; // string | 1 +>foo : string | 1 +} + +// Repro from #46909 + +function check1(x: unknown): x is (string | 0) { +>check1 : (x: unknown) => x is string | 0 +>x : unknown + + return typeof x === "string" || x === 0; +>typeof x === "string" || x === 0 : boolean +>typeof x === "string" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : unknown +>"string" : "string" +>x === 0 : boolean +>x : unknown +>0 : 0 +} + +function check2(x: unknown): x is ("hello" | 0) { +>check2 : (x: unknown) => x is 0 | "hello" +>x : unknown + + return x === "hello" || x === 0; +>x === "hello" || x === 0 : boolean +>x === "hello" : boolean +>x : unknown +>"hello" : "hello" +>x === 0 : boolean +>x : unknown +>0 : 0 +} + +function test3(x: unknown) { +>test3 : (x: unknown) => void +>x : unknown + + if (typeof x === "string" || x === 0) { +>typeof x === "string" || x === 0 : boolean +>typeof x === "string" : boolean +>typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x : unknown +>"string" : "string" +>x === 0 : boolean +>x : unknown +>0 : 0 + + x; // string | 0 +>x : string | 0 + + if (x === "hello" || x === 0) { +>x === "hello" || x === 0 : boolean +>x === "hello" : boolean +>x : string | 0 +>"hello" : "hello" +>x === 0 : boolean +>x : string | 0 +>0 : 0 + + x; // 0 | "hello" +>x : 0 | "hello" + } + } + if (check1(x)) { +>check1(x) : boolean +>check1 : (x: unknown) => x is string | 0 +>x : unknown + + x; // string | 0 +>x : string | 0 + + if (check2(x)) { +>check2(x) : boolean +>check2 : (x: unknown) => x is 0 | "hello" +>x : string | 0 + + x; // 0 | "hello" +>x : 0 | "hello" + } + } +} + +// Repro from #49588 + +function assertRelationIsNullOrStringArray(v: (string | number)[] | null): asserts v is string[] | null {} +>assertRelationIsNullOrStringArray : (v: (string | number)[] | null) => asserts v is string[] | null +>v : (string | number)[] | null +>null : null +>null : null + +function f1x(obj: (string | number)[] | null) { +>f1x : (obj: (string | number)[] | null) => void +>obj : (string | number)[] | null +>null : null + + assertRelationIsNullOrStringArray(obj); +>assertRelationIsNullOrStringArray(obj) : void +>assertRelationIsNullOrStringArray : (v: (string | number)[] | null) => asserts v is string[] | null +>obj : (string | number)[] | null + + obj; // string[] | null +>obj : string[] | null +} + diff --git a/tests/cases/compiler/narrowingUnionToUnion.ts b/tests/cases/compiler/narrowingUnionToUnion.ts new file mode 100644 index 0000000000000..f28a10060d17f --- /dev/null +++ b/tests/cases/compiler/narrowingUnionToUnion.ts @@ -0,0 +1,214 @@ +// @strict: true +// @declaration: true + +type Falsy = false | 0 | 0n | '' | null | undefined; + +declare function isFalsy(value: unknown): value is Falsy; + +function fx1(x: string | number | undefined) { + if (isFalsy(x)) { + x; // "" | 0 | undefined + } +} + +function fx2(x: T | undefined) { + if (isFalsy(x)) { + x; // T & Falsy | undefined + } +} + +function fx3(x: T) { + if (isFalsy(x)) { + x; // T & "" | T & 0 + } +} + +declare function isA(obj: unknown): obj is { a: false } | { b: 0 }; + +function fx4(obj: { b: number }) { + if (isA(obj)) { + obj; // { b: 0 } + } +} + +declare class X { x: string } +declare class XS extends X { xs: string } + +declare class Y { y: string } +declare class YS extends Y { ys: string } + +declare function isXSorY(obj: unknown): obj is XS | Y; + +function fx5(obj: X | YS, c: typeof XS | typeof Y) { + if (obj instanceof c) { + obj; // XS | YS + } + if (isXSorY(obj)) { + obj; // XS | YS + } +} + +// Repro from #31156 + +declare function isEmptyStrOrUndefined(mixed: any): mixed is "" | undefined; + +function fx10(s: string | undefined) { + if (isEmptyStrOrUndefined(s)) { + s; // "" | undefined + if (s == undefined) { + s; // undefined + } + else { + s; // "" + } + } +} + +// Repro from #37807 + +function f1(x: any): asserts x is number | undefined { } +let v1: number | string | undefined; +f1(v1); +v1; // number | undefined + +function f2(x: any): asserts x is 6 | undefined { } +let v2: number | string | undefined; +f2(v2); +v2; // 6 | undefined + +// #39105 + +declare function isEmptyString(value: string): value is ''; +declare function isMaybeEmptyString(value: string | null | undefined): value is '' | null | undefined; + +declare function isZero(value: number): value is 0; +declare function isMaybeZero(value: number | null | undefined): value is 0 | null | undefined; + +declare function isEmptyArray(value: T[]): value is []; +declare function isMaybeEmptyArray(value: T[] | null | undefined): value is [] | null | undefined; + +const TEST_CASES = [ + (value: string) => { + if (isEmptyString(value)) { + value; // "" + } + else { + value; // string + } + if (isMaybeEmptyString(value)) { + value; // "" + } + else { + value; // string + } + }, + (value?: string) => { + if (isMaybeEmptyString(value)) { + value; // "" | undefined + } + else { + value; // string + } + }, + (value: number) => { + if (isZero(value)) { + value; // 0 + } + else { + value; // number + } + if (isMaybeZero(value)) { + value; // 0 + } + else { + value; // number + } + }, + (value?: number) => { + if (isMaybeZero(value)) { + value; // 0 | undefined + } + else { + value; // number + } + }, + (value: string[]) => { + if (isEmptyArray(value)) { + value; // [] + } + else { + value; // string[] + } + if (isMaybeEmptyArray(value)) { + value; // [] + } + else { + value; // string[] + } + }, + (value?: string[]) => { + if (isMaybeEmptyArray(value)) { + value; // [] | undefined + } + else { + value; // string[] + } + }, +]; + +// Repro from #42101 + +type EmptyString = '' | null | undefined; + +function isEmpty(value: string | EmptyString): value is EmptyString { + return value === '' || value === null || value === undefined; +} + +let test: string | null | undefined; + +if (isEmpty(test)) { + test; // EmptyString +} + +// Repro from #43825 + +declare function assert(value: any): asserts value is T + +function test1(foo: number | string | boolean) { + assert<1 | string>(foo); + foo; // string | 1 +} + +// Repro from #46909 + +function check1(x: unknown): x is (string | 0) { + return typeof x === "string" || x === 0; +} + +function check2(x: unknown): x is ("hello" | 0) { + return x === "hello" || x === 0; +} + +function test3(x: unknown) { + if (typeof x === "string" || x === 0) { + x; // string | 0 + if (x === "hello" || x === 0) { + x; // 0 | "hello" + } + } + if (check1(x)) { + x; // string | 0 + if (check2(x)) { + x; // 0 | "hello" + } + } +} + +// Repro from #49588 + +function assertRelationIsNullOrStringArray(v: (string | number)[] | null): asserts v is string[] | null {} + +function f1x(obj: (string | number)[] | null) { + assertRelationIsNullOrStringArray(obj); + obj; // string[] | null +}