Skip to content

Commit 6e8e5c1

Browse files
authored
Merge pull request #28112 from Microsoft/fixInstanceofControlFlow
Fix instanceof control flow analysis
2 parents 42740d6 + cc9f45d commit 6e8e5c1

File tree

6 files changed

+201
-24
lines changed

6 files changed

+201
-24
lines changed

src/compiler/checker.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14340,6 +14340,11 @@ namespace ts {
1434014340
return false;
1434114341
}
1434214342

14343+
function hasNarrowableDeclaredType(expr: Node) {
14344+
const type = getDeclaredTypeOfReference(expr);
14345+
return !!(type && type.flags & TypeFlags.Union);
14346+
}
14347+
1434314348
function findDiscriminantProperties(sourceProperties: Symbol[], target: Type): Symbol[] | undefined {
1434414349
let result: Symbol[] | undefined;
1434514350
for (const sourceProperty of sourceProperties) {
@@ -15373,9 +15378,9 @@ namespace ts {
1537315378
// We have '==', '!=', '====', or !==' operator with 'typeof xxx' and string literal operands
1537415379
const target = getReferenceCandidate(typeOfExpr.expression);
1537515380
if (!isMatchingReference(reference, target)) {
15376-
// For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the
15377-
// narrowed type of 'y' to its declared type.
15378-
if (containsMatchingReference(reference, target)) {
15381+
// For a reference of the form 'x.y', where 'x' has a narrowable declared type, a
15382+
// 'typeof x === ...' type guard resets the narrowed type of 'y' to its declared type.
15383+
if (containsMatchingReference(reference, target) && hasNarrowableDeclaredType(target)) {
1537915384
return declaredType;
1538015385
}
1538115386
return type;
@@ -15516,9 +15521,9 @@ namespace ts {
1551615521
function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
1551715522
const left = getReferenceCandidate(expr.left);
1551815523
if (!isMatchingReference(reference, left)) {
15519-
// For a reference of the form 'x.y', an 'x instanceof T' type guard resets the
15520-
// narrowed type of 'y' to its declared type.
15521-
if (containsMatchingReference(reference, left)) {
15524+
// For a reference of the form 'x.y', where 'x' has a narrowable declared type, an
15525+
// 'x instanceof T' type guard resets the narrowed type of 'y' to its declared type.
15526+
if (containsMatchingReference(reference, left) && hasNarrowableDeclaredType(left)) {
1552215527
return declaredType;
1552315528
}
1552415529
return type;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
tests/cases/compiler/narrowingOfDottedNames.ts(45,5): error TS2564: Property 'x' has no initializer and is not definitely assigned in the constructor.
2+
tests/cases/compiler/narrowingOfDottedNames.ts(54,5): error TS2564: Property 'x' has no initializer and is not definitely assigned in the constructor.
3+
4+
5+
==== tests/cases/compiler/narrowingOfDottedNames.ts (2 errors) ====
6+
// Repro from #8383
7+
8+
class A {
9+
prop!: { a: string; };
10+
}
11+
12+
class B {
13+
prop!: { b: string; }
14+
}
15+
16+
function isA(x: any): x is A {
17+
return x instanceof A;
18+
}
19+
20+
function isB(x: any): x is B {
21+
return x instanceof B;
22+
}
23+
24+
function f1(x: A | B) {
25+
while (true) {
26+
if (x instanceof A) {
27+
x.prop.a;
28+
}
29+
else if (x instanceof B) {
30+
x.prop.b;
31+
}
32+
}
33+
}
34+
35+
function f2(x: A | B) {
36+
while (true) {
37+
if (isA(x)) {
38+
x.prop.a;
39+
}
40+
else if (isB(x)) {
41+
x.prop.b;
42+
}
43+
}
44+
}
45+
46+
// Repro from #28100
47+
48+
class Foo1
49+
{
50+
x: number; // Error
51+
~
52+
!!! error TS2564: Property 'x' has no initializer and is not definitely assigned in the constructor.
53+
constructor() {
54+
if (this instanceof Boolean) {
55+
}
56+
}
57+
}
58+
59+
class Foo2
60+
{
61+
x: number; // Error
62+
~
63+
!!! error TS2564: Property 'x' has no initializer and is not definitely assigned in the constructor.
64+
constructor() {
65+
}
66+
}
67+

tests/baselines/reference/narrowingOfDottedNames.js

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
// Repro from #8383
33

44
class A {
5-
prop: { a: string; };
5+
prop!: { a: string; };
66
}
77

88
class B {
9-
prop: { b: string; }
9+
prop!: { b: string; }
1010
}
1111

1212
function isA(x: any): x is A {
@@ -38,9 +38,28 @@ function f2(x: A | B) {
3838
}
3939
}
4040
}
41+
42+
// Repro from #28100
43+
44+
class Foo1
45+
{
46+
x: number; // Error
47+
constructor() {
48+
if (this instanceof Boolean) {
49+
}
50+
}
51+
}
52+
53+
class Foo2
54+
{
55+
x: number; // Error
56+
constructor() {
57+
}
58+
}
4159

4260

4361
//// [narrowingOfDottedNames.js]
62+
"use strict";
4463
// Repro from #8383
4564
var A = /** @class */ (function () {
4665
function A() {
@@ -78,3 +97,16 @@ function f2(x) {
7897
}
7998
}
8099
}
100+
// Repro from #28100
101+
var Foo1 = /** @class */ (function () {
102+
function Foo1() {
103+
if (this instanceof Boolean) {
104+
}
105+
}
106+
return Foo1;
107+
}());
108+
var Foo2 = /** @class */ (function () {
109+
function Foo2() {
110+
}
111+
return Foo2;
112+
}());

tests/baselines/reference/narrowingOfDottedNames.symbols

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@
44
class A {
55
>A : Symbol(A, Decl(narrowingOfDottedNames.ts, 0, 0))
66

7-
prop: { a: string; };
7+
prop!: { a: string; };
88
>prop : Symbol(A.prop, Decl(narrowingOfDottedNames.ts, 2, 9))
9-
>a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 11))
9+
>a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 12))
1010
}
1111

1212
class B {
1313
>B : Symbol(B, Decl(narrowingOfDottedNames.ts, 4, 1))
1414

15-
prop: { b: string; }
15+
prop!: { b: string; }
1616
>prop : Symbol(B.prop, Decl(narrowingOfDottedNames.ts, 6, 9))
17-
>b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 11))
17+
>b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 12))
1818
}
1919

2020
function isA(x: any): x is A {
@@ -51,22 +51,22 @@ function f1(x: A | B) {
5151
>A : Symbol(A, Decl(narrowingOfDottedNames.ts, 0, 0))
5252

5353
x.prop.a;
54-
>x.prop.a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 11))
54+
>x.prop.a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 12))
5555
>x.prop : Symbol(A.prop, Decl(narrowingOfDottedNames.ts, 2, 9))
5656
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 18, 12))
5757
>prop : Symbol(A.prop, Decl(narrowingOfDottedNames.ts, 2, 9))
58-
>a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 11))
58+
>a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 12))
5959
}
6060
else if (x instanceof B) {
6161
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 18, 12))
6262
>B : Symbol(B, Decl(narrowingOfDottedNames.ts, 4, 1))
6363

6464
x.prop.b;
65-
>x.prop.b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 11))
65+
>x.prop.b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 12))
6666
>x.prop : Symbol(B.prop, Decl(narrowingOfDottedNames.ts, 6, 9))
6767
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 18, 12))
6868
>prop : Symbol(B.prop, Decl(narrowingOfDottedNames.ts, 6, 9))
69-
>b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 11))
69+
>b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 12))
7070
}
7171
}
7272
}
@@ -83,23 +83,49 @@ function f2(x: A | B) {
8383
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 29, 12))
8484

8585
x.prop.a;
86-
>x.prop.a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 11))
86+
>x.prop.a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 12))
8787
>x.prop : Symbol(A.prop, Decl(narrowingOfDottedNames.ts, 2, 9))
8888
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 29, 12))
8989
>prop : Symbol(A.prop, Decl(narrowingOfDottedNames.ts, 2, 9))
90-
>a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 11))
90+
>a : Symbol(a, Decl(narrowingOfDottedNames.ts, 3, 12))
9191
}
9292
else if (isB(x)) {
9393
>isB : Symbol(isB, Decl(narrowingOfDottedNames.ts, 12, 1))
9494
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 29, 12))
9595

9696
x.prop.b;
97-
>x.prop.b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 11))
97+
>x.prop.b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 12))
9898
>x.prop : Symbol(B.prop, Decl(narrowingOfDottedNames.ts, 6, 9))
9999
>x : Symbol(x, Decl(narrowingOfDottedNames.ts, 29, 12))
100100
>prop : Symbol(B.prop, Decl(narrowingOfDottedNames.ts, 6, 9))
101-
>b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 11))
101+
>b : Symbol(b, Decl(narrowingOfDottedNames.ts, 7, 12))
102102
}
103103
}
104104
}
105105

106+
// Repro from #28100
107+
108+
class Foo1
109+
>Foo1 : Symbol(Foo1, Decl(narrowingOfDottedNames.ts, 38, 1))
110+
{
111+
x: number; // Error
112+
>x : Symbol(Foo1.x, Decl(narrowingOfDottedNames.ts, 43, 1))
113+
114+
constructor() {
115+
if (this instanceof Boolean) {
116+
>this : Symbol(Foo1, Decl(narrowingOfDottedNames.ts, 38, 1))
117+
>Boolean : Symbol(Boolean, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
118+
}
119+
}
120+
}
121+
122+
class Foo2
123+
>Foo2 : Symbol(Foo2, Decl(narrowingOfDottedNames.ts, 49, 1))
124+
{
125+
x: number; // Error
126+
>x : Symbol(Foo2.x, Decl(narrowingOfDottedNames.ts, 52, 1))
127+
128+
constructor() {
129+
}
130+
}
131+

tests/baselines/reference/narrowingOfDottedNames.types

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
class A {
55
>A : A
66

7-
prop: { a: string; };
7+
prop!: { a: string; };
88
>prop : { a: string; }
99
>a : string
1010
}
1111

1212
class B {
1313
>B : B
1414

15-
prop: { b: string; }
15+
prop!: { b: string; }
1616
>prop : { b: string; }
1717
>b : string
1818
}
@@ -105,3 +105,30 @@ function f2(x: A | B) {
105105
}
106106
}
107107

108+
// Repro from #28100
109+
110+
class Foo1
111+
>Foo1 : Foo1
112+
{
113+
x: number; // Error
114+
>x : number
115+
116+
constructor() {
117+
if (this instanceof Boolean) {
118+
>this instanceof Boolean : boolean
119+
>this : this
120+
>Boolean : BooleanConstructor
121+
}
122+
}
123+
}
124+
125+
class Foo2
126+
>Foo2 : Foo2
127+
{
128+
x: number; // Error
129+
>x : number
130+
131+
constructor() {
132+
}
133+
}
134+

tests/cases/compiler/narrowingOfDottedNames.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
// @strict: true
2+
13
// Repro from #8383
24

35
class A {
4-
prop: { a: string; };
6+
prop!: { a: string; };
57
}
68

79
class B {
8-
prop: { b: string; }
10+
prop!: { b: string; }
911
}
1012

1113
function isA(x: any): x is A {
@@ -37,3 +39,21 @@ function f2(x: A | B) {
3739
}
3840
}
3941
}
42+
43+
// Repro from #28100
44+
45+
class Foo1
46+
{
47+
x: number; // Error
48+
constructor() {
49+
if (this instanceof Boolean) {
50+
}
51+
}
52+
}
53+
54+
class Foo2
55+
{
56+
x: number; // Error
57+
constructor() {
58+
}
59+
}

0 commit comments

Comments
 (0)