Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
466 views
in Technique[技术] by (71.8m points)

typescript - How to make TS automatically infer type arguments on mapped types?

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]>?

question from:https://stackoverflow.com/questions/65851277/how-to-make-ts-automatically-infer-type-arguments-on-mapped-types

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.

Playground link to code


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...