Update for TS 4.1+:
With variadic tuple types introduced in TS 4.0, and recursive conditional types introduced in TS4.1, you can now write ExcludeFromTuple
more simply as:
type ExcludeFromTuple<T extends readonly any[], E> =
T extends [infer F, ...infer R] ? [F] extends [E] ? ExcludeFromTuple<R, E> :
[F, ...ExcludeFromTuple<R, E>] : []
You can verify that this works as desired:
type DepressingJourney = ExcludeFromTuple<Journey, "don't">;
// type should be ['stop', 'believing']
type SlicedPi = ExcludeFromTuple<[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9], 1 | 9>
// type SlicedPi = [3, 4, 5, 2, 6, 5, 3, 5, 8, 7]
Playground link to code
Pre TS-4.1 answer:
Yuck, this is something that really needs recursive conditional types which are not supported in TypeScript yet. If you want to use them you do so at your own risk. Usually I'd rather write a type that should be recursive and then unroll it into a fixed depth. So instead of type F<X> = ...F<X>...
, I write type F<X> = ...F0<X>...; type F0<X> = ...F1<X>...;
.
To write this I'd want to use basic "list processing" types for tuples, namely Cons<H, T>
to prepend a type H
onto a tuple T
; Head<T>
to get the first element of a tuple T
, and Tail<T>
to get the tuple T
with the first element removed. You can define those like this:
type Cons<H, T> = T extends readonly any[] ? ((h: H, ...t: T) => void) extends ((...r: infer R) => void) ? R : never : never;
type Tail<T extends readonly any[]> = ((...t: T) => void) extends ((h: any, ...r: infer R) => void) ? R : never;
type Head<T extends readonly any[]> = T[0];
Then the recursive type would look something like this:
/* type ExcludeFromTupleRecursive<T extends readonly any[], E> =
T["length"] extends 0 ? [] :
ExcludeFromTupleRecursive<Tail<T>, E> extends infer X ?
Head<T> extends E ? X : Cons<Head<T>, X> : never; */
The idea is: take the tail of the tuple T
and perform ExcludeFromTupleRecursive
on it. That's the recursion. Then, to the result, you should prepend the head of the tuple if and only if it doesn't match E
.
But that's illegally circular, so I unroll it like this:
type ExcludeFromTuple<T extends readonly any[], E> = T["length"] extends 0 ? [] : X0<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X0<T extends readonly any[], E> = T["length"] extends 0 ? [] : X1<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X1<T extends readonly any[], E> = T["length"] extends 0 ? [] : X2<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X2<T extends readonly any[], E> = T["length"] extends 0 ? [] : X3<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X3<T extends readonly any[], E> = T["length"] extends 0 ? [] : X4<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X4<T extends readonly any[], E> = T["length"] extends 0 ? [] : X5<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X5<T extends readonly any[], E> = T["length"] extends 0 ? [] : X6<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X6<T extends readonly any[], E> = T["length"] extends 0 ? [] : X7<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X7<T extends readonly any[], E> = T["length"] extends 0 ? [] : X8<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X8<T extends readonly any[], E> = T["length"] extends 0 ? [] : X9<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type X9<T extends readonly any[], E> = T["length"] extends 0 ? [] : XA<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type XA<T extends readonly any[], E> = T["length"] extends 0 ? [] : XB<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type XB<T extends readonly any[], E> = T["length"] extends 0 ? [] : XC<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type XC<T extends readonly any[], E> = T["length"] extends 0 ? [] : XD<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type XD<T extends readonly any[], E> = T["length"] extends 0 ? [] : XE<Tail<T>, E> extends infer X ? Head<T> extends E ? X : Cons<Head<T>, X> : never;
type XE<T extends readonly any[], E> = T; // bail out
Having fun yet? Let's see if it works:
type DepressingJourney = ExcludeFromTuple<Journey, "don't">;
// type should be ['stop', 'believing']
type SlicedPi = ExcludeFromTuple<[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9], 1 | 9>
// type SlicedPi = [3, 4, 5, 2, 6, 5, 3, 5, 8, 7]
Looks good to me.
Link to code