Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ConstructorParameters<T> cannot get parameters from an abstract superclass #31278

Closed
aterisetri opened this issue May 7, 2019 · 5 comments
Closed
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@aterisetri
Copy link

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.

#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: #30991, #23911

@weswigham weswigham added the Needs Investigation This issue needs a team member to investigate its status. label May 8, 2019
@RyanCavanaugh RyanCavanaugh added Working as Intended The behavior described is the intended behavior; this is not a bug and removed Needs Investigation This issue needs a team member to investigate its status. labels Aug 22, 2019
@RyanCavanaugh
Copy link
Member

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.

@MaximSagan
Copy link

MaximSagan commented May 22, 2020

@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);
  }
}

@coreywoodfield
Copy link

coreywoodfield commented Sep 2, 2020

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

@CMCDragonkai
Copy link

This doesn't work for object destructured assignment:

type AbstractConstructorParameters<T> = ConstructorParameters<(new (...args: any) => any) & T>;

abstract class Test {
    constructor ({
        a,
        b
    }: {
        a: string,
        b: string
    }) {
        console.log(a);
    }
}

class Test2 extends Test {
    constructor (
        {
            c: string,
            ...rest
        }: {
            c: string;
            rest: AbstractConstructorParameters<typeof Test>
        }
    ) {
        super(...rest);
    }

}

The reason being is that the AbstractConstructorParameters and ConstructorParameters resolves to an array. But the example above is trying to spread the first parameter into the first parameter of the subclass... not sure how to deal with it.

@RyanCavanaugh
Copy link
Member

ConstructorParameters now uses the new abstract new syntax and works as expected in 4.3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

6 participants