186

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

6 Answers 6

446
+550

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 ? (x: U)=>void : never) extends ((x: 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.

38
  • 31
    10x. I always learn new an interesting things from you. I was very close on this question stackoverflow.com/questions/50369299/… but really need a way to transform the union into an intersection Commented May 16, 2018 at 16:21
  • 6
    This answer is awesome but I really find it hard to understand how this part "That distributes the union U and repackages it into a new union where all the consitutents are in contravariant position" works :( I can't fully grasp this contravariant position part. .I thought that this code: type Param<T> = T extends (arg: infer U) => void ? U : never; type InferredParams = Param<((a: string) => void) | ((a: number) => void)>; should give me string & number but it gives me string | number. Can you explain why? Commented Jul 21, 2018 at 18:26
  • 17
    It's because bare type parameters before extends in a conditional type are distributed across any union constituents. If you want to disable distributed conditional types, you can use the trick of making the type parameter "clothed", such as a single-element tuple like this: type Param<T> = [T] extends [(arg: infer U) => void] ? U : never;. That should work the way you want.
    – jcalz
    Commented Jul 22, 2018 at 19:02
  • 4
    @RanLottem the key is distributive conditional types. The handbook explains it pretty well, in my opinion. I've expanded on it elsewhere you need more info. Good luck!
    – jcalz
    Commented May 9, 2019 at 13:51
  • 3
    In both of those examples, the relevant type is in contravariant position (parameter of a function type). If you have a type like (k: A)=>void | (k: B) => void | (k: C) => void and infer (k: infer I) => void from it, the only reasonable inference is that I is the intersection of A, B, and C. Certainly ((k: A) => void) | (k: B) => void) is not assignable to (k: A | B) => void; see typescriptlang.org/docs/handbook/release-notes/…
    – jcalz
    Commented Apr 8, 2022 at 18:56
17

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.

8

jcalz' solution to this question is [as always] perfect, however, for those who might not fully understand it, here's a bit more explanation on this quote in his answer:

Multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred.

The above tip is actually the main trick used in the definition of the UnionToIntersection type, however, the term "contra-variant" was vague to me and Googling didn't give helpful results either, so let's go over it here:

According to Wikipedia:

Many programming language type systems support subtyping. Variance refers to how subtyping between more complex types relates to subtyping between their components. For example, how should a list of Cats relate to a list of Animals? Or how should a function that returns Cat relate to a function that returns Animal?

Let's see the above explanation in code:

type A = (a: Animal) => void
type C = (d: Cat) => void
declare let aFunc: A;
declare let cFunc: C;

Now which of the below assignments do you expect to be correct?

cFunc = aFunc // 🚩 [ ]  ✅ [ ]
aFunc = cFunc // 🚩 [ ]  ✅ [ ]

First make a guess, then read on. :)

Opposite of what we might expect from "Inheritance", where values of a subtype could be assigned to a variable of their supertype, this is not true in the same direction when this variable is a parameter of a function type, and we're assigning "functions" (Wikipedia's intended "complex type"). And interestingly, it is indeed correct in the opposite direction! I.e., cFunc = aFunc is the correct one in the snippet above, and aFunc = cFunc is wrong.

Now let's see why cFunc = aFunc is the correct one. The reason it is correct is, when we assign some variable of type Y to some variable of type X, it would be "correct" only if the new type (Y in this example) doesn't break any of the usages of the old type (X in this example). For example:

a = new Animal()
c = new Cat()
a = c   // ✅ Not breaking, everywhere an Animal is used, a Cat must be useable too
        // (It is also formally known as the "Liskov Substitution Principle".)
a.eat() // ---> c.eat() ✅ No problem, Cats can eat too

Now use this same rule in case of the function types: If you are assigning a function foo of function type Foo, to a variable bar of function type Bar, then wherever you've used bar, it must remain still useable / valid.

declare let foo: (param: Animal): void
declare let bar: (param: Cat): void
a = new Animal()
c = new Cat()

// valid usages of foo:
foo(a) // ✅
foo(c) // ✅

// valid usage of bar:
bar(c) // ✅

foo = bar // ❌ wrong because 👇
foo(a)    // ❌ this one has not remained useable / valid
          // because foo expects a Cat now, but is receiving an Animal, which is not valid
foo(c)    // ✅

bar = foo // ✅ correct because 👇 all usages of bar remains still useable / valid
bar(c)    // bar expects an Animal now, and has received a Cat, which is still valid
          // ⭐ That's why we say function parameter is a **contra-variant** position for
          // a type, because it reverses the direction of the assignability.

So now we can understand why cFunc = aFunc is the correct choice!

The fun edge case of this is, you can type a function parameter as never and that allows you to assign functions with whatever type for that parameter to it:

type Foo = (a: never) => void
type Bar = (a: Function) => void
type Baz = (a: boolean) => void
type Qux = (a: SuperComplexType) => void
declare let foo: Foo
declare let bar: Bar
declare let baz: Baz
declare let qux: Qux
foo = bar // ✅
foo = baz // ✅
foo = qux // ✅

A summary of all the three co/contra/in variances using the same Cat and Animal example is:

  • Covariance: () => Cat is assignable to () => Animal, because Cat is assignable to Animal; It "preserves the direction of the assignability".
  • Contravariance: (Animal) => void is assignable to (Cat) => void, because something that expects an Animal can also take a Cat; It "reverses the direction of the assignability".
  • Invariance: (Animal) => Animal is not assignable to (Cat) => Cat, because not all returned Animals are Cats, and (Cat) => Cat is not assignable to (Animal) => Animal, because something expecting a Cat cannot take any other kind of Animal.

Now this is how jcalz' UnionToIntersection works:

type FirstHalfOfUnionToIntersection<U> = U extends any ? (k: U)=>void : never

This is a distributed conditional (because the type U before extends is a naked type (appears alone and is not part of some more complex type expression)), so runs the conditional for each of the components of the union, e.g., in case of X | Y | Z, it produces ((k: X) => void) | ((k: Y) => void) | ((k: Z) => void).

On the second half of the type, it's actually doing this:

<A_union_of_some_functions_from_first_half> extends ((k: infer I)=>void) ? I : never

This is again a distributed conditional, however, here's the interesting part: The type I that is being inferred is in a contra-variant position (it is a function parameter), so all possible inferences of it will be intersected!

Multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred.

E.g., continuing on the same X | Y | Z example, the result will be X & Y & Z.

6

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

4

You can use UnionToIntersection from a brilliant library utility-types. (1.5M weekly downloads in 2022/11)

Or similar type UnionToIntersection from another gem library type-fest (117M weekly downloads in 2022/11).

Use it as follows:

import type { UnionToIntersection } from 'utility-types'
// or
import type { UnionToIntersection } from 'type-fest'

type FunctionUnion = (() => void) | ((p: string) => void)
type FunctionIntersection = UnionToIntersection<FunctionUnion>
// (() => void) & ((p: string) => void)
5
  • 3
    This answer here is the source for both of those types. This is the 'original' union to intersection :) Commented Nov 15, 2022 at 9:10
  • 1
    what about Type = 0 | 1? I get never Commented May 29, 2023 at 11:11
  • @stackoverflow UnionToIntersection changes 0 | 1 into intersection 0 & 1 which gives never.
    – mrkvon
    Commented May 30, 2023 at 13:17
  • @mrkvon why that? what about 1 & 2? still never? Commented May 31, 2023 at 14:03
  • 1
    @stackoverflow yes, still never. i guess it's because two different constants have no type in common. in practice, result is a type that will satisfy both intersecting types (e.g. 1 & number => 1)
    – mrkvon
    Commented May 31, 2023 at 15:16
0

@jcalz, your answer here is legendary!

But consider this variation, now possible with recent TypeScript versions:

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

The trick is to change infer I to infer I extends U. Since TS 4.8, TypeScript has allowed an extends refinement on infer clauses.

There are cases where TypeScript loses the constraint on the inferred type without the extends U part of the clause. I wrote up one such failure here: recovering-record-types.

Consider using this infer I extends U variation for type intersection in modern TypeScript.

Playground link

Not the answer you're looking for? Browse other questions tagged or ask your own question.