Typescript: ConstructorParameters<T> cannot get parameters from an abstract superclass

Created on 7 May 2019  路  3Comments  路  Source: microsoft/TypeScript

Using ConstructorParameters<typeof Superclass> allows your constructor to share the same arguments as your superclass without copy and pasting them, but it doesn't work for concrete implementations of abstract classes because the superclass isn't newable.

https://github.com/microsoft/TypeScript/issues/23911 would provide an alternative way to avoid the copy and paste.

TypeScript Version: 3.5.0-dev.20190504


Search Terms: ConstructorParameters, abstract, newable

Code

abstract class Foo {
    constructor(public a: number, public b: string) {
    }

    abstract doSomething(): void;
}

class Bar extends Foo {
    constructor(...args: ConstructorParameters<typeof Foo>) {
        super(...args);

        // some other code here
    }

    doSomething() {
    }
}

Expected behavior: Bar compiles with no error.

Actual behavior:

error TS2344: Type 'typeof Foo' does not satisfy the constraint 'new (...args: any) => any'.
Cannot assign an abstract constructor type to a non-abstract constructor type.

constructor(...args: ConstructorParameters<typeof Foo>) {

Playground Link: https://www.typescriptlang.org/play/#src=abstract%20class%20Foo%20%7B%0D%0A%09constructor(public%20a%3A%20number%2C%20public%20b%3A%20string)%20%7B%0D%0A%09%7D%0D%0A%0D%0A%09abstract%20doSomething()%3A%20void%3B%0D%0A%7D%0D%0A%0D%0Aclass%20Bar%20extends%20Foo%20%7B%0D%0A%09constructor(...args%3A%20ConstructorParameters%3Ctypeof%20Foo%3E)%20%7B%0D%0A%09%09super(...args)%3B%0D%0A%0D%0A%09%09%2F%2F%20some%20other%20code%20here%0D%0A%09%7D%0D%0A%0D%0A%09doSomething()%20%7B%0D%0A%09%7D%0D%0A%7D

Related Issues: https://github.com/microsoft/TypeScript/issues/30991, https://github.com/microsoft/TypeScript/issues/23911

Working as Intended

Most helpful comment

For everyone who comes here looking for a solution, I found this workaround on reddit

abstract class Test {
    constructor(a: string, b: number) {}
}

type AbstractConstructorHelper<T> = (new (...args: any) => { [x: string]: any; }) & T;
type AbstractContructorParameters<T> = ConstructorParameters<AbstractConstructorHelper<T>>;

// Params resolved to [string, number]
type Params = AbstractContructorParameters<typeof Test>;

All 3 comments

It's intentional that abstract classes don't present as having construct signatures (for obvious reasons); the limitation here is preferable to having some new relation for infer in conditional types that would be different only in allowing that through.

@RyanCavanaugh That may be preferable if it's an either-or situation, but if it's possible to fundamentally change the way abstract classes expose their constructors, that could be invaluable for situations that @aterisetri is describing in his example code (which is the same reason I'm here).

i.e. Where you just want to pass constructor parameters through the prototype chain without redefining their properties every time.

Here's another example. If the below code ran properly, we could add a new argument to the Animal constructor (say, animalAge: number) and we dould not have to touch the constructors for Mammal, Monotreme, Platypus, or any of what count be countless other classes inheriting Animal.

abstract class Animal {
  constructor(public animalName: string) { }
}

abstract class Mammal extends Animal {
  constructor(public numberOfNipples: number, ...animalArgs: ConstructorParameters<typeof Animal>) {
    super(...animalArgs);
  }
}

abstract class Montotreme extends Mammal {
  constructor(public eggLayingCapacity: number, ...mammalArgs: ConstructorParameters<typeof Mammal>) {
    super(...mammalArgs);
  }
}


class Platypus extends Mammal {
  constructor(public venomSeverity: number, ...monotremeArgs: ConstructorParameters<typeof Monotreme>) {
    super(...monotremeArgs);
  }
}

For everyone who comes here looking for a solution, I found this workaround on reddit

abstract class Test {
    constructor(a: string, b: number) {}
}

type AbstractConstructorHelper<T> = (new (...args: any) => { [x: string]: any; }) & T;
type AbstractContructorParameters<T> = ConstructorParameters<AbstractConstructorHelper<T>>;

// Params resolved to [string, number]
type Params = AbstractContructorParameters<typeof Test>;
Was this page helpful?
0 / 5 - 0 ratings

Related issues

dlaberge picture dlaberge  路  3Comments

MartynasZilinskas picture MartynasZilinskas  路  3Comments

wmaurer picture wmaurer  路  3Comments

Antony-Jones picture Antony-Jones  路  3Comments

manekinekko picture manekinekko  路  3Comments