You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Discriminated Unions don't cast with single type #18056
test.ts(10,13): error TS2322: Type 'Choices' is not assignable to type 'never'.
Reasoning
For libraries like Redux, there are places where it makes sense to have a switch statement with a single case for Discriminated Unions. (e.g. in reducers with a single action)
Redux will pass every action in the system (more than just type: "A") to the reducer, and it's expected to ignore those by returning state. This abuses the type system a bit, but it's a really powerful pattern that works if Actions are a union.
More generally, what is the expected behaviour? It seems unintuitive that it const exhaustivenessCheck: never doesn't work for the singleton case.
If there's no union type, then the code for narrowing unions doesn't kick in (IOW a type is not equivalent to a union type of one constituent, if that were a possible thing to construct).
I'd be somewhat concerned about how this behavior would behave with strictNullChecks off - it's possible in that world for type to be null/undefined and it would not be correct to make it never in the default block.
Responding in reverse
What are your concerns w/ type=null over type="Foo"? Technically neither of them should make it to never, but because of how Redux propagates actions that line of code will eventually be hit at runtime in the union example.
We (my team) thought that was probably what was happening :). I think the confusing part is that, although Discriminated Unions are a different concept, they feel like a natural extension of string literal type guards. So, with the following types:
Regarding null, I just mean that if you wrote something like this (with strictNullChecks off) I think it'd be very confusing to issue an error like this:
typeThing={length: 0;color: string;}functionfn(x: Thing){if(x.length!==0){// Error, cannot read 'color' of type 'never'console.log(`Cannot process thing with color ${x.color} because its length is wrong`);}}
It's Real Bad ™ if people are regularly getting never-typed values which are actually observed at runtime. I think what we actually want is a subtraction type (#4183) for literals so you can say
type Action = { type: "A", payload: number } |
{ type: string - "A", payload: void };
We don't think the performance hit for trying to detect whether we should synthesize new types after every comparison is worth it relative to the difficulty of the workaround here. Non-union discriminated types (???) ought to be rather rare in practice.
Activity
RyanCavanaugh commentedon Aug 25, 2017
Less amenable to immediate editing, but you can write:
mindblight commentedon Aug 28, 2017
That's slick :). Unfortunately, it doesn't work for the
reducerpattern:Redux will pass every action in the system (more than just
type: "A") to the reducer, and it's expected to ignore those by returningstate. This abuses the type system a bit, but it's a really powerful pattern that works ifActionsare a union.More generally, what is the expected behaviour? It seems unintuitive that it
const exhaustivenessCheck: neverdoesn't work for the singleton case.RyanCavanaugh commentedon Aug 28, 2017
If there's no union type, then the code for narrowing unions doesn't kick in (IOW a type is not equivalent to a union type of one constituent, if that were a possible thing to construct).
I'd be somewhat concerned about how this behavior would behave with
strictNullChecksoff - it's possible in that world fortypeto benull/undefinedand it would not be correct to make itneverin thedefaultblock.mindblight commentedon Aug 29, 2017
Responding in reverse
What are your concerns w/
type=nullovertype="Foo"? Technically neither of them should make it tonever, but because of how Redux propagates actions that line of code will eventually be hit at runtime in the union example.We (my team) thought that was probably what was happening :). I think the confusing part is that, although Discriminated Unions are a different concept, they feel like a natural extension of string literal type guards. So, with the following types:
I can comfortably do
And,
But not,
RyanCavanaugh commentedon Aug 30, 2017
Regarding
null, I just mean that if you wrote something like this (with strictNullChecks off) I think it'd be very confusing to issue an error like this:It's Real Bad ™ if people are regularly getting
never-typed values which are actually observed at runtime. I think what we actually want is a subtraction type (#4183) for literals so you can sayUnrelated, does this work for you?
mindblight commentedon Aug 31, 2017
Answering in reverse again
Yep,
{ type: never }works :). It definitely feels hacky, but it does give us better compile-time errors.About substitution type; I think we still end up losing the
exhaustivenessCheck. It's more correct, but we give up a very useful compile-time check.matshch commentedon Sep 22, 2017
Reducer pattern works if we check
neverforaction.type, notaction:RyanCavanaugh commentedon Oct 10, 2017
We don't think the performance hit for trying to detect whether we should synthesize new types after every comparison is worth it relative to the difficulty of the workaround here. Non-union discriminated types (???) ought to be rather rare in practice.