Improve narrowing logic for instanceof, type predicate functions, and assertion functions (#49625)

* Improve narrowing logic for instanceof, type predicates, and assertions

* Accept new baselines

* Add tests

* Tweak algorithm

* Accept new baselines

* Optimize for discriminated unions
This commit is contained in:
Anders Hejlsberg 2022-07-15 14:01:55 -10:00 committed by GitHub
parent 4f29633934
commit 2c68ded954
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 1666 additions and 18 deletions

View File

@ -25405,23 +25405,32 @@ namespace ts {
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 (type.flags & TypeFlags.AnyOrUnknown) {
return candidate;
}
// 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]);
// 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 {

View File

@ -1768,9 +1768,9 @@ function f30(o: Thing | undefined) {
>foo : string | number | undefined
o.foo;
>o.foo : string | number
>o.foo : NonNullable<string | number | undefined>
>o : Thing
>foo : string | number
>foo : NonNullable<string | number | undefined>
}
}

View File

@ -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<T>(x: T | undefined) {
if (isFalsy(x)) {
x; // T & Falsy | undefined
}
}
function fx3<T extends string | number>(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<T extends X>(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<T>(value: T[]): value is [];
declare function isMaybeEmptyArray<T>(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<T>(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<T>(x: T | undefined): void;
declare function fx3<T extends string | number>(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<T extends X>(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<T>(value: T[]): value is [];
declare function isMaybeEmptyArray<T>(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<T>(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;

View File

@ -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<T>(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<T extends string | number>(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<T extends X>(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<T>(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<T>(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<T>(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))
}

View File

@ -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<T>(x: T | undefined) {
>fx2 : <T>(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<T extends string | number>(x: T) {
>fx3 : <T extends string | number>(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<T extends X>(obj: X | YS, c: typeof XS | typeof Y) {
>fx5 : <T extends X>(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<T>(value: T[]): value is [];
>isEmptyArray : <T>(value: T[]) => value is []
>value : T[]
declare function isMaybeEmptyArray<T>(value: T[] | null | undefined): value is [] | null | undefined;
>isMaybeEmptyArray : <T>(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 : <T>(value: T[]) => value is []
>value : string[]
value; // []
>value : []
}
else {
value; // string[]
>value : string[]
}
if (isMaybeEmptyArray(value)) {
>isMaybeEmptyArray(value) : boolean
>isMaybeEmptyArray : <T>(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 : <T>(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<T>(value: any): asserts value is T
>assert : <T>(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 : <T>(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
}

View File

@ -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<T>(x: T | undefined) {
if (isFalsy(x)) {
x; // T & Falsy | undefined
}
}
function fx3<T extends string | number>(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<T extends X>(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<T>(value: T[]): value is [];
declare function isMaybeEmptyArray<T>(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<T>(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
}