-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Discriminated Unions don't cast with single type #18056
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
Comments
Less amenable to immediate editing, but you can write:
|
That's slick :). Unfortunately, it doesn't work for the export type Actions = {
type: "A"
};
export function reducer(state: any, action: Actions): any {
switch (action.type) {
case "A":
return {};
default:
const exhaustivenessCheck: never = action;
return state;
}
} Redux will pass every action in the system (more than just More generally, what is the expected behaviour? It seems unintuitive that it |
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 |
Responding in reverse 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: type A = {
type: "A"
foo: number
};
type B = {
type: "B"
bar: number
}; I can comfortably do function reducer(state: any, action: A): any {
if (action.type === "A") {
return { bar: action.foo };
} else {
return state;
}
} And, function reducer(state: any, action: A | B): any {
if (action.type === "A") {
return { bar: action.foo };
} else if (action.type === "B") {
return { bar: action.bar };
} else {
const exhaustivenessCheck: never = action;
return state;
}
} But not, function reducer(state: any, action: A): any {
if (action.type === "A") {
return { bar: action.foo };
} else {
const exhaustivenessCheck: never = action;
return state;
}
} |
Regarding type Thing = {
length: 0;
color: string;
}
function fn(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
Unrelated, does this work for you? type Choices = {
type: "A"
} | { type: never };
function chooser(action: Choices): boolean {
switch (action.type) {
case "A":
return true;
default:
const exhaustivenessCheck: never = action.type;
return false;
}
} |
Answering in reverse again About substitution type; I think we still end up losing the |
Reducer pattern works if we check interface MyAction {
type: 'MY_ACTION';
}
type KnownAction = MyAction;
const reducer: Reducer<MyState> = (state: MyState, action: KnownAction) => {
switch (action.type) {
case 'MY_ACTION':
return {};
default:
const exhaustivenessCheck: never = action.type;
}
return state || {};
} |
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. |
TypeScript Version: 2.3.4
Code
Expected behavior:
Code compiles.
Actual behavior:
Receive Error:
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)
The text was updated successfully, but these errors were encountered: