Home > Net >  How to infer first element of a tuple as a tuple type
How to infer first element of a tuple as a tuple type

Time:05-15

I'm unable to properly infer the first type of a tuple of arbitrary length as a tuple type, in order to maintain its label:

type First1<T extends any[]> = T extends [infer FIRST, ...any[]] ? FIRST : never

type test1 = First1<[a: number, b: boolean, c: string]> // number


type First2<T extends any[]> = T extends [infer FIRST, ...any[]] ? [FIRST] : never

type test2 = First2<[a: number, b: boolean, c: string]> // [number] <-- obv no label


type First3<T extends any[]> = T extends [...infer FIRST, any, ...any[]] ? FIRST : never

type test3 = First3<[a: number, b: boolean, c: string]> // unknown[] <-- I tried ahah

Any ideas? Playground

Expected result: [a: number]

CodePudding user response:

The following approach works for your example code:

type First<T extends any[]> = T extends [any, ...infer R] ?
    T extends [...infer F, ...R] ? F : never : never

type Test = First<[a: number, b: boolean, c: string]>
// type Test = [a: number]

The idea is to take the tuple type T and infer from it the tuple R corresponding to everything after the first element. Once R is fixed, we can ask the compiler to split T into two tuples, the last of which is R. This returns a tuple containing just the first element, with the label intact.


Of course there are edge cases which won't work, like optional tuple elements, and rest elements in tuple types including leading and middle rest elements:

type X = First<[a?: number, b?: boolean, c?: string]>; // never
type Y = First<[...a: number[], b: boolean, c: string]> // never
type Z = First<[a: number, ...b: boolean[], c: string]> // unknown[]

I don't know that there's a good way of doing this that has no edge cases. According to GitHub comments like this one, preserving tuple labels through type transformations isn't guaranteed to work in all cases, so a partially functioning solution might be as good as it gets.

Playground link to code

CodePudding user response:

My current solution:

type Init<T extends any[]> = T extends [...infer INIT, any] ? INIT : never;

type FirstAsTuple<T extends any[]> = T extends [any]
  ? T extends [...infer F]
    ? F
    : never
  : FirstAsTuple<Init<T>>;

Playground

Waiting if anyone knows a non-recursive trick.

  • Related