diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index d3b6c86014498..7f92837be4a83 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -3473,6 +3473,20 @@ func (r *Relater) structuredTypeRelatedToWorker(source *Type, target *Type, repo } return TernaryFalse } + if r.relation == r.c.comparableRelation && source.flags&TypeFlagsTypeParameter == 0 { + // An unconstrained type parameter target could be instantiated with anything, so any + // source could overlap with it. This mirrors the source-side constraint check below; + // without it, types nested inside properties or construct signature return types (see + // issue #27910) are treated as non-overlapping on this side alone. Type parameters + // with an object constraint stay strict to preserve TS2352/TS2367 checks against + // instantiations with a different subtype of the constraint. + constraint := r.c.getConstraintOfType(target) + if constraint == nil || constraint.flags&TypeFlagsAnyOrUnknown != 0 { + if result := r.isRelatedToEx(source, r.c.unknownType, RecursionFlagsTarget, false /*reportErrors*/, nil /*headMessage*/, intersectionState); result != TernaryFalse { + return result + } + } + } case target.flags&TypeFlagsIndexedAccess != 0: if source.flags&TypeFlagsIndexedAccess != 0 { // Relate components directly before falling back to constraint relationships diff --git a/tsc/testdata/baselines/reference/compiler/comparableRelationTypeParameterTarget.errors.txt b/tsc/testdata/baselines/reference/compiler/comparableRelationTypeParameterTarget.errors.txt new file mode 100644 index 0000000000000..d969dbb8af495 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/comparableRelationTypeParameterTarget.errors.txt @@ -0,0 +1,38 @@ +comparableRelationTypeParameterTarget.ts(29,9): error TS2367: This comparison appears to be unintentional because the types 'T' and 'U' have no overlap. + + +==== comparableRelationTypeParameterTarget.ts (1 errors) ==== + // Reproduces #27910: a generic constructor type must be comparable with a concrete + // constructor it could overlap with. Previously, `Constructor === typeof Child` + // reported TS2367 ("no overlap") even though `func(Child)` accepted `Child` as + // `Constructor`. + + interface Item { } + + abstract class Base { + get Item(): T { throw 0 } + } + + class Child extends Base { + constructor(public data: any) { super() } + } + + type Constructor = new (data: any) => Base + + function func(constructor: Constructor): void { + if (constructor === Child) { + // do something + } + new constructor(null) + } + + func(Child); + + // Two unrelated type parameters still have no overlap. + function unrelated(x: T, y: U): void { + if (x === y) { + ~~~~~~~ +!!! error TS2367: This comparison appears to be unintentional because the types 'T' and 'U' have no overlap. + } + } + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/comparableRelationTypeParameterTarget.symbols b/tsc/testdata/baselines/reference/compiler/comparableRelationTypeParameterTarget.symbols new file mode 100644 index 0000000000000..b5a69f1c92fa7 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/comparableRelationTypeParameterTarget.symbols @@ -0,0 +1,74 @@ +//// [tests/cases/compiler/comparableRelationTypeParameterTarget.ts] //// + +=== comparableRelationTypeParameterTarget.ts === +// Reproduces #27910: a generic constructor type must be comparable with a concrete +// constructor it could overlap with. Previously, `Constructor === typeof Child` +// reported TS2367 ("no overlap") even though `func(Child)` accepted `Child` as +// `Constructor`. + +interface Item { } +>Item : Symbol(Item, Decl(comparableRelationTypeParameterTarget.ts, 0, 0)) + +abstract class Base { +>Base : Symbol(Base, Decl(comparableRelationTypeParameterTarget.ts, 5, 18)) +>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 7, 20)) + + get Item(): T { throw 0 } +>Item : Symbol(Base.Item, Decl(comparableRelationTypeParameterTarget.ts, 7, 24)) +>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 7, 20)) +} + +class Child extends Base { +>Child : Symbol(Child, Decl(comparableRelationTypeParameterTarget.ts, 9, 1)) +>Base : Symbol(Base, Decl(comparableRelationTypeParameterTarget.ts, 5, 18)) +>Item : Symbol(Item, Decl(comparableRelationTypeParameterTarget.ts, 0, 0)) + + constructor(public data: any) { super() } +>data : Symbol(Child.data, Decl(comparableRelationTypeParameterTarget.ts, 12, 16)) +>super : Symbol(Base, Decl(comparableRelationTypeParameterTarget.ts, 5, 18)) +} + +type Constructor = new (data: any) => Base +>Constructor : Symbol(Constructor, Decl(comparableRelationTypeParameterTarget.ts, 13, 1)) +>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 15, 17)) +>data : Symbol(data, Decl(comparableRelationTypeParameterTarget.ts, 15, 27)) +>Base : Symbol(Base, Decl(comparableRelationTypeParameterTarget.ts, 5, 18)) +>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 15, 17)) + +function func(constructor: Constructor): void { +>func : Symbol(func, Decl(comparableRelationTypeParameterTarget.ts, 15, 48)) +>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 17, 14)) +>constructor : Symbol(constructor, Decl(comparableRelationTypeParameterTarget.ts, 17, 17)) +>Constructor : Symbol(Constructor, Decl(comparableRelationTypeParameterTarget.ts, 13, 1)) +>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 17, 14)) + + if (constructor === Child) { +>constructor : Symbol(constructor, Decl(comparableRelationTypeParameterTarget.ts, 17, 17)) +>Child : Symbol(Child, Decl(comparableRelationTypeParameterTarget.ts, 9, 1)) + + // do something + } + new constructor(null) +>constructor : Symbol(constructor, Decl(comparableRelationTypeParameterTarget.ts, 17, 17)) +} + +func(Child); +>func : Symbol(func, Decl(comparableRelationTypeParameterTarget.ts, 15, 48)) +>Child : Symbol(Child, Decl(comparableRelationTypeParameterTarget.ts, 9, 1)) + +// Two unrelated type parameters still have no overlap. +function unrelated(x: T, y: U): void { +>unrelated : Symbol(unrelated, Decl(comparableRelationTypeParameterTarget.ts, 24, 12)) +>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 27, 19)) +>U : Symbol(U, Decl(comparableRelationTypeParameterTarget.ts, 27, 21)) +>x : Symbol(x, Decl(comparableRelationTypeParameterTarget.ts, 27, 25)) +>T : Symbol(T, Decl(comparableRelationTypeParameterTarget.ts, 27, 19)) +>y : Symbol(y, Decl(comparableRelationTypeParameterTarget.ts, 27, 30)) +>U : Symbol(U, Decl(comparableRelationTypeParameterTarget.ts, 27, 21)) + + if (x === y) { +>x : Symbol(x, Decl(comparableRelationTypeParameterTarget.ts, 27, 25)) +>y : Symbol(y, Decl(comparableRelationTypeParameterTarget.ts, 27, 30)) + } +} + diff --git a/tsc/testdata/baselines/reference/compiler/comparableRelationTypeParameterTarget.types b/tsc/testdata/baselines/reference/compiler/comparableRelationTypeParameterTarget.types new file mode 100644 index 0000000000000..8fe1f03ed09d4 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/comparableRelationTypeParameterTarget.types @@ -0,0 +1,66 @@ +//// [tests/cases/compiler/comparableRelationTypeParameterTarget.ts] //// + +=== comparableRelationTypeParameterTarget.ts === +// Reproduces #27910: a generic constructor type must be comparable with a concrete +// constructor it could overlap with. Previously, `Constructor === typeof Child` +// reported TS2367 ("no overlap") even though `func(Child)` accepted `Child` as +// `Constructor`. + +interface Item { } + +abstract class Base { +>Base : Base + + get Item(): T { throw 0 } +>Item : T +>0 : 0 +} + +class Child extends Base { +>Child : Child +>Base : Base + + constructor(public data: any) { super() } +>data : any +>super() : void +>super : typeof Base +} + +type Constructor = new (data: any) => Base +>Constructor : Constructor +>data : any + +function func(constructor: Constructor): void { +>func : (constructor: Constructor) => void +>constructor : Constructor + + if (constructor === Child) { +>constructor === Child : boolean +>constructor : Constructor +>Child : typeof Child + + // do something + } + new constructor(null) +>new constructor(null) : Base +>constructor : Constructor +} + +func(Child); +>func(Child) : void +>func : (constructor: Constructor) => void +>Child : typeof Child + +// Two unrelated type parameters still have no overlap. +function unrelated(x: T, y: U): void { +>unrelated : (x: T, y: U) => void +>x : T +>y : U + + if (x === y) { +>x === y : boolean +>x : T +>y : U + } +} + diff --git a/tsc/testdata/tests/cases/compiler/comparableRelationTypeParameterTarget.ts b/tsc/testdata/tests/cases/compiler/comparableRelationTypeParameterTarget.ts new file mode 100644 index 0000000000000..276c5443e6b4b --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/comparableRelationTypeParameterTarget.ts @@ -0,0 +1,34 @@ +// @noEmit: true +// @strict: true + +// Reproduces #27910: a generic constructor type must be comparable with a concrete +// constructor it could overlap with. Previously, `Constructor === typeof Child` +// reported TS2367 ("no overlap") even though `func(Child)` accepted `Child` as +// `Constructor`. + +interface Item { } + +abstract class Base { + get Item(): T { throw 0 } +} + +class Child extends Base { + constructor(public data: any) { super() } +} + +type Constructor = new (data: any) => Base + +function func(constructor: Constructor): void { + if (constructor === Child) { + // do something + } + new constructor(null) +} + +func(Child); + +// Two unrelated type parameters still have no overlap. +function unrelated(x: T, y: U): void { + if (x === y) { + } +}