Home > database >  Generic class expression broken if duplicate constructor is removed
Generic class expression broken if duplicate constructor is removed

Time:10-06

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();

Playground

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            
  • Related