[SOLVED] TypeScript how to create a generic type alias for a generic function?


Given a typed generic function, I want to create a generic alias for that function, but it seems I can’t. In other words, this doesn’t work:

// foo.tsx
function foo<T>(arg: T): T {
  return arg

type FooT = typeof foo  // works, but alias isn't generic: <T>(arg: T) => T
type FooParametersT = Parameters<typeof foo>  // sure: [unknown]
type FooReturnT = ReturnType<typeof foo>  // no problem: unknown

type GFooT<T,> = typeof foo<T,>  // yikes
type GFooParametersT<T,> = Parameters<typeof foo<T,>>  // nope
type GFooReturnT<T,> = ReturnType<typeof foo<T,>>  // fails

What I’m really trying to do is grab the type of a function in someone else’s library and then build an interface around it. For example:

import { useState } from "react"

type UseStateFnT<T,> = typeof useState<T>
const wrapUseState: (toWrap: UseStateFnT<T,>) => UseStateFnT<T,> = …

Is this possible without recreating the complex typed function signature myself?



Hello again! TypeScript 4.7 will introduce instantiation expressions as implemented in microsoft/TypeScript#47607, which will allow you to directly specify the type parameters for a generic function without actually calling the function, so there’s no longer a need to abuse classes the way shown in the rest of the answer. Here it is:

type GFooT<T,> = typeof foo<T>  // (arg: T) => T
type GFooParametersT<T,> = Parameters<typeof foo<T>>  // [arg: T]
type GFooReturnT<T,> = ReturnType<typeof foo<T>>  // T

That’s almost the same as the syntax in the question except that trailing commas are not supported after type arguments (but they are supported after type parameter declarations). Hooray!

Playground link to code


There are two different flavors of generics in TypeScript: generic functions, and generic types… and it looks like you want the compiler to transform one into the other for you, which isn’t directly supported.

To be clear:

Generic types have type parameters that need to be specified before you can use them as a specific type. For example:

type GenType<T> = (x: T) => T[];
declare const oops: GenType; // error
declare const genT: GenType<string>; // okay
const strArr = genT("hello"); // string[];
const numArr = genT(123); // error!

Here, GenType is a generic type. You need to specify the type parameter to use it as the type of a value, and then the resulting type is no longer generic. The genT function takes a string and returns a string[]. It cannot be used as a function that takes a number and returns a number[].

Generic functions, on the other hand, have a specific type that can act like any possible substitution of its type parameters. The value of a generic function type is still generic when you use it. The type parameter is attached to the call signature:

type GenFunc = <T>(x: T) => T[];
declare const genF: GenFunc;
const strArr = genF("hello"); // strArr: string[];
const numArr = genF(123); // numArr: number[];

Here, GenFunc is a specific type referring to a generic function. The genF function is still generic when it is called.

Generic functions (including generic constructor functions) can be thought of as generic values, as opposed to generic types.

These two flavors of generics are related to each other but the TypeScript type system isn’t expressive enough to talk about how they are related. In some other language, you might be able to define one in terms of the other like

type GenFunc = forall T, GenType<T>; // not TS, error


type GenType<T> = instantiate GenFunc with T; // not TS, error

but in TypeScript you can’t. Maybe if we ever get higher kinded types as requested in microsoft/TypeScript#1213… but not now. So it is not directly possible to turn GenFunc into GenType programmatically in the type system.

There are evil awful ways to coerce the compiler to calculate GenType in terms of GenFunc. The way I know of makes use of generic class property initialization and some higher order type inference for generic functions introduced in TypeScript 3.4. I’m making the compiler think it’s calculating values when I don’t actually have any, and then getting the type of one of these pretend values:

class GenTypeMaker<T> {
    getGenType!: <A extends any[], R>(cb: (...a: A) => R) => () => (...a: A) => R;
    genType = this.getGenType(null! as GenFunc)<T>()
type GenType2<T> = GenTypeMaker<T>['genType']
// type GenType2<T> = (x: T) => T[]

You can verify that GenType2<T> is the same type as GenType<T>, and if you change GenFunc into any generic function with one type parameter, GenType2<T> will change accordingly.

But I don’t know I’d want to recommend anyone actually use this method. And it doesn’t really scale or compose; if you have an object full of generic functions in one type parameter and you want to transform it to an object full of specific functions with a specified type parameter, there’s no way to use this method to get it from the type system without doing it once for each object property.

Playground link to code

Answered By – jcalz

Answer Checked By – Senaida (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *