Reputation: 67900
Consider the following code (Playground):
type Either<E, D> = ["error", E] | ["data", D]
type GenericTuple<T = any> = [T, ...T[]];
// [Either<E, D1>, Either<E, D2>, ...] -> Either<E, [D1, D2, ...]>
declare function mergeEithers<E, Ds extends GenericTuple>(
eithers: { [K in keyof Ds]: Either<E, Ds[K]> }
): Either<E, Ds>;
type MessageError = {message: string};
declare const either1: Either<MessageError, string>
declare const either2: Either<MessageError, number>
// unpacked: Either<unknown, [string, number]>
const unpacked = mergeEithers([either1, either2])
How should we write it so TS automatically infers unpacked
as Either<MessageError, [string, number]>
?
Upvotes: 0
Views: 972
Reputation: 329013
You're trying to use inference from mapped types in the type signature of mergeEithers
. I find that the compiler isn't always able to extract the desired amount of information in such cases. You are passing in a value eithers
of type { [K in keyof Ds]: Either<E, Ds[K]> }
and hoping the compiler can infer both E
and Ds
from it. Generally speaking it will be able to do something reasonable with Ds
(since it's a homomorphic mapping) and probably give up before you get anything useful for E
.
In cases where this sort of "backwards" inference isn't reliable, I usually go for the brute-force "forwards" inference: if I want the compiler to infer a type A
, I should pass in a value of type A
. To that end, we will have eithers
be of type A
, which will tend to be inferred properly. Then we will use A
to compute the desired output type which used to be called Either<E, Ds>
:
declare function mergeEithers<A extends GenericTuple<Either<any, any>>>(
eithers: A
): Either<
Extract<A[number], ["error", any]>[1],
{ [K in keyof A]: Extract<A[K], ["data", any]>[1] }
>;
Here I'm using the Extract
utility type to pull out the error and the data parts of the union. The type Extract<A[number], ["error", any]>[1]
should be the union of all the possibly-different error types of the eithers
tuple, while the type Extract<A[K], ["data", any]>[1]
should be the data type for each element K
of the eithers
tuple.
Let's see if it works:
const unpacked = mergeEithers([either1, either2])
// const unpacked: Either<MessageError, [string, number]>
Looks good.
Upvotes: 2