If you try to specify the correct typings for match()
, unfortunately, TypeScript will not infer the callback parameter types as desired. This feature is currently missing in TypeScript, and there are a few options available to deal with it - waiting for implementation (which may never happen), refactoring your code in some way, or simply giving up on that particular functionality.
The accurate typing for match()
function can be seen below:
const match = <V, C extends V[]>(
value: V,
cases: [...{ [I in keyof C]: Case<C[I]> }]
) => {
for (const [key, func] of cases) {
if (Array.isArray(key) ? key.includes(value) : key === value) {
return func(value as never);
}
}
};
In this code snippet, the type of cases
is not just merely Array<Case<V>>
, as that would not accurately track the subtypes of V
at each element of the array. Instead, a mapped array type over the new generic type parameter C
is used, which is constrained to be a subtype of V[]
. Each element of C
is wrapped with Case<⋯>
to get the corresponding element of cases
.
By calling the function like so:
const value: string = "whatever";
match(value, [
[ "something", (v) => {} ],
[ ["other", "thing"], (v) => { }]
]);
the compiler correctly infers V
to be string
, but it fails to infer C
properly. It defaults to just string[]
, causing both instances of v
within the callbacks to be of type
string</code, which isn't ideal.</p>
<hr />
<p>To work around this limitation, one possible solution is to use a helper function inside the call to <code>match()
to infer the cases incrementally instead of all at once. For example:
match(value, [
c("something", (v) => { }),
c(["other", "thing"], (v) => { })
]);
where c
is defined as:
const c = <const T,>(
value: T | Array<T>,
handler: (value: T) => void
): Case<T> => [value, handler];
This workaround helps the compiler to infer the specific types individually, making it more manageable overall, even though it requires an extra step when calling the function.
While this workaround may seem cumbersome, it provides a lightweight solution to the problem at hand until a more direct approach becomes available.
It's important to note that these workarounds depend on the specific use case and may or may not be suitable for every scenario.
Playground link to code