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
Remove unreachable case branches in case ... of #285
Conversation
|
||
|
||
""" | ||
] |
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.
Let's add a test as well to make sure we don't do anything when one of the branches has an alias for the pattern.
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.
"alias for the pattern"? Does that mean import alias? something with type alias?
Not sure what you mean. If it's about detecting the real full qualification, the test "should remove multiple unnecessary cases of project-local variant with multiple attachments when all cases are variant patterns" should already cover that.
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.
(I think that) I meant a pattern alias, such as ([ _, _ ] as something) -> 4
tests/Simplify/CaseOfTest.elm
Outdated
((_, (_, _)), [ _ ]) -> | ||
0 | ||
|
||
((_, (_, _)), _) -> |
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.
I don't think this change is better, it makes the code quite harder to read (at least for the situation in this test and the previous one).
I think we should limit ourselves to change lists to tuples of the same size:
case [ x ] of
[ 1 ] -> a
-->
case x of
1 -> a
case [ x, y ] of
[ 1, 2 ] -> a
_ -> b
-->
case ( x, y ) of
( 1, 2 ) -> a
_ -> b
case [ x, y, z ] of
[ 1, 2, 3 ] -> a
_ -> b
-->
case ( x, y, z ) of
( 1, 2, 3 ) -> a
_ -> b
and then remove the impossible branches.
That said with my proposal, I believe it is possible to introduce compiler errors where one branch is entirely covered by another without exhaustive checking. I'm not sure whether your approach has the same problem or not. I don't think so?
Anyway, I don't think this change in the current form is a straight simplification. It's a simplification plus a transformation that can be controversial.
If it's just about removing unreachable case branches, then I think I would be in favor of removing them directly if there is a _ ->
branch without the transformation to a tuple. That's also what the error message recommends.
If there is no wildcard case, then I'm unsure. I imagine replacing the pattern one for one of the impossible branches to be _ ->
could be okay. We might need to move it to be the last though. Oh, and it should not introduce any variables to the scope, otherwise that's a compiler error.
If none of the existing branches fit, then I'm unsure what we should do. Maybe not provide a fix?
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.
First: Currently, we don't simplify case-ofs with wildcard patterns at all, see the PR body section.
Second: Not converting (the beginning of) a list with known elements to a tuple defeats the whole purpose of the check IMO.
In my eyes, there is no difference between leaving case multi-variant of
or case list with some elements of
instead of matching on a structure where we know for a fact that the values exist and we don't need these ugly (potentially unnecessary) cases to handle the rest.
If this is only about readability, maybe the new nesting is better?
f213a48
to
b394b37
Compare
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.
Sorry for the (very) delayed review of this. I made some remarks, but I don't think there are blockers on this so I'll merge this and we can always improve on them in follow-up work.
Thank you a lot for the work 🙏
(PS: I tried this at work, but unfortunately it didn't report anything 😞 )
There are situations where no cases are "unnecessary" but parts of the pattern and cased expression are because they're always matched (in all examples, the second part in the tuple is unnecessary)
Should I open an issue to handle those separately or would you like for them to be included in this PR?
Please open a new issue. I think this would be a great addition.
The last 2 could also fit into no-unused I guess?
Agreed, I also think they could go in both 🤷
ExposingSomeContext exposingSomeContext -> | ||
Set.foldl | ||
(\exposedPotentialTypeAlias soFar -> | ||
case Dict.get exposedPotentialTypeAlias moduleContext.moduleCustomTypes of |
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.
Is exposedPotentialTypeAlias
the right name? This is dealing with custom types, not type aliases, right?
Same for recordTypeAlias
a few lines below.
) | ||
) | ||
) | ||
|> Dict.fromList |
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.
For later: This can probably be sped up using a single Dict.fold
|> Review.Test.expectErrors | ||
[ Review.Test.error | ||
{ message = "Unreachable case branches" | ||
, details = [ "The value between case ... of is a known A variant. However, the 1st and 2nd and 3rd case matches on a different variant which means you can remove it." ] |
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.
... and 3rd case matches on a different variant which means you can remove it.
Should be "cases matches" and "you can remove them", and maybe also "on different variants" (which I think we can use as the wording regardless of the number of cases).
(not a blocking issue)
|
||
|
||
""" | ||
] |
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.
(I think that) I meant a pattern alias, such as ([ _, _ ] as something) -> 4
unreachableCasesFixToReviewFixes : UnreachableCasesFix -> List Fix | ||
unreachableCasesFixToReviewFixes unreachableCasesFix = | ||
Fix.replaceRangeBy unreachableCasesFix.casedExpressionReplace.range unreachableCasesFix.casedExpressionReplace.replacement | ||
:: List.concat |
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.
We can remove the List.concat
here, every branch returns a List with a single fix.
Note that the rule could almost have caught this. We do List.concat (List.map (\x -> [ f x]) list)
-> List.concatMap (\x -> [ f x]) list
-> List.map (\x -> f x) list
if I'm not mistaken. But we don't do any checks for List.concat (List.map2 ... list)
.
A fresh attempt at implementing #99 (slightly less ambitious), having stopped #103.
As always, suggested refactors and nitpicks welcome.
Deliberately not included
Allowing all pattern (
_
) for the last pattern→ requires exhaustiveness check! (Because otherwise the case removal could lead to a compiler error)
→ out of scope?
This means that for example literal char, string, int/hex patterns are not checked for whether they are unnecessary because they require a
_
or variable pattern case as the last case.If you think we should tackle allowing
_
patterns some time in the future, leave #99 open, otherwise this PR closes it.Discussion
There are situations where no cases are "unnecessary" but parts of the pattern and cased expression are because they're always matched (in all examples, the second part in the tuple is unnecessary):
Should I open an issue to handle those separately or would you like for them to be included in this PR?
The last 2 could also fit into no-unused I guess?