Skip to content

Commit cee6366

Browse files
authored
Fix isTypeDerivedFrom to properly handle {} and intersections (#51631)
* Fix isTypeDerivedFrom to properly handle {} and intersections * Add tests
1 parent c460e7e commit cee6366

10 files changed

+571
-2
lines changed

src/compiler/checker.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18759,16 +18759,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1875918759
// An object type S is considered to be derived from an object type T if
1876018760
// S is a union type and every constituent of S is derived from T,
1876118761
// T is a union type and S is derived from at least one constituent of T, or
18762-
// S is a type variable with a base constraint that is derived from T,
18762+
// S is an intersection type and some constituent of S is derived from T, or
18763+
// S is a type variable with a base constraint that is derived from T, or
18764+
// T is {} and S is an object-like type (ensuring {} is less derived than Object), or
1876318765
// T is one of the global types Object and Function and S is a subtype of T, or
1876418766
// T occurs directly or indirectly in an 'extends' clause of S.
1876518767
// Note that this check ignores type parameters and only considers the
1876618768
// inheritance hierarchy.
1876718769
function isTypeDerivedFrom(source: Type, target: Type): boolean {
1876818770
return source.flags & TypeFlags.Union ? every((source as UnionType).types, t => isTypeDerivedFrom(t, target)) :
1876918771
target.flags & TypeFlags.Union ? some((target as UnionType).types, t => isTypeDerivedFrom(source, t)) :
18772+
source.flags & TypeFlags.Intersection ? some((source as IntersectionType).types, t => isTypeDerivedFrom(t, target)) :
1877018773
source.flags & TypeFlags.InstantiableNonPrimitive ? isTypeDerivedFrom(getBaseConstraintOfType(source) || unknownType, target) :
18771-
target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) :
18774+
isEmptyAnonymousObjectType(target) ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) :
18775+
target === globalObjectType ? !!(source.flags & (TypeFlags.Object | TypeFlags.NonPrimitive)) && !isEmptyAnonymousObjectType(source) :
1877218776
target === globalFunctionType ? !!(source.flags & TypeFlags.Object) && isFunctionObjectType(source as ObjectType) :
1877318777
hasBaseType(source, getTargetType(target)) || (isArrayType(target) && !isReadonlyArrayType(target) && isTypeDerivedFrom(source, globalReadonlyArrayType));
1877418778
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//// [inKeywordAndIntersection.ts]
2+
class A { a = 0 }
3+
class B { b = 0 }
4+
5+
function f10(obj: A & { x: string } | B) {
6+
if (obj instanceof Object) {
7+
obj; // A & { x: string } | B
8+
}
9+
else {
10+
obj; // Error
11+
}
12+
}
13+
14+
// Repro from #50844
15+
16+
interface InstanceOne {
17+
one(): void
18+
}
19+
20+
interface InstanceTwo {
21+
two(): void
22+
}
23+
24+
const instance = {} as InstanceOne | InstanceTwo
25+
26+
const ClassOne = {} as { new(): InstanceOne } & { foo: true };
27+
28+
if (instance instanceof ClassOne) {
29+
instance.one();
30+
}
31+
32+
33+
//// [inKeywordAndIntersection.js]
34+
"use strict";
35+
var A = /** @class */ (function () {
36+
function A() {
37+
this.a = 0;
38+
}
39+
return A;
40+
}());
41+
var B = /** @class */ (function () {
42+
function B() {
43+
this.b = 0;
44+
}
45+
return B;
46+
}());
47+
function f10(obj) {
48+
if (obj instanceof Object) {
49+
obj; // A & { x: string } | B
50+
}
51+
else {
52+
obj; // Error
53+
}
54+
}
55+
var instance = {};
56+
var ClassOne = {};
57+
if (instance instanceof ClassOne) {
58+
instance.one();
59+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
=== tests/cases/compiler/inKeywordAndIntersection.ts ===
2+
class A { a = 0 }
3+
>A : Symbol(A, Decl(inKeywordAndIntersection.ts, 0, 0))
4+
>a : Symbol(A.a, Decl(inKeywordAndIntersection.ts, 0, 9))
5+
6+
class B { b = 0 }
7+
>B : Symbol(B, Decl(inKeywordAndIntersection.ts, 0, 17))
8+
>b : Symbol(B.b, Decl(inKeywordAndIntersection.ts, 1, 9))
9+
10+
function f10(obj: A & { x: string } | B) {
11+
>f10 : Symbol(f10, Decl(inKeywordAndIntersection.ts, 1, 17))
12+
>obj : Symbol(obj, Decl(inKeywordAndIntersection.ts, 3, 13))
13+
>A : Symbol(A, Decl(inKeywordAndIntersection.ts, 0, 0))
14+
>x : Symbol(x, Decl(inKeywordAndIntersection.ts, 3, 23))
15+
>B : Symbol(B, Decl(inKeywordAndIntersection.ts, 0, 17))
16+
17+
if (obj instanceof Object) {
18+
>obj : Symbol(obj, Decl(inKeywordAndIntersection.ts, 3, 13))
19+
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
20+
21+
obj; // A & { x: string } | B
22+
>obj : Symbol(obj, Decl(inKeywordAndIntersection.ts, 3, 13))
23+
}
24+
else {
25+
obj; // Error
26+
>obj : Symbol(obj, Decl(inKeywordAndIntersection.ts, 3, 13))
27+
}
28+
}
29+
30+
// Repro from #50844
31+
32+
interface InstanceOne {
33+
>InstanceOne : Symbol(InstanceOne, Decl(inKeywordAndIntersection.ts, 10, 1))
34+
35+
one(): void
36+
>one : Symbol(InstanceOne.one, Decl(inKeywordAndIntersection.ts, 14, 23))
37+
}
38+
39+
interface InstanceTwo {
40+
>InstanceTwo : Symbol(InstanceTwo, Decl(inKeywordAndIntersection.ts, 16, 1))
41+
42+
two(): void
43+
>two : Symbol(InstanceTwo.two, Decl(inKeywordAndIntersection.ts, 18, 23))
44+
}
45+
46+
const instance = {} as InstanceOne | InstanceTwo
47+
>instance : Symbol(instance, Decl(inKeywordAndIntersection.ts, 22, 5))
48+
>InstanceOne : Symbol(InstanceOne, Decl(inKeywordAndIntersection.ts, 10, 1))
49+
>InstanceTwo : Symbol(InstanceTwo, Decl(inKeywordAndIntersection.ts, 16, 1))
50+
51+
const ClassOne = {} as { new(): InstanceOne } & { foo: true };
52+
>ClassOne : Symbol(ClassOne, Decl(inKeywordAndIntersection.ts, 24, 5))
53+
>InstanceOne : Symbol(InstanceOne, Decl(inKeywordAndIntersection.ts, 10, 1))
54+
>foo : Symbol(foo, Decl(inKeywordAndIntersection.ts, 24, 49))
55+
56+
if (instance instanceof ClassOne) {
57+
>instance : Symbol(instance, Decl(inKeywordAndIntersection.ts, 22, 5))
58+
>ClassOne : Symbol(ClassOne, Decl(inKeywordAndIntersection.ts, 24, 5))
59+
60+
instance.one();
61+
>instance.one : Symbol(InstanceOne.one, Decl(inKeywordAndIntersection.ts, 14, 23))
62+
>instance : Symbol(instance, Decl(inKeywordAndIntersection.ts, 22, 5))
63+
>one : Symbol(InstanceOne.one, Decl(inKeywordAndIntersection.ts, 14, 23))
64+
}
65+
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
=== tests/cases/compiler/inKeywordAndIntersection.ts ===
2+
class A { a = 0 }
3+
>A : A
4+
>a : number
5+
>0 : 0
6+
7+
class B { b = 0 }
8+
>B : B
9+
>b : number
10+
>0 : 0
11+
12+
function f10(obj: A & { x: string } | B) {
13+
>f10 : (obj: (A & { x: string;}) | B) => void
14+
>obj : B | (A & { x: string; })
15+
>x : string
16+
17+
if (obj instanceof Object) {
18+
>obj instanceof Object : boolean
19+
>obj : B | (A & { x: string; })
20+
>Object : ObjectConstructor
21+
22+
obj; // A & { x: string } | B
23+
>obj : B | (A & { x: string; })
24+
}
25+
else {
26+
obj; // Error
27+
>obj : never
28+
}
29+
}
30+
31+
// Repro from #50844
32+
33+
interface InstanceOne {
34+
one(): void
35+
>one : () => void
36+
}
37+
38+
interface InstanceTwo {
39+
two(): void
40+
>two : () => void
41+
}
42+
43+
const instance = {} as InstanceOne | InstanceTwo
44+
>instance : InstanceOne | InstanceTwo
45+
>{} as InstanceOne | InstanceTwo : InstanceOne | InstanceTwo
46+
>{} : {}
47+
48+
const ClassOne = {} as { new(): InstanceOne } & { foo: true };
49+
>ClassOne : (new () => InstanceOne) & { foo: true; }
50+
>{} as { new(): InstanceOne } & { foo: true } : (new () => InstanceOne) & { foo: true; }
51+
>{} : {}
52+
>foo : true
53+
>true : true
54+
55+
if (instance instanceof ClassOne) {
56+
>instance instanceof ClassOne : boolean
57+
>instance : InstanceOne | InstanceTwo
58+
>ClassOne : (new () => InstanceOne) & { foo: true; }
59+
60+
instance.one();
61+
>instance.one() : void
62+
>instance.one : () => void
63+
>instance : InstanceOne
64+
>one : () => void
65+
}
66+

tests/baselines/reference/inKeywordAndUnknown.errors.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,38 @@ tests/cases/compiler/inKeywordAndUnknown.ts(12,18): error TS2638: Type '{}' may
2020
}
2121
y; // {}
2222
}
23+
24+
// Repro from #51007
25+
26+
function isHTMLTable(table: unknown): boolean {
27+
return !!table && table instanceof Object && 'html' in table;
28+
}
29+
30+
function f1(x: unknown) {
31+
return x && x instanceof Object && 'a' in x;
32+
}
33+
34+
function f2<T>(x: T) {
35+
return x && x instanceof Object && 'a' in x;
36+
}
37+
38+
function f3(x: {}) {
39+
return x instanceof Object && 'a' in x;
40+
}
41+
42+
function f4<T extends {}>(x: T) {
43+
return x instanceof Object && 'a' in x;
44+
}
45+
46+
function f5<T>(x: T & {}) {
47+
return x instanceof Object && 'a' in x;
48+
}
49+
50+
function f6<T extends {}>(x: T & {}) {
51+
return x instanceof Object && 'a' in x;
52+
}
53+
54+
function f7<T extends object>(x: T & {}) {
55+
return x instanceof Object && 'a' in x;
56+
}
2357

tests/baselines/reference/inKeywordAndUnknown.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,40 @@ function f(x: {}, y: unknown) {
1515
}
1616
y; // {}
1717
}
18+
19+
// Repro from #51007
20+
21+
function isHTMLTable(table: unknown): boolean {
22+
return !!table && table instanceof Object && 'html' in table;
23+
}
24+
25+
function f1(x: unknown) {
26+
return x && x instanceof Object && 'a' in x;
27+
}
28+
29+
function f2<T>(x: T) {
30+
return x && x instanceof Object && 'a' in x;
31+
}
32+
33+
function f3(x: {}) {
34+
return x instanceof Object && 'a' in x;
35+
}
36+
37+
function f4<T extends {}>(x: T) {
38+
return x instanceof Object && 'a' in x;
39+
}
40+
41+
function f5<T>(x: T & {}) {
42+
return x instanceof Object && 'a' in x;
43+
}
44+
45+
function f6<T extends {}>(x: T & {}) {
46+
return x instanceof Object && 'a' in x;
47+
}
48+
49+
function f7<T extends object>(x: T & {}) {
50+
return x instanceof Object && 'a' in x;
51+
}
1852

1953

2054
//// [inKeywordAndUnknown.js]
@@ -34,3 +68,28 @@ function f(x, y) {
3468
}
3569
y; // {}
3670
}
71+
// Repro from #51007
72+
function isHTMLTable(table) {
73+
return !!table && table instanceof Object && 'html' in table;
74+
}
75+
function f1(x) {
76+
return x && x instanceof Object && 'a' in x;
77+
}
78+
function f2(x) {
79+
return x && x instanceof Object && 'a' in x;
80+
}
81+
function f3(x) {
82+
return x instanceof Object && 'a' in x;
83+
}
84+
function f4(x) {
85+
return x instanceof Object && 'a' in x;
86+
}
87+
function f5(x) {
88+
return x instanceof Object && 'a' in x;
89+
}
90+
function f6(x) {
91+
return x instanceof Object && 'a' in x;
92+
}
93+
function f7(x) {
94+
return x instanceof Object && 'a' in x;
95+
}

0 commit comments

Comments
 (0)