question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Future proof union to intersection type conversion

See original GitHub issue

Search Terms

union to intersection, type merge

Suggestion

I’d like to either standardize (meaning documented behavior) or have a less hacky (future proof) way of transforming union types to intersections.

Use Cases

The most common use case I can think of is a basic action system where each action has some kind of input data it can work with to determine its disabled status. An actionGroup which packs multiple actions into one updater function needs to request the right input data type which is compatible to all of its actions.

While it’s possible in the current version of typescript, it feels like a hack.

Examples

A more presentable form by describing a car dashboard:

interface Gauge<T> {
    display(data: T): void;
}

class SpeedGauge implements Gauge<{ speed: number; }> {
    display(data: { speed: number; }) {
    }
}

class TempGauge implements Gauge<{ temperature: number; }> {
    display(data: { temperature: number; }) {
    }
}

class RevGauge implements Gauge<{ rev: number; }> {
    display(data: { rev: number; }) {
    }
}

type ExtractGaugeType<T> = T extends Gauge<infer U> ? U : never;

// evil magic by jcalz https://stackoverflow.com/a/50375286
// I would like to future proof or have a better version of this
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;

class Dashboard<T extends Gauge<unknown>> {
    constructor(public gauges: T[]) {
    }

    display(data: UnionToIntersection<ExtractGaugeType<T>>) {
        this.gauges.forEach((g) => g.display(data));
    }
}

const myDashboard = new Dashboard([new SpeedGauge(), new TempGauge(), new RevGauge()]);

/*
the type is: { rev: number; } & { speed: number; } & { temperature: number; }
*/
myDashboard.display({ // Ok
    speed: 50,
    rev: 2000,
    temperature: 85
});

myDashboard.display({ // Error: property "rev" is missing
    speed: 50,
    temperature: 85
});

Checklist

My suggestion meets these guidelines:

  • This wouldn’t be a breaking change in existing TypeScript/JavaScript code
  • This wouldn’t change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn’t a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript’s Design Goals.

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:22
  • Comments:43 (19 by maintainers)

github_iconTop GitHub Comments

15reactions
AnyhowStepcommented, Jul 2, 2019

Might as well put an explanation for UnionToIntersection<> here.

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

And we use it like so,

type obj = UnionToIntersection<{ x : 1 }|{ y : 2 }>;

Step 1

In Step 1, we have,

//Where U = { x : 1 }|{ y : 2 }
    (
        //Step 1
        U extends any ?
            (k : U) => void :
            never
    )

This part becomes,

(
    | ((k : { x : 1 }) => void)
    | ((k : { y : 2 }) => void)
)

Playground


If we had just used (k : U) => void, we would have gotten (k : { x : 1 }|{ y : 2 }) => void. We need the conditional type (U extends any ?) to distribute the union.

(Can someone find a nice link that demonstrates conditional types distributing unions?)


Step 2

In Step 2, we have,

/*Step 1*/ extends ((k : infer I) => void) ?
    I :
    never

Which is just,

//{ x: 1 } & { y: 2 }
type result = (
    | ((k : { x : 1 }) => void)
    | ((k : { y : 2 }) => void)
) extends ((k : infer I) => void) ?
    I :
    never

Playground


Recall that the type in step 1 is,

type unionOfFoo = (
    | ((k : { x : 1 }) => void)
    | ((k : { y : 2 }) => void)
)

Imagine we had,

declare const f : unionOfFoo;

In order to invoke f(/*arg*/), this “arg” must be { x : 1, y : 2 }.

  • We don’t know if unionOfFoo is actually (k : { x : 1 }) => void
  • We don’t know if unionOfFoo is actually (k : { y : 2 }) => void

So, to safely call unionOfFoo, we have to pass in a type that will satisfy the requirements of both functions. This type happens to be { x : 1, y : 2 }.

One final playground link to play with,

Playground


Conclusion

The above wall of text is a mess. Maybe someone can rewrite it more succinctly.

However, I’m pretty sure that this UnionToIntersection<> will not be breaking any time soon. And it should not.

The Playground links may be helpful in understanding this UnionToIntersection<> type. More helpful than the walls of text, anyway.

9reactions
AnyhowStepcommented, Dec 1, 2019

https://github.com/microsoft/TypeScript/blob/d02531f650111b98269ed5b08d497156677cb1b1/src/compiler/types.ts#L6420-L6422

At this point, we might as well make it part of the lib.d.ts file as a helper type =P

Read more comments on GitHub >

github_iconTop Results From Across the Web

Transform union type to intersection type - Stack Overflow
You want union to intersection? Distributive conditional types and inference from conditional types can do that. (Don't think it's possible ...
Read more >
TypeScript Fireworks: Union, Intersection and Variance
Most recently it's for the magic of conversion from union to tuple type.) Union and Intersection: count values, not fields. Give two types...
Read more >
Empowering Union and Intersection Types with Integrated ...
While such proofs can easily be converted into reductive subtyping (without assumptions) when reductive subtyping holds for each of the assumptions, the same...
Read more >
Handbook - Unions and Intersection Types - TypeScript
Intersection types are closely related to union types, but they are used very differently. An intersection type combines multiple types into one. This...
Read more >
Intersection and Union Types - UPenn CIS
recent investigations of similar formulations of union types. Section 6 describes planned future. work. N.b. This report describes work in a preliminary ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found