Don't narrow against unions of constructor functions with instanceof (#38099)

* WIP

* Only narrows when the union is not all constructors
This commit is contained in:
Orta Therox 2020-07-07 11:34:10 -04:00 committed by GitHub
parent 9a65658b28
commit 6ebd73c84f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 237 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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