Future proof union to intersection type conversion
See original GitHub issueSearch 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:
- Created 5 years ago
- Reactions:22
- Comments:43 (19 by maintainers)
Top 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 >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Might as well put an explanation for
UnionToIntersection<>
here.And we use it like so,
Step 1
In Step 1, we have,
This part becomes,
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,
Which is just,
Playground
Recall that the type in step 1 is,
Imagine we had,
In order to invoke
f(/*arg*/)
, this “arg” must be{ x : 1, y : 2 }
.unionOfFoo
is actually(k : { x : 1 }) => void
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.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