Skip to content

Commit b40849b

Browse files
committed
Merge pull request microsoft#3064 from Microsoft/refactorNarrowTypeByInstanceOf
Refactor narrow type by instance of
2 parents dd6c87e + 69f94cd commit b40849b

8 files changed

+295
-39
lines changed

src/compiler/checker.ts

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5384,38 +5384,43 @@ module ts {
53845384
if (!isTypeSubtypeOf(rightType, globalFunctionType)) {
53855385
return type;
53865386
}
5387-
// Target type is type of prototype property
5387+
5388+
let targetType: Type;
53885389
let prototypeProperty = getPropertyOfType(rightType, "prototype");
53895390
if (prototypeProperty) {
5390-
let targetType = getTypeOfSymbol(prototypeProperty);
5391-
if (targetType !== anyType) {
5392-
// Narrow to the target type if it's a subtype of the current type
5393-
if (isTypeSubtypeOf(targetType, type)) {
5394-
return targetType;
5395-
}
5396-
// If the current type is a union type, remove all constituents that aren't subtypes of the target.
5397-
if (type.flags & TypeFlags.Union) {
5398-
return getUnionType(filter((<UnionType>type).types, t => isTypeSubtypeOf(t, targetType)));
5399-
}
5391+
// Target type is type of the protoype property
5392+
let prototypePropertyType = getTypeOfSymbol(prototypeProperty);
5393+
if (prototypePropertyType !== anyType) {
5394+
targetType = prototypePropertyType;
54005395
}
54015396
}
5402-
// Target type is type of construct signature
5403-
let constructSignatures: Signature[];
5404-
if (rightType.flags & TypeFlags.Interface) {
5405-
constructSignatures = resolveDeclaredMembers(<InterfaceType>rightType).declaredConstructSignatures;
5406-
}
5407-
else if (rightType.flags & TypeFlags.Anonymous) {
5408-
constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct);
5397+
5398+
if (!targetType) {
5399+
// Target type is type of construct signature
5400+
let constructSignatures: Signature[];
5401+
if (rightType.flags & TypeFlags.Interface) {
5402+
constructSignatures = resolveDeclaredMembers(<InterfaceType>rightType).declaredConstructSignatures;
5403+
}
5404+
else if (rightType.flags & TypeFlags.Anonymous) {
5405+
constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct);
5406+
}
5407+
5408+
if (constructSignatures && constructSignatures.length) {
5409+
targetType = getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature))));
5410+
}
54095411
}
54105412

5411-
if (constructSignatures && constructSignatures.length !== 0) {
5412-
let instanceType = getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature))));
5413-
// Pickup type from union types
5413+
if (targetType) {
5414+
// Narrow to the target type if it's a subtype of the current type
5415+
if (isTypeSubtypeOf(targetType, type)) {
5416+
return targetType;
5417+
}
5418+
// If the current type is a union type, remove all constituents that aren't subtypes of the target.
54145419
if (type.flags & TypeFlags.Union) {
5415-
return getUnionType(filter((<UnionType>type).types, t => isTypeSubtypeOf(t, instanceType)));
5420+
return getUnionType(filter((<UnionType>type).types, t => isTypeSubtypeOf(t, targetType)));
54165421
}
5417-
return instanceType;
54185422
}
5423+
54195424
return type;
54205425
}
54215426

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//// [narrowTypeByInstanceof.ts]
2+
class Match {
3+
public range(): any {
4+
return undefined;
5+
}
6+
}
7+
8+
class FileMatch {
9+
public resource(): any {
10+
return undefined;
11+
}
12+
}
13+
14+
type FileMatchOrMatch = FileMatch | Match;
15+
16+
17+
let elementA: FileMatchOrMatch, elementB: FileMatchOrMatch;
18+
19+
if (elementA instanceof FileMatch && elementB instanceof FileMatch) {
20+
let a = elementA.resource().path;
21+
let b = elementB.resource().path;
22+
} else if (elementA instanceof Match && elementB instanceof Match) {
23+
let a = elementA.range();
24+
let b = elementB.range();
25+
}
26+
27+
28+
//// [narrowTypeByInstanceof.js]
29+
var Match = (function () {
30+
function Match() {
31+
}
32+
Match.prototype.range = function () {
33+
return undefined;
34+
};
35+
return Match;
36+
})();
37+
var FileMatch = (function () {
38+
function FileMatch() {
39+
}
40+
FileMatch.prototype.resource = function () {
41+
return undefined;
42+
};
43+
return FileMatch;
44+
})();
45+
var elementA, elementB;
46+
if (elementA instanceof FileMatch && elementB instanceof FileMatch) {
47+
var a = elementA.resource().path;
48+
var b = elementB.resource().path;
49+
}
50+
else if (elementA instanceof Match && elementB instanceof Match) {
51+
var a = elementA.range();
52+
var b = elementB.range();
53+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
=== tests/cases/compiler/narrowTypeByInstanceof.ts ===
2+
class Match {
3+
>Match : Symbol(Match, Decl(narrowTypeByInstanceof.ts, 0, 0))
4+
5+
public range(): any {
6+
>range : Symbol(range, Decl(narrowTypeByInstanceof.ts, 0, 17))
7+
8+
return undefined;
9+
>undefined : Symbol(undefined)
10+
}
11+
}
12+
13+
class FileMatch {
14+
>FileMatch : Symbol(FileMatch, Decl(narrowTypeByInstanceof.ts, 4, 5))
15+
16+
public resource(): any {
17+
>resource : Symbol(resource, Decl(narrowTypeByInstanceof.ts, 6, 21))
18+
19+
return undefined;
20+
>undefined : Symbol(undefined)
21+
}
22+
}
23+
24+
type FileMatchOrMatch = FileMatch | Match;
25+
>FileMatchOrMatch : Symbol(FileMatchOrMatch, Decl(narrowTypeByInstanceof.ts, 10, 5))
26+
>FileMatch : Symbol(FileMatch, Decl(narrowTypeByInstanceof.ts, 4, 5))
27+
>Match : Symbol(Match, Decl(narrowTypeByInstanceof.ts, 0, 0))
28+
29+
30+
let elementA: FileMatchOrMatch, elementB: FileMatchOrMatch;
31+
>elementA : Symbol(elementA, Decl(narrowTypeByInstanceof.ts, 15, 3))
32+
>FileMatchOrMatch : Symbol(FileMatchOrMatch, Decl(narrowTypeByInstanceof.ts, 10, 5))
33+
>elementB : Symbol(elementB, Decl(narrowTypeByInstanceof.ts, 15, 31))
34+
>FileMatchOrMatch : Symbol(FileMatchOrMatch, Decl(narrowTypeByInstanceof.ts, 10, 5))
35+
36+
if (elementA instanceof FileMatch && elementB instanceof FileMatch) {
37+
>elementA : Symbol(elementA, Decl(narrowTypeByInstanceof.ts, 15, 3))
38+
>FileMatch : Symbol(FileMatch, Decl(narrowTypeByInstanceof.ts, 4, 5))
39+
>elementB : Symbol(elementB, Decl(narrowTypeByInstanceof.ts, 15, 31))
40+
>FileMatch : Symbol(FileMatch, Decl(narrowTypeByInstanceof.ts, 4, 5))
41+
42+
let a = elementA.resource().path;
43+
>a : Symbol(a, Decl(narrowTypeByInstanceof.ts, 18, 7))
44+
>elementA.resource : Symbol(FileMatch.resource, Decl(narrowTypeByInstanceof.ts, 6, 21))
45+
>elementA : Symbol(elementA, Decl(narrowTypeByInstanceof.ts, 15, 3))
46+
>resource : Symbol(FileMatch.resource, Decl(narrowTypeByInstanceof.ts, 6, 21))
47+
48+
let b = elementB.resource().path;
49+
>b : Symbol(b, Decl(narrowTypeByInstanceof.ts, 19, 7))
50+
>elementB.resource : Symbol(FileMatch.resource, Decl(narrowTypeByInstanceof.ts, 6, 21))
51+
>elementB : Symbol(elementB, Decl(narrowTypeByInstanceof.ts, 15, 31))
52+
>resource : Symbol(FileMatch.resource, Decl(narrowTypeByInstanceof.ts, 6, 21))
53+
54+
} else if (elementA instanceof Match && elementB instanceof Match) {
55+
>elementA : Symbol(elementA, Decl(narrowTypeByInstanceof.ts, 15, 3))
56+
>Match : Symbol(Match, Decl(narrowTypeByInstanceof.ts, 0, 0))
57+
>elementB : Symbol(elementB, Decl(narrowTypeByInstanceof.ts, 15, 31))
58+
>Match : Symbol(Match, Decl(narrowTypeByInstanceof.ts, 0, 0))
59+
60+
let a = elementA.range();
61+
>a : Symbol(a, Decl(narrowTypeByInstanceof.ts, 21, 7))
62+
>elementA.range : Symbol(Match.range, Decl(narrowTypeByInstanceof.ts, 0, 17))
63+
>elementA : Symbol(elementA, Decl(narrowTypeByInstanceof.ts, 15, 3))
64+
>range : Symbol(Match.range, Decl(narrowTypeByInstanceof.ts, 0, 17))
65+
66+
let b = elementB.range();
67+
>b : Symbol(b, Decl(narrowTypeByInstanceof.ts, 22, 7))
68+
>elementB.range : Symbol(Match.range, Decl(narrowTypeByInstanceof.ts, 0, 17))
69+
>elementB : Symbol(elementB, Decl(narrowTypeByInstanceof.ts, 15, 31))
70+
>range : Symbol(Match.range, Decl(narrowTypeByInstanceof.ts, 0, 17))
71+
}
72+
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
=== tests/cases/compiler/narrowTypeByInstanceof.ts ===
2+
class Match {
3+
>Match : Match
4+
5+
public range(): any {
6+
>range : () => any
7+
8+
return undefined;
9+
>undefined : undefined
10+
}
11+
}
12+
13+
class FileMatch {
14+
>FileMatch : FileMatch
15+
16+
public resource(): any {
17+
>resource : () => any
18+
19+
return undefined;
20+
>undefined : undefined
21+
}
22+
}
23+
24+
type FileMatchOrMatch = FileMatch | Match;
25+
>FileMatchOrMatch : Match | FileMatch
26+
>FileMatch : FileMatch
27+
>Match : Match
28+
29+
30+
let elementA: FileMatchOrMatch, elementB: FileMatchOrMatch;
31+
>elementA : Match | FileMatch
32+
>FileMatchOrMatch : Match | FileMatch
33+
>elementB : Match | FileMatch
34+
>FileMatchOrMatch : Match | FileMatch
35+
36+
if (elementA instanceof FileMatch && elementB instanceof FileMatch) {
37+
>elementA instanceof FileMatch && elementB instanceof FileMatch : boolean
38+
>elementA instanceof FileMatch : boolean
39+
>elementA : Match | FileMatch
40+
>FileMatch : typeof FileMatch
41+
>elementB instanceof FileMatch : boolean
42+
>elementB : Match | FileMatch
43+
>FileMatch : typeof FileMatch
44+
45+
let a = elementA.resource().path;
46+
>a : any
47+
>elementA.resource().path : any
48+
>elementA.resource() : any
49+
>elementA.resource : () => any
50+
>elementA : FileMatch
51+
>resource : () => any
52+
>path : any
53+
54+
let b = elementB.resource().path;
55+
>b : any
56+
>elementB.resource().path : any
57+
>elementB.resource() : any
58+
>elementB.resource : () => any
59+
>elementB : FileMatch
60+
>resource : () => any
61+
>path : any
62+
63+
} else if (elementA instanceof Match && elementB instanceof Match) {
64+
>elementA instanceof Match && elementB instanceof Match : boolean
65+
>elementA instanceof Match : boolean
66+
>elementA : Match | FileMatch
67+
>Match : typeof Match
68+
>elementB instanceof Match : boolean
69+
>elementB : Match | FileMatch
70+
>Match : typeof Match
71+
72+
let a = elementA.range();
73+
>a : any
74+
>elementA.range() : any
75+
>elementA.range : () => any
76+
>elementA : Match
77+
>range : () => any
78+
79+
let b = elementB.range();
80+
>b : any
81+
>elementB.range() : any
82+
>elementB.range : () => any
83+
>elementB : Match
84+
>range : () => any
85+
}
86+

tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.errors.txt

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(12,10): error TS2339: Property 'bar' does not exist on type 'A'.
22
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(33,5): error TS2322: Type 'string' is not assignable to type 'number'.
33
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(34,10): error TS2339: Property 'bar' does not exist on type 'B<number>'.
4-
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(63,10): error TS2339: Property 'bar2' does not exist on type 'C1'.
5-
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(82,10): error TS2339: Property 'bar' does not exist on type 'D'.
6-
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(109,10): error TS2339: Property 'bar2' does not exist on type 'E1'.
7-
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(131,11): error TS2339: Property 'foo' does not exist on type 'string | F'.
8-
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(132,11): error TS2339: Property 'bar' does not exist on type 'string | F'.
9-
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(157,11): error TS2339: Property 'foo2' does not exist on type 'G1'.
10-
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(179,11): error TS2339: Property 'bar' does not exist on type 'H'.
4+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(65,10): error TS2339: Property 'bar1' does not exist on type 'C1 | C2'.
5+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(66,10): error TS2339: Property 'bar2' does not exist on type 'C1 | C2'.
6+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(85,10): error TS2339: Property 'bar' does not exist on type 'D'.
7+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(111,10): error TS2339: Property 'bar1' does not exist on type 'E1 | E2'.
8+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(112,10): error TS2339: Property 'bar2' does not exist on type 'E1 | E2'.
9+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(134,11): error TS2339: Property 'foo' does not exist on type 'string | F'.
10+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(135,11): error TS2339: Property 'bar' does not exist on type 'string | F'.
11+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(160,11): error TS2339: Property 'foo2' does not exist on type 'G1'.
12+
tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts(182,11): error TS2339: Property 'bar' does not exist on type 'H'.
1113

1214

13-
==== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts (10 errors) ====
15+
==== tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstructorSignature.ts (12 errors) ====
1416
interface AConstructor {
1517
new (): A;
1618
}
@@ -67,21 +69,26 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru
6769
}
6870
interface C1 {
6971
foo: string;
72+
c: string;
7073
bar1: number;
7174
}
7275
interface C2 {
7376
foo: string;
77+
c: string;
7478
bar2: number;
7579
}
7680
declare var C: CConstructor;
7781

7882
var obj5: C1 | A;
79-
if (obj5 instanceof C) { // narrowed to C1.
83+
if (obj5 instanceof C) { // narrowed to C1|C2.
8084
obj5.foo;
85+
obj5.c;
8186
obj5.bar1;
87+
~~~~
88+
!!! error TS2339: Property 'bar1' does not exist on type 'C1 | C2'.
8289
obj5.bar2;
8390
~~~~
84-
!!! error TS2339: Property 'bar2' does not exist on type 'C1'.
91+
!!! error TS2339: Property 'bar2' does not exist on type 'C1 | C2'.
8592
}
8693

8794
var obj6: any;
@@ -126,12 +133,14 @@ tests/cases/conformance/expressions/typeGuards/typeGuardsWithInstanceOfByConstru
126133
declare var E: EConstructor;
127134

128135
var obj9: E1 | A;
129-
if (obj9 instanceof E) { // narrowed to E1.
136+
if (obj9 instanceof E) { // narrowed to E1 | E2
130137
obj9.foo;
131138
obj9.bar1;
139+
~~~~
140+
!!! error TS2339: Property 'bar1' does not exist on type 'E1 | E2'.
132141
obj9.bar2;
133142
~~~~
134-
!!! error TS2339: Property 'bar2' does not exist on type 'E1'.
143+
!!! error TS2339: Property 'bar2' does not exist on type 'E1 | E2'.
135144
}
136145

137146
var obj10: any;

tests/baselines/reference/typeGuardsWithInstanceOfByConstructorSignature.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,20 @@ interface CConstructor {
4949
}
5050
interface C1 {
5151
foo: string;
52+
c: string;
5253
bar1: number;
5354
}
5455
interface C2 {
5556
foo: string;
57+
c: string;
5658
bar2: number;
5759
}
5860
declare var C: CConstructor;
5961

6062
var obj5: C1 | A;
61-
if (obj5 instanceof C) { // narrowed to C1.
63+
if (obj5 instanceof C) { // narrowed to C1|C2.
6264
obj5.foo;
65+
obj5.c;
6366
obj5.bar1;
6467
obj5.bar2;
6568
}
@@ -104,7 +107,7 @@ interface E2 {
104107
declare var E: EConstructor;
105108

106109
var obj9: E1 | A;
107-
if (obj9 instanceof E) { // narrowed to E1.
110+
if (obj9 instanceof E) { // narrowed to E1 | E2
108111
obj9.foo;
109112
obj9.bar1;
110113
obj9.bar2;
@@ -213,6 +216,7 @@ if (obj4 instanceof B) {
213216
var obj5;
214217
if (obj5 instanceof C) {
215218
obj5.foo;
219+
obj5.c;
216220
obj5.bar1;
217221
obj5.bar2;
218222
}

0 commit comments

Comments
 (0)