Didii
Didii

Reputation: 1363

Match may not be exhaustive warning is incorrect

So the scala compiler is complaining that a pattern match might not be exhaustive for the method foo and I wonder why. This is the code:

abstract class Foo {
    def foo(that: Foo): Unit = (this, that) match {
        case (Foo_1(), Foo_1()) => //case 1
        case (Foo_1(), Foo_2()) => //case 2
        case (Foo_2(), Foo_1()) => //case 3
        case (Foo_2(), Foo_2()) => //case 4
            // Compiler warning
    }

    def fooThis(): Unit = this match {
        case Foo_1() => //do something
        case Foo_2() => //do something
            // Works fine
    }

    def fooThat(that: Foo): Unit = that match {
        case Foo_1() => //do something
        case Foo_2() => //do something
            // Works fine
    }
}
case class Foo_1() extends Foo
case class Foo_2() extends Foo

And this is the error:

Warning:(5, 32) match may not be exhaustive.
It would fail on the following inputs: (Foo(), _), (Foo_1(), _), (Foo_2(), _), (_, Foo()), (_, Foo_1()), (_, Foo_2()), (_, _)
    def foo(that: Foo): Unit = (this, that) match {

Since this and that are of type Foo, and Foo can only be of type Foo_1 or Foo_2, the cases in foo are all possible combinations.

I added fooThis and fooThat for sake of completeness and to show that matching Foo_1 and Foo_2 suffices. The compiler message suggests that there are other types that can be matched (i.e. Foo and _).

So why is this warning shown?

Related:


EDIT

The compiler seems to complain as soon as you use tuples. If we add a dummy variable to fooThis as follows

def fooThis(): Unit = (this, Foo_1()) match {
    case (Foo_1(),_) => //do something
    case (Foo_2(),_) => //do something
}

we get the following compiler warning

Warning:(13, 27) match may not be exhaustive.
It would fail on the following input: (_, _)
    def fooThis(): Unit = (this, Foo_1()) match {

Upvotes: 4

Views: 2911

Answers (3)

Atiq
Atiq

Reputation: 396

Figuring out all subclasses of a class is called Class Hierarchy Analysis, and doing static CHA in a language with dynamic code loading is equivalent to solving the Halting Problem.

Plus, one of the goals of Scala is separate compilation and deployment of independent modules, so the compiler simply cannot know whether or not a class is subclassed in another module, because it never looks at more than one module. (After all, you could compile a module against the interface of some other module without that module even existing on your system!) That's why sealed requires all subclasses to be defined in the same compilation unit. That's why the compiler doesn't show warnings, because its aware of the existing subclasses.

Upvotes: 0

danielnixon
danielnixon

Reputation: 4268

The Scala compiler won't give exhaustive match warnings for non-sealed traits (like your Foo). This explains why fooThis and fooThat compile without warnings.

If you want warnings here (and you should, because they're better than MatchError exceptions at runtime) you have a couple of options:

  1. Make Foo sealed. This creates an ADT, which is safe to pattern match against in the sense that you'll get exhaustivity warnings when you forget a case. Option is an ADT that you're probably familiar with from the standard library. Here, you've already got cases for both Foo_1 and Foo_2, so you won't get an exhaustivity warning. But if you ever forget either case, you will. You probably want to make Foo_1 and Foo_2 final while you're at it.
  2. Leave Foo unsealed, use Typelevel Scala and enable its -Xlint:strict-unsealed-patmat warnings.

On the other hand, the Scala compiler will give exhaustive match warnings for final case classes like Tuple2, which is what you're matching against in your foo method.

To answer "why is the warning shown?", consider what happens if we do this:

case class Foo3() extends Foo
val foo3 = Foo3()
foo3.foo(foo3)

(Answer: it throws a MatchError at runtime.)

The warning is the Scala compiler's way of helping you avoid the exception at runtime. If you want to make the warning go away, you could:

  1. Make Foo sealed (again, creating an ADT), preventing Foo3 from sneaking in elsewhere.
  2. Add a wildcard case _ => ....
  3. Make the match unchecked: ((this, that): @unchecked) match { ....

Don't do number 3, because it leaves you vulnerable to MatchErrors at runtime when someone introduces Foo3.

So, perhaps the question isn't really "why does the match in foo generate a warning", but "why doesn't the match in fooThis and fooThat generate a warning".

Upvotes: 5

Joe K
Joe K

Reputation: 18434

It seems making the abstract class sealed at least makes the compiler warning go away:

sealed abstract class Foo {

Though I'm not too sure why. It might be related to: https://issues.scala-lang.org/browse/SI-9351

Upvotes: 2

Related Questions