Remove all optional items from a Tuple type

Let say I want to turn a tuple with optional items like [1, 2, 3?, 4?] to an array containing only the required items -> [1, 2]

What i've come up with as shown below it to turn to never all optional items, and I'm stuck here.

type OnlyReq <L extends any []> = {
  [K in keyof L]-?: L [K] extends Required <L> [K]  ? L [K] : never
}

type Found = OnlyReq <[1, 2, 3?, 4?]> // [1, 2, never, never]

playground


Solution 1:

My approach here would be to write a recursive conditional type (actually tail recursive so it will work for quite long tuples in TS4.5+) that walks through the tuple until it finds that the rest of it is all optional.

Note that optional elements in tuple types cannot be followed by required elements; that is, something like [1, 2?, 3] is impossible. So if a tuple has any required elements, the first element in particular must be required.

Here's an implementation:

type OnlyReq<T extends any[], U extends any[] = []> = Partial<T> extends T ? U :
  T extends [infer F, ...infer R] ? OnlyReq<R, [...U, F]> : U

We are accumulating the result in the type parameter U (which starts off as the empty tuple []), so as soon as we decide to stop iterating we return U.

The check Partial<T> extends T uses the Partial<T> utility type to come up with an all-optional version of the input tuple. Generally speaking T extends Partial<T> is true but Partial<T> extends T is not, unless T is already the same as Partial<T>... in other words, Partial<T> extends T if and only if T is all-optional.

If the tuple T is all-optional then we return U. Also if T is empty we return U (which is what happens if T can't be split into a first element F and a rest tuple R). If T has a first element F, then we know it's required (otherwise T would be all-optional), and we can just push it onto the end of the U tuple for the recursive call for the rest of the tuple R.


Let's see if it works:

type Found = OnlyReq<[1, 2, 3?, 4?]>
// type Found = [1, 2]

Looks good.

Also note that rest elements in tuple types are also considered "optional" in this same test, so they should also be stripped:

type StripRest = OnlyReq<[string, boolean?, ...number[]]> 
// type StripRest = [string]

Indeed, a non-tuple array type (Foo[]) is equivalent to tuple consisting of just a rest element ([...Foo[]]) and so would be transformed to an empty tuple, which may or may not be what you want:

type Hmm = OnlyReq<number[]>
// type Hmm = []

I'm considering "leading" or "middle" rest elements in tuple types to be out of scope here, since they do weird things and you didn't ask about them (and I hope it doesn't come up because it's annoying to manipulate such types):

type What = OnlyReq<[...string[], number]>
// type What = []
type AlsoWhat = OnlyReq<[string, ...boolean[], number]>
//type AlsoWhat = [string]

Playground link to code