typescriptgenericsvariadic

Specify Order of Variadic Generic Arguments for Variable Parameters in TypeScript


I'm trying to do something that feels absurd, but I've gotten so close I feel it must be possible, I just can't quite get it.

I'm trying to create a generic function type such that assigning the function type

const typedFunction: Generic<
    SomeObject,
    ['keys', 'in', 'that', 'object']
>;

will produce a function whose arguments are typed, exactly:

(
    arg0: typeof SomeObject['keys'],
    arg1: typeof SomeObject['in'],
    arg2: typeof SomeObject['that'],
    arg3: typeof SomeObject['object']
)

I've arrived at two near-but-not-quite solutions;

1.

declare type SomeGeneric<
   T,
   Params extends (T[keyof T])[]
> = (...args: Params) => ReturnType;

Which preserves the order, but forces me to specify Params in a very ugly way:

const someFunction: SomeGeneric<
    SomeObject,
    [SomeObject['keys'], SomeObject['in'], ...]
> = (arg0, arg1, arg2, arg3) => { ... };

This is obviously verbose to the point of being useless, as I could just specify them directly that way.

2.

declare type SomeGeneric<
    T,
    Params extends (keyof T)[]
> = (...args: T[Params[number]][]) => ReturnType;

But, since Params[number] refers to any member of Params, this turns every argument of SomeGeneric<SomeObject, ['keys', 'in', 'that', 'object']> into a union type of SomeObject['keys'] | SomeObject['in'] | ...

What I'd like to know is if there's some way to specify that I want SomeObject to be accessed via the keys provided in order, something like ...args: SomeObject[...Params[number]] if such syntax weren't nonsensical.


Solution

  • You were pretty close with your 2nd attempt.

    You need to use mapped type to map over the elements in Params. For each index I you can get the array element Params[I] which can be used to T.

    Note that using a mapped type to map over tuple also produces a tuple since 3.1. This is essential here since a spread parameter type must be a tuple type.

    declare type SomeGeneric<
       T,
       Params extends (keyof T)[]
    > = (...args: { [I in keyof Params]: T[Params[I]] }) => void;
    
    
    type Test = {
        a: string
        b: number
        c: Date
    }
    
    const someFunction: SomeGeneric<Test, ["a", "b", "c"]> = (
        arg0, // string
        arg1, // number
        arg2  // Date
    ) => {}
    

    Playground