Exhaustive match checking warns incorrectly when cases are exhaustive but include guards

This match is clearly exhaustive, but warns that it may be inexhaustive.

def foo(bool: Boolean): Boolean = {
  val bar: Boolean = true
  bar match {
    case false => false
    case true if bool => true
    case true if !bool => true
  }
}

gives the error

[Error] ... match may not be exhaustive.
[Error] It would fail on the following input: true

I can just move the bool check and have two cases instead of three, with no guards, but it’d be neat if exhaustiveness checking could recognize when two cases are identical except for a negated guard, and then ignore the guard when doing the exhaustiveness checking

That’s a totally reasonable question. Regardless, for exhaustiveness checking to depend on the exact contents of the expressions you use as guards is never going to happen, I’m pretty confident asserting. Guards are arbitrary expressions and you can’t expect the compiler to know how those arbitrary expressions are going to behave.

(Whereas patterns aren’t arbitrary expressions; they’re patterns, far fewer things can appear in them, and that’s what makes them tractable for the compiler to analyze.)

I know it’s tempting to say “but if bool is so simple, surely the compiler could understand that!”. But it’s a slippery slope, even handling simple cases like this would be a substantial broadening in the scope of what exhaustive checking even is, both in the implementation and in our mental models of how the compiler behaves.

The good news is that exactly because the guard is so simple, it’s easy to rewrite the match so that the exhaustive checker knows what you’re doing:

(bar, bool) match {
  case (false, _) => ...
  case (true, true) => ...
  ...

this is actually better code anyway IMO.

3 Likes