简体   繁体   中英

TypeScript type inference in conditional types

I'm confused by how types are inferred in the following example

type RetType<T> = T extends (...args: (infer I)[]) => infer R ? [I, R] : any;
type X = (a: number, b: string) => void;
type Q = RetType<X>;

If you hover over the type of Q in the playground you will get [number & string, void] . It's confusing because I would expect I to be inferred as number | string number | string (union) instead of number & string (intersection).

Does anyone understand why the input arguments are inferred as an intersection instead of a union?

TL;DR: Because whatever I is, it has to be assignable to all the arguments of the function type T .


This is because function arguments are contra-variant . That just means that for one function to be used in place of another, its argument type must be the same or more general than the other's. This is pretty obvious when you take a look at an example:

type f: (arg: string) => string;
type g: (arg: "foo") => string;

// f is assignable to g, since a function expecting
// to receive any string should have no problem accepting
// the specific string "foo".

// However, the reverse isn't true. You can't assign g to f,
// since g expects to receive exactly the string "foo" for its
// argument, but if it's used in place of f, it can receive any string.

In other words, f is assignable to g because g 's argument is assignable to f 's. That reversal is the contra part.

So if T is a subtype of some mystery function type (...args: I[]) => R , argument contra-variance tells us that I must be assignable to the argument type of T .

Thus, T extends (...args: (infer I)[]) => infer R tells typescript to infer some single type I such that I can be used in place of any argument of T .

So for your type X , it should be true that whatever I is, it must be assignable to both arguments. Since the argument types are respectively number and string , we ask: what type is assignable to both of those?

Well, number & string .


*For more, you may be interested in reading about co and contra-variance .

This may not be the answer or explanation you're looking for, but this is called out in the docs :

Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred:

 type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never; type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>; // string type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>; // string & number 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM