Skip to content

Commit 511921e

Browse files
authored
Improve detection of cases where subtype reduction is unnecessary (#53435)
1 parent 37bafa5 commit 511921e

File tree

4 files changed

+128
-4
lines changed

4 files changed

+128
-4
lines changed

src/compiler/checker.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -25891,7 +25891,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2589125891
}
2589225892

2589325893
function isTypeSubsetOf(source: Type, target: Type) {
25894-
return source === target || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target as UnionType);
25894+
return !!(source === target || source.flags & TypeFlags.Never || target.flags & TypeFlags.Union && isTypeSubsetOfUnion(source, target as UnionType));
2589525895
}
2589625896

2589725897
function isTypeSubsetOfUnion(source: Type, target: UnionType) {
@@ -26715,7 +26715,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2671526715
// If an antecedent type is not a subset of the declared type, we need to perform
2671626716
// subtype reduction. This happens when a "foreign" type is injected into the control
2671726717
// flow using the instanceof operator or a user defined type predicate.
26718-
if (!isTypeSubsetOf(type, declaredType)) {
26718+
if (!isTypeSubsetOf(type, initialType)) {
2671926719
subtypeReduction = true;
2672026720
}
2672126721
if (isIncomplete(flowType)) {
@@ -26733,7 +26733,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2673326733
return type;
2673426734
}
2673526735
antecedentTypes.push(type);
26736-
if (!isTypeSubsetOf(type, declaredType)) {
26736+
if (!isTypeSubsetOf(type, initialType)) {
2673726737
subtypeReduction = true;
2673826738
}
2673926739
if (isIncomplete(flowType)) {
@@ -26808,7 +26808,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2680826808
// If an antecedent type is not a subset of the declared type, we need to perform
2680926809
// subtype reduction. This happens when a "foreign" type is injected into the control
2681026810
// flow using the instanceof operator or a user defined type predicate.
26811-
if (!isTypeSubsetOf(type, declaredType)) {
26811+
if (!isTypeSubsetOf(type, initialType)) {
2681226812
subtypeReduction = true;
2681326813
}
2681426814
// If the type at a particular antecedent path is the declared type there is no
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
=== tests/cases/compiler/noSubtypeReduction.ts ===
2+
// Repro from #53425
3+
4+
export interface IA {
5+
>IA : Symbol(IA, Decl(noSubtypeReduction.ts, 0, 0))
6+
7+
arr: { A: number; }[];
8+
>arr : Symbol(IA.arr, Decl(noSubtypeReduction.ts, 2, 21))
9+
>A : Symbol(A, Decl(noSubtypeReduction.ts, 3, 10))
10+
}
11+
12+
export interface IAB {
13+
>IAB : Symbol(IAB, Decl(noSubtypeReduction.ts, 4, 1))
14+
15+
arr: { A: number; B: number; }[];
16+
>arr : Symbol(IAB.arr, Decl(noSubtypeReduction.ts, 6, 22))
17+
>A : Symbol(A, Decl(noSubtypeReduction.ts, 7, 10))
18+
>B : Symbol(B, Decl(noSubtypeReduction.ts, 7, 21))
19+
}
20+
21+
export function F(x: IA | IAB) {
22+
>F : Symbol(F, Decl(noSubtypeReduction.ts, 8, 1))
23+
>x : Symbol(x, Decl(noSubtypeReduction.ts, 10, 18))
24+
>IA : Symbol(IA, Decl(noSubtypeReduction.ts, 0, 0))
25+
>IAB : Symbol(IAB, Decl(noSubtypeReduction.ts, 4, 1))
26+
27+
const useB = (t: number) => { };
28+
>useB : Symbol(useB, Decl(noSubtypeReduction.ts, 11, 9))
29+
>t : Symbol(t, Decl(noSubtypeReduction.ts, 11, 18))
30+
31+
for (const el of x.arr) {
32+
>el : Symbol(el, Decl(noSubtypeReduction.ts, 12, 14))
33+
>x.arr : Symbol(arr, Decl(noSubtypeReduction.ts, 2, 21), Decl(noSubtypeReduction.ts, 6, 22))
34+
>x : Symbol(x, Decl(noSubtypeReduction.ts, 10, 18))
35+
>arr : Symbol(arr, Decl(noSubtypeReduction.ts, 2, 21), Decl(noSubtypeReduction.ts, 6, 22))
36+
37+
if ('A' in el) { }
38+
>el : Symbol(el, Decl(noSubtypeReduction.ts, 12, 14))
39+
40+
if ('B' in el) {
41+
>el : Symbol(el, Decl(noSubtypeReduction.ts, 12, 14))
42+
43+
useB(el.B);
44+
>useB : Symbol(useB, Decl(noSubtypeReduction.ts, 11, 9))
45+
>el.B : Symbol(B, Decl(noSubtypeReduction.ts, 7, 21))
46+
>el : Symbol(el, Decl(noSubtypeReduction.ts, 12, 14))
47+
>B : Symbol(B, Decl(noSubtypeReduction.ts, 7, 21))
48+
}
49+
}
50+
}
51+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
=== tests/cases/compiler/noSubtypeReduction.ts ===
2+
// Repro from #53425
3+
4+
export interface IA {
5+
arr: { A: number; }[];
6+
>arr : { A: number; }[]
7+
>A : number
8+
}
9+
10+
export interface IAB {
11+
arr: { A: number; B: number; }[];
12+
>arr : { A: number; B: number; }[]
13+
>A : number
14+
>B : number
15+
}
16+
17+
export function F(x: IA | IAB) {
18+
>F : (x: IA | IAB) => void
19+
>x : IA | IAB
20+
21+
const useB = (t: number) => { };
22+
>useB : (t: number) => void
23+
>(t: number) => { } : (t: number) => void
24+
>t : number
25+
26+
for (const el of x.arr) {
27+
>el : { A: number; } | { A: number; B: number; }
28+
>x.arr : { A: number; }[] | { A: number; B: number; }[]
29+
>x : IA | IAB
30+
>arr : { A: number; }[] | { A: number; B: number; }[]
31+
32+
if ('A' in el) { }
33+
>'A' in el : boolean
34+
>'A' : "A"
35+
>el : { A: number; } | { A: number; B: number; }
36+
37+
if ('B' in el) {
38+
>'B' in el : boolean
39+
>'B' : "B"
40+
>el : { A: number; } | { A: number; B: number; }
41+
42+
useB(el.B);
43+
>useB(el.B) : void
44+
>useB : (t: number) => void
45+
>el.B : number
46+
>el : { A: number; B: number; }
47+
>B : number
48+
}
49+
}
50+
}
51+
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
// Repro from #53425
5+
6+
export interface IA {
7+
arr: { A: number; }[];
8+
}
9+
10+
export interface IAB {
11+
arr: { A: number; B: number; }[];
12+
}
13+
14+
export function F(x: IA | IAB) {
15+
const useB = (t: number) => { };
16+
for (const el of x.arr) {
17+
if ('A' in el) { }
18+
if ('B' in el) {
19+
useB(el.B);
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)