11

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
Maciej Krawczyk
  • 14,825
  • 5
  • 55
  • 67
  • Your updated try can take `GetClassParameter`, if that also works for you – hackape Apr 15 '21 at 18:50
  • You are right about that. However, passing `GetClassParameter` doesn't seem to work. – Maciej Krawczyk Apr 15 '21 at 18:55
  • 1
    And the feature that takes the general form of `UtilType> => 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. – hackape Apr 15 '21 at 18:59
  • Should be `typeof Foo` uppercase F. `typeof foo` is diff thing. – hackape Apr 15 '21 at 19:01
  • Well, I cannot use `typeof Foo` because I need to get information about the lowercase `foo`, which is `foo = Foo` I couldn't get it from `Foo`. Surely if it's not supported, the workaround is good enough, I need this for 2 classes so I could probably use conditional types or something, but it would be nicer of course if if it was flexible. – Maciej Krawczyk Apr 15 '21 at 19:04
  • 1
    I cannot find the issue anymore, but typescript omits the information of generics if they are unused. It is a design limitation. – Mirco S. Apr 15 '21 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](https://stackoverflow.com/questions/55683711/replace-generic-interface-type-parameter) years ago. Only remotely related to your problem. If you wanna dig deeper you can take a look. – hackape Apr 15 '21 at 19:08
  • I updated my question to add the workaround with conditionals to get the type for 2 or more different classes. – Maciej Krawczyk Apr 16 '21 at 07:35

1 Answers1

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

Meirion Hughes
  • 24,994
  • 12
  • 71
  • 122