Skip to content

Commit 720ec45

Browse files
authored
Improve handling of corner cases in narrowTypeByInstanceof (#52592)
1 parent 7bfe6ac commit 720ec45

File tree

5 files changed

+601
-27
lines changed

5 files changed

+601
-27
lines changed

src/compiler/checker.ts

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27031,42 +27031,32 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2703127031
}
2703227032
return type;
2703327033
}
27034-
27035-
// Check that right operand is a function type with a prototype property
2703627034
const rightType = getTypeOfExpression(expr.right);
2703727035
if (!isTypeDerivedFrom(rightType, globalFunctionType)) {
2703827036
return type;
2703927037
}
27040-
27041-
let targetType: Type | undefined;
27042-
const prototypeProperty = getPropertyOfType(rightType, "prototype" as __String);
27043-
if (prototypeProperty) {
27044-
// Target type is type of the prototype property
27045-
const prototypePropertyType = getTypeOfSymbol(prototypeProperty);
27046-
if (!isTypeAny(prototypePropertyType)) {
27047-
targetType = prototypePropertyType;
27048-
}
27049-
}
27050-
27051-
// Don't narrow from 'any' if the target type is exactly 'Object' or 'Function'
27052-
if (isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) {
27038+
const instanceType = mapType(rightType, getInstanceType);
27039+
// Don't narrow from `any` if the target type is exactly `Object` or `Function`, and narrow
27040+
// in the false branch only if the target is a non-empty object type.
27041+
if (isTypeAny(type) && (instanceType === globalObjectType || instanceType === globalFunctionType) ||
27042+
!assumeTrue && !(instanceType.flags & TypeFlags.Object && !isEmptyAnonymousObjectType(instanceType))) {
2705327043
return type;
2705427044
}
27045+
return getNarrowedType(type, instanceType, assumeTrue, /*checkDerived*/ true);
27046+
}
2705527047

27056-
if (!targetType) {
27057-
const constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct);
27058-
targetType = constructSignatures.length ?
27059-
getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))) :
27060-
emptyObjectType;
27048+
function getInstanceType(constructorType: Type) {
27049+
const prototypePropertyType = getTypeOfPropertyOfType(constructorType, "prototype" as __String);
27050+
if (prototypePropertyType && !isTypeAny(prototypePropertyType)) {
27051+
return prototypePropertyType;
2706127052
}
27062-
27063-
// We can't narrow a union based off instanceof without negated types see #31576 for more info
27064-
if (!assumeTrue && rightType.flags & TypeFlags.Union) {
27065-
const nonConstructorTypeInUnion = find((rightType as UnionType).types, (t) => !isConstructorType(t));
27066-
if (!nonConstructorTypeInUnion) return type;
27053+
const constructSignatures = getSignaturesOfType(constructorType, SignatureKind.Construct);
27054+
if (constructSignatures.length) {
27055+
return getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature))));
2706727056
}
27068-
27069-
return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true);
27057+
// We use the empty object type to indicate we don't know the type of objects created by
27058+
// this constructor function.
27059+
return emptyObjectType;
2707027060
}
2707127061

2707227062
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) {
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//// [narrowByInstanceof.ts]
2+
interface A { a: string }
3+
interface B { b: string }
4+
interface C { c: string }
5+
6+
type AA = {
7+
(): void;
8+
prototype: A;
9+
}
10+
11+
type BB = {
12+
new(): B;
13+
}
14+
15+
function foo(x: A | B | C, A: AA, B: BB, AB: AA | BB) {
16+
if (x instanceof A) {
17+
x; // A
18+
}
19+
else {
20+
x; // B | C
21+
}
22+
if (x instanceof B) {
23+
x; // B
24+
}
25+
else {
26+
x; // A | C
27+
}
28+
if (x instanceof AB) {
29+
x; // A | B
30+
}
31+
else {
32+
x; // A | B | C
33+
}
34+
}
35+
36+
function bar(target: any, Promise: any) {
37+
if (target instanceof Promise) {
38+
target.__then();
39+
}
40+
}
41+
42+
// Repro from #52571
43+
44+
class PersonMixin extends Function {
45+
public check(o: any) {
46+
return typeof o === "object" && o !== null && o instanceof Person;
47+
}
48+
}
49+
50+
const cls = new PersonMixin();
51+
52+
class Person {
53+
work(): void { console.log("work") }
54+
sayHi(): void { console.log("Hi") }
55+
}
56+
57+
class Car {
58+
sayHi(): void { console.log("Wof Wof") }
59+
}
60+
61+
function test(o: Person | Car) {
62+
if (o instanceof cls) {
63+
console.log("Is Person");
64+
(o as Person).work()
65+
}
66+
else {
67+
console.log("Is Car")
68+
o.sayHi();
69+
}
70+
}
71+
72+
73+
//// [narrowByInstanceof.js]
74+
"use strict";
75+
var __extends = (this && this.__extends) || (function () {
76+
var extendStatics = function (d, b) {
77+
extendStatics = Object.setPrototypeOf ||
78+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
79+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
80+
return extendStatics(d, b);
81+
};
82+
return function (d, b) {
83+
if (typeof b !== "function" && b !== null)
84+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
85+
extendStatics(d, b);
86+
function __() { this.constructor = d; }
87+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
88+
};
89+
})();
90+
function foo(x, A, B, AB) {
91+
if (x instanceof A) {
92+
x; // A
93+
}
94+
else {
95+
x; // B | C
96+
}
97+
if (x instanceof B) {
98+
x; // B
99+
}
100+
else {
101+
x; // A | C
102+
}
103+
if (x instanceof AB) {
104+
x; // A | B
105+
}
106+
else {
107+
x; // A | B | C
108+
}
109+
}
110+
function bar(target, Promise) {
111+
if (target instanceof Promise) {
112+
target.__then();
113+
}
114+
}
115+
// Repro from #52571
116+
var PersonMixin = /** @class */ (function (_super) {
117+
__extends(PersonMixin, _super);
118+
function PersonMixin() {
119+
return _super !== null && _super.apply(this, arguments) || this;
120+
}
121+
PersonMixin.prototype.check = function (o) {
122+
return typeof o === "object" && o !== null && o instanceof Person;
123+
};
124+
return PersonMixin;
125+
}(Function));
126+
var cls = new PersonMixin();
127+
var Person = /** @class */ (function () {
128+
function Person() {
129+
}
130+
Person.prototype.work = function () { console.log("work"); };
131+
Person.prototype.sayHi = function () { console.log("Hi"); };
132+
return Person;
133+
}());
134+
var Car = /** @class */ (function () {
135+
function Car() {
136+
}
137+
Car.prototype.sayHi = function () { console.log("Wof Wof"); };
138+
return Car;
139+
}());
140+
function test(o) {
141+
if (o instanceof cls) {
142+
console.log("Is Person");
143+
o.work();
144+
}
145+
else {
146+
console.log("Is Car");
147+
o.sayHi();
148+
}
149+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
=== tests/cases/compiler/narrowByInstanceof.ts ===
2+
interface A { a: string }
3+
>A : Symbol(A, Decl(narrowByInstanceof.ts, 0, 0))
4+
>a : Symbol(A.a, Decl(narrowByInstanceof.ts, 0, 13))
5+
6+
interface B { b: string }
7+
>B : Symbol(B, Decl(narrowByInstanceof.ts, 0, 25))
8+
>b : Symbol(B.b, Decl(narrowByInstanceof.ts, 1, 13))
9+
10+
interface C { c: string }
11+
>C : Symbol(C, Decl(narrowByInstanceof.ts, 1, 25))
12+
>c : Symbol(C.c, Decl(narrowByInstanceof.ts, 2, 13))
13+
14+
type AA = {
15+
>AA : Symbol(AA, Decl(narrowByInstanceof.ts, 2, 25))
16+
17+
(): void;
18+
prototype: A;
19+
>prototype : Symbol(prototype, Decl(narrowByInstanceof.ts, 5, 13))
20+
>A : Symbol(A, Decl(narrowByInstanceof.ts, 0, 0))
21+
}
22+
23+
type BB = {
24+
>BB : Symbol(BB, Decl(narrowByInstanceof.ts, 7, 1))
25+
26+
new(): B;
27+
>B : Symbol(B, Decl(narrowByInstanceof.ts, 0, 25))
28+
}
29+
30+
function foo(x: A | B | C, A: AA, B: BB, AB: AA | BB) {
31+
>foo : Symbol(foo, Decl(narrowByInstanceof.ts, 11, 1))
32+
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
33+
>A : Symbol(A, Decl(narrowByInstanceof.ts, 0, 0))
34+
>B : Symbol(B, Decl(narrowByInstanceof.ts, 0, 25))
35+
>C : Symbol(C, Decl(narrowByInstanceof.ts, 1, 25))
36+
>A : Symbol(A, Decl(narrowByInstanceof.ts, 13, 26))
37+
>AA : Symbol(AA, Decl(narrowByInstanceof.ts, 2, 25))
38+
>B : Symbol(B, Decl(narrowByInstanceof.ts, 13, 33))
39+
>BB : Symbol(BB, Decl(narrowByInstanceof.ts, 7, 1))
40+
>AB : Symbol(AB, Decl(narrowByInstanceof.ts, 13, 40))
41+
>AA : Symbol(AA, Decl(narrowByInstanceof.ts, 2, 25))
42+
>BB : Symbol(BB, Decl(narrowByInstanceof.ts, 7, 1))
43+
44+
if (x instanceof A) {
45+
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
46+
>A : Symbol(A, Decl(narrowByInstanceof.ts, 13, 26))
47+
48+
x; // A
49+
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
50+
}
51+
else {
52+
x; // B | C
53+
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
54+
}
55+
if (x instanceof B) {
56+
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
57+
>B : Symbol(B, Decl(narrowByInstanceof.ts, 13, 33))
58+
59+
x; // B
60+
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
61+
}
62+
else {
63+
x; // A | C
64+
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
65+
}
66+
if (x instanceof AB) {
67+
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
68+
>AB : Symbol(AB, Decl(narrowByInstanceof.ts, 13, 40))
69+
70+
x; // A | B
71+
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
72+
}
73+
else {
74+
x; // A | B | C
75+
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
76+
}
77+
}
78+
79+
function bar(target: any, Promise: any) {
80+
>bar : Symbol(bar, Decl(narrowByInstanceof.ts, 32, 1))
81+
>target : Symbol(target, Decl(narrowByInstanceof.ts, 34, 13))
82+
>Promise : Symbol(Promise, Decl(narrowByInstanceof.ts, 34, 25))
83+
84+
if (target instanceof Promise) {
85+
>target : Symbol(target, Decl(narrowByInstanceof.ts, 34, 13))
86+
>Promise : Symbol(Promise, Decl(narrowByInstanceof.ts, 34, 25))
87+
88+
target.__then();
89+
>target : Symbol(target, Decl(narrowByInstanceof.ts, 34, 13))
90+
}
91+
}
92+
93+
// Repro from #52571
94+
95+
class PersonMixin extends Function {
96+
>PersonMixin : Symbol(PersonMixin, Decl(narrowByInstanceof.ts, 38, 1))
97+
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
98+
99+
public check(o: any) {
100+
>check : Symbol(PersonMixin.check, Decl(narrowByInstanceof.ts, 42, 36))
101+
>o : Symbol(o, Decl(narrowByInstanceof.ts, 43, 17))
102+
103+
return typeof o === "object" && o !== null && o instanceof Person;
104+
>o : Symbol(o, Decl(narrowByInstanceof.ts, 43, 17))
105+
>o : Symbol(o, Decl(narrowByInstanceof.ts, 43, 17))
106+
>o : Symbol(o, Decl(narrowByInstanceof.ts, 43, 17))
107+
>Person : Symbol(Person, Decl(narrowByInstanceof.ts, 48, 30))
108+
}
109+
}
110+
111+
const cls = new PersonMixin();
112+
>cls : Symbol(cls, Decl(narrowByInstanceof.ts, 48, 5))
113+
>PersonMixin : Symbol(PersonMixin, Decl(narrowByInstanceof.ts, 38, 1))
114+
115+
class Person {
116+
>Person : Symbol(Person, Decl(narrowByInstanceof.ts, 48, 30))
117+
118+
work(): void { console.log("work") }
119+
>work : Symbol(Person.work, Decl(narrowByInstanceof.ts, 50, 14))
120+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
121+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
122+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
123+
124+
sayHi(): void { console.log("Hi") }
125+
>sayHi : Symbol(Person.sayHi, Decl(narrowByInstanceof.ts, 51, 40))
126+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
127+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
128+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
129+
}
130+
131+
class Car {
132+
>Car : Symbol(Car, Decl(narrowByInstanceof.ts, 53, 1))
133+
134+
sayHi(): void { console.log("Wof Wof") }
135+
>sayHi : Symbol(Car.sayHi, Decl(narrowByInstanceof.ts, 55, 11))
136+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
137+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
138+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
139+
}
140+
141+
function test(o: Person | Car) {
142+
>test : Symbol(test, Decl(narrowByInstanceof.ts, 57, 1))
143+
>o : Symbol(o, Decl(narrowByInstanceof.ts, 59, 14))
144+
>Person : Symbol(Person, Decl(narrowByInstanceof.ts, 48, 30))
145+
>Car : Symbol(Car, Decl(narrowByInstanceof.ts, 53, 1))
146+
147+
if (o instanceof cls) {
148+
>o : Symbol(o, Decl(narrowByInstanceof.ts, 59, 14))
149+
>cls : Symbol(cls, Decl(narrowByInstanceof.ts, 48, 5))
150+
151+
console.log("Is Person");
152+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
153+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
154+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
155+
156+
(o as Person).work()
157+
>(o as Person).work : Symbol(Person.work, Decl(narrowByInstanceof.ts, 50, 14))
158+
>o : Symbol(o, Decl(narrowByInstanceof.ts, 59, 14))
159+
>Person : Symbol(Person, Decl(narrowByInstanceof.ts, 48, 30))
160+
>work : Symbol(Person.work, Decl(narrowByInstanceof.ts, 50, 14))
161+
}
162+
else {
163+
console.log("Is Car")
164+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
165+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
166+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
167+
168+
o.sayHi();
169+
>o.sayHi : Symbol(sayHi, Decl(narrowByInstanceof.ts, 51, 40), Decl(narrowByInstanceof.ts, 55, 11))
170+
>o : Symbol(o, Decl(narrowByInstanceof.ts, 59, 14))
171+
>sayHi : Symbol(sayHi, Decl(narrowByInstanceof.ts, 51, 40), Decl(narrowByInstanceof.ts, 55, 11))
172+
}
173+
}
174+

0 commit comments

Comments
 (0)