-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Pattern matching via switch should not require default case if all cases are handled #1835
Comments
Thanks for the detailed error report! Support for exhaustive refinements has been a TODO for awhile. I think we're using #451 as the master tracking issue for this. @bhosmer has done a little preliminary work on this kind of stuff, but exhaustive switch statement refinements for tagged unions is still a TODO |
@gabelevi in the meantime are there any recommended patterns one could follow to avoid a pitfalls mentioned ? Another class of issues / regressions we tend to run into is caused by changes like this: /* @flow */
type Message =
| { tag: "Increment" }
| { tag: "Decrement" }
const update =
(model:number, message:Message):number => {
switch (message.tag) {
case "Inc":
return model + 1
case "Dec":
return model - 1
default:
throw Error('Invalid message')
}
} Note flow will type check this fine. But you'll encounter runtime error, as changes to |
@bhosmer I wonder if the work you've being doing would also have some solution for the issue mentioned in last comment. |
related, possible workaround : http://stackoverflow.com/questions/40338895/sealed-case-classes-in-flow |
|
Thanks @jhegedus42, I'm definitely going to employ this workaround |
Unfortunately it seems that it only covers cases where types in the union are type declarations with sentinel field, but it does not work at all if say classes are joined into a union like in this example type TodoTy = {
text:string;
completed:boolean;
id:number;
};
class TodoFunctions {
make(t:string,id:number):TodoTy{
return {text:t,id:id,completed:false}
}
toggle(t:TodoTy):TodoTy {
return {...t, completed:!t.completed};
}
}
class ADD_TODO {
type = 'ADD_TODO'
text:string
id:number
}
class TOGGLE_TODO {
type = 'TOGGLE_TODO'
id:number
}
type TodoActionTy =
| ADD_TODO
| TOGGLE_TODO
const todo = (todo:TodoTy, action:TodoActionTy) : TodoTy => {
switch (action.type){
case 'not really a value of .type field' :
return { id:action.id, text:action.text, completed: false};
case 'neither is this one':
if (todo.id !== action.id) {return todo;}
return {...todo, completed:!todo.completed};
default : (action: empty)
throw Error()
}
} What I can't even explain is that removing one of the arms of the switch statement seems to trigger flow error 👀 type TodoTy = {
text:string;
completed:boolean;
id:number;
};
class TodoFunctions {
make(t:string,id:number):TodoTy{
return {text:t,id:id,completed:false}
}
toggle(t:TodoTy):TodoTy {
return {...t, completed:!t.completed};
}
}
class ADD_TODO {
type = 'ADD_TODO'
text:string
id:number
}
class TOGGLE_TODO {
type = 'TOGGLE_TODO'
id:number
}
type TodoActionTy =
| ADD_TODO
| TOGGLE_TODO
const todo = (todo:TodoTy, action:TodoActionTy) : TodoTy => {
switch (action.type){
case 'neither is this one':
if (todo.id !== action.id) {return todo;}
return {...todo, completed:!todo.completed};
default : (action: empty)
throw Error()
}
} |
This seems to work: type A = { type: 'a' }
type B = { type: 'b' }
type AB = A | B
const fn = (v: AB): number => {
switch (v.type) {
case 'a':
return 1
case 'b':
default:
return 2
}
}
const v1 = fn({ type: 'a' })
const v2 = fn({ type: 'b' }) |
Sure but, if you add type `C` into that union and forget to update your
`fn` you won't get any errors reported as you'll handle `C` as `B` this is
also why requiring default case is problematic
On Wed, Jul 5, 2017 at 11:22 AM Gabor Sar ***@***.***> wrote:
This seems to work:
type A = { type: 'a' }
type B = { type: 'b' }
type AB = A | B
const fn = (v: AB): number => {
switch (v.type) {
case 'a':
return 1
case 'b':
default:
return 2
}
}
const v1 = fn({ type: 'a' })const v2 = fn({ type: 'b' })
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#1835 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AABS9NjhdYuLp-wJ6QsDJxv-cgmDRj8Gks5sK9RtgaJpZM4IlK6D>
.
--
Regards
--
Irakli Gozalishvili
Web: http://www.jeditoolkit.com/
|
@Gozala I do agree with you! :) |
If anyone is interested this is a solution I have being using to avoid pitfalls outlined in this thread: |
Here is an example of valid code that flow fails to type check:
Flow produces following errors:
I believe what error is trying to tell is that if non of the cases match
update
would returnundefined
, but I would expect flow to infer that all possible cases are handled and there for do not fail to type check this code.Unfortunately this issue is far worse that it may seem as only way to satisfy type checker is to handle else / default clause, which means type checker won't be able to infer a lot of possible errors. For example consider solution to make flow type check.
Now above code type checks fine and does pretty much what originally was intended. The problem is that with such code flow no longer will be able to detect unhandled cases. For example consider following evolution of the code above:
Notice that flow is type checks this code fine even though there is a typo in handling
Sqrt
messages & error will only appear at runtime (and that's only because our default case throws instead of say returningmodel
back).The text was updated successfully, but these errors were encountered: