Skip to content

Commit 73ada7a

Browse files
authored
Merge pull request #12251 from Microsoft/fixTypePredicateStructuralMatch
Fix type predicates with structurally identical types
2 parents 0ba2348 + b6b4361 commit 73ada7a

8 files changed

+365
-16
lines changed

src/compiler/checker.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9741,20 +9741,20 @@ namespace ts {
97419741
}
97429742

97439743
if (targetType) {
9744-
return getNarrowedType(type, targetType, assumeTrue);
9744+
return getNarrowedType(type, targetType, assumeTrue, isTypeInstanceOf);
97459745
}
97469746

97479747
return type;
97489748
}
97499749

9750-
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean) {
9750+
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) {
97519751
if (!assumeTrue) {
9752-
return filterType(type, t => !isTypeInstanceOf(t, candidate));
9752+
return filterType(type, t => !isRelated(t, candidate));
97539753
}
97549754
// If the current type is a union type, remove all constituents that couldn't be instances of
97559755
// the candidate type. If one or more constituents remain, return a union of those.
97569756
if (type.flags & TypeFlags.Union) {
9757-
const assignableType = filterType(type, t => isTypeInstanceOf(t, candidate));
9757+
const assignableType = filterType(type, t => isRelated(t, candidate));
97589758
if (!(assignableType.flags & TypeFlags.Never)) {
97599759
return assignableType;
97609760
}
@@ -9790,7 +9790,7 @@ namespace ts {
97909790
const predicateArgument = callExpression.arguments[predicate.parameterIndex];
97919791
if (predicateArgument) {
97929792
if (isMatchingReference(reference, predicateArgument)) {
9793-
return getNarrowedType(type, predicate.type, assumeTrue);
9793+
return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf);
97949794
}
97959795
if (containsMatchingReference(reference, predicateArgument)) {
97969796
return declaredType;
@@ -9803,7 +9803,7 @@ namespace ts {
98039803
const accessExpression = invokedExpression as ElementAccessExpression | PropertyAccessExpression;
98049804
const possibleReference = skipParentheses(accessExpression.expression);
98059805
if (isMatchingReference(reference, possibleReference)) {
9806-
return getNarrowedType(type, predicate.type, assumeTrue);
9806+
return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf);
98079807
}
98089808
if (containsMatchingReference(reference, possibleReference)) {
98099809
return declaredType;

tests/baselines/reference/controlFlowBinaryOrExpression.symbols

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,19 @@ if (isNodeList(sourceObj)) {
6464
>sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3))
6565

6666
sourceObj.length;
67-
>sourceObj.length : Symbol(NodeList.length, Decl(controlFlowBinaryOrExpression.ts, 10, 27))
67+
>sourceObj.length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33))
6868
>sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3))
69-
>length : Symbol(NodeList.length, Decl(controlFlowBinaryOrExpression.ts, 10, 27))
69+
>length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33))
7070
}
7171

7272
if (isHTMLCollection(sourceObj)) {
7373
>isHTMLCollection : Symbol(isHTMLCollection, Decl(controlFlowBinaryOrExpression.ts, 18, 67))
7474
>sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3))
7575

7676
sourceObj.length;
77-
>sourceObj.length : Symbol(HTMLCollection.length, Decl(controlFlowBinaryOrExpression.ts, 14, 33))
77+
>sourceObj.length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33))
7878
>sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3))
79-
>length : Symbol(HTMLCollection.length, Decl(controlFlowBinaryOrExpression.ts, 14, 33))
79+
>length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33))
8080
}
8181

8282
if (isNodeList(sourceObj) || isHTMLCollection(sourceObj)) {
@@ -86,8 +86,8 @@ if (isNodeList(sourceObj) || isHTMLCollection(sourceObj)) {
8686
>sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3))
8787

8888
sourceObj.length;
89-
>sourceObj.length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33))
89+
>sourceObj.length : Symbol(NodeList.length, Decl(controlFlowBinaryOrExpression.ts, 10, 27))
9090
>sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3))
91-
>length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33))
91+
>length : Symbol(NodeList.length, Decl(controlFlowBinaryOrExpression.ts, 10, 27))
9292
}
9393

tests/baselines/reference/controlFlowBinaryOrExpression.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ if (isNodeList(sourceObj)) {
8080

8181
sourceObj.length;
8282
>sourceObj.length : number
83-
>sourceObj : NodeList
83+
>sourceObj : NodeList | HTMLCollection
8484
>length : number
8585
}
8686

@@ -91,7 +91,7 @@ if (isHTMLCollection(sourceObj)) {
9191

9292
sourceObj.length;
9393
>sourceObj.length : number
94-
>sourceObj : HTMLCollection
94+
>sourceObj : NodeList | HTMLCollection
9595
>length : number
9696
}
9797

@@ -102,11 +102,11 @@ if (isNodeList(sourceObj) || isHTMLCollection(sourceObj)) {
102102
>sourceObj : NodeList | HTMLCollection | { a: string; }
103103
>isHTMLCollection(sourceObj) : boolean
104104
>isHTMLCollection : (sourceObj: any) => sourceObj is HTMLCollection
105-
>sourceObj : HTMLCollection | { a: string; }
105+
>sourceObj : { a: string; }
106106

107107
sourceObj.length;
108108
>sourceObj.length : number
109-
>sourceObj : NodeList | HTMLCollection
109+
>sourceObj : NodeList
110110
>length : number
111111
}
112112

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
tests/cases/compiler/instanceofWithStructurallyIdenticalTypes.ts(32,18): error TS2339: Property 'item' does not exist on type 'never'.
2+
3+
4+
==== tests/cases/compiler/instanceofWithStructurallyIdenticalTypes.ts (1 errors) ====
5+
// Repro from #7271
6+
7+
class C1 { item: string }
8+
class C2 { item: string[] }
9+
class C3 { item: string }
10+
11+
function foo1(x: C1 | C2 | C3): string {
12+
if (x instanceof C1) {
13+
return x.item;
14+
}
15+
else if (x instanceof C2) {
16+
return x.item[0];
17+
}
18+
else if (x instanceof C3) {
19+
return x.item;
20+
}
21+
return "error";
22+
}
23+
24+
function isC1(c: C1 | C2 | C3): c is C1 { return c instanceof C1 }
25+
function isC2(c: C1 | C2 | C3): c is C2 { return c instanceof C2 }
26+
function isC3(c: C1 | C2 | C3): c is C3 { return c instanceof C3 }
27+
28+
function foo2(x: C1 | C2 | C3): string {
29+
if (isC1(x)) {
30+
return x.item;
31+
}
32+
else if (isC2(x)) {
33+
return x.item[0];
34+
}
35+
else if (isC3(x)) {
36+
return x.item;
37+
~~~~
38+
!!! error TS2339: Property 'item' does not exist on type 'never'.
39+
}
40+
return "error";
41+
}
42+
43+
// More tests
44+
45+
class A { a: string }
46+
class A1 extends A { }
47+
class A2 { a: string }
48+
class B extends A { b: string }
49+
50+
function goo(x: A) {
51+
if (x instanceof A) {
52+
x; // A
53+
}
54+
else {
55+
x; // never
56+
}
57+
if (x instanceof A1) {
58+
x; // A1
59+
}
60+
else {
61+
x; // A
62+
}
63+
if (x instanceof A2) {
64+
x; // A2
65+
}
66+
else {
67+
x; // A
68+
}
69+
if (x instanceof B) {
70+
x; // B
71+
}
72+
else {
73+
x; // A
74+
}
75+
}
76+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//// [typePredicateStructuralMatch.ts]
2+
// Repro from #12235
3+
4+
getResults1([]);
5+
getResults1({data: []});
6+
7+
getResults2([]);
8+
getResults2({data: []});
9+
10+
type Result = { value: string };
11+
type Results = Result[];
12+
13+
function isResponseInData<T>(value: T | { data: T}): value is { data: T } {
14+
return value.hasOwnProperty('data');
15+
}
16+
17+
function getResults1(value: Results | { data: Results }): Results {
18+
return isResponseInData(value) ? value.data : value;
19+
}
20+
21+
function isPlainResponse<T>(value: T | { data: T}): value is T {
22+
return !value.hasOwnProperty('data');
23+
}
24+
25+
function getResults2(value: Results | { data: Results }): Results {
26+
return isPlainResponse(value) ? value : value.data;
27+
}
28+
29+
//// [typePredicateStructuralMatch.js]
30+
// Repro from #12235
31+
getResults1([]);
32+
getResults1({ data: [] });
33+
getResults2([]);
34+
getResults2({ data: [] });
35+
function isResponseInData(value) {
36+
return value.hasOwnProperty('data');
37+
}
38+
function getResults1(value) {
39+
return isResponseInData(value) ? value.data : value;
40+
}
41+
function isPlainResponse(value) {
42+
return !value.hasOwnProperty('data');
43+
}
44+
function getResults2(value) {
45+
return isPlainResponse(value) ? value : value.data;
46+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
=== tests/cases/compiler/typePredicateStructuralMatch.ts ===
2+
// Repro from #12235
3+
4+
getResults1([]);
5+
>getResults1 : Symbol(getResults1, Decl(typePredicateStructuralMatch.ts, 13, 1))
6+
7+
getResults1({data: []});
8+
>getResults1 : Symbol(getResults1, Decl(typePredicateStructuralMatch.ts, 13, 1))
9+
>data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 3, 13))
10+
11+
getResults2([]);
12+
>getResults2 : Symbol(getResults2, Decl(typePredicateStructuralMatch.ts, 21, 1))
13+
14+
getResults2({data: []});
15+
>getResults2 : Symbol(getResults2, Decl(typePredicateStructuralMatch.ts, 21, 1))
16+
>data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 6, 13))
17+
18+
type Result = { value: string };
19+
>Result : Symbol(Result, Decl(typePredicateStructuralMatch.ts, 6, 24))
20+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 8, 15))
21+
22+
type Results = Result[];
23+
>Results : Symbol(Results, Decl(typePredicateStructuralMatch.ts, 8, 32))
24+
>Result : Symbol(Result, Decl(typePredicateStructuralMatch.ts, 6, 24))
25+
26+
function isResponseInData<T>(value: T | { data: T}): value is { data: T } {
27+
>isResponseInData : Symbol(isResponseInData, Decl(typePredicateStructuralMatch.ts, 9, 24))
28+
>T : Symbol(T, Decl(typePredicateStructuralMatch.ts, 11, 26))
29+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 11, 29))
30+
>T : Symbol(T, Decl(typePredicateStructuralMatch.ts, 11, 26))
31+
>data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 11, 41))
32+
>T : Symbol(T, Decl(typePredicateStructuralMatch.ts, 11, 26))
33+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 11, 29))
34+
>data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 11, 63))
35+
>T : Symbol(T, Decl(typePredicateStructuralMatch.ts, 11, 26))
36+
37+
return value.hasOwnProperty('data');
38+
>value.hasOwnProperty : Symbol(Object.hasOwnProperty, Decl(lib.d.ts, --, --))
39+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 11, 29))
40+
>hasOwnProperty : Symbol(Object.hasOwnProperty, Decl(lib.d.ts, --, --))
41+
}
42+
43+
function getResults1(value: Results | { data: Results }): Results {
44+
>getResults1 : Symbol(getResults1, Decl(typePredicateStructuralMatch.ts, 13, 1))
45+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 15, 21))
46+
>Results : Symbol(Results, Decl(typePredicateStructuralMatch.ts, 8, 32))
47+
>data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 15, 39))
48+
>Results : Symbol(Results, Decl(typePredicateStructuralMatch.ts, 8, 32))
49+
>Results : Symbol(Results, Decl(typePredicateStructuralMatch.ts, 8, 32))
50+
51+
return isResponseInData(value) ? value.data : value;
52+
>isResponseInData : Symbol(isResponseInData, Decl(typePredicateStructuralMatch.ts, 9, 24))
53+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 15, 21))
54+
>value.data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 15, 39))
55+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 15, 21))
56+
>data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 15, 39))
57+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 15, 21))
58+
}
59+
60+
function isPlainResponse<T>(value: T | { data: T}): value is T {
61+
>isPlainResponse : Symbol(isPlainResponse, Decl(typePredicateStructuralMatch.ts, 17, 1))
62+
>T : Symbol(T, Decl(typePredicateStructuralMatch.ts, 19, 25))
63+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 19, 28))
64+
>T : Symbol(T, Decl(typePredicateStructuralMatch.ts, 19, 25))
65+
>data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 19, 40))
66+
>T : Symbol(T, Decl(typePredicateStructuralMatch.ts, 19, 25))
67+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 19, 28))
68+
>T : Symbol(T, Decl(typePredicateStructuralMatch.ts, 19, 25))
69+
70+
return !value.hasOwnProperty('data');
71+
>value.hasOwnProperty : Symbol(Object.hasOwnProperty, Decl(lib.d.ts, --, --))
72+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 19, 28))
73+
>hasOwnProperty : Symbol(Object.hasOwnProperty, Decl(lib.d.ts, --, --))
74+
}
75+
76+
function getResults2(value: Results | { data: Results }): Results {
77+
>getResults2 : Symbol(getResults2, Decl(typePredicateStructuralMatch.ts, 21, 1))
78+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 23, 21))
79+
>Results : Symbol(Results, Decl(typePredicateStructuralMatch.ts, 8, 32))
80+
>data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 23, 39))
81+
>Results : Symbol(Results, Decl(typePredicateStructuralMatch.ts, 8, 32))
82+
>Results : Symbol(Results, Decl(typePredicateStructuralMatch.ts, 8, 32))
83+
84+
return isPlainResponse(value) ? value : value.data;
85+
>isPlainResponse : Symbol(isPlainResponse, Decl(typePredicateStructuralMatch.ts, 17, 1))
86+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 23, 21))
87+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 23, 21))
88+
>value.data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 23, 39))
89+
>value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 23, 21))
90+
>data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 23, 39))
91+
}

0 commit comments

Comments
 (0)