diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 58b4c2ac47f..0a39022e276 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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((rightType).types, (t) => !isConstructorType(t)); + if (!nonConstructorTypeInUnion) return type; + } + return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom); } diff --git a/tests/baselines/reference/doesNotNarrowUnionOfConstructorsWithInstanceof.js b/tests/baselines/reference/doesNotNarrowUnionOfConstructorsWithInstanceof.js new file mode 100644 index 00000000000..8776d219601 --- /dev/null +++ b/tests/baselines/reference/doesNotNarrowUnionOfConstructorsWithInstanceof.js @@ -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'. +} diff --git a/tests/baselines/reference/doesNotNarrowUnionOfConstructorsWithInstanceof.symbols b/tests/baselines/reference/doesNotNarrowUnionOfConstructorsWithInstanceof.symbols new file mode 100644 index 00000000000..efb71bef135 --- /dev/null +++ b/tests/baselines/reference/doesNotNarrowUnionOfConstructorsWithInstanceof.symbols @@ -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)) +} + diff --git a/tests/baselines/reference/doesNotNarrowUnionOfConstructorsWithInstanceof.types b/tests/baselines/reference/doesNotNarrowUnionOfConstructorsWithInstanceof.types new file mode 100644 index 00000000000..4e86d514f87 --- /dev/null +++ b/tests/baselines/reference/doesNotNarrowUnionOfConstructorsWithInstanceof.types @@ -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 +} + diff --git a/tests/cases/compiler/doesNotNarrowUnionOfConstructorsWithInstanceof.ts b/tests/cases/compiler/doesNotNarrowUnionOfConstructorsWithInstanceof.ts new file mode 100644 index 00000000000..be07d81d07e --- /dev/null +++ b/tests/cases/compiler/doesNotNarrowUnionOfConstructorsWithInstanceof.ts @@ -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'. +}