Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to retrieve the type of the generic property from a type?

Tags:

typescript

type Parent = {
    fun<T>(a: T, b: string):void
}
type ChildFunArgs = Parameters<Parent['fun']<number>> // got an error

If the above code can run, so I can do this:

const childFunArgs: ChildFunArgs = [1, '1']
like image 688
aweiu Avatar asked Apr 06 '19 07:04

aweiu


1 Answers

There's no higher-order type manipulation that will do this for you automatically purely in the type system.

If you are okay with something with some small runtime impact (meaning it emits some JS code) and with ugly nasty looking type munging, and if you are using TypeScript 3.4 and above, you can get your type like this:

const dummyParams =
    ((x => () => x) as <A extends any[]>(
        f: (...args: A) => any) => () => A
    )(null! as Parent['fun'])<number>();
type ChildFunArgs = typeof dummyParams
// type ChildFunArgs = [number, string]

You can verify that ChildFunArgs is the type you expect. At runtime, the emitted code is essentially

const dummyParams = ((x => () => x))(null)();

which has the same effect as

const dummyParams = null;

So there is runtime impact but it is minimal.


How does that ugly stuff work? We are fooling the compiler into thinking we have a function like this:

declare function params<A extends any[]>(f: (...args: A)=>any): () => A;

This hypothetical params() function would accept a function argument f, and return a new function of zero arguments which returns a tuple whose type is f's parameter types. Hmm, that's confusing. What I mean is that params((x: string, y: boolean)=>123) would return a new function of the type () => [string, boolean]. Such a function is essentially impossible to implement, but we don't have to really implement it.

Then we pretend we have a value of the type of the fun method of Parent:

 declare const parentFun: Parent['fun'];

And we call params on it:

const paramsParentFun = params(parentFun);
// const paramsParentFun: <T>() => [T, string]

Due to recent improvements in higher-order function handling, the compiler infers paramsParentFun to be of type <T>() => [T, string]. Now you can pretend to call this function and manually specify number for T:

const dummyParams = paramsParentFun<number>();
// const dummyParams: [number, string]

And voila! You get what you're looking for.

The shorter code above is the same as this but it uses type assertions to lie to the compiler about the existence of these other functions and values, so that the emitted JavaScript doesn't have extra mess in it.

Okay, hope that helps. Good luck!

like image 86
jcalz Avatar answered Oct 18 '22 03:10

jcalz