Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 73ada7a

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

8 files changed

+365-16Lines changed: 365 additions & 16 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/compiler/checker.ts‎

Copy file name to clipboardExpand all lines: src/compiler/checker.ts
+6-6Lines 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;
Collapse file

‎tests/baselines/reference/controlFlowBinaryOrExpression.symbols‎

Copy file name to clipboardExpand all lines: tests/baselines/reference/controlFlowBinaryOrExpression.symbols
+6-6Lines 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

Collapse file

‎tests/baselines/reference/controlFlowBinaryOrExpression.types‎

Copy file name to clipboardExpand all lines: tests/baselines/reference/controlFlowBinaryOrExpression.types
+4-4Lines 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

Collapse file
+76Lines 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+
Collapse file
+46Lines 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+
}
Collapse file
+91Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.