Skip to content

Commit 4ddd195

Browse files
committed
Fix TS2367 false positive when comparing generic constructor types with concrete constructors
Signed-off-by: RXVEN-1907 <viswajeet2502@gmail.com>
1 parent 5237fd4 commit 4ddd195

5 files changed

Lines changed: 226 additions & 0 deletions

File tree

‎tsc/internal/checker/relater.go‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3473,6 +3473,20 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo
34733473
}
34743474
return TernaryFalse
34753475
}
3476+
if r.relation == r.c.comparableRelation && source.flags&TypeFlagsTypeParameter == 0 {
3477+
// An unconstrained type parameter target could be instantiated with anything, so any
3478+
// source could overlap with it. This mirrors the source-side constraint check below;
3479+
// without it, types nested inside properties or construct signature return types (see
3480+
// issue #27910) are treated as non-overlapping on this side alone. Type parameters
3481+
// with an object constraint stay strict to preserve TS2352/TS2367 checks against
3482+
// instantiations with a different subtype of the constraint.
3483+
constraint := r.c.getConstraintOfType(target)
3484+
if constraint == nil || constraint.flags&TypeFlagsAnyOrUnknown != 0 {
3485+
if result := r.isRelatedToEx(source, r.c.unknownType, RecursionFlagsTarget, false /*reportErrors*/, nil /*headMessage*/, intersectionState); result != TernaryFalse {
3486+
return result
3487+
}
3488+
}
3489+
}
34763490
case target.flags&TypeFlagsIndexedAccess != 0:
34773491
if source.flags&TypeFlagsIndexedAccess != 0 {
34783492
// Relate components directly before falling back to constraint relationships
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
comparableRelationTypeParameterTarget.ts(29,9): error TS2367: This comparison appears to be unintentional because the types 'T' and 'U' have no overlap.
2+
3+
4+
==== comparableRelationTypeParameterTarget.ts (1 errors) ====
5+
// Reproduces #27910: a generic constructor type must be comparable with a concrete
6+
// constructor it could overlap with. Previously, `Constructor<T> === typeof Child`
7+
// reported TS2367 ("no overlap") even though `func(Child)` accepted `Child` as
8+
// `Constructor<T>`.
9+
10+
interface Item { }
11+
12+
abstract class Base<T> {
13+
get Item(): T { throw 0 }
14+
}
15+
16+
class Child extends Base<Item> {
17+
constructor(public data: any) { super() }
18+
}
19+
20+
type Constructor<T> = new (data: any) => Base<T>
21+
22+
function func<T>(constructor: Constructor<T>): void {
23+
if (constructor === Child) {
24+
// do something
25+
}
26+
new constructor(null)
27+
}
28+
29+
func(Child);
30+
31+
// Two unrelated type parameters still have no overlap.
32+
function unrelated<T, U>(x: T, y: U): void {
33+
if (x === y) {
34+
~~~~~~~
35+
!!! error TS2367: This comparison appears to be unintentional because the types 'T' and 'U' have no overlap.
36+
}
37+
}
38+
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//// [tests/cases/compiler/comparableRelationTypeParameterTarget.ts] ////
2+
3+
=== comparableRelationTypeParameterTarget.ts ===
4+
// Reproduces #27910: a generic constructor type must be comparable with a concrete
5+
// constructor it could overlap with. Previously, `Constructor<T> === typeof Child`
6+
// reported TS2367 ("no overlap") even though `func(Child)` accepted `Child` as
7+
// `Constructor<T>`.
8+
9+
interface Item { }
10+
>Item : Symbol(Item, Decl(comparableRelationTypeParameterTarget.ts, 0, 0))
11+
12+
abstract class Base<T> {
13+
>Base : Symbol(Base, Decl(comparableRelationTypeParameterTarget.ts, 5, 18))
14+
>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 7, 20))
15+
16+
get Item(): T { throw 0 }
17+
>Item : Symbol(Base.Item, Decl(comparableRelationTypeParameterTarget.ts, 7, 24))
18+
>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 7, 20))
19+
}
20+
21+
class Child extends Base<Item> {
22+
>Child : Symbol(Child, Decl(comparableRelationTypeParameterTarget.ts, 9, 1))
23+
>Base : Symbol(Base, Decl(comparableRelationTypeParameterTarget.ts, 5, 18))
24+
>Item : Symbol(Item, Decl(comparableRelationTypeParameterTarget.ts, 0, 0))
25+
26+
constructor(public data: any) { super() }
27+
>data : Symbol(Child.data, Decl(comparableRelationTypeParameterTarget.ts, 12, 16))
28+
>super : Symbol(Base, Decl(comparableRelationTypeParameterTarget.ts, 5, 18))
29+
}
30+
31+
type Constructor<T> = new (data: any) => Base<T>
32+
>Constructor : Symbol(Constructor, Decl(comparableRelationTypeParameterTarget.ts, 13, 1))
33+
>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 15, 17))
34+
>data : Symbol(data, Decl(comparableRelationTypeParameterTarget.ts, 15, 27))
35+
>Base : Symbol(Base, Decl(comparableRelationTypeParameterTarget.ts, 5, 18))
36+
>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 15, 17))
37+
38+
function func<T>(constructor: Constructor<T>): void {
39+
>func : Symbol(func, Decl(comparableRelationTypeParameterTarget.ts, 15, 48))
40+
>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 17, 14))
41+
>constructor : Symbol(constructor, Decl(comparableRelationTypeParameterTarget.ts, 17, 17))
42+
>Constructor : Symbol(Constructor, Decl(comparableRelationTypeParameterTarget.ts, 13, 1))
43+
>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 17, 14))
44+
45+
if (constructor === Child) {
46+
>constructor : Symbol(constructor, Decl(comparableRelationTypeParameterTarget.ts, 17, 17))
47+
>Child : Symbol(Child, Decl(comparableRelationTypeParameterTarget.ts, 9, 1))
48+
49+
// do something
50+
}
51+
new constructor(null)
52+
>constructor : Symbol(constructor, Decl(comparableRelationTypeParameterTarget.ts, 17, 17))
53+
}
54+
55+
func(Child);
56+
>func : Symbol(func, Decl(comparableRelationTypeParameterTarget.ts, 15, 48))
57+
>Child : Symbol(Child, Decl(comparableRelationTypeParameterTarget.ts, 9, 1))
58+
59+
// Two unrelated type parameters still have no overlap.
60+
function unrelated<T, U>(x: T, y: U): void {
61+
>unrelated : Symbol(unrelated, Decl(comparableRelationTypeParameterTarget.ts, 24, 12))
62+
>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 27, 19))
63+
>U : Symbol(U, Decl(comparableRelationTypeParameterTarget.ts, 27, 21))
64+
>x : Symbol(x, Decl(comparableRelationTypeParameterTarget.ts, 27, 25))
65+
>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 27, 19))
66+
>y : Symbol(y, Decl(comparableRelationTypeParameterTarget.ts, 27, 30))
67+
>U : Symbol(U, Decl(comparableRelationTypeParameterTarget.ts, 27, 21))
68+
69+
if (x === y) {
70+
>x : Symbol(x, Decl(comparableRelationTypeParameterTarget.ts, 27, 25))
71+
>y : Symbol(y, Decl(comparableRelationTypeParameterTarget.ts, 27, 30))
72+
}
73+
}
74+
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//// [tests/cases/compiler/comparableRelationTypeParameterTarget.ts] ////
2+
3+
=== comparableRelationTypeParameterTarget.ts ===
4+
// Reproduces #27910: a generic constructor type must be comparable with a concrete
5+
// constructor it could overlap with. Previously, `Constructor<T> === typeof Child`
6+
// reported TS2367 ("no overlap") even though `func(Child)` accepted `Child` as
7+
// `Constructor<T>`.
8+
9+
interface Item { }
10+
11+
abstract class Base<T> {
12+
>Base : Base<T>
13+
14+
get Item(): T { throw 0 }
15+
>Item : T
16+
>0 : 0
17+
}
18+
19+
class Child extends Base<Item> {
20+
>Child : Child
21+
>Base : Base<Item>
22+
23+
constructor(public data: any) { super() }
24+
>data : any
25+
>super() : void
26+
>super : typeof Base
27+
}
28+
29+
type Constructor<T> = new (data: any) => Base<T>
30+
>Constructor : Constructor<T>
31+
>data : any
32+
33+
function func<T>(constructor: Constructor<T>): void {
34+
>func : <T>(constructor: Constructor<T>) => void
35+
>constructor : Constructor<T>
36+
37+
if (constructor === Child) {
38+
>constructor === Child : boolean
39+
>constructor : Constructor<T>
40+
>Child : typeof Child
41+
42+
// do something
43+
}
44+
new constructor(null)
45+
>new constructor(null) : Base<T>
46+
>constructor : Constructor<T>
47+
}
48+
49+
func(Child);
50+
>func(Child) : void
51+
>func : <T>(constructor: Constructor<T>) => void
52+
>Child : typeof Child
53+
54+
// Two unrelated type parameters still have no overlap.
55+
function unrelated<T, U>(x: T, y: U): void {
56+
>unrelated : <T, U>(x: T, y: U) => void
57+
>x : T
58+
>y : U
59+
60+
if (x === y) {
61+
>x === y : boolean
62+
>x : T
63+
>y : U
64+
}
65+
}
66+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// @noEmit: true
2+
// @strict: true
3+
4+
// Reproduces #27910: a generic constructor type must be comparable with a concrete
5+
// constructor it could overlap with. Previously, `Constructor<T> === typeof Child`
6+
// reported TS2367 ("no overlap") even though `func(Child)` accepted `Child` as
7+
// `Constructor<T>`.
8+
9+
interface Item { }
10+
11+
abstract class Base<T> {
12+
get Item(): T { throw 0 }
13+
}
14+
15+
class Child extends Base<Item> {
16+
constructor(public data: any) { super() }
17+
}
18+
19+
type Constructor<T> = new (data: any) => Base<T>
20+
21+
function func<T>(constructor: Constructor<T>): void {
22+
if (constructor === Child) {
23+
// do something
24+
}
25+
new constructor(null)
26+
}
27+
28+
func(Child);
29+
30+
// Two unrelated type parameters still have no overlap.
31+
function unrelated<T, U>(x: T, y: U): void {
32+
if (x === y) {
33+
}
34+
}

0 commit comments

Comments
 (0)