With the introduction of recursive conditional types in TypeScript 4.1, you can now create a type called CombinationOf<T>
using this syntax:
type CombinationOf<T> =
T extends [infer U1, ...infer R] ? (
R extends [] ? never : EitherOrBoth<U1,
R extends [infer U2] ? U2 : CombinationOf<R>
>
) : never;
This definition closely resembles your original one, maintaining the structure while disregarding the functionality of the EitherOrBoth<>
type. It's worth noting that because your initial definition returns never
for single-element tuples, this revised version is slightly more complex. (I would have expected CombinationOf<[X]>
to simply be
X</code, but oh well 🤷♂️)</p>
<p>If <code>T
has 0 or 1 elements, the result is
never
. For two elements, it's
EitherOrBoth<U1, U2>
, where
U1
and
U2
are the first two elements of
T
. Else, it's
EitherOrBoth<U1, CombinationOf<R>>
, where
R
represents the rest of tuple
T</code after <code>U1
.
There is indeed a limited depth allowed, typically around 10 levels:
type TestDepth = CombinationOf<[1, 2, 3, 4, 5, 6, 7, 8, 9]> // works fine
type TestDepthBad = CombinationOf<[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]> // error, exceeds depth limit
You could potentially refactor CombinationOf
to group tuples into sets of 3 or 4, raise the depth limit—albeit with heightened complexity—and possibly achieve an increased limit (say 30 or 40... although playing around hasn't yielded values higher than 25), though it may not be warranted.
To demonstrate equivalence between these types, comparing CombinationOf
against yours (CombinationOfOrig
) can be done manually or via an inspector function:
type IfEquals<T, U, Y = unknown, N = never> =
(<G>() => G extends T ? 1 : 2) extends
(<G>() => G extends U ? 1 : 2) ? Y : N;
type TestEqualCombinationOf<T> =
IfEquals<CombinationOfOrig<T>, CombinationOf<T>, "Same", "Oops">;
In this example, IfEquals<T, U, Y, N>
evaluates to Y
if T
and U
are identical types, else N
. Refer to this GitHub comment for further insights on this equality operator.
TestEqualCombinationOf<T>
will output "Same"
when CombinationOfOrig<T>
matches CombinationOf<T>
, otherwise "Oops"
. Below are some test results:
type TestEmpty = TestEqualCombinationOf<[]> // Same
type TestSingleton = TestEqualCombinationOf<[Data1]> // Same
type TestPair = TestEqualCombinationOf<[Data1, Data2]>; // Same
type TestTriple = TestEqualCombinationOf<[Data1, Data2, Data3]>; // Same
interface Data4 { four: 4 }
type TestQuadruple = TestEqualCombinationOf<[Data1, Data2, Data3, Data4]>; // Same
interface Data5 { five: 5 }
type TestQuintuple = TestEqualCombinationOf<[Data1, Data2, Data3, Data4, Data5]>; // Oops
Hence, for tuples up to length 4, both definitions appear equivalent experimentally. The "Oops" result for TestQuintuple
was anticipated, given the original limitation to tuples of length 4.
Link to Play area code