I stumbled upon this weird behaviour,
If I remove one of the constructor signatures in IFoo
the compiler triggers the following error : Type 'typeof (Anonymous class)' is not assignable to type 'IFoo'.
What is actually happening ?
type Foo<T> = {
[S in keyof T]: T[S]
}
interface IFoo {
new <T>(): Foo<T>;
new <T>(): Foo<T>; // KO if removed
}
const Foo: IFoo = (class {})
const foo = new Foo();
TL;DR: Overloads which are generic are not properly type-checked, as described in microsoft/TypeScript#26631 and microsoft/TypeScript#50050.
Your
IFoo
interface claims to be the type of a generic class constructor that takes one type argumentT
but no actual value arguments, and returns a class instance of typeFoo<T>
which is more or less the same asT
(since it's the identity mapped type that copies all non-signature properties). So I had a valueFoo
of typeIFoo
, then presumably I could write this:which would be emitted as the following JavaScript:
But both
withStringA
andwithNumberA
are initialized with exactly the same construct call,new Foo()
. How canwithStringA.a
be of typestring
butwithNumberA.a
be of typenumber
? There's no plausible mechanism by which such a thing can happen; without magic,IFoo
is unimplementable, at least not safely.In particular,
class {}
does not properly implementIFoo
. It has no properties at all, let alone ana
property that is magicallystring
ornumber
depending on information unavailable at runtime. If you try to run this, you will get a runtime error:So if
IFoo
cannot be safely implemented, why doesn't the compiler warn you about it?Indeed, as you noticed, you do get warned when you remove the second construct signature:
This error is expected and good.
But as soon as you add a second, overloaded construct signature, the error disappears:
Why?
The issue is that generic overloads are not properly type-checked, as described in microsoft/TypeScript#26631 and microsoft/TypeScript#50050. Type parameters are replaced with the unsafe
any
type, which means that the compiler checks the assignment ofclass {}
as ifIFoo
were:which is the same as
And indeed,
class {}
does match that type:So there's no compiler error, even though there should be.
This is effectively a design limitation in TypeScript; it is a bug, but if they fix it, a lot of code which currently works (and happens to be safe) will probably start to spew compiler warnings (because the compiler can't verify overload safety very well).
So, if you do decide to use multiple generic call or construct signatures for a type, be careful with how you implement it, because the compiler can silently fail to catch mistakes.
Playground link to code