UPDATES FOR TS4.7+
Greetings once more! TypeScript 4.7 is set to introduce a new feature called instantiation expressions, detailed in this pull request by Microsoft. This update will allow you to specify type parameters for a generic function directly, without needing to call the function itself. Here's how it works:
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
The syntax is almost identical to what was mentioned in the initial question, except that trailing commas are not permitted after type arguments (although they can still be used post-type parameter declarations). Exciting news indeed!
Link to Playground with code
PREVIOUS ANSWER FOR TS4.6-
In TypeScript, generics come in two forms: generic functions and generic types. It seems like you're aiming to transform one into the other, which isn't directly supported.
To clarify:
Generic types require type parameters to be specified before they can be utilized 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!
In this scenario, GenType
is a generic type. You must provide the type parameter to use it as the type of a value, transforming it from a generic to a specific type. The genT
function takes a string
and returns a string[]
. It cannot process an input of number
and output a number[]
.
On the other hand, generic functions have a specific type that can act as any substitution of its type parameters. The value remains generic even when the function is called, with the type parameter linked 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
refers to a specific type representing a generic function, maintaining its generic nature during usage.
Generic functions, including generic constructor functions, could be seen as generic values, distinct from generic types.
While these two types of generics are interconnected, the TypeScript type system lacks the capability to articulate their relationship. In other languages, you might define one in terms of the other like:
type GenFunc = forall T, GenType<T>; // not possible in TS, error
or
type GenType<T> = instantiate GenFunc with T; // not feasible in TS, error
Such transformations in the type system from GenFunc
to GenType
are currently beyond TypeScript's scope. However, future enhancements such as higher kinded types might enable this functionality (microsoft/TypeScript#1213).
There exist complicated methods to coerce the compiler into deriving GenType
from GenFunc
. One approach involves leveraging generic class property initialization alongside higher order type inference for generic functions, first introduced in TypeScript 3.4. By creating artificial values within the compiler context, we can extract the desired type:
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[]
This methodology demonstrates that GenType2<T>
mirrors the same type as GenType<T>
, adjusting dynamically with changes to GenFunc
. While technically feasible, I hesitate to endorse this workaround due to complexity and lack of scalability, especially if extending it across multiple object properties.
Playground link showcasing the code