Reputation: 2336
Example 1 makes perfect sense to me
type X = (1 | 2 | 3) extends (infer I) ? [I] : never;
// X = [1 | 2 | 3]
Example 2 I don't know why the type variables are now intersected
type X = (
((_: 1) => void) | ((_: 2) => void) | ((_: 3) => void)
) extends ((_: infer I) => void) ? [I] : never;
// X = [1 & 2 & 3]
My guess is that has some relationship/similarity to this:
type X = { x: number } | { y: number } | { z: number };
type Y = keyof X;
// Y = x & y & z
However, I am not able to convince myself I understood it from first principle. Would love to hear how this can be explained.
Upvotes: 6
Views: 386
Reputation: 51669
It was expained in the release notes when type inference from conditional types was introduced. Whether it will be a union or intersection depends on the variance of the inferred type. Intuitively, if a type is inferred as a common type for several values, it can be any of them (union), but if it's inferred as argument type for several functions, it must be acceptable by any of them (intersection).
Quote from the release notes:
The following example demonstrates how multiple candidates for the same type variable in co-variant positions causes a union type to be inferred:
type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;
type T10 = Foo<{ a: string, b: string }>; // string
type T11 = Foo<{ a: string, b: number }>; // string | number
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
Further discussion can be found in the PR implementing this feature.
As a side note, it enables some cool tricks like union to intersection type conversion.
Upvotes: 5