If you're searching for a way to manipulate types at a higher level, the type system won't provide a direct solution automatically.
However, if you're willing to tolerate a slight runtime impact, along with some complex type manipulation, and you're using TypeScript 3.4 or later, you can achieve your desired 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 confirm that the type of ChildFunArgs
is as expected. The runtime behavior of the emitted code is essentially:
const dummyParams = ((x => () => x))(null)();
which is equivalent to:
const dummyParams = null;
So, while there is a runtime impact, it is minimal.
How does this intricate process work? Essentially, we trick the compiler by creating a hypothetical function:
declare function params<A extends any[]>(f: (...args: A)=>any): () => A;
This function, if it existed, would take a function argument f
and return a new function that yields a tuple matching the parameter types of f
. While impossible to actually implement, we don't need to.
We then simulate having a value of the type of the fun
method of Parent
:
declare const parentFun: Parent['fun'];
Next, we call params
on it:
const paramsParentFun = params(parentFun);
// const paramsParentFun: <T>() => [T, string]
Thanks to recent improvements in higher-order function handling, the compiler infers the type of paramsParentFun
as <T>() => [T, string]
. Now, you can simulate calling this function and manually specifying number
as T
:
const dummyParams = paramsParentFun<number>();
// const dummyParams: [number, string]
And there you have it! You've achieved your desired outcome.
The condensed code utilizes type assertions to deceive the compiler about the existence of other functions and values, resulting in cleaner emitted JavaScript code.
I hope this explanation clarifies things for you. Best of luck!