12

I need to implement a type GetClassParameter<T> that would work like this:

class Foo<T> {

}

type foo = Foo<string>

type x = GetClassParameter<foo>; // should be string

I'm sorry if it's a duplicate, I couldn't find it. I only found a hardcoded solution (source):

type GetFooParameter<T extends Foo<any>> = T extends Foo<infer R> ? R : unknown;

I tried to do something like this:

class Foo<T> {
    public value: T;
    public value2: string;
    constructor (value: T) {
        this.value = value;
    }
}

type BaseT<T> = {
  value: T  // if removed, it wouldn't work
}
type GetClassParameter<T extends BaseT<any>> = T extends BaseT<infer R> ? R : unknown;

type x = GetClassParameter<foo> // string - almost works, requires shared property with a T

The above almost works but requires BaseT to have a value: T property.

Is there a way to do it without hardcoding anything, assuming the target class has only one generic parameter?

Update:

Another take, unsuccessful.

type ClassLike<T> = (new <T>(...args: any[]) => any);
type GetClassParameter<T extends ClassLike<any>> = T extends ClassLike<infer R> ? R : unknown;
type x = GetClassParameter<foo> // error, does not satisfy constraint

Update 2

It's not possible currently. Nevertheless, I tried a hack to define BaseT with value property and then removed it. It doesn't work. I'm adding it as a reference if someone had a similar idea to save you time. playground

Update 3

I'm adding a workaround I'm using to get the class parameter type for 2 classes that have nothing in common (it can be extended to cover more classes just by adding additional conditional).

playground

class Alpha<T> {
    private a: T;
}

class Beta<T> {
    private b: T;
}

type GetClassParameterForAlphaBeta<T extends Alpha<any> | Beta<any>> =
    T extends Alpha<infer R>
    ? R : T extends Beta<infer R>
    ? R : unknown;

type alpha = Alpha<string>
type beta = Beta<number>

type x = GetClassParameterForAlphaBeta<alpha> // string
type y = GetClassParameterForAlphaBeta<beta> // number
8
  • Your updated try can take GetClassParameter<typeof Foo>, if that also works for you Commented Apr 15, 2021 at 18:50
  • You are right about that. However, passing GetClassParameter<typeof foo> doesn't seem to work. Commented Apr 15, 2021 at 18:55
  • 1
    And the feature that takes the general form of UtilType<F<T>> => T is not supported by TS. Higher kinded type is the term, and TS doesn’t have it. So forget perfect solution, best you can get is workarounds with limitations. Your two takes are actually good enough. Commented Apr 15, 2021 at 18:59
  • 1
    I cannot find the issue anymore, but typescript omits the information of generics if they are unused. It is a design limitation. Commented Apr 15, 2021 at 19:05
  • 1
    I don’t have better answer for your problem, you’ve hit the limit of TS. FYI I wrote an answer about higher kinded type in TS years ago. Only remotely related to your problem. If you wanna dig deeper you can take a look. Commented Apr 15, 2021 at 19:08

1 Answer 1

4

Cannot be done yet - purely as a type. There is an open issue that aims to allow passing higher-kind generic types through: https://github.com/microsoft/TypeScript/issues/1213

Its an issue that is a bane to many trying to type highly moddable js-libs (like levelup).

Just to give you a much easier example of something that doesn't work:

interface Dummy<T> {};

declare function getParam<P, T extends Dummy<P>>(a: T): P;

let a = getParam(null as Dummy<string>);

here a is unknown

the only real work around is to move the generic parameter into the dummy interface as a fake property - but then everything passed to this will need that fake property defined too - or you're back to unknown

interface Dummy<T> {
  a?: T
};

declare function getParam<P, T extends Dummy<P>>(a: T): T["a"]

let a = getParam(null as Foo<string>);

a is now string but typescript still has no idea what P is

Sign up to request clarification or add additional context in comments.

1 Comment

Not related but it is possible to do in Flow

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.