I've been tackling a challenge of migrating a sizable JS code base to TypeScript, and I've hit a roadblock when trying to annotate a specific higher order function...
doStuff()
takes fn1
as an argument and wraps it to return a new function that accepts all arguments except the first one from fn1
. It's somewhat like this:
const fn1 = (arg_1, arg_2, arg_3, arg_n) => { return 'Hello World' }
const doStuff = (fn) => (...args) => {
argument1 = getSomeStuffHere()
return fn(argument1, ...args)
}
const test = doStuff(fn1)
let result = test('arg2', 'arg3', 'arg4')
It's important to note that there is just one doStuff()
but numerous fnX()
functions with different numbers of arguments and type combinations that are wrapped by it. Ensuring that the functions created by doStuff have correct typings is crucial; "any => any" won't suffice!
After much trial and error, I've finally arrived at this solution:
// For testing purposes, here's a simplified example
type MagicObj = {}
const myMagicObject = {}
type Wrapped<T, R> =
T extends [MagicObj, any, any, any, any] ? (a: T[1], b: T[2], c: T[3], d: T[4]) => R :
T extends [MagicObj, any, any, any] ? (a: T[1], b: T[2], c: T[3]) => R :
T extends [MagicObj, any, any] ? (a: T[1], b: T[2]) => R :
T extends [MagicObj, any] ? (a: T[1]) => R :
T extends [MagicObj] ? () => R :
unknown;
const doStuff = <T extends any[], R>(fn: (...args: T) => R): Wrapped<T, R> => (...args) => fn(myMagicObject, ...args)
// Testing examples
const fn1 = (obj: MagicObj, p1: string, p2: boolean, p3: number): string => { return 'Hello World' }
const fn2 = (obj: MagicObj, p1: number, p2: string) => { return 'Hello Mars' }
const fn3 = (obj: MagicObj, p1: boolean) => { return 'Hello The Moon' }
const test1 = doStuff(fn1)
let result1 = test1('str', true, 123)
const test2 = doStuff(fn2)
let result2 = test2(123, 'str')
const test3 = doStuff(fn3)
let result3 = test3(true)
This approach seems to be somewhat functional. The type hinting intellisense in VSCode is displaying the expected types for the test1, 2, 3, and result variables at the end of the example. However, the function returned by doStuff
(...args) => fn(myMagicObject, ...args)
, no matter how I try to annotate it, consistently triggers an error similar to Type '(...args: any[]) => any' is not assignable to type 'Wrapped<T, R>'.ts(2322)
Is there any suggestion on how to achieve this?