Fix isTypeDerivedFrom to properly handle {} and intersections (#51631)

* Fix isTypeDerivedFrom to properly handle {} and intersections

* Add tests
This commit is contained in:
Anders Hejlsberg
2022-11-29 11:29:35 -05:00
committed by GitHub
parent c460e7e892
commit cee6366c48
10 changed files with 571 additions and 2 deletions

View File

@@ -18759,7 +18759,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// An object type S is considered to be derived from an object type T if
// S is a union type and every constituent of S is derived from T,
// T is a union type and S is derived from at least one constituent of T, or
// S is a type variable with a base constraint that is derived from T,
// S is an intersection type and some constituent of S is derived from T, or
// S is a type variable with a base constraint that is derived from T, or
// T is {} and S is an object-like type (ensuring {} is less derived than Object), or
// T is one of the global types Object and Function and S is a subtype of T, or
// T occurs directly or indirectly in an 'extends' clause of S.
// Note that this check ignores type parameters and only considers the
@@ -18767,8 +18769,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function isTypeDerivedFrom(source: Type, target: Type): boolean {
return source.flags & TypeFlags.Union ? every((source as UnionType).types, t => isTypeDerivedFrom(t, target)) :
target.flags & TypeFlags.Union ? some((target as UnionType).types, t => isTypeDerivedFrom(source, t)) :
source.flags & TypeFlags.Intersection ? some((source as IntersectionType).types, t => isTypeDerivedFrom(t, target)) :
source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) :
target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) :
isEmptyAnonymousObjectType(target) ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) :
target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) && !isEmptyAnonymousObjectType(source) :
target === globalFunctionType ? !!(source.flags & TypeFlags.Object) && isFunctionObjectType(source as ObjectType) :
hasBaseType(source, getTargetType(target)) || (isArrayType(target) && !isReadonlyArrayType(target) && isTypeDerivedFrom(source, globalReadonlyArrayType));
}

View File

@@ -0,0 +1,59 @@
//// [inKeywordAndIntersection.ts]
class A { a = 0 }
class B { b = 0 }
function f10(obj: A & { x: string } | B) {
if (obj instanceof Object) {
obj; // A & { x: string } | B
}
else {
obj; // Error
}
}
// Repro from #50844
interface InstanceOne {
one(): void
}
interface InstanceTwo {
two(): void
}
const instance = {} as InstanceOne | InstanceTwo
const ClassOne = {} as { new(): InstanceOne } & { foo: true };
if (instance instanceof ClassOne) {
instance.one();
}
//// [inKeywordAndIntersection.js]
"use strict";
var A = /** @class */ (function () {
function A() {
this.a = 0;
}
return A;
}());
var B = /** @class */ (function () {
function B() {
this.b = 0;
}
return B;
}());
function f10(obj) {
if (obj instanceof Object) {
obj; // A & { x: string } | B
}
else {
obj; // Error
}
}
var instance = {};
var ClassOne = {};
if (instance instanceof ClassOne) {
instance.one();
}

View File

@@ -0,0 +1,65 @@
=== tests/cases/compiler/inKeywordAndIntersection.ts ===
class A { a = 0 }
>A : Symbol(A, Decl(inKeywordAndIntersection.ts, 0, 0))
>a : Symbol(A.a, Decl(inKeywordAndIntersection.ts, 0, 9))
class B { b = 0 }
>B : Symbol(B, Decl(inKeywordAndIntersection.ts, 0, 17))
>b : Symbol(B.b, Decl(inKeywordAndIntersection.ts, 1, 9))
function f10(obj: A & { x: string } | B) {
>f10 : Symbol(f10, Decl(inKeywordAndIntersection.ts, 1, 17))
>obj : Symbol(obj, Decl(inKeywordAndIntersection.ts, 3, 13))
>A : Symbol(A, Decl(inKeywordAndIntersection.ts, 0, 0))
>x : Symbol(x, Decl(inKeywordAndIntersection.ts, 3, 23))
>B : Symbol(B, Decl(inKeywordAndIntersection.ts, 0, 17))
if (obj instanceof Object) {
>obj : Symbol(obj, Decl(inKeywordAndIntersection.ts, 3, 13))
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
obj; // A & { x: string } | B
>obj : Symbol(obj, Decl(inKeywordAndIntersection.ts, 3, 13))
}
else {
obj; // Error
>obj : Symbol(obj, Decl(inKeywordAndIntersection.ts, 3, 13))
}
}
// Repro from #50844
interface InstanceOne {
>InstanceOne : Symbol(InstanceOne, Decl(inKeywordAndIntersection.ts, 10, 1))
one(): void
>one : Symbol(InstanceOne.one, Decl(inKeywordAndIntersection.ts, 14, 23))
}
interface InstanceTwo {
>InstanceTwo : Symbol(InstanceTwo, Decl(inKeywordAndIntersection.ts, 16, 1))
two(): void
>two : Symbol(InstanceTwo.two, Decl(inKeywordAndIntersection.ts, 18, 23))
}
const instance = {} as InstanceOne | InstanceTwo
>instance : Symbol(instance, Decl(inKeywordAndIntersection.ts, 22, 5))
>InstanceOne : Symbol(InstanceOne, Decl(inKeywordAndIntersection.ts, 10, 1))
>InstanceTwo : Symbol(InstanceTwo, Decl(inKeywordAndIntersection.ts, 16, 1))
const ClassOne = {} as { new(): InstanceOne } & { foo: true };
>ClassOne : Symbol(ClassOne, Decl(inKeywordAndIntersection.ts, 24, 5))
>InstanceOne : Symbol(InstanceOne, Decl(inKeywordAndIntersection.ts, 10, 1))
>foo : Symbol(foo, Decl(inKeywordAndIntersection.ts, 24, 49))
if (instance instanceof ClassOne) {
>instance : Symbol(instance, Decl(inKeywordAndIntersection.ts, 22, 5))
>ClassOne : Symbol(ClassOne, Decl(inKeywordAndIntersection.ts, 24, 5))
instance.one();
>instance.one : Symbol(InstanceOne.one, Decl(inKeywordAndIntersection.ts, 14, 23))
>instance : Symbol(instance, Decl(inKeywordAndIntersection.ts, 22, 5))
>one : Symbol(InstanceOne.one, Decl(inKeywordAndIntersection.ts, 14, 23))
}

View File

@@ -0,0 +1,66 @@
=== tests/cases/compiler/inKeywordAndIntersection.ts ===
class A { a = 0 }
>A : A
>a : number
>0 : 0
class B { b = 0 }
>B : B
>b : number
>0 : 0
function f10(obj: A & { x: string } | B) {
>f10 : (obj: (A & { x: string;}) | B) => void
>obj : B | (A & { x: string; })
>x : string
if (obj instanceof Object) {
>obj instanceof Object : boolean
>obj : B | (A & { x: string; })
>Object : ObjectConstructor
obj; // A & { x: string } | B
>obj : B | (A & { x: string; })
}
else {
obj; // Error
>obj : never
}
}
// Repro from #50844
interface InstanceOne {
one(): void
>one : () => void
}
interface InstanceTwo {
two(): void
>two : () => void
}
const instance = {} as InstanceOne | InstanceTwo
>instance : InstanceOne | InstanceTwo
>{} as InstanceOne | InstanceTwo : InstanceOne | InstanceTwo
>{} : {}
const ClassOne = {} as { new(): InstanceOne } & { foo: true };
>ClassOne : (new () => InstanceOne) & { foo: true; }
>{} as { new(): InstanceOne } & { foo: true } : (new () => InstanceOne) & { foo: true; }
>{} : {}
>foo : true
>true : true
if (instance instanceof ClassOne) {
>instance instanceof ClassOne : boolean
>instance : InstanceOne | InstanceTwo
>ClassOne : (new () => InstanceOne) & { foo: true; }
instance.one();
>instance.one() : void
>instance.one : () => void
>instance : InstanceOne
>one : () => void
}

View File

@@ -20,4 +20,38 @@ tests/cases/compiler/inKeywordAndUnknown.ts(12,18): error TS2638: Type '{}' may
}
y; // {}
}
// Repro from #51007
function isHTMLTable(table: unknown): boolean {
return !!table && table instanceof Object && 'html' in table;
}
function f1(x: unknown) {
return x && x instanceof Object && 'a' in x;
}
function f2<T>(x: T) {
return x && x instanceof Object && 'a' in x;
}
function f3(x: {}) {
return x instanceof Object && 'a' in x;
}
function f4<T extends {}>(x: T) {
return x instanceof Object && 'a' in x;
}
function f5<T>(x: T & {}) {
return x instanceof Object && 'a' in x;
}
function f6<T extends {}>(x: T & {}) {
return x instanceof Object && 'a' in x;
}
function f7<T extends object>(x: T & {}) {
return x instanceof Object && 'a' in x;
}

View File

@@ -15,6 +15,40 @@ function f(x: {}, y: unknown) {
}
y; // {}
}
// Repro from #51007
function isHTMLTable(table: unknown): boolean {
return !!table && table instanceof Object && 'html' in table;
}
function f1(x: unknown) {
return x && x instanceof Object && 'a' in x;
}
function f2<T>(x: T) {
return x && x instanceof Object && 'a' in x;
}
function f3(x: {}) {
return x instanceof Object && 'a' in x;
}
function f4<T extends {}>(x: T) {
return x instanceof Object && 'a' in x;
}
function f5<T>(x: T & {}) {
return x instanceof Object && 'a' in x;
}
function f6<T extends {}>(x: T & {}) {
return x instanceof Object && 'a' in x;
}
function f7<T extends object>(x: T & {}) {
return x instanceof Object && 'a' in x;
}
//// [inKeywordAndUnknown.js]
@@ -34,3 +68,28 @@ function f(x, y) {
}
y; // {}
}
// Repro from #51007
function isHTMLTable(table) {
return !!table && table instanceof Object && 'html' in table;
}
function f1(x) {
return x && x instanceof Object && 'a' in x;
}
function f2(x) {
return x && x instanceof Object && 'a' in x;
}
function f3(x) {
return x instanceof Object && 'a' in x;
}
function f4(x) {
return x instanceof Object && 'a' in x;
}
function f5(x) {
return x instanceof Object && 'a' in x;
}
function f6(x) {
return x instanceof Object && 'a' in x;
}
function f7(x) {
return x instanceof Object && 'a' in x;
}

View File

@@ -31,3 +31,98 @@ function f(x: {}, y: unknown) {
>y : Symbol(y, Decl(inKeywordAndUnknown.ts, 2, 17))
}
// Repro from #51007
function isHTMLTable(table: unknown): boolean {
>isHTMLTable : Symbol(isHTMLTable, Decl(inKeywordAndUnknown.ts, 15, 1))
>table : Symbol(table, Decl(inKeywordAndUnknown.ts, 19, 21))
return !!table && table instanceof Object && 'html' in table;
>table : Symbol(table, Decl(inKeywordAndUnknown.ts, 19, 21))
>table : Symbol(table, Decl(inKeywordAndUnknown.ts, 19, 21))
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>table : Symbol(table, Decl(inKeywordAndUnknown.ts, 19, 21))
}
function f1(x: unknown) {
>f1 : Symbol(f1, Decl(inKeywordAndUnknown.ts, 21, 1))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 23, 12))
return x && x instanceof Object && 'a' in x;
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 23, 12))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 23, 12))
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 23, 12))
}
function f2<T>(x: T) {
>f2 : Symbol(f2, Decl(inKeywordAndUnknown.ts, 25, 1))
>T : Symbol(T, Decl(inKeywordAndUnknown.ts, 27, 12))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 27, 15))
>T : Symbol(T, Decl(inKeywordAndUnknown.ts, 27, 12))
return x && x instanceof Object && 'a' in x;
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 27, 15))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 27, 15))
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 27, 15))
}
function f3(x: {}) {
>f3 : Symbol(f3, Decl(inKeywordAndUnknown.ts, 29, 1))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 31, 12))
return x instanceof Object && 'a' in x;
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 31, 12))
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 31, 12))
}
function f4<T extends {}>(x: T) {
>f4 : Symbol(f4, Decl(inKeywordAndUnknown.ts, 33, 1))
>T : Symbol(T, Decl(inKeywordAndUnknown.ts, 35, 12))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 35, 26))
>T : Symbol(T, Decl(inKeywordAndUnknown.ts, 35, 12))
return x instanceof Object && 'a' in x;
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 35, 26))
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 35, 26))
}
function f5<T>(x: T & {}) {
>f5 : Symbol(f5, Decl(inKeywordAndUnknown.ts, 37, 1))
>T : Symbol(T, Decl(inKeywordAndUnknown.ts, 39, 12))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 39, 15))
>T : Symbol(T, Decl(inKeywordAndUnknown.ts, 39, 12))
return x instanceof Object && 'a' in x;
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 39, 15))
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 39, 15))
}
function f6<T extends {}>(x: T & {}) {
>f6 : Symbol(f6, Decl(inKeywordAndUnknown.ts, 41, 1))
>T : Symbol(T, Decl(inKeywordAndUnknown.ts, 43, 12))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 43, 26))
>T : Symbol(T, Decl(inKeywordAndUnknown.ts, 43, 12))
return x instanceof Object && 'a' in x;
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 43, 26))
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 43, 26))
}
function f7<T extends object>(x: T & {}) {
>f7 : Symbol(f7, Decl(inKeywordAndUnknown.ts, 45, 1))
>T : Symbol(T, Decl(inKeywordAndUnknown.ts, 47, 12))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 47, 30))
>T : Symbol(T, Decl(inKeywordAndUnknown.ts, 47, 12))
return x instanceof Object && 'a' in x;
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 47, 30))
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(inKeywordAndUnknown.ts, 47, 30))
}

View File

@@ -40,3 +40,125 @@ function f(x: {}, y: unknown) {
>y : Record<"a", unknown>
}
// Repro from #51007
function isHTMLTable(table: unknown): boolean {
>isHTMLTable : (table: unknown) => boolean
>table : unknown
return !!table && table instanceof Object && 'html' in table;
>!!table && table instanceof Object && 'html' in table : boolean
>!!table && table instanceof Object : boolean
>!!table : boolean
>!table : boolean
>table : unknown
>table instanceof Object : boolean
>table : {}
>Object : ObjectConstructor
>'html' in table : boolean
>'html' : "html"
>table : Object
}
function f1(x: unknown) {
>f1 : (x: unknown) => unknown
>x : unknown
return x && x instanceof Object && 'a' in x;
>x && x instanceof Object && 'a' in x : unknown
>x && x instanceof Object : unknown
>x : unknown
>x instanceof Object : boolean
>x : {}
>Object : ObjectConstructor
>'a' in x : boolean
>'a' : "a"
>x : Object
}
function f2<T>(x: T) {
>f2 : <T>(x: T) => boolean
>x : T
return x && x instanceof Object && 'a' in x;
>x && x instanceof Object && 'a' in x : boolean
>x && x instanceof Object : boolean
>x : T
>x instanceof Object : boolean
>x : NonNullable<T>
>Object : ObjectConstructor
>'a' in x : boolean
>'a' : "a"
>x : T & Object
}
function f3(x: {}) {
>f3 : (x: {}) => boolean
>x : {}
return x instanceof Object && 'a' in x;
>x instanceof Object && 'a' in x : boolean
>x instanceof Object : boolean
>x : {}
>Object : ObjectConstructor
>'a' in x : boolean
>'a' : "a"
>x : Object
}
function f4<T extends {}>(x: T) {
>f4 : <T extends {}>(x: T) => boolean
>x : T
return x instanceof Object && 'a' in x;
>x instanceof Object && 'a' in x : boolean
>x instanceof Object : boolean
>x : T
>Object : ObjectConstructor
>'a' in x : boolean
>'a' : "a"
>x : T & Object
}
function f5<T>(x: T & {}) {
>f5 : <T>(x: T & {}) => boolean
>x : T & {}
return x instanceof Object && 'a' in x;
>x instanceof Object && 'a' in x : boolean
>x instanceof Object : boolean
>x : T & {}
>Object : ObjectConstructor
>'a' in x : boolean
>'a' : "a"
>x : T & Object
}
function f6<T extends {}>(x: T & {}) {
>f6 : <T extends {}>(x: T & {}) => boolean
>x : T & {}
return x instanceof Object && 'a' in x;
>x instanceof Object && 'a' in x : boolean
>x instanceof Object : boolean
>x : T & {}
>Object : ObjectConstructor
>'a' in x : boolean
>'a' : "a"
>x : T & Object
}
function f7<T extends object>(x: T & {}) {
>f7 : <T extends object>(x: T & {}) => boolean
>x : T & {}
return x instanceof Object && 'a' in x;
>x instanceof Object && 'a' in x : boolean
>x instanceof Object : boolean
>x : T & {}
>Object : ObjectConstructor
>'a' in x : boolean
>'a' : "a"
>x : T & {}
}

View File

@@ -0,0 +1,31 @@
// @strict: true
class A { a = 0 }
class B { b = 0 }
function f10(obj: A & { x: string } | B) {
if (obj instanceof Object) {
obj; // A & { x: string } | B
}
else {
obj; // Error
}
}
// Repro from #50844
interface InstanceOne {
one(): void
}
interface InstanceTwo {
two(): void
}
const instance = {} as InstanceOne | InstanceTwo
const ClassOne = {} as { new(): InstanceOne } & { foo: true };
if (instance instanceof ClassOne) {
instance.one();
}

View File

@@ -16,3 +16,37 @@ function f(x: {}, y: unknown) {
}
y; // {}
}
// Repro from #51007
function isHTMLTable(table: unknown): boolean {
return !!table && table instanceof Object && 'html' in table;
}
function f1(x: unknown) {
return x && x instanceof Object && 'a' in x;
}
function f2<T>(x: T) {
return x && x instanceof Object && 'a' in x;
}
function f3(x: {}) {
return x instanceof Object && 'a' in x;
}
function f4<T extends {}>(x: T) {
return x instanceof Object && 'a' in x;
}
function f5<T>(x: T & {}) {
return x instanceof Object && 'a' in x;
}
function f6<T extends {}>(x: T & {}) {
return x instanceof Object && 'a' in x;
}
function f7<T extends object>(x: T & {}) {
return x instanceof Object && 'a' in x;
}