In the process of developing a utility function to merge two TypeScript lists, I encountered the following challenge:
type TupleCombine<L extends unknown[], R extends unknown[]> =
L extends [infer LH, ...infer LT]
? R extends [infer RH, ...infer RT]
? [LH & RH, ...TupleCombine<LT, RT>] // Merging tuples with 1+ elements
: [LH & R[number], ...TupleCombine<LT, R>] // Merging tuple with list
: R extends [infer RH, ...infer RT]
? [RH & L[number], ...TupleCombine<L, RT>] // Merging list with tuple
: L extends []
? R extends []
? [] // Merging empty tuples
: R[number] // Merging empty tuple with list
: R extends []
? L[number] // Merging empty list with tuple
: (L[number] & R[number])[]; // Merging list types
I am now seeking a way to generalize this utility function so that I can avoid repeating similar cases for scenarios like "tuple unions" and other combinations involving tuple types.
My initial idea for this generalized version is as follows:
type TupleMerge<L extends unknown[], R extends unknown[], Combinator, Default> =
L extends [infer LH, ...infer LT]
? R extends [infer RH, ...infer RT]
? [Combinator<LH, RH>, ...TupleMerge<LT, RT, Combinator, Default>]
: [Combinator<LH, R[number]>, ...TupleMerge<LT, R, Combinator, Default>]
: R extends [infer RH, ...infer RT]
? [Combinator<L[number], R>, ...TupleMerge<L, RT, Combinator, Default>]
: L extends []
? R extends []
? []
: [Combinator<Default, R[number]>]
: R extends []
? [Combinator<L[number], Default>]
: Combinator<L[number], R[number]>[];
Theoretically, I would specify a Combinator
generic type in the following manner:
type Intersect<L, R> = L & R;
type TupleIntersect<L extends unknown[], R extends unknown[]> = TupleMerge<L, R, Intersect, unknown>;
type Union<L, R> = L | R;
type TupleIntersect<L extends unknown[], R extends unknown[]> = TupleMerge<L, R, Union, never>;
However, the TypeScript compiler does not permit this due to the restriction that "type parameters aren't generic types."
Is there a legitimate approach to achieving my objective using TypeScript? Or have I delved too deep into comparing TypeScript generic types with C++ template metaprogramming and template templates?