Home > Blockchain >  How to properly extract generic parameters of function in Typescript
How to properly extract generic parameters of function in Typescript

Time:06-18

Usually I use Typescript's Parameters to receive all parameters required by a function of a class:

get = (...params: Parameters<Controller["get"]>) => {}

It works just fine with predefined types in function signature like this: get(id: number).

I'm currently trying to get parameters of function that has the following signature:

functionName<T>(callback: () => Promise<T>)

But with the Parameters it supposes that the T is never and I can't pass my own T into the parameters of such arrow function to get something like Parameters<Controller["get"]<T>> to get proper generic when I call my function like in the beginning.

Edit 1: Minimal Reproducible Example

class Test {
    genericFunction<T>(obj: T): T {return obj;}
    genericFunctionPromise<T>(c: () => Promise<T>): Promise<T> {return c();}

    example(params: Parameters<Test["genericFunction"]>) {};
    examplePromise(params: Parameters<Test["genericFunctionPromise"]>) {params};
}

Example 1 Example 2

CodePudding user response:

TypeScript 4.7 introduced instantiation expressions as implemented in microsoft/TypeScript#47606, so if you have a value f of a generic function type, you can specify the type parameters of that function without calling it. Let's say your function declaration is

declare function f<T, U>(x: T, y: U): [T, U];

Then you can instantiate T and U with, say, number and string, to get a new expression:

const g = f<number, string>; // instantiation expression
// const g: (x: number, y: string) => [number, string]

You can even use the typeof type operator to get the type of such a expression without actually creating one:

type G = typeof f<number, string>;
// type G = (x: number, y: string) => [number, string]

Unfortunately, you can't do this purely at the type level (see this comment on ms/TS#47606). You need that variable f, or a property of a variable. If you only have the type of f, then you can't instantiate its type parameters

type F = typeof f;
// type F = <T, U>(x: T, y: U) => [T, U]

type Nope = F<number, string>; // error, F is not generic!

Luckily, inside of Test, you do have a value of the generic function type you care about. It's this.genericFunction or this.genericFunctionPromise:

class Test {
  genericFunction<T>(obj: T): T { return obj; }
  genericFunctionPromise<T>(c: () => Promise<T>): Promise<T> { return c(); }

  example<T>(
     ...params: Parameters<typeof this.genericFunction<T>>) {
     // error ------------------> ^^^^
  } 
  examplePromise<T>(
    ...params: Parameters<typeof this.genericFunctionPromise<T>>
    // error ------------------> ^^^^
  ) { params } 
}        

new Test().examplePromise(() => Promise.resolve(100));
// (method) Test.examplePromise<number>(c: () => Promise<number>): void 

And that actually works... except that there are errors on this in TypeScript 4.7. Ugh. This turns out to be a bug in TypeScript, see microsoft/TypeScript#49451. This bug has been fixed at microsoft/TypeScript#49521, so typescript@next nightly build will work, and it will be released with TypeScript 4.8.

If you need to get this working in TS4.7, you could declare a fake variable of type Test and use it instead, since we just care about types:

declare const __test: Test; // fake

class Test {
  genericFunction<T>(obj: T): T { return obj; }
  genericFunctionPromise<T>(c: () => Promise<T>): Promise<T> { return c(); }

  example<T>(
    ...params: Parameters<typeof __test.genericFunction<T>>) {
  }
  examplePromise<T>(
    ...params: Parameters<typeof __test.genericFunctionPromise<T>>) {
    params
  }
}

Playground link to code

  • Related