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

Inconsistency in narrowing behavior between non-union and union #33670

Closed
soul-codes opened this issue Sep 30, 2019 · 2 comments
Closed

Inconsistency in narrowing behavior between non-union and union #33670

soul-codes opened this issue Sep 30, 2019 · 2 comments
Labels
Duplicate An existing issue was already created

Comments

@soul-codes
Copy link

soul-codes commented Sep 30, 2019

TypeScript Version: 3.7.0-dev.20190930

Search Terms: union never switch

Code

Compare the type of the switch argument at the default: branch of the three switches below:

const a = "foo"
switch (a) {
    case "foo": break;
    default: 
        a // never
}

const b = {
    discriminant: "foo" as const
} as { discriminant: "foo" } | { discriminant: "bar" }

switch (b.discriminant){
    case "foo": break;
    case "bar": break;
    default:
        b // never
}

const c = {
    discriminant: "foo" as const
} as { discriminant: "foo" }

switch (c.discriminant){
    case "foo": break;
    default:
        c // object, but was expecting never
}

Expected behavior:
c should be never to be consistent with a and b.

Actual behavior:
c is still the type { discriminant: "foo" }.

Playground Link: here

Context
I have this type-guarding function that acts as an identity function on the second argument but requires that the first argument is never.

export function unreachable<T>(never: never, result: T): T {
  return result;
}

I then use this to create a throw statement at the end of a switch that will type-error if I expanded the union that went into the switch and neglected to implement a branch that handles the new subtype. This allows me to perform a run-time assertion of an unsupported option and check that I have implemented all supported options in one line.

switch (union.discriminant) {
  case "foo": return doThis();
  case "bar": return doThat();
  default:
    throw Error(unreachable(union, "Unrecognized union type."));

It works beautifully until the union is not not actually a union at all (see case c in the example above). This ruined my ability to keep the switch form plus unreachable generically regardless of whether the switch argument is a discriminated union or just a simple object. The thing is, I run into situations all the time where the switch will first art with one discriminant value and grow later. But at any point in time I want to be able to capture both the run-time error and the compile-time check that all valid options have been exhausted. This includes when there is only one case on the switch.

Therefore I would find it useful that the behavior between c and b above are consistent. That way, I can use the switch boilerplate starting with the non-union and then expand it as I go.

Related Issues:
This seems to be related (but still somewhat different) to #20375 and #30557. I'm fine if this is unified into an existing issue as the essence of this issue remains addressed.

@soul-codes soul-codes changed the title Inconsistency of narrowing behavior between constant and union Inconsistency in narrowing behavior between non-union and union Sep 30, 2019
@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Oct 14, 2019
@RyanCavanaugh
Copy link
Member

#30557 is the correct target duplicate. Thanks!

@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' 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
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants