Kenji Yoshida
Kenji Yoshida

Reputation: 3118

strange "match may not be exhaustive" warnings

in Scala 2.10.2

sealed abstract class A  

sealed trait Foo{
  sealed abstract class B extends A 
  final class C extends B  
  final class D extends B  
  final class E extends A  
}

object Main extends Foo{

  def bar = (new C(): A) match{
    case _: C => "c" 
    case _: D => "d"
    case _: E => "e"
  }

}

compiler says

[warn] A.scala:12: match may not be exhaustive.
[warn] It would fail on the following inputs: C(), D(), E()
[warn]   def bar = (new C(): A) match{

but Main#bar success and return "c".

Am I doing something wrong? or this is scalac bug?

https://github.com/scalaz/scalaz/issues/468

Upvotes: 2

Views: 1176

Answers (1)

som-snytt
som-snytt

Reputation: 39577

The path-dependent Cs of different Foos are different.

That may be why it complains. (There are known bugs in the warnings.) (Such as this one.)

  final class C extends B   {
    def f(c: C) = "ok"  // adding this to C
  }

object Test extends App {
  val f = new Foo { }
  Console println (X bar new f.C())
  val c = new f.C
  c.f(X.c)  // doesn't compile
}
object X extends Foo{
  val c = new C
  def bar(a: A) = a match {
    case _: C => "c"
    case _: D => "d"
    case _: E => "e"
  }
}

This better represents what you're able to do, and silences the warnings:

  def bar(a: A) = a match {
    case _: Foo#C => "c"     // instanceof Foo$C etc
    case _: Foo#D => "d"
    case _: Foo#E => "e"
  }

Update: There is more to be said, namely, an open issue I happened to notice while looking at an unrelated stackoverflow. (An actual one, not the Q&A site.)

In short, it tries to optimize away the "outer" pointer from nested classes to their enclosing instances, and if that happens, you can no longer include the outer instance in the match. Normally it will test both instanceof and that its outer is the right one.

Moving bar into the trait and removing final disables the optimization and fixes the match.

  public static java.lang.String bar(badseal.Foo, badseal.A);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=4, args_size=2
         0: aload_1       
         1: astore_2      
         2: aload_2       
         3: instanceof    #9                  // class badseal/Foo$D
         6: ifeq          26
         9: aload_2       
        10: checkcast     #9                  // class badseal/Foo$D
        13: invokevirtual #13                 // Method badseal/Foo$D.badseal$Foo$D$$$outer:()Lbadseal/Foo;
        16: aload_0       
        17: if_acmpne     26
        20: ldc           #15                 // String d

If the inner classes are final, scalac will at least complain:

badseal.scala:17: warning: The outer reference in this type test cannot be checked at run time.
    case _: C => "c" 
          ^

But if the match is in the object, the heuristic for this message seems to break so you don't see it anymore.

sealed abstract class A

trait Foo {
  sealed abstract class B extends A
  class C extends B
  class D extends B
  class E extends A
  def bar(a: A) = a match {
    case _: C => "c"
    case _: D => "d"
    case _: E => "e"
  }
}

object X extends Foo

Then

  val f1 = new Foo { }
  Console println X.bar(new f1.C)

Correctly warns and correctly throws.

apm@mara:~/tmp$ skalac -unchecked badseal.scala ; skala badseal.Test
badseal.scala:11: warning: match may not be exhaustive.
It would fail on the following inputs: C(), D(), E()
  def bar(a: A) = a match {
                  ^
one warning found
scala.MatchError: badseal.Foo$C@756bc09d (of class badseal.Foo$C)

Upvotes: 4

Related Questions