I'm trying to make a function that returns an array of numbers, from 0 to n, but I want the returned type to also be the known array statically.
const makeFoo = (n: number) => [...Array(n).keys()];
const foo1 = [0, 1, 2] as const; // readonly [0, 1, 2]
const foo2 = makeFoo(3); // number[]
console.log(foo1); // [0, 1, 2]
console.log(foo2); // [0, 1, 2]
While foo1
and foo2
, have the same value at runtime, their types are different.
How do I define makeFoo
's return type to be like foo1
? (readonly [0, 1, ... n]
where n
is also known statically e.g. makeFoo = <T extends number>(n: T) => ...
)
You could use a recursive helper type which computes the return type based on a generic argument. However, you will need a type assertion for that (I don't think there's a way without) since [...Array(n).keys()]
will always be inferred as number[]
.
type MakeFoo<N extends number, R extends any[] = []> = R["length"] extends N
? R
: MakeFoo<N, [...R, R["length"]]>;
function makeFoo<N extends number>(n: N): MakeFoo<N> {
return [...Array(n).keys()] as MakeFoo<N>
}
const foo3 = makeFoo(3);
// ^? const foo3: [0, 1, 2]
const foo100 = makeFoo(100);
// ^? const foo100: [0, 1, 2, 3, 4, 5, 6, 7, ... , 99]
Caveat: This only works for a maximum of 999 items. Anything above will resolve to any
.
const foo1000 = makeFoo(1000);
// ~~~~~~~~~~~~~
// Type instantiation is excessively deep and possibly infinite.