diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6c0e83eb339b0..800f7e1c8ea83 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -25864,7 +25864,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function isTypeSubsetOf(source: Type, target: Type) { - return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target as UnionType); + return !!(source === target || source.flags & TypeFlags.Never || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target as UnionType)); } function isTypeSubsetOfUnion(source: Type, target: UnionType) { @@ -26688,7 +26688,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // If an antecedent type is not a subset of the declared type, we need to perform // subtype reduction. This happens when a "foreign" type is injected into the control // flow using the instanceof operator or a user defined type predicate. - if (!isTypeSubsetOf(type, declaredType)) { + if (!isTypeSubsetOf(type, initialType)) { subtypeReduction = true; } if (isIncomplete(flowType)) { @@ -26706,7 +26706,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; } antecedentTypes.push(type); - if (!isTypeSubsetOf(type, declaredType)) { + if (!isTypeSubsetOf(type, initialType)) { subtypeReduction = true; } if (isIncomplete(flowType)) { @@ -26781,7 +26781,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // If an antecedent type is not a subset of the declared type, we need to perform // subtype reduction. This happens when a "foreign" type is injected into the control // flow using the instanceof operator or a user defined type predicate. - if (!isTypeSubsetOf(type, declaredType)) { + if (!isTypeSubsetOf(type, initialType)) { subtypeReduction = true; } // If the type at a particular antecedent path is the declared type there is no diff --git a/tests/baselines/reference/noSubtypeReduction.symbols b/tests/baselines/reference/noSubtypeReduction.symbols new file mode 100644 index 0000000000000..d1190531567f2 --- /dev/null +++ b/tests/baselines/reference/noSubtypeReduction.symbols @@ -0,0 +1,51 @@ +=== tests/cases/compiler/noSubtypeReduction.ts === +// Repro from #53425 + +export interface IA { +>IA : Symbol(IA, Decl(noSubtypeReduction.ts, 0, 0)) + + arr: { A: number; }[]; +>arr : Symbol(IA.arr, Decl(noSubtypeReduction.ts, 2, 21)) +>A : Symbol(A, Decl(noSubtypeReduction.ts, 3, 10)) +} + +export interface IAB { +>IAB : Symbol(IAB, Decl(noSubtypeReduction.ts, 4, 1)) + + arr: { A: number; B: number; }[]; +>arr : Symbol(IAB.arr, Decl(noSubtypeReduction.ts, 6, 22)) +>A : Symbol(A, Decl(noSubtypeReduction.ts, 7, 10)) +>B : Symbol(B, Decl(noSubtypeReduction.ts, 7, 21)) +} + +export function F(x: IA | IAB) { +>F : Symbol(F, Decl(noSubtypeReduction.ts, 8, 1)) +>x : Symbol(x, Decl(noSubtypeReduction.ts, 10, 18)) +>IA : Symbol(IA, Decl(noSubtypeReduction.ts, 0, 0)) +>IAB : Symbol(IAB, Decl(noSubtypeReduction.ts, 4, 1)) + + const useB = (t: number) => { }; +>useB : Symbol(useB, Decl(noSubtypeReduction.ts, 11, 9)) +>t : Symbol(t, Decl(noSubtypeReduction.ts, 11, 18)) + + for (const el of x.arr) { +>el : Symbol(el, Decl(noSubtypeReduction.ts, 12, 14)) +>x.arr : Symbol(arr, Decl(noSubtypeReduction.ts, 2, 21), Decl(noSubtypeReduction.ts, 6, 22)) +>x : Symbol(x, Decl(noSubtypeReduction.ts, 10, 18)) +>arr : Symbol(arr, Decl(noSubtypeReduction.ts, 2, 21), Decl(noSubtypeReduction.ts, 6, 22)) + + if ('A' in el) { } +>el : Symbol(el, Decl(noSubtypeReduction.ts, 12, 14)) + + if ('B' in el) { +>el : Symbol(el, Decl(noSubtypeReduction.ts, 12, 14)) + + useB(el.B); +>useB : Symbol(useB, Decl(noSubtypeReduction.ts, 11, 9)) +>el.B : Symbol(B, Decl(noSubtypeReduction.ts, 7, 21)) +>el : Symbol(el, Decl(noSubtypeReduction.ts, 12, 14)) +>B : Symbol(B, Decl(noSubtypeReduction.ts, 7, 21)) + } + } +} + diff --git a/tests/baselines/reference/noSubtypeReduction.types b/tests/baselines/reference/noSubtypeReduction.types new file mode 100644 index 0000000000000..c8b51d7c01c9c --- /dev/null +++ b/tests/baselines/reference/noSubtypeReduction.types @@ -0,0 +1,51 @@ +=== tests/cases/compiler/noSubtypeReduction.ts === +// Repro from #53425 + +export interface IA { + arr: { A: number; }[]; +>arr : { A: number; }[] +>A : number +} + +export interface IAB { + arr: { A: number; B: number; }[]; +>arr : { A: number; B: number; }[] +>A : number +>B : number +} + +export function F(x: IA | IAB) { +>F : (x: IA | IAB) => void +>x : IA | IAB + + const useB = (t: number) => { }; +>useB : (t: number) => void +>(t: number) => { } : (t: number) => void +>t : number + + for (const el of x.arr) { +>el : { A: number; } | { A: number; B: number; } +>x.arr : { A: number; }[] | { A: number; B: number; }[] +>x : IA | IAB +>arr : { A: number; }[] | { A: number; B: number; }[] + + if ('A' in el) { } +>'A' in el : boolean +>'A' : "A" +>el : { A: number; } | { A: number; B: number; } + + if ('B' in el) { +>'B' in el : boolean +>'B' : "B" +>el : { A: number; } | { A: number; B: number; } + + useB(el.B); +>useB(el.B) : void +>useB : (t: number) => void +>el.B : number +>el : { A: number; B: number; } +>B : number + } + } +} + diff --git a/tests/cases/compiler/noSubtypeReduction.ts b/tests/cases/compiler/noSubtypeReduction.ts new file mode 100644 index 0000000000000..cca87bb435800 --- /dev/null +++ b/tests/cases/compiler/noSubtypeReduction.ts @@ -0,0 +1,22 @@ +// @strict: true +// @noEmit: true + +// Repro from #53425 + +export interface IA { + arr: { A: number; }[]; +} + +export interface IAB { + arr: { A: number; B: number; }[]; +} + +export function F(x: IA | IAB) { + const useB = (t: number) => { }; + for (const el of x.arr) { + if ('A' in el) { } + if ('B' in el) { + useB(el.B); + } + } +}