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

Partially overlapping union discriminators are not assignable from a union of discriminating values #45230

Open
ammut opened this issue Jul 29, 2021 · 2 comments
Assignees
Labels
Needs Investigation This issue needs a team member to investigate its status. Rescheduled This issue was previously scheduled to an earlier milestone

Comments

@ammut
Copy link

ammut commented Jul 29, 2021

Bug Report

🔎 Search Terms

tagged, discriminated, union, partially, overlapping, infinite, types, not, assignable

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about Type system behavior and Classes

I tried versions from v3.3.3 to v4.3.5, as those were available in the Playground at this time.

Note that between v3.3.3 and v3.5.1 something changed that affects type C from my example below. In v3.3.3 the assignment to const c: C is an error, from v3.5.1 its okay.

⏯ Playground Link

Playground link with relevant code

💻 Code

// Original Problem:
type A = { aprop: 'a' | RegExp } | { aprop: 'b' | RegExp };
declare const aprop: 'a' | 'b';
const a: A = { aprop }; // error

// Distillation:
type B = { bprop: 1 | string } | { bprop: 2 | string }
declare const bprop: 1 | 2;
const b: B = { bprop } // same error

// error appears only for "infinite" types, this works fine
type C = { cprop: 1 | 3 } | { cprop: 2 | 3 }
declare const cprop: 1 | 2;
const c: C = { cprop } // C['cprop'] == 1 | 2 | 3

// more detailed example
type Infinite = string;
type D = { dprop: 1 | Infinite } | { dprop: 2 | Infinite }
type Dprop = D['dprop']; // 1 | 2 | string -> "merging" seems to work correctly
declare const dprop: 1 | 2;
const d1: D = { dprop: 1 } // OK
const d2: D = { dprop: 2 } // OK
const d3: D = { dprop } // error

🙁 Actual behavior

The assignments to const a: A, const b: B, and const d3: D raise an error.

🙂 Expected behavior

As demonstrated in the assignments to const d1: D and const d2: D, my partially discriminating property dprop can be either of the two discriminating values. So It should be possible to assign to dprop a value that is either of them, as in the assignment to const d3: D.

@jcalz
Copy link
Contributor

jcalz commented Jul 29, 2021

between v3.3.3 and v3.5.1 something changed

The change was "smarter union type checking", implemented in #30779 and released in TS3.5.

@RyanCavanaugh
Copy link
Member

In general if you have some type { a: U1, b: U2, c: U3, d, U4 }, you can't tell if that's going to be assignable to some target type without doing a computation involving size(U1) * size(U2) * size(U3) * ... different expansions of the type to enumerate all possible inhabitants. So we're always going to sometimes reject some of these assignments because the special-casing to allow the trivial cases has some limits.

That said, I don't see why this should matter when a non-literal type appears in the target.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Jul 29, 2021
@RyanCavanaugh RyanCavanaugh added this to the TypeScript 4.5.0 milestone Jul 29, 2021
@RyanCavanaugh RyanCavanaugh added the Rescheduled This issue was previously scheduled to an earlier milestone label Dec 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status. Rescheduled This issue was previously scheduled to an earlier milestone
Projects
None yet
Development

No branches or pull requests

4 participants