Merge pull request #29714 from Microsoft/fixInstanceofTypeofControlFlow

Fix instanceof and typeof control flow
This commit is contained in:
Anders Hejlsberg 2019-02-04 12:22:52 -08:00 committed by GitHub
commit b7c5c073f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 317 additions and 10 deletions

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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))
}
}

View file

@ -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
}
}

View file

@ -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;
}
}