TypeScript - Append type to end of Tuple type

Viewed 1150

Is it possible to write a helper that will append an additional type to a given tuple type?

type A = Append<['a', 'b'], 'c'> // resulting in ['a', 'b', 'c']

I would also be interested in joining 2 tuple types:

type B = Join<['a', 'b'], ['c', 'd']> // resulting in ['a', 'b', 'c', 'd']
1 Answers

In TS it is quite simple to write functions which will shift and unshift elements from the tuple. Consider:

type Shift<T extends Array<any>> 
= ((...a: T) => any) extends ((a: any, ...result: infer Result) => any) 
? Result 
  : never;

type Unshift<A, T extends Array<any>> 
= ((a: A, ...b: T) => any) extends ((...result: infer Result) => any) 
? Result 
  : never;

type A = Shift<['a', 'b', 'c']> // A is ['b','c']
type B = Unshift<'a', A> // B is again ['a', 'b', 'c']

But you have ask about other way round, this is more tricky but by using above Shift and Unshift its doable. Consider:

type Append<
  T extends any[]
  , A
  , ReducedT extends any[] = T
  , Result extends any[] = [A]
  , Rest extends any[] = Shift<ReducedT>
  , Last extends T[keyof T] = T[Rest['length']]
  > = {
  [K in keyof ReducedT]: Rest['length'] extends 0 ? Unshift<Last, Result> : Append<T, A, Rest, Unshift<Last, Result>> 
}[0]

type R = Append<['a', 'b', 'c'], 'd'>
// ['a', 'b', 'c', 'd']

type Merge<
  A extends any[]
  , B extends any[]
  , ReducedA extends any[] = A
  , Result extends any[] = B
  , Rest extends any[] = Shift<ReducedA>
  , Last extends A[keyof A] = A[Rest['length']]
  > = {
  [K in keyof ReducedA]: Rest['length'] extends 0 ? Unshift<Last, Result> : Merge<A, B, Rest, Unshift<Last, Result>> 
}[0]

type AB = Merge<['a', 'b', 'c'], ['d','e','f']>
// ['a', 'b', 'c', 'd', 'e','f']

Full code is available in the playground.

Both types are based on recursion types, for example taking the first type Append:

  • ReducedT is an array which we will use to take out elements from the original Array
  • Result - this is our final array, we put things to it (pay attention we start from [A] so from element we want to have last
  • Rest - it represents next iteration of ReducedT so we shift one element out
  • Last - last element from original array
  • Rest['length'] extends 0 ? Unshift<Last, Result> : Append<T, A, Rest, Unshift<Last, Result>> - every iteration checks if our reduced array is not empty, if is we do last unshift and end the recursion, if its not empty we Append again with smaller ReducedT and bigger Result as we shift from one and unshift to the second.
  • [K in keyof ReducedT] is here only to trick TS type system, as without it TS would complain about infinite call
Related