Improve handling of corner cases in `narrowTypeByInstanceof` (#52592)

This commit is contained in:
Anders Hejlsberg 2023-02-06 11:32:33 -08:00 коммит произвёл GitHub
Родитель 7bfe6ac49a
Коммит 720ec45be5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 601 добавлений и 27 удалений

Просмотреть файл

@ -27031,42 +27031,32 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
return type;
}
// Check that right operand is a function type with a prototype property
const rightType = getTypeOfExpression(expr.right);
if (!isTypeDerivedFrom(rightType, globalFunctionType)) {
return type;
}
let targetType: Type | undefined;
const prototypeProperty = getPropertyOfType(rightType, "prototype" as __String);
if (prototypeProperty) {
// Target type is type of the prototype property
const prototypePropertyType = getTypeOfSymbol(prototypeProperty);
if (!isTypeAny(prototypePropertyType)) {
targetType = prototypePropertyType;
}
}
// Don't narrow from 'any' if the target type is exactly 'Object' or 'Function'
if (isTypeAny(type) && (targetType === globalObjectType || targetType === globalFunctionType)) {
const instanceType = mapType(rightType, getInstanceType);
// Don't narrow from `any` if the target type is exactly `Object` or `Function`, and narrow
// in the false branch only if the target is a non-empty object type.
if (isTypeAny(type) && (instanceType === globalObjectType || instanceType === globalFunctionType) ||
!assumeTrue && !(instanceType.flags & TypeFlags.Object && !isEmptyAnonymousObjectType(instanceType))) {
return type;
}
return getNarrowedType(type, instanceType, assumeTrue, /*checkDerived*/ true);
}
if (!targetType) {
const constructSignatures = getSignaturesOfType(rightType, SignatureKind.Construct);
targetType = constructSignatures.length ?
getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature)))) :
emptyObjectType;
function getInstanceType(constructorType: Type) {
const prototypePropertyType = getTypeOfPropertyOfType(constructorType, "prototype" as __String);
if (prototypePropertyType && !isTypeAny(prototypePropertyType)) {
return prototypePropertyType;
}
// 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 as UnionType).types, (t) => !isConstructorType(t));
if (!nonConstructorTypeInUnion) return type;
const constructSignatures = getSignaturesOfType(constructorType, SignatureKind.Construct);
if (constructSignatures.length) {
return getUnionType(map(constructSignatures, signature => getReturnTypeOfSignature(getErasedSignature(signature))));
}
return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true);
// We use the empty object type to indicate we don't know the type of objects created by
// this constructor function.
return emptyObjectType;
}
function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) {

Просмотреть файл

@ -0,0 +1,149 @@
//// [narrowByInstanceof.ts]
interface A { a: string }
interface B { b: string }
interface C { c: string }
type AA = {
(): void;
prototype: A;
}
type BB = {
new(): B;
}
function foo(x: A | B | C, A: AA, B: BB, AB: AA | BB) {
if (x instanceof A) {
x; // A
}
else {
x; // B | C
}
if (x instanceof B) {
x; // B
}
else {
x; // A | C
}
if (x instanceof AB) {
x; // A | B
}
else {
x; // A | B | C
}
}
function bar(target: any, Promise: any) {
if (target instanceof Promise) {
target.__then();
}
}
// Repro from #52571
class PersonMixin extends Function {
public check(o: any) {
return typeof o === "object" && o !== null && o instanceof Person;
}
}
const cls = new PersonMixin();
class Person {
work(): void { console.log("work") }
sayHi(): void { console.log("Hi") }
}
class Car {
sayHi(): void { console.log("Wof Wof") }
}
function test(o: Person | Car) {
if (o instanceof cls) {
console.log("Is Person");
(o as Person).work()
}
else {
console.log("Is Car")
o.sayHi();
}
}
//// [narrowByInstanceof.js]
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
function foo(x, A, B, AB) {
if (x instanceof A) {
x; // A
}
else {
x; // B | C
}
if (x instanceof B) {
x; // B
}
else {
x; // A | C
}
if (x instanceof AB) {
x; // A | B
}
else {
x; // A | B | C
}
}
function bar(target, Promise) {
if (target instanceof Promise) {
target.__then();
}
}
// Repro from #52571
var PersonMixin = /** @class */ (function (_super) {
__extends(PersonMixin, _super);
function PersonMixin() {
return _super !== null && _super.apply(this, arguments) || this;
}
PersonMixin.prototype.check = function (o) {
return typeof o === "object" && o !== null && o instanceof Person;
};
return PersonMixin;
}(Function));
var cls = new PersonMixin();
var Person = /** @class */ (function () {
function Person() {
}
Person.prototype.work = function () { console.log("work"); };
Person.prototype.sayHi = function () { console.log("Hi"); };
return Person;
}());
var Car = /** @class */ (function () {
function Car() {
}
Car.prototype.sayHi = function () { console.log("Wof Wof"); };
return Car;
}());
function test(o) {
if (o instanceof cls) {
console.log("Is Person");
o.work();
}
else {
console.log("Is Car");
o.sayHi();
}
}

Просмотреть файл

@ -0,0 +1,174 @@
=== tests/cases/compiler/narrowByInstanceof.ts ===
interface A { a: string }
>A : Symbol(A, Decl(narrowByInstanceof.ts, 0, 0))
>a : Symbol(A.a, Decl(narrowByInstanceof.ts, 0, 13))
interface B { b: string }
>B : Symbol(B, Decl(narrowByInstanceof.ts, 0, 25))
>b : Symbol(B.b, Decl(narrowByInstanceof.ts, 1, 13))
interface C { c: string }
>C : Symbol(C, Decl(narrowByInstanceof.ts, 1, 25))
>c : Symbol(C.c, Decl(narrowByInstanceof.ts, 2, 13))
type AA = {
>AA : Symbol(AA, Decl(narrowByInstanceof.ts, 2, 25))
(): void;
prototype: A;
>prototype : Symbol(prototype, Decl(narrowByInstanceof.ts, 5, 13))
>A : Symbol(A, Decl(narrowByInstanceof.ts, 0, 0))
}
type BB = {
>BB : Symbol(BB, Decl(narrowByInstanceof.ts, 7, 1))
new(): B;
>B : Symbol(B, Decl(narrowByInstanceof.ts, 0, 25))
}
function foo(x: A | B | C, A: AA, B: BB, AB: AA | BB) {
>foo : Symbol(foo, Decl(narrowByInstanceof.ts, 11, 1))
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
>A : Symbol(A, Decl(narrowByInstanceof.ts, 0, 0))
>B : Symbol(B, Decl(narrowByInstanceof.ts, 0, 25))
>C : Symbol(C, Decl(narrowByInstanceof.ts, 1, 25))
>A : Symbol(A, Decl(narrowByInstanceof.ts, 13, 26))
>AA : Symbol(AA, Decl(narrowByInstanceof.ts, 2, 25))
>B : Symbol(B, Decl(narrowByInstanceof.ts, 13, 33))
>BB : Symbol(BB, Decl(narrowByInstanceof.ts, 7, 1))
>AB : Symbol(AB, Decl(narrowByInstanceof.ts, 13, 40))
>AA : Symbol(AA, Decl(narrowByInstanceof.ts, 2, 25))
>BB : Symbol(BB, Decl(narrowByInstanceof.ts, 7, 1))
if (x instanceof A) {
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
>A : Symbol(A, Decl(narrowByInstanceof.ts, 13, 26))
x; // A
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
}
else {
x; // B | C
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
}
if (x instanceof B) {
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
>B : Symbol(B, Decl(narrowByInstanceof.ts, 13, 33))
x; // B
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
}
else {
x; // A | C
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
}
if (x instanceof AB) {
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
>AB : Symbol(AB, Decl(narrowByInstanceof.ts, 13, 40))
x; // A | B
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
}
else {
x; // A | B | C
>x : Symbol(x, Decl(narrowByInstanceof.ts, 13, 13))
}
}
function bar(target: any, Promise: any) {
>bar : Symbol(bar, Decl(narrowByInstanceof.ts, 32, 1))
>target : Symbol(target, Decl(narrowByInstanceof.ts, 34, 13))
>Promise : Symbol(Promise, Decl(narrowByInstanceof.ts, 34, 25))
if (target instanceof Promise) {
>target : Symbol(target, Decl(narrowByInstanceof.ts, 34, 13))
>Promise : Symbol(Promise, Decl(narrowByInstanceof.ts, 34, 25))
target.__then();
>target : Symbol(target, Decl(narrowByInstanceof.ts, 34, 13))
}
}
// Repro from #52571
class PersonMixin extends Function {
>PersonMixin : Symbol(PersonMixin, Decl(narrowByInstanceof.ts, 38, 1))
>Function : Symbol(Function, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
public check(o: any) {
>check : Symbol(PersonMixin.check, Decl(narrowByInstanceof.ts, 42, 36))
>o : Symbol(o, Decl(narrowByInstanceof.ts, 43, 17))
return typeof o === "object" && o !== null && o instanceof Person;
>o : Symbol(o, Decl(narrowByInstanceof.ts, 43, 17))
>o : Symbol(o, Decl(narrowByInstanceof.ts, 43, 17))
>o : Symbol(o, Decl(narrowByInstanceof.ts, 43, 17))
>Person : Symbol(Person, Decl(narrowByInstanceof.ts, 48, 30))
}
}
const cls = new PersonMixin();
>cls : Symbol(cls, Decl(narrowByInstanceof.ts, 48, 5))
>PersonMixin : Symbol(PersonMixin, Decl(narrowByInstanceof.ts, 38, 1))
class Person {
>Person : Symbol(Person, Decl(narrowByInstanceof.ts, 48, 30))
work(): void { console.log("work") }
>work : Symbol(Person.work, Decl(narrowByInstanceof.ts, 50, 14))
>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, --, --))
sayHi(): void { console.log("Hi") }
>sayHi : Symbol(Person.sayHi, Decl(narrowByInstanceof.ts, 51, 40))
>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, --, --))
}
class Car {
>Car : Symbol(Car, Decl(narrowByInstanceof.ts, 53, 1))
sayHi(): void { console.log("Wof Wof") }
>sayHi : Symbol(Car.sayHi, Decl(narrowByInstanceof.ts, 55, 11))
>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, --, --))
}
function test(o: Person | Car) {
>test : Symbol(test, Decl(narrowByInstanceof.ts, 57, 1))
>o : Symbol(o, Decl(narrowByInstanceof.ts, 59, 14))
>Person : Symbol(Person, Decl(narrowByInstanceof.ts, 48, 30))
>Car : Symbol(Car, Decl(narrowByInstanceof.ts, 53, 1))
if (o instanceof cls) {
>o : Symbol(o, Decl(narrowByInstanceof.ts, 59, 14))
>cls : Symbol(cls, Decl(narrowByInstanceof.ts, 48, 5))
console.log("Is Person");
>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, --, --))
(o as Person).work()
>(o as Person).work : Symbol(Person.work, Decl(narrowByInstanceof.ts, 50, 14))
>o : Symbol(o, Decl(narrowByInstanceof.ts, 59, 14))
>Person : Symbol(Person, Decl(narrowByInstanceof.ts, 48, 30))
>work : Symbol(Person.work, Decl(narrowByInstanceof.ts, 50, 14))
}
else {
console.log("Is Car")
>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, --, --))
o.sayHi();
>o.sayHi : Symbol(sayHi, Decl(narrowByInstanceof.ts, 51, 40), Decl(narrowByInstanceof.ts, 55, 11))
>o : Symbol(o, Decl(narrowByInstanceof.ts, 59, 14))
>sayHi : Symbol(sayHi, Decl(narrowByInstanceof.ts, 51, 40), Decl(narrowByInstanceof.ts, 55, 11))
}
}

Просмотреть файл

@ -0,0 +1,190 @@
=== tests/cases/compiler/narrowByInstanceof.ts ===
interface A { a: string }
>a : string
interface B { b: string }
>b : string
interface C { c: string }
>c : string
type AA = {
>AA : { (): void; prototype: A; }
(): void;
prototype: A;
>prototype : A
}
type BB = {
>BB : new () => B
new(): B;
}
function foo(x: A | B | C, A: AA, B: BB, AB: AA | BB) {
>foo : (x: A | B | C, A: AA, B: BB, AB: AA | BB) => void
>x : A | B | C
>A : AA
>B : BB
>AB : AA | BB
if (x instanceof A) {
>x instanceof A : boolean
>x : A | B | C
>A : AA
x; // A
>x : A
}
else {
x; // B | C
>x : B | C
}
if (x instanceof B) {
>x instanceof B : boolean
>x : A | B | C
>B : BB
x; // B
>x : B
}
else {
x; // A | C
>x : A | C
}
if (x instanceof AB) {
>x instanceof AB : boolean
>x : A | B | C
>AB : AA | BB
x; // A | B
>x : A | B
}
else {
x; // A | B | C
>x : A | B | C
}
}
function bar(target: any, Promise: any) {
>bar : (target: any, Promise: any) => void
>target : any
>Promise : any
if (target instanceof Promise) {
>target instanceof Promise : boolean
>target : any
>Promise : any
target.__then();
>target.__then() : any
>target.__then : any
>target : any
>__then : any
}
}
// Repro from #52571
class PersonMixin extends Function {
>PersonMixin : PersonMixin
>Function : Function
public check(o: any) {
>check : (o: any) => boolean
>o : any
return typeof o === "object" && o !== null && o instanceof Person;
>typeof o === "object" && o !== null && o instanceof Person : boolean
>typeof o === "object" && o !== null : boolean
>typeof o === "object" : boolean
>typeof o : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>o : any
>"object" : "object"
>o !== null : boolean
>o : any
>null : null
>o instanceof Person : boolean
>o : any
>Person : typeof Person
}
}
const cls = new PersonMixin();
>cls : PersonMixin
>new PersonMixin() : PersonMixin
>PersonMixin : typeof PersonMixin
class Person {
>Person : Person
work(): void { console.log("work") }
>work : () => void
>console.log("work") : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>"work" : "work"
sayHi(): void { console.log("Hi") }
>sayHi : () => void
>console.log("Hi") : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>"Hi" : "Hi"
}
class Car {
>Car : Car
sayHi(): void { console.log("Wof Wof") }
>sayHi : () => void
>console.log("Wof Wof") : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>"Wof Wof" : "Wof Wof"
}
function test(o: Person | Car) {
>test : (o: Person | Car) => void
>o : Person | Car
if (o instanceof cls) {
>o instanceof cls : boolean
>o : Person | Car
>cls : PersonMixin
console.log("Is Person");
>console.log("Is Person") : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>"Is Person" : "Is Person"
(o as Person).work()
>(o as Person).work() : void
>(o as Person).work : () => void
>(o as Person) : Person
>o as Person : Person
>o : Person | Car
>work : () => void
}
else {
console.log("Is Car")
>console.log("Is Car") : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>"Is Car" : "Is Car"
o.sayHi();
>o.sayHi() : void
>o.sayHi : (() => void) | (() => void)
>o : Person | Car
>sayHi : (() => void) | (() => void)
}
}

Просмотреть файл

@ -0,0 +1,71 @@
// @strict: true
interface A { a: string }
interface B { b: string }
interface C { c: string }
type AA = {
(): void;
prototype: A;
}
type BB = {
new(): B;
}
function foo(x: A | B | C, A: AA, B: BB, AB: AA | BB) {
if (x instanceof A) {
x; // A
}
else {
x; // B | C
}
if (x instanceof B) {
x; // B
}
else {
x; // A | C
}
if (x instanceof AB) {
x; // A | B
}
else {
x; // A | B | C
}
}
function bar(target: any, Promise: any) {
if (target instanceof Promise) {
target.__then();
}
}
// Repro from #52571
class PersonMixin extends Function {
public check(o: any) {
return typeof o === "object" && o !== null && o instanceof Person;
}
}
const cls = new PersonMixin();
class Person {
work(): void { console.log("work") }
sayHi(): void { console.log("Hi") }
}
class Car {
sayHi(): void { console.log("Wof Wof") }
}
function test(o: Person | Car) {
if (o instanceof cls) {
console.log("Is Person");
(o as Person).work()
}
else {
console.log("Is Car")
o.sayHi();
}
}