Skip to content

Intersection of discriminated union (of records) and a record is incorrect #47612

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

Closed
RebeccaStevens opened this issue Jan 26, 2022 · 3 comments
Closed
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@RebeccaStevens
Copy link

Bug Report

🔎 Search Terms

intersection with discriminated union

This might be a duplicate of #33654 but I'm not completely sure. This issue has a simpler example anyway.
It also seems to be a duplicate of #9919 which was reportedly fixed. This is either regression or a missed case.

🕗 Version & Regression Information

  • This is the behavior in every version I tried (v3.3 and v4.5), and I reviewed the FAQ for entries about intersection with discriminated union.

⏯ Playground Link

Playground link with relevant code

💻 Code

type X = ({ a: string } | { b: number }) & { a: unknown };
type Y = X["a"]; // actual: unknown, expected: string

🙁 Actual behavior

type Y is unknown.

🙂 Expected behavior

type Y should be string.

Why:

  • When discriminating { a: string } | { b: number } with "a must be defined as a field", we get { a: string }.
  • { a: string } & { a: unknown } can be simplified to { a: string }
@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Jan 26, 2022

This is the intended behavior. Through distributivity:

type X = ({ a: string } | { b: number }) & { a: unknown };
// is equivalent to
type X = ({ a: string } & { a: unknown }) | ({ b: number } & { a: unknown });
//                                          ^^^^^^^^^ this one  ^^^^^^^^^^^^

so { b: 3, a: true } is a legal inhabitant of the second constituent of the union, so given an arbitrary value of type X you can't be guaranteed to get a string from it

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jan 26, 2022
@RebeccaStevens
Copy link
Author

That makes sense.

What's different about the follow case then? How come TypeScript is able to determine that the resulting type is string here?

declare const foo: { a: string } | { b: number };
declare function hasA(value: unknown): value is typeof value & { a: unknown };

if (hasA(foo)) {
    type Z = typeof foo['a']; // string
}

Playground

@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants