Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript - get type of generic class parameter

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
like image 894
Maciej Krawczyk Avatar asked Apr 15 '21 18:04

Maciej Krawczyk


1 Answers

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

like image 125
Meirion Hughes Avatar answered Sep 21 '22 22:09

Meirion Hughes