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
Match Exhaustiveness Testing Ignores Guard Statements #5365
Comments
Imported From: https://issues.scala-lang.org/browse/SI-5365?orig=1 |
@adriaanm said: |
Richard Bradley (richard.bradley) said (edited on Apr 24, 2015 10:10:16 AM UTC): Are you saying that "if" clauses / case guards defeat the exhaustiveness checks by design? (See also scapegoat-scala/scapegoat#45 ) |
@adriaanm said: I understand it's disappointing, and it's not very intuitive why we bail out when there are guards, but our key design goal is not to have spurious warnings, as that would undermine their utility while annoying a lot of users. In general, we can't statically decide whether a guard is true or false (in the extreme it could be a method call that calls into some REST API), so we have to punt. You're right that there are simple cases where we could, and I believe we do (or could) track the same level of information as our constant folder does, but guards that depend on the value of patterns are quite tricky to reason about in general. I would be happy to include a contribution that meets our design goals and refines the handling of guards, but I suspect it would be a significant effort. cheers |
Richard Bradley (richard.bradley) said (edited on Apr 24, 2015 4:55:49 PM UTC):
I agree that scalac can't determine the result of all "if" guards at compile time, but I don't think the right fix is to try to do that in simple cases. I think that scalac should treat all "if" guards as possibly-false at compile time, and adjust the exhaustiveness checks accordingly. So code like: x match {
case Some(n) if n % 2 == 0 => "even"
case None => "nothing"
} would give an exhaustiveness warning, and users would know that they need to do either: x match {
case Some(n) if n % 2 == 0 => "even"
case Some(n) => "odd"
case None => "nothing"
} or x match {
case Some(n) if n % 2 == 0 => "even"
case _ => "not even"
} to fix the warning.
Are you saying that treating "if" guards as possibly-false would lead to spurious warnings? I don't think that the warning in the example above would be suprious, I think it would be correct and very helpful. |
@adriaanm said: |
Richard Bradley (richard.bradley) said: x match {
case p if g1 => ...
case p if g2 => ...
} to just: x match {
case p if g1 => ...
case p => ...
} This will a) allow the compiler to verify exhaustiveness and b) will help the reader to understand the code. |
@adriaanm said: |
Richard Bradley (richard.bradley) said (edited on Apr 27, 2015 4:00:28 PM UTC):
Are you asking about the case with code like x match {
case p if g1 => ...
case p if g2 => ...
case p if g3 => ...
} ... and where the compiler cannot establish that Exactly the same principle applies. The compiler ought to complain that the match may not be exhaustive, and give an error like: warning: match may not be exhaustive.
It would fail on the following input: p
(Note that 'if' guard clauses assumed to be false for the purposes of exhaustiveness checks.) Then the user will realise that it is unhelpful to both the compiler and to the readers of their code to expect them to infer that x match {
case p if g1 => ...
case p if g2 => ...
case p => ...
} This is much easier to read; it is much easier for the compiler to verify, and (assuming that the identity is in fact true, and that the tests have no side-effects), it is completely equivalent. Therefore I think that:
|
Richard Bradley (richard.bradley) said: int i;
if (g1) {
i = 1
} else if (g2) {
i = 2
} else if (g3) {
i = 3
}
println(i) It is not considered a burden on developers that they need to "help" the compiler (and readers of their code) by changing the last block to an "else", like: int i;
if (g1) {
i = 1
} else if (g2) {
i = 2
} else {
i = 3
}
println(i) I think this is how people expect the compiler to behave in this case.) |
Mark Feeney (overthink) said: I would value a compiler flag that will cause a warning when exhaustiveness checks are disabled. |
@SethTisue said: |
@non said: What would be the process for reopening this? |
@lrytz said: There are cases today when you get a spurious warning: if you know more than the compiler about the possible values that can reach the match. That's why we already have the I don't see an example (yet?) where the change proposed here would lead to new spurious warnings.
One argument might be that an incomplete match is like an assertion (in the shape of a |
@adriaanm said: |
@adriaanm said: It would be nice if we could do a corpus analysis to see whether there are any matches out there that violate the (valid) style guidelines above (maybe under a -Y flag included in the PR?) |
@retronym said: |
@SethTisue said: |
Having this is one of my biggest Scala desires, it has been for years. I really hope we can convince someone to implement it. |
scala/scala#4929 could certainly be revived |
Just now fixed a bug that would have been caught by the compiler if it would default to assuming that guards always fail. IMHO that would improve the current situation. |
FWIW I just tried it in Dotty and saw the same |
@smarter did this regress in dotty? Should the label be removed? |
It's possible I got it wrong at the time. I've opened lampepfl/dotty#8708 |
Actually fixed in dotty now: lampepfl/dotty#8698 :) |
It seems that guarded match expressions are not tested for completeness at compile time. Or, more accurately, it seems that guarded case statements are optimistically considered complete. For example, the following code will compile without "warning: match is not exhaustive!", even though calling it with a value of 1 will result in a MatchError.
The equivalent code in Ocaml does print a non-exhaustive match warning at compile time.
Obviously the compiler can't decide whether or not arbitrary combinations of guards are complete, but I was surprised didn't treat guards pessimistically like Ocaml does. This actually bit us in production code, when a method like this:
was refactored to look like this:
which resulted in runtime MatchErrors.
I understand that adding a warning that treats guarded match statements pessimistically could be annoying in cases when the combined guards actually are safe, but in these instances the warning could easily be dismissed with an annotation or a noop unguarded match. Why don't we give Scala users the option for added safety?
The text was updated successfully, but these errors were encountered: