Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down
33 changes: 33 additions & 0 deletions tests/baselines/reference/narrowingOfDottedNames.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

88 changes: 88 additions & 0 deletions tests/baselines/reference/narrowingOfDottedNames.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
}
Expand Down Expand Up @@ -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;
}
}
72 changes: 72 additions & 0 deletions tests/baselines/reference/narrowingOfDottedNames.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}

77 changes: 77 additions & 0 deletions tests/baselines/reference/narrowingOfDottedNames.types
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

33 changes: 33 additions & 0 deletions tests/cases/compiler/narrowingOfDottedNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}