GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Allow non-unit types in union discriminants by ahejlsberg · Pull Request #27695 · microsoft/TypeScript

Conversation

@ahejlsberg

@ahejlsberg Anders Hejlsberg (ahejlsberg) commented Oct 10, 2018

Copy link
Copy Markdown
Member

With this PR we permit non-unit types in union type discriminants. Specifically, we now consider a property of a union type to be a discriminant property if it has a union type containing at least one unit type and no instantiable types. For example:

type Result<T> = { error?: undefined, value: T } | { error: Error };

function test(x: Result<number>) {
    if (!x.error) {
        x.value;  // number
    }
    else {
        x.error.message;  // string
    }
}

test({ value: 10 });
test({ error: new Error("boom") });

Inspired by #27631 which was closed in favor of this PR.

Fixes #24193.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

jack-williams, alexrock, yortus, j-oliveras, sean-vieira, dderevjanik, ulrichb, yanchith, KSXGitHub, ondrowan, and 23 more reacted with thumbs up emoji cshaa and sergey-shandar reacted with heart emoji
All reactions
  • 👍 33 reactions
  • ❤️ 2 reactions
Anders Hejlsberg (ahejlsberg) added 5 commits October 9, 2018 18:41
@ahejlsberg
Allow discriminant property to contain some non-unit types
d069875
@ahejlsberg
Discriminant must include at least one unit type and no instantiable …
c110f57
…types
@ahejlsberg
Accept new baselines
7737cd5
@ahejlsberg
Add tests
22907bf
@ahejlsberg
Accept new baselines
6cdba6d
@ahejlsberg
Anders Hejlsberg (ahejlsberg) requested review from Daniel Rosenwasser (DanielRosenwasser) and Ryan Cavanaugh (RyanCavanaugh) October 10, 2018 22:53
@ahejlsberg Anders Hejlsberg (ahejlsberg) mentioned this pull request Oct 10, 2018
Fix #24193: Allow nullable discriminants #27631
Closed
@jack-williams

Jack Williams (jack-williams) commented Oct 10, 2018

Copy link
Copy Markdown
Collaborator

I mentioned it in the old PR, but I think it is worth recording in the PR that gets merged:

This change will have the side-effect of enabling excess property checking for certain union types that were previous not subject to the checks. Unions types are only checked if they have a discriminant property, and this change distinguishes more discriminant types. Technically this is a breaking change.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

jack-williams
Jack Williams (jack-williams) reviewed Oct 10, 2018
View reviewed changes
Comment thread tests/cases/conformance/types/union/discriminatedUnionTypes2.ts
x.b; // Error
}
}

@jack-williams Jack Williams (jack-williams) Oct 10, 2018

Copy link
Copy Markdown
Collaborator

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it be worth adding a test that includes constrained generics? My reasoning being that in the future it might be worth considering discriminants that have constrained parameters because they are comparable to literals in some form. Having a test that would flag any changes might be helpful. Something like:

function f21<T extends number>(x: { a: undefined; b: { x: number } } | { a: T, b: { y: number } }) {
    if (x.a === undefined) {
        x.b = { y: 42 };
    }
}

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@weswigham

Wesley Wigham (weswigham) commented Oct 11, 2018

Copy link
Copy Markdown
Member
function f(x: { kind: true, a: string } | { kind: "b" } | { kind: string, c: string }) {
    if (x.kind === true) {
        x.a;
    }
    else if (x.kind !== "b") {
        x.c; // Should be `{ kind: string, c: string }`
    }
    else {
        x; // should be `{ kind: "b" } | { kind: string, c: string }`
    }
}

doesn't seem to work yet with this PR.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@jack-williams

Jack Williams (jack-williams) commented Oct 12, 2018 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Collaborator

Wesley Wigham (@weswigham)
EDIT: Correction, the reduction is irrelevant in this case. The problem is that the literal is comparable to the string.

Think the issue with your example is that literal reduction loses type "b", so when it comes to narrowing by inequality it does nothing. What would be the impact of disabling literal reduction when getting the property of a union type?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ahejlsberg

Anders Hejlsberg (ahejlsberg) commented Oct 13, 2018

Copy link
Copy Markdown
Member Author

Jack Williams (@jack-williams) You're actually right about type string absorbing type "b" in a union type. All of the narrowing logic in the control flow analyzer uses union type operations so in those operations we really only can discriminate on true | string. We do have the ability to construct non-reduced union types such as "b" | string, but that capability is used only to make contextual types a bit more precise and isn't actually observable in types that the user might declare or operate on.

I think it is fine to document that discriminants must be truly disjoint types, and that string and number can't be used to define "default" cases. If we really wanted to support such a default pattern in a meaningful way, we'd need to verify that the specialized cases are subtypes of their corresponding default cases, and we'd need to narrow to the specialized cases consistently. But that's beyond the scope of this PR and I don't even think we want to go there.

c69 reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@jack-williams

Jack Williams (jack-williams) commented Oct 15, 2018

Copy link
Copy Markdown
Collaborator

Anders Hejlsberg (@ahejlsberg) Thanks for the explanation. I managed to confuse myself when I experimented with turning off union reduction and finding that it did not have an effect in this example.

But that's beyond the scope of this PR and I don't even think we want to go there.

Yes, I agree with your assessment here. The asymmetry brought about by trying to narrow types like "b" | string that are influenced by inequality, but not equality, leads to code that can be quite subtle.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@weswigham Wesley Wigham (weswigham) mentioned this pull request Oct 15, 2018
Fix circular mapped type instantiations for arrays and tuples #27911
Merged
@weswigham

Wesley Wigham (weswigham) commented Oct 15, 2018

Copy link
Copy Markdown
Member

Fair enough. I just wanted to point out that we still don't narrow all the things we look like we could because of how the broader string type and the specific literal type are related.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

weswigham
Wesley Wigham (weswigham) approved these changes Oct 15, 2018
View reviewed changes

@weswigham Wesley Wigham (weswigham) left a comment •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case, this definitely looks like an improvement, and we can always adjust it to recognize more types as discriminable in the future if need be.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@ahejlsberg
Anders Hejlsberg (ahejlsberg) merged commit 9767522 into master Oct 15, 2018
@ahejlsberg
Anders Hejlsberg (ahejlsberg) deleted the mixedDiscriminantTypes branch October 15, 2018 20:10
@jack-williams Jack Williams (jack-williams) mentioned this pull request Oct 25, 2018
Unable to discriminate union type because of "Property does not exist" #28138
Closed
@bmingles

Brian Ingles (bmingles) commented Oct 30, 2018

Copy link
Copy Markdown

Any chance this will allow using tuples as discriminants?

e.g.

type RouteA = {
  tokens: ['a', string]
};

type RouteB = {
  tokens: ['b', string, string]
};

type Route =
  | RouteA
  | RouteB;

const route: Route = ...

if(route.tokens[0] === 'a') {
   // compiler knows this is RouteA
}
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ksaldana1

Kevin Saldaña (ksaldana1) commented Nov 14, 2018

Copy link
Copy Markdown

Brian Ingles (@bmingles) I believe your example will narrow correctly since v3.1.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

This was referenced Dec 20, 2018
Logical and loses narrowed types when testing for truthyness on union types #29106
Closed
Fix discriminant property check #29110
Merged
@dragomirtitian Titian Cernicova-Dragomir (dragomirtitian) mentioned this pull request Mar 20, 2019
Type guard not work in union type of objects #30506
Closed
@jack-williams Jack Williams (jack-williams) mentioned this pull request Aug 5, 2019
Regression in discriminated unions when a common property may be boolean #32657
Closed
@octogonz Pete Gonzalez (octogonz) mentioned this pull request Aug 26, 2021
Union not narrowed with typeof x.y as a discriminant #32399
Closed
@richard-ling Richard Ling (richard-ling) mentioned this pull request Mar 12, 2025
Consider property access a form of type guards #1260
Open
@microsoft Microsoft (microsoft) locked as resolved and limited conversation to collaborators Oct 21, 2025
@typescript-bot TypeScript Bot (typescript-bot) assigned Anders Hejlsberg (ahejlsberg) Oct 21, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

@weswigham Wesley Wigham (weswigham) weswigham approved these changes
@DanielRosenwasser Daniel Rosenwasser (DanielRosenwasser) Awaiting requested review from DanielRosenwasser
@RyanCavanaugh Ryan Cavanaugh (RyanCavanaugh) Awaiting requested review from RyanCavanaugh
+1 more reviewer
@jack-williams Jack Williams (jack-williams) jack-williams left review comments
Reviewers whose approvals may not affect merge requirements

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@ahejlsberg Anders Hejlsberg (ahejlsberg)

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Allow to use null as discriminator for tagged union

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@ahejlsberg @jack-williams @weswigham @bmingles @ksaldana1
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.