Adam Baranyai
Adam Baranyai

Reputation: 3867

Extending class type is not assignable to base class type in typescript

I have the following code:

class Base<TAttributes extends {} = {}> {
    constructor(attributes: TAttributes) {}
}

class Extended extends Base<{test: string}> {

}

function test(test: typeof Base) {};

test(Extended);

What I am trying to achieve, is to create a function, which expects a constructor of Base class type, and I am trying to pass it constructors of more specific (extended) types. Unfortunatly, because of the generics, typescript complains, that:

Argument of type 'typeof Extended' is not assignable to parameter of type 'typeof Base'. Types of construct signatures are incompatible. Type 'new (attributes: { test: string; }) => Extended' is not assignable to type 'new <TAttributes extends {} = {}>(attributes: TAttributes) => Base'. Types of parameters 'attributes' and 'attributes' are incompatible. Type 'TAttributes' is not assignable to type '{ test: string; }'. Property 'test' is missing in type '{}' but required in type '{ test: string; }'

I can't figure out, what am I doing wrongly here. If I remove the generic parameters from the base class, typescript is not complaining anymore. Could someone point me in the direction, on how to achieve my desired outcome?

Here's a playground link also.

Upvotes: 3

Views: 2432

Answers (2)

Guerric P
Guerric P

Reputation: 31815

If you need to use only the static methods from a base class and not its constructor, you can use a NoConstructor helper type in order to keep only the properties from the base class, and remove the constructor function part of it:

class Base<TAttributes extends {} = {}> {
    static log() {
        console.log('log');
    }
    constructor(attributes: TAttributes) { }
}

class Extended extends Base<{ test: string }> {

}

type NoConstructor<T> = Pick<T, keyof T>;

function test(test: NoConstructor<typeof Base>) {
    test.log(); // OK
};

test(Extended); // OK

TypeScript playground

Upvotes: 1

Alex Wayne
Alex Wayne

Reputation: 187034

Try to write the implementation of test(). How could you possibly know what arguments to pass to the constructor?

In your example Base could be instantiated by:

new Base()

But Extended requires a { test: string } argument.

new Extended({ test: 'qwe' })

So you cannot treat the Extended constructor like the Base constructor. It has a different type.

Instances of a child class are always assignable to types of the parent class, but the same is not true for constructors which can change drastically.

You could do something like:

function test<
  Args extends unknown[],
  Instance extends unknown
>(
  ctor: new (...args: Args) => Instance,
  ...args: Args
): Instance {
  return new ctor(...args)
}

In this example, the test function takes a ctor (any constructor) and args (the arguments that constructor requires). And then returns whatever Instance types that constructor makes.

// works
test(Base, {});
test(Extended, { test: 'qwe' });

But this will work with any constructor, and not just whatever some Base is.

Playground

Upvotes: 0

Related Questions