Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intersecting discriminated union breaks type narrowing #9919

Closed
gcnew opened this issue Jul 24, 2016 · 8 comments
Closed

Intersecting discriminated union breaks type narrowing #9919

gcnew opened this issue Jul 24, 2016 · 8 comments
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript

Comments

@gcnew
Copy link
Contributor

gcnew commented Jul 24, 2016

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.

@yortus
Copy link
Contributor

yortus commented Jul 25, 2016

Sounds like you want the compiler to be aware of the distributive law:

T & (U | V)      (T & U) | (T & V)

@jods4
Copy link

jods4 commented Aug 22, 2016

Got caught by this today. I was unsure if it was too far-fetched to open an issue or not ;)

@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
Copy link
Contributor

yortus commented 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
Copy link

jods4 commented Aug 23, 2016

@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
Copy link
Contributor

yortus commented Aug 23, 2016

This issue is about narrowing. I think we are saying the same thing.

@Igmat
Copy link

Igmat commented 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
Copy link
Member

Fix now available in #11717.

@gcnew
Copy link
Contributor Author

gcnew commented Oct 19, 2016

Great news!

@yortus turned out to be right! :) (in implementation sense, in logical there has never been any doubt)

@mhegazy mhegazy added Fixed A PR has been merged for this issue and removed In Discussion Not yet reached consensus labels Oct 19, 2016
@mhegazy mhegazy added this to the TypeScript 2.1 milestone Oct 19, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

7 participants