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();
CodePudding user response:
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 argument T
but no actual value arguments, and returns a class instance of type Foo<T>
which is more or less the same as T
(since it's the identity mapped type that copies all non-signature properties). So I had a value Foo
of type IFoo
, then presumably I could write this:
declare const Foo: IFoo;
const withStringA = new Foo<{a: string}>();
withStringA.a.toUpperCase();
// const withStringA: Foo<{ a: string; }>
const withNumberA = new Foo<{a: number}>();
// const withNumberA: Foo<{ a: number; }>
withNumberA.a.toFixed();
which would be emitted as the following JavaScript:
const withStringA = new Foo();
withStringA.a.toUpperCase();
const withNumberA = new Foo();
withNumberA.a.toFixed();
But both withStringA
and withNumberA
are initialized with exactly the same construct call, new Foo()
. How can withStringA.a
be of type string
but withNumberA.a
be of type number
? 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 implement IFoo
. It has no properties at all, let alone an a
property that is magically string
or number
depending on information unavailable at runtime. If you try to run this, you will get a runtime error:
const Foo: IFoo = class { };
const withStringA = new Foo<{ a: string }>();
// const withStringA: Foo<{ a: string; }>
withStringA.a.toUpperCase(); // RUNTIME ERROR