Skip to content

Commit b7c5c07

Browse files
authored
Merge pull request #29714 from Microsoft/fixInstanceofTypeofControlFlow
Fix instanceof and typeof control flow
2 parents f86b635 + 0262259 commit b7c5c07

File tree

6 files changed

+317
-10
lines changed

6 files changed

+317
-10
lines changed

src/compiler/checker.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15045,9 +15045,8 @@ namespace ts {
1504515045
return false;
1504615046
}
1504715047

15048-
function hasNarrowableDeclaredType(expr: Node) {
15049-
const type = getDeclaredTypeOfReference(expr);
15050-
return !!(type && type.flags & TypeFlags.Union);
15048+
function isSyntheticThisPropertyAccess(expr: Node) {
15049+
return isAccessExpression(expr) && expr.expression.kind === SyntaxKind.ThisKeyword && !!(expr.expression.flags & NodeFlags.Synthesized);
1505115050
}
1505215051

1505315052
function findDiscriminantProperties(sourceProperties: Symbol[], target: Type): Symbol[] | undefined {
@@ -16107,9 +16106,9 @@ namespace ts {
1610716106
// We have '==', '!=', '====', or !==' operator with 'typeof xxx' and string literal operands
1610816107
const target = getReferenceCandidate(typeOfExpr.expression);
1610916108
if (!isMatchingReference(reference, target)) {
16110-
// For a reference of the form 'x.y', where 'x' has a narrowable declared type, a
16111-
// 'typeof x === ...' type guard resets the narrowed type of 'y' to its declared type.
16112-
if (containsMatchingReference(reference, target) && hasNarrowableDeclaredType(target)) {
16109+
// For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the
16110+
// narrowed type of 'y' to its declared type.
16111+
if (containsMatchingReference(reference, target)) {
1611316112
return declaredType;
1611416113
}
1611516114
return type;
@@ -16264,9 +16263,14 @@ namespace ts {
1626416263
function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
1626516264
const left = getReferenceCandidate(expr.left);
1626616265
if (!isMatchingReference(reference, left)) {
16267-
// For a reference of the form 'x.y', where 'x' has a narrowable declared type, an
16268-
// 'x instanceof T' type guard resets the narrowed type of 'y' to its declared type.
16269-
if (containsMatchingReference(reference, left) && hasNarrowableDeclaredType(left)) {
16266+
// For a reference of the form 'x.y', an 'x instanceof T' type guard resets the
16267+
// narrowed type of 'y' to its declared type. We do this because preceding 'x.y'
16268+
// references might reference a different 'y' property. However, we make an exception
16269+
// for property accesses where x is a synthetic 'this' expression, indicating that we
16270+
// were called from isPropertyInitializedInConstructor. Without this exception,
16271+
// initializations of 'this' properties that occur before a 'this instanceof XXX'
16272+
// check would not be considered.
16273+
if (containsMatchingReference(reference, left) && !isSyntheticThisPropertyAccess(reference)) {
1627016274
return declaredType;
1627116275
}
1627216276
return type;
@@ -27221,7 +27225,7 @@ namespace ts {
2722127225
reference.expression.parent = reference;
2722227226
reference.parent = constructor;
2722327227
reference.flowNode = constructor.returnFlowNode;
27224-
const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType));
27228+
const flowType = getFlowTypeOfReference(reference, getOptionalType(propType));
2722527229
return !(getFalsyFlags(flowType) & TypeFlags.Undefined);
2722627230
}
2722727231

tests/baselines/reference/narrowingOfDottedNames.errors.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,37 @@ tests/cases/compiler/narrowingOfDottedNames.ts(54,5): error TS2564: Property 'x'
6464
constructor() {
6565
}
6666
}
67+
68+
// Repro from #29513
69+
70+
class AInfo {
71+
a_count: number = 1;
72+
}
73+
74+
class BInfo {
75+
b_count: number = 1;
76+
}
77+
78+
class Base {
79+
id: number = 0;
80+
}
81+
82+
class A2 extends Base {
83+
info!: AInfo;
84+
}
85+
86+
class B2 extends Base {
87+
info!: BInfo;
88+
}
89+
90+
let target: Base = null as any;
91+
92+
while (target) {
93+
if (target instanceof A2) {
94+
target.info.a_count = 3;
95+
}
96+
else if (target instanceof B2) {
97+
const j: BInfo = target.info;
98+
}
99+
}
67100

tests/baselines/reference/narrowingOfDottedNames.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,57 @@ class Foo2
5656
constructor() {
5757
}
5858
}
59+
60+
// Repro from #29513
61+
62+
class AInfo {
63+
a_count: number = 1;
64+
}
65+
66+
class BInfo {
67+
b_count: number = 1;
68+
}
69+
70+
class Base {
71+
id: number = 0;
72+
}
73+
74+
class A2 extends Base {
75+
info!: AInfo;
76+
}
77+
78+
class B2 extends Base {
79+
info!: BInfo;
80+
}
81+
82+
let target: Base = null as any;
83+
84+
while (target) {
85+
if (target instanceof A2) {
86+
target.info.a_count = 3;
87+
}
88+
else if (target instanceof B2) {
89+
const j: BInfo = target.info;
90+
}
91+
}
5992

6093

6194
//// [narrowingOfDottedNames.js]
6295
"use strict";
6396
// Repro from #8383
97+
var __extends = (this && this.__extends) || (function () {
98+
var extendStatics = function (d, b) {
99+
extendStatics = Object.setPrototypeOf ||
100+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
101+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
102+
return extendStatics(d, b);
103+
};
104+
return function (d, b) {
105+
extendStatics(d, b);
106+
function __() { this.constructor = d; }
107+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
108+
};
109+
})();
64110
var A = /** @class */ (function () {
65111
function A() {
66112
}
@@ -110,3 +156,45 @@ var Foo2 = /** @class */ (function () {
110156
}
111157
return Foo2;
112158
}());
159+
// Repro from #29513
160+
var AInfo = /** @class */ (function () {
161+
function AInfo() {
162+
this.a_count = 1;
163+
}
164+
return AInfo;
165+
}());
166+
var BInfo = /** @class */ (function () {
167+
function BInfo() {
168+
this.b_count = 1;
169+
}
170+
return BInfo;
171+
}());
172+
var Base = /** @class */ (function () {
173+
function Base() {
174+
this.id = 0;
175+
}
176+
return Base;
177+
}());
178+
var A2 = /** @class */ (function (_super) {
179+
__extends(A2, _super);
180+
function A2() {
181+
return _super !== null && _super.apply(this, arguments) || this;
182+
}
183+
return A2;
184+
}(Base));
185+
var B2 = /** @class */ (function (_super) {
186+
__extends(B2, _super);
187+
function B2() {
188+
return _super !== null && _super.apply(this, arguments) || this;
189+
}
190+
return B2;
191+
}(Base));
192+
var target = null;
193+
while (target) {
194+
if (target instanceof A2) {
195+
target.info.a_count = 3;
196+
}
197+
else if (target instanceof B2) {
198+
var j = target.info;
199+
}
200+
}

tests/baselines/reference/narrowingOfDottedNames.symbols

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,75 @@ class Foo2
129129
}
130130
}
131131

132+
// Repro from #29513
133+
134+
class AInfo {
135+
>AInfo : Symbol(AInfo, Decl(narrowingOfDottedNames.ts, 56, 1))
136+
137+
a_count: number = 1;
138+
>a_count : Symbol(AInfo.a_count, Decl(narrowingOfDottedNames.ts, 60, 13))
139+
}
140+
141+
class BInfo {
142+
>BInfo : Symbol(BInfo, Decl(narrowingOfDottedNames.ts, 62, 1))
143+
144+
b_count: number = 1;
145+
>b_count : Symbol(BInfo.b_count, Decl(narrowingOfDottedNames.ts, 64, 13))
146+
}
147+
148+
class Base {
149+
>Base : Symbol(Base, Decl(narrowingOfDottedNames.ts, 66, 1))
150+
151+
id: number = 0;
152+
>id : Symbol(Base.id, Decl(narrowingOfDottedNames.ts, 68, 12))
153+
}
154+
155+
class A2 extends Base {
156+
>A2 : Symbol(A2, Decl(narrowingOfDottedNames.ts, 70, 1))
157+
>Base : Symbol(Base, Decl(narrowingOfDottedNames.ts, 66, 1))
158+
159+
info!: AInfo;
160+
>info : Symbol(A2.info, Decl(narrowingOfDottedNames.ts, 72, 23))
161+
>AInfo : Symbol(AInfo, Decl(narrowingOfDottedNames.ts, 56, 1))
162+
}
163+
164+
class B2 extends Base {
165+
>B2 : Symbol(B2, Decl(narrowingOfDottedNames.ts, 74, 1))
166+
>Base : Symbol(Base, Decl(narrowingOfDottedNames.ts, 66, 1))
167+
168+
info!: BInfo;
169+
>info : Symbol(B2.info, Decl(narrowingOfDottedNames.ts, 76, 23))
170+
>BInfo : Symbol(BInfo, Decl(narrowingOfDottedNames.ts, 62, 1))
171+
}
172+
173+
let target: Base = null as any;
174+
>target : Symbol(target, Decl(narrowingOfDottedNames.ts, 80, 3))
175+
>Base : Symbol(Base, Decl(narrowingOfDottedNames.ts, 66, 1))
176+
177+
while (target) {
178+
>target : Symbol(target, Decl(narrowingOfDottedNames.ts, 80, 3))
179+
180+
if (target instanceof A2) {
181+
>target : Symbol(target, Decl(narrowingOfDottedNames.ts, 80, 3))
182+
>A2 : Symbol(A2, Decl(narrowingOfDottedNames.ts, 70, 1))
183+
184+
target.info.a_count = 3;
185+
>target.info.a_count : Symbol(AInfo.a_count, Decl(narrowingOfDottedNames.ts, 60, 13))
186+
>target.info : Symbol(A2.info, Decl(narrowingOfDottedNames.ts, 72, 23))
187+
>target : Symbol(target, Decl(narrowingOfDottedNames.ts, 80, 3))
188+
>info : Symbol(A2.info, Decl(narrowingOfDottedNames.ts, 72, 23))
189+
>a_count : Symbol(AInfo.a_count, Decl(narrowingOfDottedNames.ts, 60, 13))
190+
}
191+
else if (target instanceof B2) {
192+
>target : Symbol(target, Decl(narrowingOfDottedNames.ts, 80, 3))
193+
>B2 : Symbol(B2, Decl(narrowingOfDottedNames.ts, 74, 1))
194+
195+
const j: BInfo = target.info;
196+
>j : Symbol(j, Decl(narrowingOfDottedNames.ts, 87, 13))
197+
>BInfo : Symbol(BInfo, Decl(narrowingOfDottedNames.ts, 62, 1))
198+
>target.info : Symbol(B2.info, Decl(narrowingOfDottedNames.ts, 76, 23))
199+
>target : Symbol(target, Decl(narrowingOfDottedNames.ts, 80, 3))
200+
>info : Symbol(B2.info, Decl(narrowingOfDottedNames.ts, 76, 23))
201+
}
202+
}
203+

tests/baselines/reference/narrowingOfDottedNames.types

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,80 @@ class Foo2
132132
}
133133
}
134134

135+
// Repro from #29513
136+
137+
class AInfo {
138+
>AInfo : AInfo
139+
140+
a_count: number = 1;
141+
>a_count : number
142+
>1 : 1
143+
}
144+
145+
class BInfo {
146+
>BInfo : BInfo
147+
148+
b_count: number = 1;
149+
>b_count : number
150+
>1 : 1
151+
}
152+
153+
class Base {
154+
>Base : Base
155+
156+
id: number = 0;
157+
>id : number
158+
>0 : 0
159+
}
160+
161+
class A2 extends Base {
162+
>A2 : A2
163+
>Base : Base
164+
165+
info!: AInfo;
166+
>info : AInfo
167+
}
168+
169+
class B2 extends Base {
170+
>B2 : B2
171+
>Base : Base
172+
173+
info!: BInfo;
174+
>info : BInfo
175+
}
176+
177+
let target: Base = null as any;
178+
>target : Base
179+
>null as any : any
180+
>null : null
181+
182+
while (target) {
183+
>target : Base
184+
185+
if (target instanceof A2) {
186+
>target instanceof A2 : boolean
187+
>target : Base
188+
>A2 : typeof A2
189+
190+
target.info.a_count = 3;
191+
>target.info.a_count = 3 : 3
192+
>target.info.a_count : number
193+
>target.info : AInfo
194+
>target : A2
195+
>info : AInfo
196+
>a_count : number
197+
>3 : 3
198+
}
199+
else if (target instanceof B2) {
200+
>target instanceof B2 : boolean
201+
>target : Base
202+
>B2 : typeof B2
203+
204+
const j: BInfo = target.info;
205+
>j : BInfo
206+
>target.info : BInfo
207+
>target : B2
208+
>info : BInfo
209+
}
210+
}
211+

tests/cases/compiler/narrowingOfDottedNames.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,36 @@ class Foo2
5757
constructor() {
5858
}
5959
}
60+
61+
// Repro from #29513
62+
63+
class AInfo {
64+
a_count: number = 1;
65+
}
66+
67+
class BInfo {
68+
b_count: number = 1;
69+
}
70+
71+
class Base {
72+
id: number = 0;
73+
}
74+
75+
class A2 extends Base {
76+
info!: AInfo;
77+
}
78+
79+
class B2 extends Base {
80+
info!: BInfo;
81+
}
82+
83+
let target: Base = null as any;
84+
85+
while (target) {
86+
if (target instanceof A2) {
87+
target.info.a_count = 3;
88+
}
89+
else if (target instanceof B2) {
90+
const j: BInfo = target.info;
91+
}
92+
}

0 commit comments

Comments
 (0)