Skip to content

Commit 3f1ec7a

Browse files
authored
Merge pull request #10216 from Microsoft/structurallyIdenticalInstanceof
Improve instanceof with structurally identical types
2 parents 3f6aa3f + fe1854e commit 3f1ec7a

7 files changed

+664
-13
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5924,6 +5924,13 @@ namespace ts {
59245924
return isTypeRelatedTo(source, target, assignableRelation);
59255925
}
59265926

5927+
// A type S is considered to be an instance of a type T if S and T are the same type or if S is a
5928+
// subtype of T but not structurally identical to T. This specifically means that two distinct but
5929+
// structurally identical types (such as two classes) are not considered instances of each other.
5930+
function isTypeInstanceOf(source: Type, target: Type): boolean {
5931+
return source === target || isTypeSubtypeOf(source, target) && !isTypeIdenticalTo(source, target);
5932+
}
5933+
59275934
/**
59285935
* This is *not* a bi-directional relationship.
59295936
* If one needs to check both directions for comparability, use a second call to this function or 'checkTypeComparableTo'.
@@ -8575,12 +8582,12 @@ namespace ts {
85758582

85768583
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean) {
85778584
if (!assumeTrue) {
8578-
return filterType(type, t => !isTypeSubtypeOf(t, candidate));
8585+
return filterType(type, t => !isTypeInstanceOf(t, candidate));
85798586
}
8580-
// If the current type is a union type, remove all constituents that aren't assignable to
8587+
// If the current type is a union type, remove all constituents that couldn't be instances of
85818588
// the candidate type. If one or more constituents remain, return a union of those.
85828589
if (type.flags & TypeFlags.Union) {
8583-
const assignableConstituents = filter((<UnionType>type).types, t => isTypeAssignableTo(t, candidate));
8590+
const assignableConstituents = filter((<UnionType>type).types, t => isTypeInstanceOf(t, candidate));
85848591
if (assignableConstituents.length) {
85858592
return getUnionType(assignableConstituents);
85868593
}

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(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33))
67+
>sourceObj.length : Symbol(NodeList.length, Decl(controlFlowBinaryOrExpression.ts, 10, 27))
6868
>sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3))
69-
>length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33))
69+
>length : Symbol(NodeList.length, Decl(controlFlowBinaryOrExpression.ts, 10, 27))
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(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33))
77+
>sourceObj.length : Symbol(HTMLCollection.length, Decl(controlFlowBinaryOrExpression.ts, 14, 33))
7878
>sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3))
79-
>length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33))
79+
>length : Symbol(HTMLCollection.length, 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(NodeList.length, Decl(controlFlowBinaryOrExpression.ts, 10, 27))
89+
>sourceObj.length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33))
9090
>sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3))
91-
>length : Symbol(NodeList.length, Decl(controlFlowBinaryOrExpression.ts, 10, 27))
91+
>length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33))
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 | HTMLCollection
83+
>sourceObj : NodeList
8484
>length : number
8585
}
8686

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

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

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

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

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
//// [instanceofWithStructurallyIdenticalTypes.ts]
2+
// Repro from #7271
3+
4+
class C1 { item: string }
5+
class C2 { item: string[] }
6+
class C3 { item: string }
7+
8+
function foo1(x: C1 | C2 | C3): string {
9+
if (x instanceof C1) {
10+
return x.item;
11+
}
12+
else if (x instanceof C2) {
13+
return x.item[0];
14+
}
15+
else if (x instanceof C3) {
16+
return x.item;
17+
}
18+
return "error";
19+
}
20+
21+
function isC1(c: C1 | C2 | C3): c is C1 { return c instanceof C1 }
22+
function isC2(c: C1 | C2 | C3): c is C2 { return c instanceof C2 }
23+
function isC3(c: C1 | C2 | C3): c is C3 { return c instanceof C3 }
24+
25+
function foo2(x: C1 | C2 | C3): string {
26+
if (isC1(x)) {
27+
return x.item;
28+
}
29+
else if (isC2(x)) {
30+
return x.item[0];
31+
}
32+
else if (isC3(x)) {
33+
return x.item;
34+
}
35+
return "error";
36+
}
37+
38+
// More tests
39+
40+
class A { a: string }
41+
class A1 extends A { }
42+
class A2 { a: string }
43+
class B extends A { b: string }
44+
45+
function goo(x: A) {
46+
if (x instanceof A) {
47+
x; // A
48+
}
49+
else {
50+
x; // never
51+
}
52+
if (x instanceof A1) {
53+
x; // A1
54+
}
55+
else {
56+
x; // A
57+
}
58+
if (x instanceof A2) {
59+
x; // A2
60+
}
61+
else {
62+
x; // A
63+
}
64+
if (x instanceof B) {
65+
x; // B
66+
}
67+
else {
68+
x; // A
69+
}
70+
}
71+
72+
73+
//// [instanceofWithStructurallyIdenticalTypes.js]
74+
// Repro from #7271
75+
var __extends = (this && this.__extends) || function (d, b) {
76+
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
77+
function __() { this.constructor = d; }
78+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
79+
};
80+
var C1 = (function () {
81+
function C1() {
82+
}
83+
return C1;
84+
}());
85+
var C2 = (function () {
86+
function C2() {
87+
}
88+
return C2;
89+
}());
90+
var C3 = (function () {
91+
function C3() {
92+
}
93+
return C3;
94+
}());
95+
function foo1(x) {
96+
if (x instanceof C1) {
97+
return x.item;
98+
}
99+
else if (x instanceof C2) {
100+
return x.item[0];
101+
}
102+
else if (x instanceof C3) {
103+
return x.item;
104+
}
105+
return "error";
106+
}
107+
function isC1(c) { return c instanceof C1; }
108+
function isC2(c) { return c instanceof C2; }
109+
function isC3(c) { return c instanceof C3; }
110+
function foo2(x) {
111+
if (isC1(x)) {
112+
return x.item;
113+
}
114+
else if (isC2(x)) {
115+
return x.item[0];
116+
}
117+
else if (isC3(x)) {
118+
return x.item;
119+
}
120+
return "error";
121+
}
122+
// More tests
123+
var A = (function () {
124+
function A() {
125+
}
126+
return A;
127+
}());
128+
var A1 = (function (_super) {
129+
__extends(A1, _super);
130+
function A1() {
131+
_super.apply(this, arguments);
132+
}
133+
return A1;
134+
}(A));
135+
var A2 = (function () {
136+
function A2() {
137+
}
138+
return A2;
139+
}());
140+
var B = (function (_super) {
141+
__extends(B, _super);
142+
function B() {
143+
_super.apply(this, arguments);
144+
}
145+
return B;
146+
}(A));
147+
function goo(x) {
148+
if (x instanceof A) {
149+
x; // A
150+
}
151+
else {
152+
x; // never
153+
}
154+
if (x instanceof A1) {
155+
x; // A1
156+
}
157+
else {
158+
x; // A
159+
}
160+
if (x instanceof A2) {
161+
x; // A2
162+
}
163+
else {
164+
x; // A
165+
}
166+
if (x instanceof B) {
167+
x; // B
168+
}
169+
else {
170+
x; // A
171+
}
172+
}

0 commit comments

Comments
 (0)