Merge pull request #29714 from Microsoft/fixInstanceofTypeofControlFlow
Fix instanceof and typeof control flow
This commit is contained in:
commit
b7c5c073f8
6 changed files with 317 additions and 10 deletions
|
@ -15045,9 +15045,8 @@ namespace ts {
|
|||
return false;
|
||||
}
|
||||
|
||||
function hasNarrowableDeclaredType(expr: Node) {
|
||||
const type = getDeclaredTypeOfReference(expr);
|
||||
return !!(type && type.flags & TypeFlags.Union);
|
||||
function isSyntheticThisPropertyAccess(expr: Node) {
|
||||
return isAccessExpression(expr) && expr.expression.kind === SyntaxKind.ThisKeyword && !!(expr.expression.flags & NodeFlags.Synthesized);
|
||||
}
|
||||
|
||||
function findDiscriminantProperties(sourceProperties: Symbol[], target: Type): Symbol[] | undefined {
|
||||
|
@ -16107,9 +16106,9 @@ namespace ts {
|
|||
// We have '==', '!=', '====', or !==' operator with 'typeof xxx' and string literal operands
|
||||
const target = getReferenceCandidate(typeOfExpr.expression);
|
||||
if (!isMatchingReference(reference, target)) {
|
||||
// For a reference of the form 'x.y', where 'x' has a narrowable declared type, a
|
||||
// 'typeof x === ...' type guard resets the narrowed type of 'y' to its declared type.
|
||||
if (containsMatchingReference(reference, target) && hasNarrowableDeclaredType(target)) {
|
||||
// For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the
|
||||
// narrowed type of 'y' to its declared type.
|
||||
if (containsMatchingReference(reference, target)) {
|
||||
return declaredType;
|
||||
}
|
||||
return type;
|
||||
|
@ -16264,9 +16263,14 @@ namespace ts {
|
|||
function narrowTypeByInstanceof(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
|
||||
const left = getReferenceCandidate(expr.left);
|
||||
if (!isMatchingReference(reference, left)) {
|
||||
// For a reference of the form 'x.y', where 'x' has a narrowable declared type, an
|
||||
// 'x instanceof T' type guard resets the narrowed type of 'y' to its declared type.
|
||||
if (containsMatchingReference(reference, left) && hasNarrowableDeclaredType(left)) {
|
||||
// For a reference of the form 'x.y', an 'x instanceof T' type guard resets the
|
||||
// narrowed type of 'y' to its declared type. We do this because preceding 'x.y'
|
||||
// references might reference a different 'y' property. However, we make an exception
|
||||
// for property accesses where x is a synthetic 'this' expression, indicating that we
|
||||
// were called from isPropertyInitializedInConstructor. Without this exception,
|
||||
// initializations of 'this' properties that occur before a 'this instanceof XXX'
|
||||
// check would not be considered.
|
||||
if (containsMatchingReference(reference, left) && !isSyntheticThisPropertyAccess(reference)) {
|
||||
return declaredType;
|
||||
}
|
||||
return type;
|
||||
|
@ -27221,7 +27225,7 @@ namespace ts {
|
|||
reference.expression.parent = reference;
|
||||
reference.parent = constructor;
|
||||
reference.flowNode = constructor.returnFlowNode;
|
||||
const flowType = getFlowTypeOfReference(reference, propType, getOptionalType(propType));
|
||||
const flowType = getFlowTypeOfReference(reference, getOptionalType(propType));
|
||||
return !(getFalsyFlags(flowType) & TypeFlags.Undefined);
|
||||
}
|
||||
|
||||
|
|
|
@ -64,4 +64,37 @@ tests/cases/compiler/narrowingOfDottedNames.ts(54,5): error TS2564: Property 'x'
|
|||
constructor() {
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #29513
|
||||
|
||||
class AInfo {
|
||||
a_count: number = 1;
|
||||
}
|
||||
|
||||
class BInfo {
|
||||
b_count: number = 1;
|
||||
}
|
||||
|
||||
class Base {
|
||||
id: number = 0;
|
||||
}
|
||||
|
||||
class A2 extends Base {
|
||||
info!: AInfo;
|
||||
}
|
||||
|
||||
class B2 extends Base {
|
||||
info!: BInfo;
|
||||
}
|
||||
|
||||
let target: Base = null as any;
|
||||
|
||||
while (target) {
|
||||
if (target instanceof A2) {
|
||||
target.info.a_count = 3;
|
||||
}
|
||||
else if (target instanceof B2) {
|
||||
const j: BInfo = target.info;
|
||||
}
|
||||
}
|
||||
|
|
@ -56,11 +56,57 @@ class Foo2
|
|||
constructor() {
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #29513
|
||||
|
||||
class AInfo {
|
||||
a_count: number = 1;
|
||||
}
|
||||
|
||||
class BInfo {
|
||||
b_count: number = 1;
|
||||
}
|
||||
|
||||
class Base {
|
||||
id: number = 0;
|
||||
}
|
||||
|
||||
class A2 extends Base {
|
||||
info!: AInfo;
|
||||
}
|
||||
|
||||
class B2 extends Base {
|
||||
info!: BInfo;
|
||||
}
|
||||
|
||||
let target: Base = null as any;
|
||||
|
||||
while (target) {
|
||||
if (target instanceof A2) {
|
||||
target.info.a_count = 3;
|
||||
}
|
||||
else if (target instanceof B2) {
|
||||
const j: BInfo = target.info;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// [narrowingOfDottedNames.js]
|
||||
"use strict";
|
||||
// Repro from #8383
|
||||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = function (d, b) {
|
||||
extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return extendStatics(d, b);
|
||||
};
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
var A = /** @class */ (function () {
|
||||
function A() {
|
||||
}
|
||||
|
@ -110,3 +156,45 @@ var Foo2 = /** @class */ (function () {
|
|||
}
|
||||
return Foo2;
|
||||
}());
|
||||
// Repro from #29513
|
||||
var AInfo = /** @class */ (function () {
|
||||
function AInfo() {
|
||||
this.a_count = 1;
|
||||
}
|
||||
return AInfo;
|
||||
}());
|
||||
var BInfo = /** @class */ (function () {
|
||||
function BInfo() {
|
||||
this.b_count = 1;
|
||||
}
|
||||
return BInfo;
|
||||
}());
|
||||
var Base = /** @class */ (function () {
|
||||
function Base() {
|
||||
this.id = 0;
|
||||
}
|
||||
return Base;
|
||||
}());
|
||||
var A2 = /** @class */ (function (_super) {
|
||||
__extends(A2, _super);
|
||||
function A2() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
return A2;
|
||||
}(Base));
|
||||
var B2 = /** @class */ (function (_super) {
|
||||
__extends(B2, _super);
|
||||
function B2() {
|
||||
return _super !== null && _super.apply(this, arguments) || this;
|
||||
}
|
||||
return B2;
|
||||
}(Base));
|
||||
var target = null;
|
||||
while (target) {
|
||||
if (target instanceof A2) {
|
||||
target.info.a_count = 3;
|
||||
}
|
||||
else if (target instanceof B2) {
|
||||
var j = target.info;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,3 +129,75 @@ class Foo2
|
|||
}
|
||||
}
|
||||
|
||||
// Repro from #29513
|
||||
|
||||
class AInfo {
|
||||
>AInfo : Symbol(AInfo, Decl(narrowingOfDottedNames.ts, 56, 1))
|
||||
|
||||
a_count: number = 1;
|
||||
>a_count : Symbol(AInfo.a_count, Decl(narrowingOfDottedNames.ts, 60, 13))
|
||||
}
|
||||
|
||||
class BInfo {
|
||||
>BInfo : Symbol(BInfo, Decl(narrowingOfDottedNames.ts, 62, 1))
|
||||
|
||||
b_count: number = 1;
|
||||
>b_count : Symbol(BInfo.b_count, Decl(narrowingOfDottedNames.ts, 64, 13))
|
||||
}
|
||||
|
||||
class Base {
|
||||
>Base : Symbol(Base, Decl(narrowingOfDottedNames.ts, 66, 1))
|
||||
|
||||
id: number = 0;
|
||||
>id : Symbol(Base.id, Decl(narrowingOfDottedNames.ts, 68, 12))
|
||||
}
|
||||
|
||||
class A2 extends Base {
|
||||
>A2 : Symbol(A2, Decl(narrowingOfDottedNames.ts, 70, 1))
|
||||
>Base : Symbol(Base, Decl(narrowingOfDottedNames.ts, 66, 1))
|
||||
|
||||
info!: AInfo;
|
||||
>info : Symbol(A2.info, Decl(narrowingOfDottedNames.ts, 72, 23))
|
||||
>AInfo : Symbol(AInfo, Decl(narrowingOfDottedNames.ts, 56, 1))
|
||||
}
|
||||
|
||||
class B2 extends Base {
|
||||
>B2 : Symbol(B2, Decl(narrowingOfDottedNames.ts, 74, 1))
|
||||
>Base : Symbol(Base, Decl(narrowingOfDottedNames.ts, 66, 1))
|
||||
|
||||
info!: BInfo;
|
||||
>info : Symbol(B2.info, Decl(narrowingOfDottedNames.ts, 76, 23))
|
||||
>BInfo : Symbol(BInfo, Decl(narrowingOfDottedNames.ts, 62, 1))
|
||||
}
|
||||
|
||||
let target: Base = null as any;
|
||||
>target : Symbol(target, Decl(narrowingOfDottedNames.ts, 80, 3))
|
||||
>Base : Symbol(Base, Decl(narrowingOfDottedNames.ts, 66, 1))
|
||||
|
||||
while (target) {
|
||||
>target : Symbol(target, Decl(narrowingOfDottedNames.ts, 80, 3))
|
||||
|
||||
if (target instanceof A2) {
|
||||
>target : Symbol(target, Decl(narrowingOfDottedNames.ts, 80, 3))
|
||||
>A2 : Symbol(A2, Decl(narrowingOfDottedNames.ts, 70, 1))
|
||||
|
||||
target.info.a_count = 3;
|
||||
>target.info.a_count : Symbol(AInfo.a_count, Decl(narrowingOfDottedNames.ts, 60, 13))
|
||||
>target.info : Symbol(A2.info, Decl(narrowingOfDottedNames.ts, 72, 23))
|
||||
>target : Symbol(target, Decl(narrowingOfDottedNames.ts, 80, 3))
|
||||
>info : Symbol(A2.info, Decl(narrowingOfDottedNames.ts, 72, 23))
|
||||
>a_count : Symbol(AInfo.a_count, Decl(narrowingOfDottedNames.ts, 60, 13))
|
||||
}
|
||||
else if (target instanceof B2) {
|
||||
>target : Symbol(target, Decl(narrowingOfDottedNames.ts, 80, 3))
|
||||
>B2 : Symbol(B2, Decl(narrowingOfDottedNames.ts, 74, 1))
|
||||
|
||||
const j: BInfo = target.info;
|
||||
>j : Symbol(j, Decl(narrowingOfDottedNames.ts, 87, 13))
|
||||
>BInfo : Symbol(BInfo, Decl(narrowingOfDottedNames.ts, 62, 1))
|
||||
>target.info : Symbol(B2.info, Decl(narrowingOfDottedNames.ts, 76, 23))
|
||||
>target : Symbol(target, Decl(narrowingOfDottedNames.ts, 80, 3))
|
||||
>info : Symbol(B2.info, Decl(narrowingOfDottedNames.ts, 76, 23))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -132,3 +132,80 @@ class Foo2
|
|||
}
|
||||
}
|
||||
|
||||
// Repro from #29513
|
||||
|
||||
class AInfo {
|
||||
>AInfo : AInfo
|
||||
|
||||
a_count: number = 1;
|
||||
>a_count : number
|
||||
>1 : 1
|
||||
}
|
||||
|
||||
class BInfo {
|
||||
>BInfo : BInfo
|
||||
|
||||
b_count: number = 1;
|
||||
>b_count : number
|
||||
>1 : 1
|
||||
}
|
||||
|
||||
class Base {
|
||||
>Base : Base
|
||||
|
||||
id: number = 0;
|
||||
>id : number
|
||||
>0 : 0
|
||||
}
|
||||
|
||||
class A2 extends Base {
|
||||
>A2 : A2
|
||||
>Base : Base
|
||||
|
||||
info!: AInfo;
|
||||
>info : AInfo
|
||||
}
|
||||
|
||||
class B2 extends Base {
|
||||
>B2 : B2
|
||||
>Base : Base
|
||||
|
||||
info!: BInfo;
|
||||
>info : BInfo
|
||||
}
|
||||
|
||||
let target: Base = null as any;
|
||||
>target : Base
|
||||
>null as any : any
|
||||
>null : null
|
||||
|
||||
while (target) {
|
||||
>target : Base
|
||||
|
||||
if (target instanceof A2) {
|
||||
>target instanceof A2 : boolean
|
||||
>target : Base
|
||||
>A2 : typeof A2
|
||||
|
||||
target.info.a_count = 3;
|
||||
>target.info.a_count = 3 : 3
|
||||
>target.info.a_count : number
|
||||
>target.info : AInfo
|
||||
>target : A2
|
||||
>info : AInfo
|
||||
>a_count : number
|
||||
>3 : 3
|
||||
}
|
||||
else if (target instanceof B2) {
|
||||
>target instanceof B2 : boolean
|
||||
>target : Base
|
||||
>B2 : typeof B2
|
||||
|
||||
const j: BInfo = target.info;
|
||||
>j : BInfo
|
||||
>target.info : BInfo
|
||||
>target : B2
|
||||
>info : BInfo
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,3 +57,36 @@ class Foo2
|
|||
constructor() {
|
||||
}
|
||||
}
|
||||
|
||||
// Repro from #29513
|
||||
|
||||
class AInfo {
|
||||
a_count: number = 1;
|
||||
}
|
||||
|
||||
class BInfo {
|
||||
b_count: number = 1;
|
||||
}
|
||||
|
||||
class Base {
|
||||
id: number = 0;
|
||||
}
|
||||
|
||||
class A2 extends Base {
|
||||
info!: AInfo;
|
||||
}
|
||||
|
||||
class B2 extends Base {
|
||||
info!: BInfo;
|
||||
}
|
||||
|
||||
let target: Base = null as any;
|
||||
|
||||
while (target) {
|
||||
if (target instanceof A2) {
|
||||
target.info.a_count = 3;
|
||||
}
|
||||
else if (target instanceof B2) {
|
||||
const j: BInfo = target.info;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue