Skip to content

Commit 6ebd73c

Browse files
author
Orta Therox
authored
Don't narrow against unions of constructor functions with instanceof (#38099)
* WIP * Only narrows when the union is not all constructors
1 parent 9a65658 commit 6ebd73c

5 files changed

+237
-0
lines changed

src/compiler/checker.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21800,6 +21800,12 @@ namespace ts {
2180021800
emptyObjectType;
2180121801
}
2180221802

21803+
// We can't narrow a union based off instanceof without negated types see #31576 for more info
21804+
if (!assumeTrue && rightType.flags & TypeFlags.Union) {
21805+
const nonConstructorTypeInUnion = find((<UnionType>rightType).types, (t) => !isConstructorType(t));
21806+
if (!nonConstructorTypeInUnion) return type;
21807+
}
21808+
2180321809
return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom);
2180421810
}
2180521811

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//// [doesNotNarrowUnionOfConstructorsWithInstanceof.ts]
2+
class A {
3+
length: 1
4+
constructor() {
5+
this.length = 1
6+
}
7+
}
8+
9+
class B {
10+
length: 2
11+
constructor() {
12+
this.length = 2
13+
}
14+
}
15+
16+
function getTypedArray(flag: boolean) {
17+
return flag ? new A() : new B();
18+
}
19+
function getTypedArrayConstructor(flag: boolean) {
20+
return flag ? A : B;
21+
}
22+
const a = getTypedArray(true); // A | B
23+
const b = getTypedArrayConstructor(false); // A constructor | B constructor
24+
25+
if (!(a instanceof b)) {
26+
console.log(a.length); // Used to be property 'length' does not exist on type 'never'.
27+
}
28+
29+
30+
//// [doesNotNarrowUnionOfConstructorsWithInstanceof.js]
31+
var A = /** @class */ (function () {
32+
function A() {
33+
this.length = 1;
34+
}
35+
return A;
36+
}());
37+
var B = /** @class */ (function () {
38+
function B() {
39+
this.length = 2;
40+
}
41+
return B;
42+
}());
43+
function getTypedArray(flag) {
44+
return flag ? new A() : new B();
45+
}
46+
function getTypedArrayConstructor(flag) {
47+
return flag ? A : B;
48+
}
49+
var a = getTypedArray(true); // A | B
50+
var b = getTypedArrayConstructor(false); // A constructor | B constructor
51+
if (!(a instanceof b)) {
52+
console.log(a.length); // Used to be property 'length' does not exist on type 'never'.
53+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
=== tests/cases/compiler/doesNotNarrowUnionOfConstructorsWithInstanceof.ts ===
2+
class A {
3+
>A : Symbol(A, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 0))
4+
5+
length: 1
6+
>length : Symbol(A.length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 9))
7+
8+
constructor() {
9+
this.length = 1
10+
>this.length : Symbol(A.length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 9))
11+
>this : Symbol(A, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 0))
12+
>length : Symbol(A.length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 9))
13+
}
14+
}
15+
16+
class B {
17+
>B : Symbol(B, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 5, 1))
18+
19+
length: 2
20+
>length : Symbol(B.length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 7, 9))
21+
22+
constructor() {
23+
this.length = 2
24+
>this.length : Symbol(B.length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 7, 9))
25+
>this : Symbol(B, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 5, 1))
26+
>length : Symbol(B.length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 7, 9))
27+
}
28+
}
29+
30+
function getTypedArray(flag: boolean) {
31+
>getTypedArray : Symbol(getTypedArray, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 12, 1))
32+
>flag : Symbol(flag, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 14, 23))
33+
34+
return flag ? new A() : new B();
35+
>flag : Symbol(flag, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 14, 23))
36+
>A : Symbol(A, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 0))
37+
>B : Symbol(B, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 5, 1))
38+
}
39+
function getTypedArrayConstructor(flag: boolean) {
40+
>getTypedArrayConstructor : Symbol(getTypedArrayConstructor, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 16, 1))
41+
>flag : Symbol(flag, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 17, 34))
42+
43+
return flag ? A : B;
44+
>flag : Symbol(flag, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 17, 34))
45+
>A : Symbol(A, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 0))
46+
>B : Symbol(B, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 5, 1))
47+
}
48+
const a = getTypedArray(true); // A | B
49+
>a : Symbol(a, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 20, 5))
50+
>getTypedArray : Symbol(getTypedArray, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 12, 1))
51+
52+
const b = getTypedArrayConstructor(false); // A constructor | B constructor
53+
>b : Symbol(b, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 21, 5))
54+
>getTypedArrayConstructor : Symbol(getTypedArrayConstructor, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 16, 1))
55+
56+
if (!(a instanceof b)) {
57+
>a : Symbol(a, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 20, 5))
58+
>b : Symbol(b, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 21, 5))
59+
60+
console.log(a.length); // Used to be property 'length' does not exist on type 'never'.
61+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
62+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
63+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
64+
>a.length : Symbol(length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 9), Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 7, 9))
65+
>a : Symbol(a, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 20, 5))
66+
>length : Symbol(length, Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 0, 9), Decl(doesNotNarrowUnionOfConstructorsWithInstanceof.ts, 7, 9))
67+
}
68+
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
=== tests/cases/compiler/doesNotNarrowUnionOfConstructorsWithInstanceof.ts ===
2+
class A {
3+
>A : A
4+
5+
length: 1
6+
>length : 1
7+
8+
constructor() {
9+
this.length = 1
10+
>this.length = 1 : 1
11+
>this.length : 1
12+
>this : this
13+
>length : 1
14+
>1 : 1
15+
}
16+
}
17+
18+
class B {
19+
>B : B
20+
21+
length: 2
22+
>length : 2
23+
24+
constructor() {
25+
this.length = 2
26+
>this.length = 2 : 2
27+
>this.length : 2
28+
>this : this
29+
>length : 2
30+
>2 : 2
31+
}
32+
}
33+
34+
function getTypedArray(flag: boolean) {
35+
>getTypedArray : (flag: boolean) => A | B
36+
>flag : boolean
37+
38+
return flag ? new A() : new B();
39+
>flag ? new A() : new B() : A | B
40+
>flag : boolean
41+
>new A() : A
42+
>A : typeof A
43+
>new B() : B
44+
>B : typeof B
45+
}
46+
function getTypedArrayConstructor(flag: boolean) {
47+
>getTypedArrayConstructor : (flag: boolean) => typeof A | typeof B
48+
>flag : boolean
49+
50+
return flag ? A : B;
51+
>flag ? A : B : typeof A | typeof B
52+
>flag : boolean
53+
>A : typeof A
54+
>B : typeof B
55+
}
56+
const a = getTypedArray(true); // A | B
57+
>a : A | B
58+
>getTypedArray(true) : A | B
59+
>getTypedArray : (flag: boolean) => A | B
60+
>true : true
61+
62+
const b = getTypedArrayConstructor(false); // A constructor | B constructor
63+
>b : typeof A | typeof B
64+
>getTypedArrayConstructor(false) : typeof A | typeof B
65+
>getTypedArrayConstructor : (flag: boolean) => typeof A | typeof B
66+
>false : false
67+
68+
if (!(a instanceof b)) {
69+
>!(a instanceof b) : boolean
70+
>(a instanceof b) : boolean
71+
>a instanceof b : boolean
72+
>a : A | B
73+
>b : typeof A | typeof B
74+
75+
console.log(a.length); // Used to be property 'length' does not exist on type 'never'.
76+
>console.log(a.length) : void
77+
>console.log : (...data: any[]) => void
78+
>console : Console
79+
>log : (...data: any[]) => void
80+
>a.length : 1 | 2
81+
>a : A | B
82+
>length : 1 | 2
83+
}
84+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
class A {
2+
length: 1
3+
constructor() {
4+
this.length = 1
5+
}
6+
}
7+
8+
class B {
9+
length: 2
10+
constructor() {
11+
this.length = 2
12+
}
13+
}
14+
15+
function getTypedArray(flag: boolean) {
16+
return flag ? new A() : new B();
17+
}
18+
function getTypedArrayConstructor(flag: boolean) {
19+
return flag ? A : B;
20+
}
21+
const a = getTypedArray(true); // A | B
22+
const b = getTypedArrayConstructor(false); // A constructor | B constructor
23+
24+
if (!(a instanceof b)) {
25+
console.log(a.length); // Used to be property 'length' does not exist on type 'never'.
26+
}

0 commit comments

Comments
 (0)