This article is a guided exploratory learning note. I hope you can patiently and carefully follow the rhythm of the article's content to understand it bit by bit. I believe you will gain something!

At first, I just wanted to learn about a common TypeScript problem: Union to Tuple, which converts a union type like `"a" | "b"`

into the corresponding tuple `["a", "b"]`

. This problem is quite comprehensive, so let's see how to tackle it.

Those familiar with using `infer`

as a tool might naturally think of the following way to perform a conditional type inference:

```
type UnionToTuple<Union> = Union extends infer A | infer B ? [A, B] : never;
type IncorrectTuple = UnionToTuple<"a" | "b">;
```

However, the result is not what we expected. The result of `IncorrectTuple`

is `["a", "a"] | ["b" | "b"]`

, which is because in TypeScript, "performing conditional type inference on bare type parameters" is designed to be "distributive." That is, if the left side of `extends`

is a union type, TypeScript will break down the union type on the left side of the `extends`

operator for judgment. So, it is actually doing: `'a' extends ...`

and `b extends ...`

, which shows that this approach does not work.

To solve this problem, we need to use some other "tricky transformations," or you could say we need to leverage certain features of TypeScript. These tricks belong to "knowledge content," not something derived from knowledge you have already learned. Therefore, to enhance your "gymnastics ability," you can only rely on reading and absorbing a large number of examples to accumulate enough experience.

Upon careful consideration of what we need to achieve, it is not difficult to realize that since we want to construct a tuple from each item in the Union type, the first step is to extract `"a"`

and `"b"`

from the example above.

Let's look at Example 1:

```
type U1_1 = (a: number) => void
type U1_2 = (a: boolean) => void
type U1 = ((arg: U1_1) => void) | ((arg: U1_2) => void)
type TEST = U1 extends (arg: infer I) => void ? I : never
// TEST: ((a: number) => void) & ((a: boolean) => void)
```

We are pleasantly surprised to find that performing conditional type inference on a union type yields an intersection type! What does this computation mean? Let's interpret it:

The union type on the left side of `extends`

means: this is a function, its parameter is also a function, and it has a single parameter. This parameter `arg`

can be either `number`

or `boolean`

.

According to TypeScript's function parameter types having the property of "contravariance", we can conclude that `arg`

must be the intersection type of `U1_1`

and `U1_2`

, i.e., `U1_1 & U1_2`

. If you are unclear about the reason for this conclusion, please read the article in the link carefully to clarify the concepts of covariance and contravariance.

We can write a helper type to construct `"a" | "b"`

into `((arg: (a: "a") => void) => void) | ((arg: (a: "b") => void) => void)`

, and then convert it into an intersection type:

```
type UnionToFnUnions<U> = U extends any
// Since almost all types extend any,
// we can construct with U here
? (k: (x: U) => void) => void
: never
type UnionToFnIntersections<U> = UnionToFnUnions<U> extends (arg: infer I) => void ? I : never
```

Now let's look at Example 2 to understand a feature of TypeScript:

```
type T1 = (
((arg1: number) => void)
& ((arg1: boolean) => void)
)
interface T2 {
(arg1: number): void
(arg1: boolean): void
}
type T3 = T1 extends T2 ? true : false
// ^? true
```

We reach the conclusion: **In TypeScript, multiple function types with different signatures as an intersection type are equivalent to a function interface type with multiple overloads. On such an interface type, we can perform the following infer inference:**

```
type FnIntersectionToTuple<T> = T extends {
(x: infer A): void;
(x: infer B): void;
} ? [A, B] : never;
```

Finally, we see a glimmer of hope! We have found a winding path to obtain the desired result. However, this seems imperfect because the number of elements in the final tuple depends entirely on how many `infer`

we wrote!

Is there a way to "loop" through each function overload?

Let's look at the following example:

```
type F1 = (arg: "a") => void
type F2 = (arg: "b") => void
type F3 = (arg: "c") => void
type F_TEST1 = (F1 & F2 & F3) extends { (a: infer R): void; } ? R : never;
// ^? "c"
```

Hey! How come we only got `"c"`

? Where did `"a"`

and `"b"`

go? Don't panic; this is due to TypeScript's design. You can treat this as a certain "feature" of TypeScript, to be used as a tool in gymnastics!

Our conclusion is: **In TypeScript, when performing conditional inference on multiple function overloads, only the last overload form will be targeted.**

We can integrate the feature learned from the above example into the function intersection type we inferred earlier and write a helper type:

```
type IntersectionPop<U> = UnionToFnIntersections<U> extends { (a: infer A): void; } ? A : never;
```

Now we can extract each item from the Union, and next we need to write another helper type to help us insert them into the final tuple:

```
type Prepend<U, T extends any[]> =
((a: U, ...r: T) => void) extends (...r: infer R) => void ? R : never;
```

Great! Everything is ready. Now let's write the final type calculation:

```
type UnionToTupleRecursively<Union, Result extends any[]> = {
1: Result;
0: UnionToTupleRecursively<
Exclude<Union, IntersectionPop<Union>>, // Recursively remove the already popped items
Prepend<IntersectionPop<Union>, Result> // Insert the popped items into Result
>
}[[Union] extends [never] ? 1 : 0];
type UnionToTuple<U> = UnionToTupleRecursively<U, []>;
```

Actually, the problem ends here, but if you, like me, are a bit of a perfectionist, you will find a flaw after trying this `UnionToTuple`

:

```
type FINAL_1 = UnionToTupleRecursively<"a" | "b", []>
// ^? [a: "a", a: "b"]
```

The resulting `FINAL_1`

is a labeled tuple. Although this does not affect its nature, it should be said that there is no difference in usage from `["a", "b"]`

, but it looks a bit strange. To remove this label, we can consider wrapping it in another transformation as follows:

```
type RemoveLabels<Tuple, Result extends any[]> = Tuple extends [infer E, ...infer Rest]
? RemoveLabels<Rest, [...Result, E]>
: Result
type UnionToTuple<U> = RemoveLabels<UnionToTupleRecursively<U, []>, []>;
type FINAL_2 = UnionToTupleRecursively<"a" | "b", []>
// ^? ["a", "b"]
```

The Union to Tuple problem concludes here. Let's summarize the various properties we have learned:

- Performing conditional type inference on bare type parameters is distributive.
- A union type can be transformed into a function union by treating each item as a parameter, and then converted into a function intersection using the property of parameter contravariance.
- The intersection type of function overloads can be rewritten as an equivalent interface form.
- When performing conditional inference on multiple function overloads, only the last overload form will be targeted.

Recently, the Volar team encountered a problem while writing LSP services for Vue's emits types. I summarized the essence of the problem as follows:

Vue's

`defineEmits`

requires users to provide a type parameter, and it will return a type similar to`(event: 'foo', arg: T1) => void & (event: 'bar', arg1: T2, arg2: T3) => void`

.

Now we need to extract all "first parameter" types from this returned intersection type and union them.

We can easily speculate that the final type calculation will likely be a recursive one similar to the above, ultimately resulting in a Tuple. Although Union to Tuple is challenging, Tuple to Union is quite easy; we just need to do `Tuple[number]`

:

```
type ExtractFirstArgRecursively<I, Result extends any[]> = {
1: Result;
0: ExtractFirstArgRecursively<
NarrowIntersection<I, PopIntersectionFuncs<I>>,
Prepend<GetIntersectionFuncsLastOneFirstArg<I>, Result>
>
}[[I] extends [never] ? 1 : 0];
```

Then we implement the helper types for intermediate calculations:

```
// Utilizing the conclusion we summarized in 4
type PopIntersectionFuncs<I> = I extends (...args: infer A) => infer R ? (...args: A) => R : never
type GetIntersectionFuncsLastOneFirstArg<I> = I extends (firstArg: infer F, ...rest: infer P) => void ? F : never
```

The `NarrowIntersection`

type has a small pitfall worth mentioning. Initially, I wrote it like this:

```
type NarrowIntersection<I, T> = I extends (T & infer R) ? R : never
```

However, this always resulted in an extra `never`

in the final Tuple. I suspected that the narrowing here was problematic, so I manually wrote out the intermediate steps:

```
type E1 = ((event: 'foo', arg: number) => void)
type E2 = ((event: 'bar', arg1: string, arg2: number) => void)
type E3 = ((event: 'fee', arg: boolean) => void)
type TT = (E1 & E2 & E3)
type Last1 = PopIntersectionFuncs<TT>
type NTT1 = NarrowIntersection<TT, Last1>
// ^? ((event: 'foo', arg: number) => void) & ((event: 'bar', arg1: string, arg2: number) => void)
// Result = ["fee"]
type Last2 = PopIntersectionFuncs<NTT1>
type NTT2 = NarrowIntersection<NTT1, Last2>
// ^? (event: 'foo', arg: number) => void
// Result = ["bar", "fee"]
type Last3 = PopIntersectionFuncs<NTT2>
type NTT3 = NarrowIntersection<NTT2, Last3>
// ^? unknown
// Result = ["foo", "bar", "fee"]
type Last4 = PopIntersectionFuncs<NTT3>
type NTT5 = NarrowIntersection<NTT3, Last4>
// ^? never ---> will terminate recursion, no further rounds
// Result = [never, "foo", "bar", "fee"]
```

So ultimately, this `unknown`

caused the issue. We can reference some useful utility types from the internet, `IsUnknown`

, to solve this problem:

```
type IsAny<T> = 0 extends 1 & T ? true : false
type IsNever<T> = [T] extends [never] ? true : false
type IsUnknown<T> = IsAny<T> extends true
? false
: unknown extends T
? true
: false
type NarrowIntersection<I, T> = I extends (T & infer R)
? IsUnknown<R> extends true
? never
: R
: never
```

Great! Now let's provide a final answer to Volar's problem:

```
type GetAllOverloadsFirstArg<I> = RemoveLabels<ExtractFirstArgRecursively<I, []>, []>
type FINAL = GetAllOverloadsFirstArg<TT>
// ["foo", "bar", "fee"]
```

This pull request with the changes can be found here, and the code has been merged into Volar Vue v1.8.4.