Skip to content

Commit fa89ce6

Browse files
author
Orta Therox
authored
Remove assignability cases in getNarrowedType + an isArray improvement for readonly arrays (#39258)
* Explore using a different isArray declaration * Add tests and the new isArray definition * Baseline updates * Upda the isArray type
1 parent 1d2278b commit fa89ce6

23 files changed

+700
-63
lines changed

src/compiler/checker.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21984,15 +21984,11 @@ namespace ts {
2198421984
return assignableType;
2198521985
}
2198621986
}
21987-
// If the candidate type is a subtype of the target type, narrow to the candidate type.
21988-
// Otherwise, if the target type is assignable to the candidate type, keep the target type.
21989-
// Otherwise, if the candidate type is assignable to the target type, narrow to the candidate
21990-
// type. Otherwise, the types are completely unrelated, so narrow to an intersection of the
21991-
// two types.
21992-
return isTypeSubtypeOf(candidate, type) ? candidate :
21993-
isTypeAssignableTo(type, candidate) ? type :
21994-
isTypeAssignableTo(candidate, type) ? candidate :
21995-
getIntersectionType([type, candidate]);
21987+
21988+
// If the candidate type is a subtype of the target type, narrow to the candidate type,
21989+
// if the target type is a subtype of the candidate type, narrow to the target type,
21990+
// otherwise, narrow to an intersection of the two types.
21991+
return isTypeSubtypeOf(candidate, type) ? candidate : isTypeSubtypeOf(type, candidate) ? type : getIntersectionType([type, candidate]);
2199621992
}
2199721993

2199821994
function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {

src/lib/es5.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1376,7 +1376,7 @@ interface ArrayConstructor {
13761376
(arrayLength?: number): any[];
13771377
<T>(arrayLength: number): T[];
13781378
<T>(...items: T[]): T[];
1379-
isArray(arg: any): arg is any[];
1379+
isArray<T>(arg: T | {}): arg is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[];
13801380
readonly prototype: any[];
13811381
}
13821382

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//// [consistentUnionSubtypeReduction.ts]
2+
// https://github.com/microsoft/TypeScript/issues/31155
3+
4+
declare const MyArray: {
5+
isArray<T>(arg: T | {}): arg is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[];
6+
};
7+
8+
declare const a: readonly string[] | string;
9+
declare const b: string[] | string;
10+
declare const c: unknown;
11+
12+
if (MyArray.isArray(a)) {
13+
a; // readonly string[]
14+
}
15+
else {
16+
a; // string
17+
}
18+
a; // readonly string[] | string;
19+
20+
if (MyArray.isArray(b)) {
21+
b; // string[] | string;
22+
}
23+
else {
24+
b; // string
25+
}
26+
b; // string[] | string;
27+
28+
if (MyArray.isArray(c)) {
29+
c; // any[]
30+
}
31+
32+
33+
function f<T>(x: T) {
34+
const a: readonly T[] | string = null!;
35+
const b: T[] | string = null!;
36+
const c: T = null!;
37+
38+
if (MyArray.isArray(a)) {
39+
a; // readonly T[]
40+
}
41+
else {
42+
a; // string
43+
}
44+
a; // readonly T[] | string;
45+
46+
if (MyArray.isArray(b)) {
47+
b; // T[]
48+
}
49+
else {
50+
b; // string
51+
}
52+
b;
53+
54+
if (MyArray.isArray(c)) {
55+
c; // T & (T extends readonly any[] ? readonly any[] : any[])
56+
}
57+
}
58+
59+
60+
//// [consistentUnionSubtypeReduction.js]
61+
// https://github.com/microsoft/TypeScript/issues/31155
62+
if (MyArray.isArray(a)) {
63+
a; // readonly string[]
64+
}
65+
else {
66+
a; // string
67+
}
68+
a; // readonly string[] | string;
69+
if (MyArray.isArray(b)) {
70+
b; // string[] | string;
71+
}
72+
else {
73+
b; // string
74+
}
75+
b; // string[] | string;
76+
if (MyArray.isArray(c)) {
77+
c; // any[]
78+
}
79+
function f(x) {
80+
var a = null;
81+
var b = null;
82+
var c = null;
83+
if (MyArray.isArray(a)) {
84+
a; // readonly T[]
85+
}
86+
else {
87+
a; // string
88+
}
89+
a; // readonly T[] | string;
90+
if (MyArray.isArray(b)) {
91+
b; // T[]
92+
}
93+
else {
94+
b; // string
95+
}
96+
b;
97+
if (MyArray.isArray(c)) {
98+
c; // T & (T extends readonly any[] ? readonly any[] : any[])
99+
}
100+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
=== tests/cases/compiler/consistentUnionSubtypeReduction.ts ===
2+
// https://github.com/microsoft/TypeScript/issues/31155
3+
4+
declare const MyArray: {
5+
>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13))
6+
7+
isArray<T>(arg: T | {}): arg is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[];
8+
>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
9+
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 3, 12))
10+
>arg : Symbol(arg, Decl(consistentUnionSubtypeReduction.ts, 3, 15))
11+
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 3, 12))
12+
>arg : Symbol(arg, Decl(consistentUnionSubtypeReduction.ts, 3, 15))
13+
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 3, 12))
14+
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 3, 12))
15+
16+
};
17+
18+
declare const a: readonly string[] | string;
19+
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13))
20+
21+
declare const b: string[] | string;
22+
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13))
23+
24+
declare const c: unknown;
25+
>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 8, 13))
26+
27+
if (MyArray.isArray(a)) {
28+
>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
29+
>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13))
30+
>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
31+
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13))
32+
33+
a; // readonly string[]
34+
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13))
35+
}
36+
else {
37+
a; // string
38+
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13))
39+
}
40+
a; // readonly string[] | string;
41+
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 6, 13))
42+
43+
if (MyArray.isArray(b)) {
44+
>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
45+
>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13))
46+
>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
47+
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13))
48+
49+
b; // string[] | string;
50+
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13))
51+
}
52+
else {
53+
b; // string
54+
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13))
55+
}
56+
b; // string[] | string;
57+
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 7, 13))
58+
59+
if (MyArray.isArray(c)) {
60+
>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
61+
>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13))
62+
>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
63+
>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 8, 13))
64+
65+
c; // any[]
66+
>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 8, 13))
67+
}
68+
69+
70+
function f<T>(x: T) {
71+
>f : Symbol(f, Decl(consistentUnionSubtypeReduction.ts, 28, 1))
72+
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11))
73+
>x : Symbol(x, Decl(consistentUnionSubtypeReduction.ts, 31, 14))
74+
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11))
75+
76+
const a: readonly T[] | string = null!;
77+
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9))
78+
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11))
79+
80+
const b: T[] | string = null!;
81+
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9))
82+
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11))
83+
84+
const c: T = null!;
85+
>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 34, 9))
86+
>T : Symbol(T, Decl(consistentUnionSubtypeReduction.ts, 31, 11))
87+
88+
if (MyArray.isArray(a)) {
89+
>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
90+
>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13))
91+
>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
92+
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9))
93+
94+
a; // readonly T[]
95+
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9))
96+
}
97+
else {
98+
a; // string
99+
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9))
100+
}
101+
a; // readonly T[] | string;
102+
>a : Symbol(a, Decl(consistentUnionSubtypeReduction.ts, 32, 9))
103+
104+
if (MyArray.isArray(b)) {
105+
>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
106+
>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13))
107+
>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
108+
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9))
109+
110+
b; // T[]
111+
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9))
112+
}
113+
else {
114+
b; // string
115+
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9))
116+
}
117+
b;
118+
>b : Symbol(b, Decl(consistentUnionSubtypeReduction.ts, 33, 9))
119+
120+
if (MyArray.isArray(c)) {
121+
>MyArray.isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
122+
>MyArray : Symbol(MyArray, Decl(consistentUnionSubtypeReduction.ts, 2, 13))
123+
>isArray : Symbol(isArray, Decl(consistentUnionSubtypeReduction.ts, 2, 24))
124+
>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 34, 9))
125+
126+
c; // T & (T extends readonly any[] ? readonly any[] : any[])
127+
>c : Symbol(c, Decl(consistentUnionSubtypeReduction.ts, 34, 9))
128+
}
129+
}
130+
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
=== tests/cases/compiler/consistentUnionSubtypeReduction.ts ===
2+
// https://github.com/microsoft/TypeScript/issues/31155
3+
4+
declare const MyArray: {
5+
>MyArray : { isArray<T>(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; }
6+
7+
isArray<T>(arg: T | {}): arg is T extends readonly any[] ? (unknown extends T ? never : readonly any[]) : any[];
8+
>isArray : <T>(arg: T | {}) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
9+
>arg : {} | T
10+
11+
};
12+
13+
declare const a: readonly string[] | string;
14+
>a : string | readonly string[]
15+
16+
declare const b: string[] | string;
17+
>b : string | string[]
18+
19+
declare const c: unknown;
20+
>c : unknown
21+
22+
if (MyArray.isArray(a)) {
23+
>MyArray.isArray(a) : boolean
24+
>MyArray.isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
25+
>MyArray : { isArray<T>(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; }
26+
>isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
27+
>a : string | readonly string[]
28+
29+
a; // readonly string[]
30+
>a : readonly string[]
31+
}
32+
else {
33+
a; // string
34+
>a : string
35+
}
36+
a; // readonly string[] | string;
37+
>a : string | readonly string[]
38+
39+
if (MyArray.isArray(b)) {
40+
>MyArray.isArray(b) : boolean
41+
>MyArray.isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
42+
>MyArray : { isArray<T>(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; }
43+
>isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
44+
>b : string | string[]
45+
46+
b; // string[] | string;
47+
>b : string[]
48+
}
49+
else {
50+
b; // string
51+
>b : string
52+
}
53+
b; // string[] | string;
54+
>b : string | string[]
55+
56+
if (MyArray.isArray(c)) {
57+
>MyArray.isArray(c) : boolean
58+
>MyArray.isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
59+
>MyArray : { isArray<T>(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; }
60+
>isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
61+
>c : unknown
62+
63+
c; // any[]
64+
>c : any[]
65+
}
66+
67+
68+
function f<T>(x: T) {
69+
>f : <T>(x: T) => void
70+
>x : T
71+
72+
const a: readonly T[] | string = null!;
73+
>a : string | readonly T[]
74+
>null! : null
75+
>null : null
76+
77+
const b: T[] | string = null!;
78+
>b : string | T[]
79+
>null! : null
80+
>null : null
81+
82+
const c: T = null!;
83+
>c : T
84+
>null! : null
85+
>null : null
86+
87+
if (MyArray.isArray(a)) {
88+
>MyArray.isArray(a) : boolean
89+
>MyArray.isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
90+
>MyArray : { isArray<T>(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; }
91+
>isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
92+
>a : string | readonly T[]
93+
94+
a; // readonly T[]
95+
>a : readonly T[]
96+
}
97+
else {
98+
a; // string
99+
>a : string
100+
}
101+
a; // readonly T[] | string;
102+
>a : string | readonly T[]
103+
104+
if (MyArray.isArray(b)) {
105+
>MyArray.isArray(b) : boolean
106+
>MyArray.isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
107+
>MyArray : { isArray<T>(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; }
108+
>isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
109+
>b : string | T[]
110+
111+
b; // T[]
112+
>b : T[]
113+
}
114+
else {
115+
b; // string
116+
>b : string
117+
}
118+
b;
119+
>b : string | T[]
120+
121+
if (MyArray.isArray(c)) {
122+
>MyArray.isArray(c) : boolean
123+
>MyArray.isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
124+
>MyArray : { isArray<T>(arg: {} | T): arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]; }
125+
>isArray : <T>(arg: {} | T) => arg is T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[]
126+
>c : T
127+
128+
c; // T & (T extends readonly any[] ? readonly any[] : any[])
129+
>c : T & (T extends readonly any[] ? unknown extends T ? never : readonly any[] : any[])
130+
}
131+
}
132+

0 commit comments

Comments
 (0)