I'm trying to implement a function that looks like Jest's test.each iterator:
// with "as const"
forEach([
[ 1, 2, 3 ],
[ "a", "b", "c" ],
] as const, (first, second, third) => {
// ...
});
// without "as const"
forEach([
[ 1, 2, 3 ],
[ "a", "b", "c" ],
], (first, second, third) => {
// ...
});
The goal here is to make arguments first, second, and third strongly typed: without as const they all should be string | number; with as const they should be respectively 1 | "a", 2 | "b", and 3 | "c". The actual implementation of this function is irrelevant and might not even make sense, given its name.
I came this close to actually achieve the desired effect (see Playground):
// implementation is not needed
declare function forEach<
Lists extends ReadonlyArray<ReadonlyArray<unknown>>,
>(
lists: Lists,
iterator: (...args: Lists[number]) => void,
): void;
I also thought about going with whatever Jest's typings are, but their approach is messy and fragile, I don't want to do this.
The arguments are properly typed, but there are still compiler errors in both cases:
with
as const:The type
readonly [1, 2, 3]is 'readonly' and cannot be assigned to the mutable type[first: 1 | "a", second: 2 | "b", third: 3 | "c"]without
as const:Type
number[] | string[]is not assignable to type[first: string | number, second: string | number, third: string | number]. Target requires 3 element(s) but source may have fewer.
Is there a way to define the forEach function to satisfy both use cases?