I have an utility to create a statically sized array:
export type StaticArray<T, L extends number, R extends any[] = []> = R extends {
length: L;
}
? R
: StaticArray<T, L, [...R, T]>;
I can verify it works:
let myVar: StaticArray<number, 3>;
// ^? let myVar: [number, number, number]
However, I want to make it work with sum types, but I'm not sure how it's possible. I would love this case to have that result:
let myVar: StaticArray<number, 3 | 2 | 1>;
// ^? let myVar: [number, number, number] | [number, number] | [number]
But I doesn't seem to work, since my StaticArray
type stops at the smallest number it sees.
You're saying that you want StaticArray<T, L>
to distribute over unions in L
, so that StaticArray<T, L1 | L2>
is equivalent to StaticArray<T, L1> | StaticArray<T, L2>
.
The general way to do this is to use a distributive conditional type. When you write a conditional type of the form T extends U ? V : W
where T
is a generic type parameter, TypeScript will automatically distribute over unions in T
. If all you want is to turn a non-distributive type into a distributive one, you can wrap it with a "no-op" distributive conditional type like T extends any ? ⋯ : never
. That looks like it does nothing (T
always extends any
) but it has the effect of distributing over unions.
So that leads us to write
type StaticArray<T, L extends number, R extends any[] = []> =
L extends any ? R extends { length: L; } ? R : StaticArray<T, L, [...R, T]> : never;
and you can see that it works as desired:
let myVar: StaticArray<number, 3>;
// ^? let myVar: [number, number, number]
let myVar2: StaticArray<number, 3 | 2 | 1>;
// ^? let myVar2: [number] | [number, number] | [number, number, number]
Note that the any
in T extends any ? ⋯ : never
can be replaced with anything were you're sure T
will extend it. You can use unknown
, or T
, or some constraint (like number
for L
), depending on your preferences.