diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ba234e4981bef..8272133c53f00 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21761,15 +21761,11 @@ namespace ts { 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 the candidate type is a subtype of the target type, narrow to the candidate type, + // if the target type is a subtype of the candidate type, narrow to the target type, + // otherwise, narrow to an intersection of the two types. + return isTypeSubtypeOf(candidate, type) ? candidate : isTypeSubtypeOf(type, candidate) ? type : getIntersectionType([type, candidate]); } function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type { diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index e111d36d117ad..a60a3a2adb539 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -1376,7 +1376,7 @@ interface ArrayConstructor { (arrayLength?: number): any[]; (arrayLength: number): T[]; (...items: T[]): T[]; - isArray(arg: any): arg is any[]; + isArray(arg: T | {}): arg is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[]; readonly prototype: any[]; } diff --git a/tests/baselines/reference/consistentUnionSubtypeReduction.js b/tests/baselines/reference/consistentUnionSubtypeReduction.js new file mode 100644 index 0000000000000..ce583be6d33e9 --- /dev/null +++ b/tests/baselines/reference/consistentUnionSubtypeReduction.js @@ -0,0 +1,100 @@ +//// [consistentUnionSubtypeReduction.ts] +// https://github.com/microsoft/TypeScript/issues/31155 + +declare const MyArray: { + isArray(arg: T | {}): arg is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[]; +}; + +declare const a: readonly string[] | string; +declare const b: string[] | string; +declare const c: unknown; + +if (MyArray.isArray(a)) { + a; // readonly string[] +} +else { + a; // string +} +a; // readonly string[] | string; + +if (MyArray.isArray(b)) { + b; // string[] | string; +} +else { + b; // string +} +b; // string[] | string; + +if (MyArray.isArray(c)) { + c; // any[] +} + + +function f(x: T) { + const a: readonly T[] | string = null!; + const b: T[] | string = null!; + const c: T = null!; + + if (MyArray.isArray(a)) { + a; // readonly T[] + } + else { + a; // string + } + a; // readonly T[] | string; + + if (MyArray.isArray(b)) { + b; // T[] + } + else { + b; // string + } + b; + + if (MyArray.isArray(c)) { + c; // T & (T extends readonly any[] ? readonly any[] : any[]) + } +} + + +//// [consistentUnionSubtypeReduction.js] +// https://github.com/microsoft/TypeScript/issues/31155 +if (MyArray.isArray(a)) { + a; // readonly string[] +} +else { + a; // string +} +a; // readonly string[] | string; +if (MyArray.isArray(b)) { + b; // string[] | string; +} +else { + b; // string +} +b; // string[] | string; +if (MyArray.isArray(c)) { + c; // any[] +} +function f(x) { + var a = null; + var b = null; + var c = null; + if (MyArray.isArray(a)) { + a; // readonly T[] + } + else { + a; // string + } + a; // readonly T[] | string; + if (MyArray.isArray(b)) { + b; // T[] + } + else { + b; // string + } + b; + if (MyArray.isArray(c)) { + c; // T & (T extends readonly any[] ? readonly any[] : any[]) + } +} diff --git a/tests/baselines/reference/consistentUnionSubtypeReduction.symbols b/tests/baselines/reference/consistentUnionSubtypeReduction.symbols new file mode 100644 index 0000000000000..44333b25cf617 --- /dev/null +++ b/tests/baselines/reference/consistentUnionSubtypeReduction.symbols @@ -0,0 +1,130 @@ +=== tests/cases/compiler/consistentUnionSubtypeReduction.ts === +// https://github.com/microsoft/TypeScript/issues/31155 + +declare const MyArray: { +>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13)) + + isArray(arg: T | {}): arg is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[]; +>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24)) +>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 3, 12)) +>arg : Symbol(arg, Decl(consistentUnionSubtypeReduction.ts, 3, 15)) +>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 3, 12)) +>arg : Symbol(arg, Decl(consistentUnionSubtypeReduction.ts, 3, 15)) +>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 3, 12)) +>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 3, 12)) + +}; + +declare const a: readonly string[] | string; +>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13)) + +declare const b: string[] | string; +>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13)) + +declare const c: unknown; +>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 8, 13)) + +if (MyArray.isArray(a)) { +>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24)) +>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13)) +>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24)) +>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13)) + + a; // readonly string[] +>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13)) +} +else { + a; // string +>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13)) +} +a; // readonly string[] | string; +>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13)) + +if (MyArray.isArray(b)) { +>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24)) +>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13)) +>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24)) +>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13)) + + b; // string[] | string; +>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13)) +} +else { + b; // string +>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13)) +} +b; // string[] | string; +>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13)) + +if (MyArray.isArray(c)) { +>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24)) +>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13)) +>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24)) +>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 8, 13)) + + c; // any[] +>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 8, 13)) +} + + +function f(x: T) { +>f : Symbol(f, Decl(consistentUnionSubtypeReduction.ts, 28, 1)) +>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11)) +>x : Symbol(x, Decl(consistentUnionSubtypeReduction.ts, 31, 14)) +>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11)) + + const a: readonly T[] | string = null!; +>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9)) +>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11)) + + const b: T[] | string = null!; +>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9)) +>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11)) + + const c: T = null!; +>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 34, 9)) +>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11)) + + if (MyArray.isArray(a)) { +>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24)) +>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13)) +>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24)) +>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9)) + + a; // readonly T[] +>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9)) + } + else { + a; // string +>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9)) + } + a; // readonly T[] | string; +>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9)) + + if (MyArray.isArray(b)) { +>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24)) +>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13)) +>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24)) +>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9)) + + b; // T[] +>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9)) + } + else { + b; // string +>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9)) + } + b; +>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9)) + + if (MyArray.isArray(c)) { +>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24)) +>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13)) +>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24)) +>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 34, 9)) + + c; // T & (T extends readonly any[] ? readonly any[] : any[]) +>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 34, 9)) + } +} + diff --git a/tests/baselines/reference/consistentUnionSubtypeReduction.types b/tests/baselines/reference/consistentUnionSubtypeReduction.types new file mode 100644 index 0000000000000..059fb51b16cda --- /dev/null +++ b/tests/baselines/reference/consistentUnionSubtypeReduction.types @@ -0,0 +1,132 @@ +=== tests/cases/compiler/consistentUnionSubtypeReduction.ts === +// https://github.com/microsoft/TypeScript/issues/31155 + +declare const MyArray: { +>MyArray : { isArray(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; } + + isArray(arg: T | {}): arg is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[]; +>isArray : (arg: T | {}) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] +>arg : {} | T + +}; + +declare const a: readonly string[] | string; +>a : string | readonly string[] + +declare const b: string[] | string; +>b : string | string[] + +declare const c: unknown; +>c : unknown + +if (MyArray.isArray(a)) { +>MyArray.isArray(a) : boolean +>MyArray.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] +>MyArray : { isArray(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; } +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] +>a : string | readonly string[] + + a; // readonly string[] +>a : readonly string[] +} +else { + a; // string +>a : string +} +a; // readonly string[] | string; +>a : string | readonly string[] + +if (MyArray.isArray(b)) { +>MyArray.isArray(b) : boolean +>MyArray.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] +>MyArray : { isArray(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; } +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] +>b : string | string[] + + b; // string[] | string; +>b : string[] +} +else { + b; // string +>b : string +} +b; // string[] | string; +>b : string | string[] + +if (MyArray.isArray(c)) { +>MyArray.isArray(c) : boolean +>MyArray.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] +>MyArray : { isArray(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; } +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] +>c : unknown + + c; // any[] +>c : any[] +} + + +function f(x: T) { +>f : (x: T) => void +>x : T + + const a: readonly T[] | string = null!; +>a : string | readonly T[] +>null! : null +>null : null + + const b: T[] | string = null!; +>b : string | T[] +>null! : null +>null : null + + const c: T = null!; +>c : T +>null! : null +>null : null + + if (MyArray.isArray(a)) { +>MyArray.isArray(a) : boolean +>MyArray.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] +>MyArray : { isArray(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; } +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] +>a : string | readonly T[] + + a; // readonly T[] +>a : readonly T[] + } + else { + a; // string +>a : string + } + a; // readonly T[] | string; +>a : string | readonly T[] + + if (MyArray.isArray(b)) { +>MyArray.isArray(b) : boolean +>MyArray.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] +>MyArray : { isArray(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; } +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] +>b : string | T[] + + b; // T[] +>b : T[] + } + else { + b; // string +>b : string + } + b; +>b : string | T[] + + if (MyArray.isArray(c)) { +>MyArray.isArray(c) : boolean +>MyArray.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] +>MyArray : { isArray(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; } +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] +>c : T + + c; // T & (T extends readonly any[] ? readonly any[] : any[]) +>c : T & (T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]) + } +} + diff --git a/tests/baselines/reference/declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.types b/tests/baselines/reference/declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.types index 7aff4276be765..2501ef2882d18 100644 --- a/tests/baselines/reference/declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.types +++ b/tests/baselines/reference/declarationsWithRecursiveInternalTypesProduceUniqueTypeParams.types @@ -67,9 +67,9 @@ export const updateIfChanged = (t: T) => { >assign : { (target: T, source: U): T & U; (target: T, source1: U, source2: V): T & U & V; (target: T, source1: U, source2: V, source3: W): T & U & V & W; (target: object, ...sources: any[]): any; } >Array.isArray(u) ? [] : {} : undefined[] | {} >Array.isArray(u) : boolean ->Array.isArray : (arg: any) => arg is any[] +>Array.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >Array : ArrayConstructor ->isArray : (arg: any) => arg is any[] +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >u : U >[] : undefined[] >{} : {} diff --git a/tests/baselines/reference/fixSignatureCaching.types b/tests/baselines/reference/fixSignatureCaching.types index cf367966c0260..8765e8fc310f4 100644 --- a/tests/baselines/reference/fixSignatureCaching.types +++ b/tests/baselines/reference/fixSignatureCaching.types @@ -1109,9 +1109,9 @@ define(function () { >Array : ArrayConstructor Array.isArray : function (value) { return Object.prototype.toString.call(value) === '[object Array]'; }; ->Array.isArray : (arg: any) => arg is any[] +>Array.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >Array : ArrayConstructor ->isArray : (arg: any) => arg is any[] +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >function (value) { return Object.prototype.toString.call(value) === '[object Array]'; } : (value: any) => boolean >value : any >Object.prototype.toString.call(value) === '[object Array]' : boolean diff --git a/tests/baselines/reference/instanceOfAssignability.types b/tests/baselines/reference/instanceOfAssignability.types index 4e65e644a3b54..a927b3bc7d87c 100644 --- a/tests/baselines/reference/instanceOfAssignability.types +++ b/tests/baselines/reference/instanceOfAssignability.types @@ -70,8 +70,8 @@ function fn2(x: Base) { // 1.5: y: Base // Want: y: Derived1 let y = x; ->y : Derived1 ->x : Derived1 +>y : Base & Derived1 +>x : Base & Derived1 } } @@ -104,8 +104,8 @@ function fn4(x: Base|Derived2) { // 1.5: y: {} // Want: Derived1 let y = x; ->y : Derived1 ->x : Derived1 +>y : (Base & Derived1) | (Derived2 & Derived1) +>x : (Base & Derived1) | (Derived2 & Derived1) } } diff --git a/tests/baselines/reference/isArray.types b/tests/baselines/reference/isArray.types index bc452b12bef01..2e70eca8fb188 100644 --- a/tests/baselines/reference/isArray.types +++ b/tests/baselines/reference/isArray.types @@ -5,9 +5,9 @@ var maybeArray: number | number[]; if (Array.isArray(maybeArray)) { >Array.isArray(maybeArray) : boolean ->Array.isArray : (arg: any) => arg is any[] +>Array.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >Array : ArrayConstructor ->isArray : (arg: any) => arg is any[] +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >maybeArray : number | number[] maybeArray.length; // OK diff --git a/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.types b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.types index 8fb03bc413f30..51ee436e6dd34 100644 --- a/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.types +++ b/tests/baselines/reference/literalFreshnessPropagationOnNarrowing.types @@ -66,9 +66,9 @@ function f2() { >a4 : string | false | (string | false)[] >Array.isArray(elOrA) ? elOrA : [elOrA] : (string | false)[] >Array.isArray(elOrA) : boolean ->Array.isArray : (arg: any) => arg is any[] +>Array.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >Array : ArrayConstructor ->isArray : (arg: any) => arg is any[] +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >elOrA : string | false | (string | false)[] >elOrA : (string | false)[] >[elOrA] : (string | false)[] @@ -83,9 +83,9 @@ function f2() { >...Array.isArray(elOrA) ? elOrA : [elOrA] : string | false >Array.isArray(elOrA) ? elOrA : [elOrA] : (string | false)[] >Array.isArray(elOrA) : boolean ->Array.isArray : (arg: any) => arg is any[] +>Array.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >Array : ArrayConstructor ->isArray : (arg: any) => arg is any[] +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >elOrA : string | false | (string | false)[] >elOrA : (string | false)[] >[elOrA] : (string | false)[] diff --git a/tests/baselines/reference/malformedTags.types b/tests/baselines/reference/malformedTags.types index c3af959586790..c5d63bfd3cb5a 100644 --- a/tests/baselines/reference/malformedTags.types +++ b/tests/baselines/reference/malformedTags.types @@ -6,7 +6,7 @@ */ var isArray = Array.isArray; >isArray : Function ->Array.isArray : (arg: any) => arg is any[] +>Array.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >Array : ArrayConstructor ->isArray : (arg: any) => arg is any[] +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] diff --git a/tests/baselines/reference/noIterationTypeErrorsInCFA.types b/tests/baselines/reference/noIterationTypeErrorsInCFA.types index 9d0122627ffae..a008015c7d756 100644 --- a/tests/baselines/reference/noIterationTypeErrorsInCFA.types +++ b/tests/baselines/reference/noIterationTypeErrorsInCFA.types @@ -10,9 +10,9 @@ export function doRemove(dds: F | F[]) { if (!Array.isArray(dds)) { >!Array.isArray(dds) : boolean >Array.isArray(dds) : boolean ->Array.isArray : (arg: any) => arg is any[] +>Array.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >Array : ArrayConstructor ->isArray : (arg: any) => arg is any[] +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >dds : F | F[] dds = [dds] diff --git a/tests/baselines/reference/parserharness.types b/tests/baselines/reference/parserharness.types index 4c047e08d9115..f4af8d0d64b8d 100644 --- a/tests/baselines/reference/parserharness.types +++ b/tests/baselines/reference/parserharness.types @@ -3068,13 +3068,13 @@ module Harness { >(Array.isArray && Array.isArray(arg)) || arg instanceof Array : boolean >(Array.isArray && Array.isArray(arg)) : boolean >Array.isArray && Array.isArray(arg) : boolean ->Array.isArray : (arg: any) => arg is any[] +>Array.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >Array : ArrayConstructor ->isArray : (arg: any) => arg is any[] +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >Array.isArray(arg) : boolean ->Array.isArray : (arg: any) => arg is any[] +>Array.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >Array : ArrayConstructor ->isArray : (arg: any) => arg is any[] +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >arg : any >arg instanceof Array : boolean >arg : any diff --git a/tests/baselines/reference/partiallyDiscriminantedUnions.types b/tests/baselines/reference/partiallyDiscriminantedUnions.types index f1d44249c0eb2..c9afb116ca3e6 100644 --- a/tests/baselines/reference/partiallyDiscriminantedUnions.types +++ b/tests/baselines/reference/partiallyDiscriminantedUnions.types @@ -77,9 +77,9 @@ function isShape(s : Shapes): s is Shape { return !Array.isArray(s); >!Array.isArray(s) : boolean >Array.isArray(s) : boolean ->Array.isArray : (arg: any) => arg is any[] +>Array.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >Array : ArrayConstructor ->isArray : (arg: any) => arg is any[] +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >s : Shapes } diff --git a/tests/baselines/reference/spreadBooleanRespectsFreshness.types b/tests/baselines/reference/spreadBooleanRespectsFreshness.types index cd20dc31944b6..195582d79391a 100644 --- a/tests/baselines/reference/spreadBooleanRespectsFreshness.types +++ b/tests/baselines/reference/spreadBooleanRespectsFreshness.types @@ -22,9 +22,9 @@ foo1 = [...Array.isArray(foo2) ? foo2 : [foo2]]; >...Array.isArray(foo2) ? foo2 : [foo2] : FooBase >Array.isArray(foo2) ? foo2 : [foo2] : FooArray >Array.isArray(foo2) : boolean ->Array.isArray : (arg: any) => arg is any[] +>Array.isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >Array : ArrayConstructor ->isArray : (arg: any) => arg is any[] +>isArray : (arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[] >foo2 : Foo >foo2 : FooArray >[foo2] : FooBase[] diff --git a/tests/baselines/reference/typeGuardIntersectionTypes.symbols b/tests/baselines/reference/typeGuardIntersectionTypes.symbols index 21da29e434fec..f99148a9f0500 100644 --- a/tests/baselines/reference/typeGuardIntersectionTypes.symbols +++ b/tests/baselines/reference/typeGuardIntersectionTypes.symbols @@ -176,17 +176,17 @@ function identifyBeast(beast: Beast) { >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) if (beast.legs === 4) { ->beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>beast.legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) ->legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) log(`pegasus - 4 legs, wings`); >log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 48, 1)) } else if (beast.legs === 2) { ->beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>beast.legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) ->legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) log(`bird - 2 legs, wings`); >log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 48, 1)) @@ -194,9 +194,9 @@ function identifyBeast(beast: Beast) { else { log(`unknown - ${beast.legs} legs, wings`); >log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 48, 1)) ->beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>beast.legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) ->legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) } } @@ -204,9 +204,9 @@ function identifyBeast(beast: Beast) { else { log(`manbearpig - ${beast.legs} legs, no wings`); >log : Symbol(log, Decl(typeGuardIntersectionTypes.ts, 48, 1)) ->beast.legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>beast.legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) >beast : Symbol(beast, Decl(typeGuardIntersectionTypes.ts, 64, 23)) ->legs : Symbol(Legged.legs, Decl(typeGuardIntersectionTypes.ts, 56, 21)) +>legs : Symbol(legs, Decl(typeGuardIntersectionTypes.ts, 55, 38), Decl(typeGuardIntersectionTypes.ts, 56, 21)) } } diff --git a/tests/baselines/reference/typeGuardIntersectionTypes.types b/tests/baselines/reference/typeGuardIntersectionTypes.types index a9f43b4917930..bf119d5743c6e 100644 --- a/tests/baselines/reference/typeGuardIntersectionTypes.types +++ b/tests/baselines/reference/typeGuardIntersectionTypes.types @@ -166,12 +166,12 @@ function identifyBeast(beast: Beast) { if (hasWings(beast)) { >hasWings(beast) : boolean >hasWings : (x: Beast) => x is Winged ->beast : Legged +>beast : Beast & Legged if (beast.legs === 4) { >beast.legs === 4 : boolean >beast.legs : number ->beast : Legged & Winged +>beast : Beast & Legged & Winged >legs : number >4 : 4 @@ -183,7 +183,7 @@ function identifyBeast(beast: Beast) { else if (beast.legs === 2) { >beast.legs === 2 : boolean >beast.legs : number ->beast : Legged & Winged +>beast : Beast & Legged & Winged >legs : number >2 : 2 @@ -198,7 +198,7 @@ function identifyBeast(beast: Beast) { >log : (s: string) => void >`unknown - ${beast.legs} legs, wings` : string >beast.legs : number ->beast : Legged & Winged +>beast : Beast & Legged & Winged >legs : number } } @@ -210,7 +210,7 @@ function identifyBeast(beast: Beast) { >log : (s: string) => void >`manbearpig - ${beast.legs} legs, no wings` : string >beast.legs : number ->beast : Legged +>beast : Beast & Legged >legs : number } } diff --git a/tests/baselines/reference/typeGuardsWithInstanceOf.errors.txt b/tests/baselines/reference/typeGuardsWithInstanceOf.errors.txt index 56a6973582f20..c6f44d4f15274 100644 --- a/tests/baselines/reference/typeGuardsWithInstanceOf.errors.txt +++ b/tests/baselines/reference/typeGuardsWithInstanceOf.errors.txt @@ -4,8 +4,8 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts(7,20) ==== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts (1 errors) ==== interface I { global: string; } - var result: I; - var result2: I; + var result!: I; + var result2!: I; if (!(result instanceof RegExp)) { result = result2; @@ -13,4 +13,32 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts(7,20) ~~~~~~ !!! error TS2339: Property 'global' does not exist on type 'never'. !!! error TS2339: The intersection 'I & RegExp' was reduced to 'never' because property 'global' has conflicting types in some constituents. - } \ No newline at end of file + } + + // Repro from #31155 + + interface OnChanges { + onChanges(changes: Record): void + } + interface Validator { + validate(): null | Record; + } + + class C { + validate() { + return {} + } + } + + function foo() { + let v: Validator & Partial = null as any; + if (v instanceof C) { + v // Validator & Partial & C + } + v // Validator & Partial via subtype reduction + if (v.onChanges) { + v.onChanges({}); + } + } + + \ No newline at end of file diff --git a/tests/baselines/reference/typeGuardsWithInstanceOf.js b/tests/baselines/reference/typeGuardsWithInstanceOf.js index 34af7037f0bd9..689712ad22e90 100644 --- a/tests/baselines/reference/typeGuardsWithInstanceOf.js +++ b/tests/baselines/reference/typeGuardsWithInstanceOf.js @@ -1,12 +1,40 @@ //// [typeGuardsWithInstanceOf.ts] interface I { global: string; } -var result: I; -var result2: I; +var result!: I; +var result2!: I; if (!(result instanceof RegExp)) { result = result2; } else if (!result.global) { -} +} + +// Repro from #31155 + +interface OnChanges { + onChanges(changes: Record): void +} +interface Validator { + validate(): null | Record; +} + +class C { + validate() { + return {} + } +} + +function foo() { + let v: Validator & Partial = null as any; + if (v instanceof C) { + v // Validator & Partial & C + } + v // Validator & Partial via subtype reduction + if (v.onChanges) { + v.onChanges({}); + } +} + + //// [typeGuardsWithInstanceOf.js] var result; @@ -16,3 +44,21 @@ if (!(result instanceof RegExp)) { } else if (!result.global) { } +var C = /** @class */ (function () { + function C() { + } + C.prototype.validate = function () { + return {}; + }; + return C; +}()); +function foo() { + var v = null; + if (v instanceof C) { + v; // Validator & Partial & C + } + v; // Validator & Partial via subtype reduction + if (v.onChanges) { + v.onChanges({}); + } +} diff --git a/tests/baselines/reference/typeGuardsWithInstanceOf.symbols b/tests/baselines/reference/typeGuardsWithInstanceOf.symbols index 431e8c5ae91be..018c6bac6da52 100644 --- a/tests/baselines/reference/typeGuardsWithInstanceOf.symbols +++ b/tests/baselines/reference/typeGuardsWithInstanceOf.symbols @@ -3,11 +3,11 @@ interface I { global: string; } >I : Symbol(I, Decl(typeGuardsWithInstanceOf.ts, 0, 0)) >global : Symbol(I.global, Decl(typeGuardsWithInstanceOf.ts, 0, 13)) -var result: I; +var result!: I; >result : Symbol(result, Decl(typeGuardsWithInstanceOf.ts, 1, 3)) >I : Symbol(I, Decl(typeGuardsWithInstanceOf.ts, 0, 0)) -var result2: I; +var result2!: I; >result2 : Symbol(result2, Decl(typeGuardsWithInstanceOf.ts, 2, 3)) >I : Symbol(I, Decl(typeGuardsWithInstanceOf.ts, 0, 0)) @@ -22,3 +22,64 @@ if (!(result instanceof RegExp)) { } else if (!result.global) { >result : Symbol(result, Decl(typeGuardsWithInstanceOf.ts, 1, 3)) } + +// Repro from #31155 + +interface OnChanges { +>OnChanges : Symbol(OnChanges, Decl(typeGuardsWithInstanceOf.ts, 7, 1)) + + onChanges(changes: Record): void +>onChanges : Symbol(OnChanges.onChanges, Decl(typeGuardsWithInstanceOf.ts, 11, 21)) +>changes : Symbol(changes, Decl(typeGuardsWithInstanceOf.ts, 12, 14)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +} +interface Validator { +>Validator : Symbol(Validator, Decl(typeGuardsWithInstanceOf.ts, 13, 1)) + + validate(): null | Record; +>validate : Symbol(Validator.validate, Decl(typeGuardsWithInstanceOf.ts, 14, 21)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +} + +class C { +>C : Symbol(C, Decl(typeGuardsWithInstanceOf.ts, 16, 1)) + + validate() { +>validate : Symbol(C.validate, Decl(typeGuardsWithInstanceOf.ts, 18, 9)) + + return {} + } +} + +function foo() { +>foo : Symbol(foo, Decl(typeGuardsWithInstanceOf.ts, 22, 1)) + + let v: Validator & Partial = null as any; +>v : Symbol(v, Decl(typeGuardsWithInstanceOf.ts, 25, 7)) +>Validator : Symbol(Validator, Decl(typeGuardsWithInstanceOf.ts, 13, 1)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>OnChanges : Symbol(OnChanges, Decl(typeGuardsWithInstanceOf.ts, 7, 1)) + + if (v instanceof C) { +>v : Symbol(v, Decl(typeGuardsWithInstanceOf.ts, 25, 7)) +>C : Symbol(C, Decl(typeGuardsWithInstanceOf.ts, 16, 1)) + + v // Validator & Partial & C +>v : Symbol(v, Decl(typeGuardsWithInstanceOf.ts, 25, 7)) + } + v // Validator & Partial via subtype reduction +>v : Symbol(v, Decl(typeGuardsWithInstanceOf.ts, 25, 7)) + + if (v.onChanges) { +>v.onChanges : Symbol(onChanges, Decl(typeGuardsWithInstanceOf.ts, 11, 21)) +>v : Symbol(v, Decl(typeGuardsWithInstanceOf.ts, 25, 7)) +>onChanges : Symbol(onChanges, Decl(typeGuardsWithInstanceOf.ts, 11, 21)) + + v.onChanges({}); +>v.onChanges : Symbol(onChanges, Decl(typeGuardsWithInstanceOf.ts, 11, 21)) +>v : Symbol(v, Decl(typeGuardsWithInstanceOf.ts, 25, 7)) +>onChanges : Symbol(onChanges, Decl(typeGuardsWithInstanceOf.ts, 11, 21)) + } +} + + diff --git a/tests/baselines/reference/typeGuardsWithInstanceOf.types b/tests/baselines/reference/typeGuardsWithInstanceOf.types index fbc7636d5fcbc..198769effc2c4 100644 --- a/tests/baselines/reference/typeGuardsWithInstanceOf.types +++ b/tests/baselines/reference/typeGuardsWithInstanceOf.types @@ -2,10 +2,10 @@ interface I { global: string; } >global : string -var result: I; +var result!: I; >result : I -var result2: I; +var result2!: I; >result2 : I if (!(result instanceof RegExp)) { @@ -26,3 +26,62 @@ if (!(result instanceof RegExp)) { >result : never >global : any } + +// Repro from #31155 + +interface OnChanges { + onChanges(changes: Record): void +>onChanges : (changes: Record) => void +>changes : Record +} +interface Validator { + validate(): null | Record; +>validate : () => null | Record +>null : null +} + +class C { +>C : C + + validate() { +>validate : () => {} + + return {} +>{} : {} + } +} + +function foo() { +>foo : () => void + + let v: Validator & Partial = null as any; +>v : Validator & Partial +>null as any : any +>null : null + + if (v instanceof C) { +>v instanceof C : boolean +>v : Validator & Partial +>C : typeof C + + v // Validator & Partial & C +>v : Validator & Partial & C + } + v // Validator & Partial via subtype reduction +>v : Validator & Partial + + if (v.onChanges) { +>v.onChanges : ((changes: Record) => void) | undefined +>v : Validator & Partial +>onChanges : ((changes: Record) => void) | undefined + + v.onChanges({}); +>v.onChanges({}) : void +>v.onChanges : (changes: Record) => void +>v : Validator & Partial +>onChanges : (changes: Record) => void +>{} : {} + } +} + + diff --git a/tests/cases/compiler/consistentUnionSubtypeReduction.ts b/tests/cases/compiler/consistentUnionSubtypeReduction.ts new file mode 100644 index 0000000000000..94fd44239a1c0 --- /dev/null +++ b/tests/cases/compiler/consistentUnionSubtypeReduction.ts @@ -0,0 +1,56 @@ +// https://github.com/microsoft/TypeScript/issues/31155 + +declare const MyArray: { + isArray(arg: T | {}): arg is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[]; +}; + +declare const a: readonly string[] | string; +declare const b: string[] | string; +declare const c: unknown; + +if (MyArray.isArray(a)) { + a; // readonly string[] +} +else { + a; // string +} +a; // readonly string[] | string; + +if (MyArray.isArray(b)) { + b; // string[] | string; +} +else { + b; // string +} +b; // string[] | string; + +if (MyArray.isArray(c)) { + c; // any[] +} + + +function f(x: T) { + const a: readonly T[] | string = null!; + const b: T[] | string = null!; + const c: T = null!; + + if (MyArray.isArray(a)) { + a; // readonly T[] + } + else { + a; // string + } + a; // readonly T[] | string; + + if (MyArray.isArray(b)) { + b; // T[] + } + else { + b; // string + } + b; + + if (MyArray.isArray(c)) { + c; // T & (T extends readonly any[] ? readonly any[] : any[]) + } +} diff --git a/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts b/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts index 2750eb96ebfbb..ec647d9bb7c9a 100644 --- a/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts +++ b/tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOf.ts @@ -1,8 +1,37 @@ -interface I { global: string; } -var result: I; -var result2: I; +// @strictNullChecks: true + +interface I { global: string; } +var result!: I; +var result2!: I; if (!(result instanceof RegExp)) { result = result2; } else if (!result.global) { -} \ No newline at end of file +} + +// Repro from #31155 + +interface OnChanges { + onChanges(changes: Record): void +} +interface Validator { + validate(): null | Record; +} + +class C { + validate() { + return {} + } +} + +function foo() { + let v: Validator & Partial = null as any; + if (v instanceof C) { + v // Validator & Partial & C + } + v // Validator & Partial via subtype reduction + if (v.onChanges) { + v.onChanges({}); + } +} +