GeezerVison is showing github.com — Intersecting discriminated union breaks type narrowing · Issue #9919 · microsoft/TypeScript
Intersecting discriminated union breaks type narrowing #9919Description
TypeScript Version: nightly (2.0.0-dev.201xxxxx)
Code
type ToString = {
toString(): string;
}
type BoxedValue = { kind: 'int', num: number }
| { kind: 'string', str: string }
type IntersectionFail = BoxedValue & ToString
type IntersectionInline = { kind: 'int', num: number } & ToString
| { kind: 'string', str: string } & ToString
function getValueAsString(value: IntersectionFail): string {
if (value.kind === 'int') {
// Property 'num' does not exist on type '({ kind: "int"; num: number; } | { kind: "string"; str: string; }) & { toString(): string; }'.
return '' + value.num;
}
// Property 'str' does not exist on type '({ kind: "int"; num: number; } | { kind: "string"; str: string; }) & { toString(): string; }'.
return value.str;
}
If a discriminated union is enhanced via intersection, type narrowing based on the discrimination field starts to fail. This behaviour is not exhibited if a new discriminated union is created with the intersection being inlined into each of the respective options.
The function getValueAsString(value: IntersectionFail): string fails to compile, while getValueAsString(value: IntersectionInline): string compiles just fine. Unfortunately, it is IntersectionFail that is the result of natural program evolution. Reacted by Anatoly Kudinov Activityyortus commented on Jul 25, 2016 Sounds like you want the compiler to be aware of the distributive law:
T & (U | V) ≡ (T & U) | (T & V) jods4 commented on Aug 22, 2016 Got caught by this today. I was unsure if it was too far-fetched to open an issue or not ;)
Troy Gerwien (@yortus) I don't think about it as "distributive" law, although I guess you can see it that way.
I am thinking of "intersection" types like "extensions" or types glued together. So it kind of makes sense that you can narrow any "part" of your intersection type.
The tricky part with seeing it as "distributive" is that it may explode quickly, consider: (A|B) & (C|D) & (E|F|G) and you have a union type of 12 parts.
But you can process it without combinatorial explosion. If you narrow the discriminant property of (C|D) to D only, you are left with (A|B) & D & (E|F|G). yortus commented on Aug 23, 2016 The distributive law is just the 'set theory' principle that makes it valid. Whether the compiler expands all the terms or not is implementation detail, I guess with tradeoffs either way. As you point out it could create an explosion of types. On the other hand, doing a full expansion would allow complex types to be 'normalized', so you could tell when they are identical, eliminate redundant terms, etc. jods4 commented on Aug 23, 2016 Troy Gerwien (@yortus) I'm nitpicking here: I think what the compiler is lacking is the distributivity of the narrowing operation. Because it doesn't seem to narrow intersection types and that's the problem in (A|B) & C and it would be the same problem in (A&C) | (B&C).
It seems what the compiler lacks is knowledge that, if n() represents narrowing a type n(A & B) == n(A) & n(B). If this rule was applied by the compiler, both types constructions above would correctly be narrowed to A&C when n(A|B) == A. yortus commented on Aug 23, 2016 This issue is about narrowing. I think we are saying the same thing. Igmat commented on Sep 28, 2016 Is here some progress? I've checked a lot of discussions, but haven't found any decision. Would be distributive law for unions and intersections implemented? And if yes, in what release we can expect that? ahejlsberg commented on Oct 18, 2016 gcnew commented on Oct 19, 2016 ContributorAuthor More actions Great news!
Troy Gerwien (@yortus) turned out to be right! :) (in implementation sense, in logical there has never been any doubt)
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|