Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Union to Intersection

Tags:

typescript

While skimming through the TypeScript challenges, I came across a particularly interesting one: How to turn a union to an intersection.

Unable to figure it out myself, I turned to the solutions where I found a great approach here and an even greater explanation given here by @jcalz.

The only problem in my way was this question: effectively a user tried to break down the solution in multiple separate statements and, to my surprise, the result was not the same. Instead of foo & bar we were getting foo | bar. Putting the solution back together as a "one-liner" the result gets "restored": foo & bar.

// Type definition
type UnionToIntersection<U> = (U extends any ? (arg: U) => any : never) extends ((arg: infer I) => void) 
  ? I 
  : never;

// As expected 👍 ('foo' & 'bar' is never)
type I = UnionToIntersection<'foo' | 'bar'>;  // never

// Let's break-down `UnionToIntersection`

type A = 'foo' | 'bar';

type B = A extends any ? (arg: A) => any : never // (arg: A) => any;

// This should have been 'foo' & 'bar' (never) just like `type I` 🤯
type C = B extends ((arg: infer I) => void) ? I : never //  'foo' | 'bar'

What's going on here? Shouldn't type I and type C be the same?

like image 660
stratis Avatar asked Jan 29 '26 17:01

stratis


2 Answers

effectively a user tried to break down the solution in multiple separate statements

The user made a mistake. In the original type, U is a generic type which gets distributed in the conditional.

In B, you use A which is not a generic type. No distribution takes place.

If we modify B to also have a generic type U (which we just default to be A), we will see the same result never.

type B<U = A> = U extends any ? (arg: U) => any : never 
type C = B extends ((arg: infer I) => void) ? I : never 
//   ^? never

Playground

like image 91
Tobias S. Avatar answered Feb 01 '26 18:02

Tobias S.


Distributive conditional types work for (naked) parameter types.

Replace your

type B = A extends any ? (arg: A) => any : never // (arg: A) => any;

with

type BT<T> = T extends any ? (arg: T) => any : never;
type B = BT<A>

to see the difference

like image 42
kikon Avatar answered Feb 01 '26 18:02

kikon



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!