TypeScript: Is it possible to get the return type of a generic function?

前端 未结 3 355
失恋的感觉
失恋的感觉 2020-12-17 16:27

I have exported a function from some module that looks like:

export function MyFunc() {
    return {
        foo: (in: A) => void
    }
}
         


        
相关标签:
3条回答
  • 2020-12-17 16:29

    Expanding on Aidin's answer, I've managed to make ReplaceTypeIn, which works for any number of elements, objects as well as arrays, and uses only a couple of lines:

    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Derived from https://stackoverflow.com/a/53808212 by jcalz (https://stackoverflow.com/users/2887218)
    export type IfEquals<T, U, Y=unknown, N=never> =
        (<G>() => G extends T ? 1 : 2) extends
        (<G>() => G extends U ? 1 : 2) ? Y : N;
    
    type ReplaceType<T, FROM_TYPE, TO_TYPE> = IfEquals<T, FROM_TYPE, TO_TYPE, T>;
    type ReplaceTypeIn<T, FROM_TYPE, TO_TYPE> = {
      [K in keyof T]: ReplaceType<T[K], FROM_TYPE, TO_TYPE>;
    };
    
    // ~~~~~~~~~~~~~~~~~~~~~~ SAMPLE FUNCTIONS ~~~~~~~~~~~~~~~~~~~
    
    export function MyFunc1<T extends unknown>() {
      return {
        foo: (os: T) => {}
      }
    }
    
    function MyFunc2<T extends unknown>(r: 55, p: T, x: boolean): T {
        return p;
    }
    
    // ~~~~~~~~~~~~~~~~~~~~~ FIXATIONS ~~~~~~~~~~~~~~~~~~~~
    
    // Derived from https://stackoverflow.com/a/52964723 by Titian (https://stackoverflow.com/users/125734)
    class Helper1 <T extends unknown> {
        Fixate = (...args: ReplaceTypeIn<Parameters<typeof MyFunc1>, unknown, T>) => MyFunc1<T>(...args);
    }
    type FixatedFunc1<T extends unknown> = Helper1<T>['Fixate'];
    
    // -- Usage
    
    type ForNumber1 = FixatedFunc1<number> //  {foo: (os: number) => void;}
    type ForString1 = FixatedFunc1<string> //  {foo: (os: string) => void;}
    
    // ~~~~~~~~~~~~~~~~~~~
    
    class Helper2 <T extends unknown> {
        Fixate = (...args: ReplaceTypeIn<Parameters<typeof MyFunc2>, unknown, T>) => MyFunc2<T>(...args);
    }
    type FixatedFunc2<T extends unknown> = Helper2<T>['Fixate'];
    
    // -- Usage
    
    type ForNumber2 = FixatedFunc2<number> //  (r: 55, p: number, x: boolean) => number
    type ForString2 = FixatedFunc2<string> //  (r: 55, p: string, x: boolean) => string
    

    Here's a playground link for the example above.

    0 讨论(0)
  • 2020-12-17 16:43

    There is a proposal to allow using typeof with arbitrary expressions to allow things like getting the return type of a generic functions for a specific type argument (see here and here)

    A more generic workaround that works today is to use a generic class with a field that is tied to the return type of the function. We can then extract the field of the class. Since for classes we can specify generic type parameters in type expressions we can extract the generic form of the return type:

    export function MyFunc<A>() {
      return {
        foo: (os : A) => {}
      }
    }
    
    class Helper <T> {
      Return = MyFunc<T>()
    }
    type FuncReturnType<T> = Helper<T>['Return']
    type ForBool = FuncReturnType<boolean> //  {foo: (os: boolean) => void;}
    type ForString = FuncReturnType<string> //  {foo: (os: string) => void;}
    

    Note If you have constraints of A you will need to duplicate those on T in Helper and FuncReturnType, that is unavoidable unfortunately.

    0 讨论(0)
  • 2020-12-17 16:53

    Titian's solution above works great if the Generic is only applied inside the function body.

    However, there are cases that the Generic Type is a part of the arguments and/or return type. e.g.

    function MyFunc3<T extends object | number | string>(r: number, p: T, x: boolean): T {
        return p;
    }
    

    So, to generalize Titian's solution and support fixating both the arguments and the return type of any generic function, I wrote the following:

    Required Utility Types

    // From https://stackoverflow.com/a/53808212 by jcalz (https://stackoverflow.com/users/2887218)
    export type IfEquals<T, U, Y=unknown, N=never> =
        (<G>() => G extends T ? 1 : 2) extends
        (<G>() => G extends U ? 1 : 2) ? Y : N;
    
    // Aidin: Please comment if you could make the following shorter!
    type ReplaceType<T, FROM_TYPE, TO_TYPE> = IfEquals<T, FROM_TYPE, TO_TYPE, T>;
    type ReplaceTypeInArray<ARR, F, T> =
        ARR extends [] ? []
        : ARR extends [infer P0] ? [P0 extends F ? T : P0]
        : ARR extends [infer P0, infer P1] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>]
        : ARR extends [infer P0, infer P1, infer P2] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>]
        : ARR extends [infer P0, infer P1, infer P2, infer P3] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>]
        : ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>]
        : ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>]
        : ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5, infer P6] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>, ReplaceType<P6, F, T>]
        : ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5, infer P6, infer P7] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>, ReplaceType<P6, F, T>, ReplaceType<P7, F, T>]
        : ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5, infer P6, infer P7, infer P8] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>, ReplaceType<P6, F, T>, ReplaceType<P7, F, T>, ReplaceType<P8, F, T>]
        : never;
    

    Sample Functions

    type ALL = string | number | object | boolean;
    
    export function MyFunc1<T extends ALL>() {
      return {
        foo: (os : T) => {}
      }
    }
    
    function MyFunc2<T extends ALL>(r: 55, p: T, x: boolean): T {
        return p;
    }
    

    Fixation examples

    // Inspired by https://stackoverflow.com/a/52964723 by Titian (https://stackoverflow.com/users/125734)
    class Helper1 <T extends ALL> {
        Fixate = (...args: ReplaceTypeInArray<Parameters<typeof MyFunc1>, ALL, T>) => MyFunc1<T>(...args);
    }
    type FixatedFunc1<T extends ALL> = Helper1<T>['Fixate'];
    
    // -- Usage
    
    type ForNumber1 = FixatedFunc1<number> //  {foo: (os: number) => void;}
    type ForString1 = FixatedFunc1<string> //  {foo: (os: string) => void;}
    
    // ~~~~~~~~~~~~~~~~~~~
    
    class Helper2 <T extends ALL> {
        Fixate = (...args: ReplaceTypeInArray<Parameters<typeof MyFunc2>, ALL, T>) => MyFunc2<T>(...args);
    }
    type FixatedFunc2<T extends ALL> = Helper2<T>['Fixate'];
    
    // -- Usage
    
    type ForNumber2 = FixatedFunc2<number> //  (args_0: 55, args_1: number, args_2: boolean) => number
    type ForString2 = FixatedFunc2<string> //  (args_0: 55, args_1: string, args_2: boolean) => string
    

    Playground Link (Contains all 3 parts)

    Now, one can simply use ReturnType<T> or Parameteres<T> on any of these fixated function types!

    0 讨论(0)
提交回复
热议问题