Transform union type to intersection type

Typescript

Typescript Problem Overview


Is there a way to transform a union type into an intersection type :

type FunctionUnion = () => void | (p: string) => void
type FunctionIntersection = () => void & (p: string) => void

I would like to apply a transformation to FunctionUnion to get FunctionIntersection

Typescript Solutions


Solution 1 - Typescript

You want union to intersection? Distributive conditional types and inference from conditional types can do that. (Don't think it's possible to do intersection-to-union though, sorry) Here's the evil magic:

type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

That distributes the union U and repackages it into a new union where all the consitutents are in contravariant position. That allows the type to be inferred as an intersection I, as mentioned in the handbook:

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


Let's see if it works.

First let me parenthesize your FunctionUnion and FunctionIntersection because TypeScript seems to bind the union/intersection more tightly than function return:

type FunctionUnion = (() => void) | ((p: string) => void);
type FunctionIntersection = (() => void) & ((p: string) => void);

Testing:

type SynthesizedFunctionIntersection = UnionToIntersection<FunctionUnion>
// inspects as 
// type SynthesizedFunctionIntersection = (() => void) & ((p: string) => void)

Looks good!

Be careful that in general UnionToIntersection<> exposes some details of what TypeScript thinks is an actual union. For example, boolean is apparently internally represented as true | false, so

type Weird = UnionToIntersection<string | number | boolean>

becomes

type Weird = string & number & true & false

which in TS3.6+ gets eagerly reduced to

type Weird = never

because it's impossible to have a value which is string and number and true and false.

Hope that helps. Good luck!

Solution 2 - Typescript

There is also a very related problem when you would like an intersection of several types, but not necessarily convert unions to intersections. There is just no way to get right to intersections without resorting to temporary unions!

The problem is that types we would like to get an intersection of might have unions inside, which will be converted to intersections too. Guards to the rescue:

// union to intersection converter by @jcalz
// Intersect<{ a: 1 } | { b: 2 }> = { a: 1 } & { b: 2 }
type Intersect<T> = (T extends any ? ((x: T) => 0) : never) extends ((x: infer R) => 0) ? R : never

// get keys of tuple
// TupleKeys<[string, string, string]> = 0 | 1 | 2
type TupleKeys<T extends any[]> = Exclude<keyof T, keyof []>

// apply { foo: ... } to every type in tuple
// Foo<[1, 2]> = { 0: { foo: 1 }, 1: { foo: 2 } }
type Foo<T extends any[]> = {
    [K in TupleKeys<T>]: {foo: T[K]}
}

// get union of field types of an object (another answer by @jcalz again, I guess)
// Values<{ a: string, b: number }> = string | number
type Values<T> = T[keyof T]

// TS won't believe the result will always have a field "foo"
// so we have to check for it with a conditional first
type Unfoo<T> = T extends { foo: any } ? T["foo"] : never

// combine three helpers to get an intersection of all the item types
type IntersectItems<T extends any[]> = Unfoo<Intersect<Values<Foo<T>>>>

type Test = [
    { a: 1 } | { b: 2 },
    { c: 3 },
]

// this is what we wanted
type X = IntersectItems<Test> // { a: 1, c: 3 } | { b: 2, c: 3 }

// this is not what we wanted
type Y = Intersect<Test[number]> // { a: 1, b: 2, c: 3 }

The execution in the given example goes like this

IntersectItems<[{ a: 1 } | { b: 2 }, { c: 3 }]> =
Unfoo<Intersect<Values<Foo<[{ a: 1 } | { b: 2 }, { c: 3 }]>>>> =
Unfoo<Intersect<Values<{0: { foo: { a: 1 } | { b: 2 } }, 1: { foo: { c: 3 } }}>>> =
Unfoo<Intersect<{ foo: { a: 1 } | { b: 2 } } | { foo: { c: 3 } }>> =
Unfoo<(({ foo: { a: 1 } | { b: 2 } } | { foo: { c: 3 } }) extends any ? ((x: T) => 0) : never) extends ((x: infer R) => 0) ? R : never> =
Unfoo<(({ foo: { a: 1 } | { b: 2 } } extends any ? ((x: T) => 0) : never) | ({ foo: { c: 3 } } extends any ? ((x: T) => 0) : never)) extends ((x: infer R) => 0) ? R : never> =
Unfoo<(((x: { foo: { a: 1 } | { b: 2 } }) => 0) | ((x: { foo: { c: 3 } }) => 0)) extends ((x: infer R) => 0) ? R : never> =
Unfoo<{ foo: { a: 1 } | { b: 2 } } & { foo: { c: 3 } }> =
({ foo: { a: 1 } | { b: 2 } } & { foo: { c: 3 } })["foo"] =
({ a: 1 } | { b: 2 }) & { c: 3 } =
{ a: 1 } & { c: 3 } | { b: 2 } & { c: 3 }

Hopefully this also shows some other useful techniques.

Solution 3 - Typescript

I extended @jcalz's answer slightly to get around the boolean issue he described.

type UnionToIntersectionHelper<U> = (
  U extends unknown ? (k: U) => void : never
) extends (k: infer I) => void
  ? I
  : never;

type UnionToIntersection<U> = boolean extends U
  ? UnionToIntersectionHelper<Exclude<U, boolean>> & boolean
  : UnionToIntersectionHelper<U>;

this basically prevents it from converting the true | false under the hood to a true & false, preserving the boolean nature of it.

Now it will correctly say UnionToIntersection<boolean> is boolean, not never, while still correctly saying UnionToIntersection<boolean | string> is never

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTitian Cernicova-DragomirView Question on Stackoverflow
Solution 1 - TypescriptjcalzView Answer on Stackoverflow
Solution 2 - Typescriptpolkovnikov.phView Answer on Stackoverflow
Solution 3 - TypescriptbdwainView Answer on Stackoverflow