2
votes

I have identified a problem when using intersection types in TypeScript...

I have three type aliases:

  • Prototype<T> - expresses an object/class that has a prototype property.
  • DefaultCtor<T> - expresses an object/class with a default constructor.
  • ParameterizedCtor<T> expresses an object/class with a parameterized constructor.

I have tried these intersection permutations:

  • Prototype<T> & DefaultCtor<T> - works fine.
  • Prototype<T> & ParameterizedCtor<T> - raises a compiler error.

Example

type Prototype<T> = {
    prototype: T;
}

type DefaultCtor<T> = {
    new(): T;
}

type ParameterizedCtor<T> = {
    new(...args: any[]): T
}

function makeDefault<T>(ctor: Prototype<T> & DefaultCtor<T>): T {
    return new ctor();
}

function makeWithArgs<T>(ctor: Prototype<T> & ParameterizedCtor<T>, ...args: any[]): T {
    return new ctor(...args);
    // ERROR: Cannot use 'new' with an expression whose type lacks a call or construct signature.
}

Try it in the Playground

Error The error occurs with the Prototype<T> & ParameterizedCtor<T> intersection:

Cannot use 'new' with an expression whose type lacks a call or construct signature.

Why does the TypeScript compiler recognise the Prototype<T> & DefaultCtor<T> intersection type as having a constructor, but not the Prototype<T> & ParameterizedCtor<T> intersection type?

2
Using ParameterizedCtor on its own works as well.H.B.
@H.B. Yes, I meant to add that to the post. Good spot!Matthew Layton
Maybe it's a bug, seems odd.H.B.
@H.B. Yes, I think so too. I think this will need the attention of the TypeScript language team.Matthew Layton
@H.B. Found a workaround for now...check my answer.Matthew Layton

2 Answers

4
votes

This seems to be a known bug, filed at Microsoft/TypeScript#17388, reported in July of 2017. It is slated to be fixed for TypeScript v2.9, but the history of the issue looks like the fix version has been pushed back a few times. Go there and 👍 if you feel so inclined, I guess? Hope that helps. Good luck.

0
votes

Workaround

In some edge cases using a cast will provide an interim solution while the TypeScript team come up with a fix.

Example

function make<T>(ctor: Prototype<T> & ParameterizedCtor<T>, ...args: any[]): T {
    console.log(ctor.prototype);
    return new (ctor as ParameterizedCtor<T>)(...args);
}