TypeScript: Generic `{ new(...args: any[]): T }` parameter doesn't work for abstract classes

Following up on https://github.com/Microsoft/TypeScript/issues/5236#issuecomment-147738916, the proposed solution doesn’t cover all bases now that abstract classes are supported.

abstract class A {}

class B extends A {}

function isType<T>(value: any, key: { new(...args: any[]): T }): boolean {
  return value instanceof key;
}

console.log(isType((new B), A))

Error TS2345: Argument of type ‘typeof A’ is not assignable to parameter of type ‘new (…args: any[]) => A’. Cannot assign an abstract constructor type to a non-abstract constructor type.

On a related note, interfaces and abstract classes feel a bit kludgey since I can’t seem to verify that an arbitrary object actually conforms to them. I can require them as parameter types and use user-defined type guards, but there’s no generic equivalent to instanceof.

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Reactions: 14
  • Comments: 21 (7 by maintainers)

Most upvoted comments

+1 for this issue, I have a real-life scenario as well where I have a function which accepts constructors but only for reflection purposes; e.g. it does instanceof checks but nothing else with the constructor, just like the above examply:

function isType<T>(value: any, key: { new(...args: any[]): T }): boolean {
  return value instanceof key;
}

Currently I have to cast the type to any when passing it to isType. Being able to mark the constructor as (possibly) abstract in the type would solve this issue.

My use case is mixin applied to abstract class:

type Constructor<T> = new(...args: any[]) => T;

abstract class AbstractBase {}

function Mixin<T extends Constructor<object>>(Base: T) {
    return class extends Base {};
}

class Derived1 extends Mixin(AbstractBase) {}  // error
class Derived2 extends Mixin(AbstractBase as Constructor<AbstractBase>) {}

Currently I need to cast AbstractBase to Constructor<AbstractBase>. It would be nice to have some possibly abstract constructor type or class type. Here is my example with more details.

the error is that A is not constructable. your function isType says it expects things that it can construct, though in the implementation it never constructs anything. so you can not pass an abstract class to a function that may use it to construct objects. consider:

function makeInstance(constructor: new()=> T): T {
    return new constructor();
}

makeInstace(B); // OK
makeInstace(A); // Error as you would expect

i think we need a way to enable the scenario, though this needs a propsal

@unional As mentioned in my linked issue above, you can do the following and everything works as expected:

class Container {
  get<T>(key: (new (...args: any[]) => T) | Function): T
}
abstract class AbstractClass {}
const instance = container.get(AbstractClass); // is of type AbstractClass

@vojtechhabarta wrote:

type Constructor<T> = new(…args: any[]) => T; class Derived1 extends Mixin(AbstractBase) {} // error class Derived2 extends Mixin(AbstractBase as Constructor<AbstractBase>) {}

Upon receiving AbstractBase cast to Constructor within Mixin(), the compiler both fails to inform me of failures to implement abstract properties of AbstractBase and fails to require that I declare such a partially-implemented class as abstract.

@AdamWillden I didn’t get the explanation in the last edit, but I think I agree with the first notion.

an abstract class is a class for all intents except that you can’t use it directly with new. so it makes no sense to give it a new call signature.

However you should be able to inherit it and call it as super(), and right now typescript uses the new signature as the super() signature.

so maybe add an optional explicit super signature, and allow the new signature to be used as a fallback?

to clarify, here’s some pseudo-types following @sccolbert 's code example:

interface Class<T> {
  super (...args: any[]): T;  // this is the signature for calling it as `super` from a child class. if it's not declared, it's automatically inferred from the new signature
}

interface ConcreteClass<T> extends Class<T>{
  new : typeof this[super]; 
}

one can also think of something like this:

interface FinalClass<T>{
  super: never;
  new : (...args: any[]): T;
}

but I really don’t know how I feel about that