As mentioned previously, TypeScript is currently unable to handle the type transformation you require. There is a need for features like "generic generics", "higher order generics", "type constructor parameters", or "higher kinded types" as discussed in microsoft/TypeScript#1213. While TypeScript allows specific type functions, writing a generic one is not possible.
To illustrate, consider an imaginary type-like entity called TypeFunc
, where if F extends TypeFunc
, then F
becomes a generic type that accepts a type parameter such as F<T>
. This concept would allow us to define:
// Hypothetical TypeScript syntax:
type Foo<F extends TypeFunc> = {x: F<string>}
type G = Foo<Array> // type G = {x: Array<string>}
type H = Foo<Promise> // type H = {x: Promise<string>}
In this scenario, while Array
and Promise
are specific type functions, F
remains generic, enabling delayed application of type parameters to Array
and Promise
.
Potentially, we could define your wrapper()
function like so:
// Theoretical TypeScript code:
type Input<T extends Record<keyof T, TypeFunc>> =
{ [K in keyof T]: { foo: <A>(...args: any) => T[K]<A> }};
type Output<T extends Record<keyof T, TypeFunc>, K extends keyof T, A> =
T[K]<A>;
declare const f: <T extends Record<keyof T, TypeFunc>>(
t: Input<T>) => <K extends keyof T, A>() => Output<T, K, A>;
const wrapper = f(dict);
However, due to the absence of higher kinded types in TypeScript, implementing this is not feasible.
Your utilization of instantiation expressions is intriguing but falls short of achieving the desired outcome. The current limitation in TypeScript leads to the formation of union types when calling indexed access types within the implementation of wrapper()
, preventing full realization of the intended functionality described in microsoft/TypeScript#47240.
Despite the possibilities offered by instantiation expressions, they do not deliver the expected higher kinded types capabilities.
Exploring alternative options, a manual approach involving instantiation expressions for each key of dict
may be used to construct a specific type function aligned with your requirements. By validating that dict
aligns with this constructed type function, any divergence from the expected behavior would trigger warnings, as illustrated below:
type DictMap<A> = {
one: ReturnType<typeof dict.one.foo<A>>,
two: ReturnType<typeof dict.two.foo<A>>,
three: ReturnType<typeof dict.three.foo<A>>,
};
const wrapper = <K extends keyof Dict, A>() => {
const dictSentinel: { [P in keyof Dict]: { foo(a: A): DictMap<A>[P] } } = dict;
return null! as DictMap<A>[K];
}
The usage of DictMap<A>[K]
accurately depicts the output of dict[k]<A>
for keys of type
K</code, facilitating consistent behavior and immediate error detection if <code>dict
undergoes modifications.
While not elegant, this approach represents a practical workaround until the introduction of higher kinded types in TypeScript.
Link to Playground for full code