Typescript ReturnType of generic function

TypescriptGenerics

Typescript Problem Overview


The new ReturnType in TypeScript 2.8 is a really useful feature that lets you extract the return type of a particular function.

function foo(e: number): number {
    return e;
}

type fooReturn = ReturnType<typeof foo>; // number

However, I'm having trouble using it in the context of generic functions.

function foo<T>(e: T): T {
    return e;
}

type fooReturn = ReturnType<typeof foo>; // type fooReturn = {}

type fooReturn = ReturnType<typeof foo<number>>; // syntax error

type fooReturn = ReturnType<(typeof foo)<number>>; // syntax error

Is there a way extract the return type that a generic function would have given particular type parameters?

Typescript Solutions


Solution 1 - Typescript

This is my currently working solution for extracting un-exported internal types of imported libraries (like knex):

// foo is an imported function that I have no control over
function foo<T>(e: T): InternalType<T> {
    return e;
}

class Wrapper<T> {
  // wrapped has no explicit return type so we can infer it
  wrapped(e: T) {
    return foo<T>(e)
  }
}

type FooInternalType<T> = ReturnType<Wrapper<T>['wrapped']>
type Y = FooInternalType<number>
// Y === InternalType<number>

Solution 2 - Typescript

If you want to get some special generic type, You can use a fake function to wrap it.

const wrapperFoo = () => foo<number>()
type Return = ReturnType<typeof wrapperFoo>

More complex demo

function getList<T>(): {
  list: T[],
  add: (v: T) => void,
  remove: (v: T) => void,
  // ...blahblah
}
const wrapperGetList = () => getList<number>()
type List = ReturnType<typeof wrapperGetList>
// List = {list: number[], add: (v: number) => void, remove: (v: number) => void, ...blahblah}

Solution 3 - Typescript

This was previously impossible to do in a purely generic fashion, but will be in Typescript 4.7. The pattern is called an "Instantiation Expression". The relevant PR is here. Excerpt from the description:

> > typescript > function makeBox<T>(value: T) { > return { value }; > }; > > const makeStringBox = makeBox<string>; // (value: string) => { value: string } > const stringBox = makeStringBox('abc'); // { value: string } > > const ErrorMap = Map<string, Error>; // new () => Map<string, Error> > const errorMap = new ErrorMap(); // Map<string, Error> > > ... > > A particularly useful pattern is to > create generic type aliases for applications of `typeof` that reference > type parameters in type instantiation expressions: > >typescript > type BoxFunc = typeof makeBox; // (value: T) => { value: T } > type Box = ReturnType>; // { value: T } type > StringBox = Box; // { value: string } > ```

Solution 4 - Typescript

I found a good and easy way to achieve this if you can change the function definition. In my case, I needed to use the typescript type Parameters with a generic function, precisely I was trying Parameters<typeof foo<T>> and effectively it doesn't work. So the best way to achieve this is changing the function definition by an interface function definition, this also will work with the typescript type ReturnType.

Here an example following the case described by the OP:

function foo<T>(e: T): T {
   return e;
}

type fooReturn = ReturnType<typeof foo<number>>; // Damn! it throws error

// BUT if you try defining your function as an interface like this:

interface foo<T>{
   (e: T): T
}

type fooReturn = ReturnType<foo<number>> //it's number, It works!!!
type fooParams = Parameters<foo<string>> //it also works!! it is [string]

//and you can use the interface in this way
const myfoo: foo<number> = (asd: number) => {
    return asd;
};

myfoo(7);

Solution 5 - Typescript

I found a solution. You decide if it fits your needs :)

Declare your function args and return type using a interface

interface Foo<T, V> {
  (t: T, v: V): [T, V]
}

Implement your function this way using Parameters and ReturnType

function foo<T, V>(...[t, v]: Parameters<Foo<T, V>>): ReturnType<Foo<T, V>> {
  return [t, v]; // [T, V]
}

Call your function normally, or get return type using ReturnType

foo(1, 'a') // [number, string]
type Test = ReturnType<Foo<number, number>> // [number, number]

Solution 6 - Typescript

const wrapperFoo = (process.env.NODE_ENV === 'typescript_helper' ? foo<number>(1) : undefined)!
type Return = typeof wrapperFoo

TypeScript Playground

Here's another use case where there is some default type which is not exported.

// Not exported
interface Unaccessible1 {
    z: number
    x: string
}

function foo1<T extends Unaccessible1>(e: T): T {
    return e;
}

const wrapperFoo1 = (process.env.NODE_ENV === 'typescript_helper' ? foo1.apply(0, 0 as any) : undefined)!
type ReturnFoo1 = typeof wrapperFoo1 // Unaccessible1

interface Unaccessible2 {
    y: number
    c: string
}

function foo2<T extends Unaccessible2>(e: T, arg2: number, arg3: string, arg4: Function): T {
    return e;
}

const wrapperFoo2 = (process.env.NODE_ENV === 'typescript_helper' ? foo2.apply(0, 0 as any) : undefined)!
type ReturnFoo2 = typeof wrapperFoo2 // Unaccessible2

TypeScript Playground

Solution 7 - Typescript

TypeScript compiler does not see typeof foo as generic type. I'd say it's a bug in the compiler.

However, TypeScript has callable interfaces which can be generic without any problems, so if you introduce a callable interface compatible with the signature of your function, you can implement your own equivalent of ReturnType like this:

function foo<T>(x: T): T {
  return x;
}


interface Callable<R> {
  (...args: any[]): R;
}

type GenericReturnType<R, X> = X extends Callable<R> ? R : never;

type N = GenericReturnType<number, typeof foo>; // number

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionsamfrancesView Question on Stackoverflow
Solution 1 - TypescriptColinView Answer on Stackoverflow
Solution 2 - TypescriptXGHeavenView Answer on Stackoverflow
Solution 3 - TypescriptEric HaynesView Answer on Stackoverflow
Solution 4 - TypescriptValfar DeveloperView Answer on Stackoverflow
Solution 5 - TypescriptiamandrewlucaView Answer on Stackoverflow
Solution 6 - TypescriptDimitar NestorovView Answer on Stackoverflow
Solution 7 - TypescriptartemView Answer on Stackoverflow