Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 (union) instead of number & string (intersection).

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

like image 934
David Karapetyan Avatar asked Jan 03 '23 10:01

David Karapetyan


1 Answers

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.

like image 179
CRice Avatar answered Feb 20 '23 17:02

CRice