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.
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>>;
Waiting if anyone knows a non-recursive trick.