Skip to content

Commit

Permalink
Don't narrow against unions of constructor functions with instanceof (#…
Browse files Browse the repository at this point in the history
…38099)

* WIP

* Only narrows when the union is not all constructors
  • Loading branch information
Orta Therox authored Jul 7, 2020
1 parent 9a65658 commit 6ebd73c
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21800,6 +21800,12 @@ namespace ts {
emptyObjectType;
}

// We can't narrow a union based off instanceof without negated types see #31576 for more info
if (!assumeTrue && rightType.flags & TypeFlags.Union) {
const nonConstructorTypeInUnion = find((<UnionType>rightType).types, (t) => !isConstructorType(t));
if (!nonConstructorTypeInUnion) return type;
}

return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//// [doesNotNarrowUnionOfConstructorsWithInstanceof.ts]
class A {
length: 1
constructor() {
this.length = 1
}
}

class B {
length: 2
constructor() {
this.length = 2
}
}

function getTypedArray(flag: boolean) {
return flag ? new A() : new B();
}
function getTypedArrayConstructor(flag: boolean) {
return flag ? A : B;
}
const a = getTypedArray(true); // A | B
const b = getTypedArrayConstructor(false); // A constructor | B constructor

if (!(a instanceof b)) {
console.log(a.length); // Used to be property 'length' does not exist on type 'never'.
}


//// [doesNotNarrowUnionOfConstructorsWithInstanceof.js]
var A = /** @class */ (function () {
function A() {
this.length = 1;
}
return A;
}());
var B = /** @class */ (function () {
function B() {
this.length = 2;
}
return B;
}());
function getTypedArray(flag) {
return flag ? new A() : new B();
}
function getTypedArrayConstructor(flag) {
return flag ? A : B;
}
var a = getTypedArray(true); // A | B
var b = getTypedArrayConstructor(false); // A constructor | B constructor
if (!(a instanceof b)) {
console.log(a.length); // Used to be property 'length' does not exist on type 'never'.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
=== tests/cases/compiler/doesNotNarrowUnionOfConstructorsWithInstanceof.ts ===
class A {
>A : Symbol(A, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 0))

length: 1
>length : Symbol(A.length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 9))

constructor() {
this.length = 1
>this.length : Symbol(A.length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 9))
>this : Symbol(A, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 0))
>length : Symbol(A.length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 9))
}
}

class B {
>B : Symbol(B, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 5, 1))

length: 2
>length : Symbol(B.length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 7, 9))

constructor() {
this.length = 2
>this.length : Symbol(B.length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 7, 9))
>this : Symbol(B, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 5, 1))
>length : Symbol(B.length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 7, 9))
}
}

function getTypedArray(flag: boolean) {
>getTypedArray : Symbol(getTypedArray, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 12, 1))
>flag : Symbol(flag, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 14, 23))

return flag ? new A() : new B();
>flag : Symbol(flag, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 14, 23))
>A : Symbol(A, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 0))
>B : Symbol(B, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 5, 1))
}
function getTypedArrayConstructor(flag: boolean) {
>getTypedArrayConstructor : Symbol(getTypedArrayConstructor, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 16, 1))
>flag : Symbol(flag, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 17, 34))

return flag ? A : B;
>flag : Symbol(flag, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 17, 34))
>A : Symbol(A, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 0))
>B : Symbol(B, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 5, 1))
}
const a = getTypedArray(true); // A | B
>a : Symbol(a, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 20, 5))
>getTypedArray : Symbol(getTypedArray, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 12, 1))

const b = getTypedArrayConstructor(false); // A constructor | B constructor
>b : Symbol(b, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 21, 5))
>getTypedArrayConstructor : Symbol(getTypedArrayConstructor, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 16, 1))

if (!(a instanceof b)) {
>a : Symbol(a, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 20, 5))
>b : Symbol(b, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 21, 5))

console.log(a.length); // Used to be property 'length' does not exist on type 'never'.
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>a.length : Symbol(length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 9), Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 7, 9))
>a : Symbol(a, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 20, 5))
>length : Symbol(length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 9), Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 7, 9))
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
=== tests/cases/compiler/doesNotNarrowUnionOfConstructorsWithInstanceof.ts ===
class A {
>A : A

length: 1
>length : 1

constructor() {
this.length = 1
>this.length = 1 : 1
>this.length : 1
>this : this
>length : 1
>1 : 1
}
}

class B {
>B : B

length: 2
>length : 2

constructor() {
this.length = 2
>this.length = 2 : 2
>this.length : 2
>this : this
>length : 2
>2 : 2
}
}

function getTypedArray(flag: boolean) {
>getTypedArray : (flag: boolean) => A | B
>flag : boolean

return flag ? new A() : new B();
>flag ? new A() : new B() : A | B
>flag : boolean
>new A() : A
>A : typeof A
>new B() : B
>B : typeof B
}
function getTypedArrayConstructor(flag: boolean) {
>getTypedArrayConstructor : (flag: boolean) => typeof A | typeof B
>flag : boolean

return flag ? A : B;
>flag ? A : B : typeof A | typeof B
>flag : boolean
>A : typeof A
>B : typeof B
}
const a = getTypedArray(true); // A | B
>a : A | B
>getTypedArray(true) : A | B
>getTypedArray : (flag: boolean) => A | B
>true : true

const b = getTypedArrayConstructor(false); // A constructor | B constructor
>b : typeof A | typeof B
>getTypedArrayConstructor(false) : typeof A | typeof B
>getTypedArrayConstructor : (flag: boolean) => typeof A | typeof B
>false : false

if (!(a instanceof b)) {
>!(a instanceof b) : boolean
>(a instanceof b) : boolean
>a instanceof b : boolean
>a : A | B
>b : typeof A | typeof B

console.log(a.length); // Used to be property 'length' does not exist on type 'never'.
>console.log(a.length) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>a.length : 1 | 2
>a : A | B
>length : 1 | 2
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class A {
length: 1
constructor() {
this.length = 1
}
}

class B {
length: 2
constructor() {
this.length = 2
}
}

function getTypedArray(flag: boolean) {
return flag ? new A() : new B();
}
function getTypedArrayConstructor(flag: boolean) {
return flag ? A : B;
}
const a = getTypedArray(true); // A | B
const b = getTypedArrayConstructor(false); // A constructor | B constructor

if (!(a instanceof b)) {
console.log(a.length); // Used to be property 'length' does not exist on type 'never'.
}

0 comments on commit 6ebd73c

Please sign in to comment.