Search code examples
scalacase-classextractor

Difference between home made extractor and case class extractor


According to the scala specification, the extractor built by case classes is the following (scala specification §5.3.2):

def unapply[tps](x: c[tps]) =
  if (x eq null) scala.None
  else scala.Some(x.xs11, ..., x.xs1k)

For implementation reasons, I want to be able to mimic the behavior of this extractor on a non-case class. However, my implementation fails to reproduce the same behavior.

Here is an example of the difference i have:

trait A

sealed trait B[X <: A]{ val x: X }

case class C[X <: A](x: X) extends B[X]

class D[X <: A](val x: X) extends B[X]

object D {
  def unapply[X <: A](d: D[X]): Option[X] =
    if (d eq None) None
    else Some(d.x)
}

def ext[X <: A](b: B[X]) = b match {
  case C(x) => Some(x)
  case D(x) => Some(x)
  case _ => None
}

I have the following warning :

<console>:37: warning: non variable type-argument X in type pattern D[X] is unchecked since it is eliminated by erasure
     case D(x) => Some(x)

Notice the warning occurs only in the D case, not in the case-class textractor case. Do you have any idea about the cause of the warning / about what I should do to avoid this warning ?

Note: If you want to test it in REPL, the easiest way is:

  1. To activate unchecked warning

    scala> :power

    scala> settings.unchecked.value = true

  2. To copy above code in paste mode:

    scala> :paste

    [copy/paste]

    [ctrl + D]

Edit: As Antoras mentioned it should be a compiler bug, maybe the scala version could be useful: scala 2.9.0.1 (after a quick test, still there in scala 2.9.1RC2)


Solution

  • This seems to be a compiler bug. I have analyzed the output of the compiler AST (with fsc -Xprint:typer <name_of_file>.scala). It interprets both as the same:

    ...
        final <synthetic> object C extends java.lang.Object with ScalaObject with Serializable {
          def this(): object test.Test.C = {
            C.super.this();
            ()
          };
          final override def toString(): java.lang.String = "C";
          case <synthetic> def unapply[X >: Nothing <: test.Test.A](x$0: test.Test.C[X]): Option[X] = if (x$0.==(null))
            scala.this.None
          else
            scala.Some.apply[X](x$0.x);
          case <synthetic> def apply[X >: Nothing <: test.Test.A](x: X): test.Test.C[X] = new test.Test.C[X](x);
          protected def readResolve(): java.lang.Object = Test.this.C
        };
    ...
        final object D extends java.lang.Object with ScalaObject {
          def this(): object test.Test.D = {
            D.super.this();
            ()
          };
          def unapply[X >: Nothing <: test.Test.A](d: test.Test.D[X]): Option[X] = if (d.eq(null))
            scala.None
          else
            scala.Some.apply[X](d.x)
        };
    ...
    

    The method signature of both methods unapply are identical.

    Furthermore the code works fine (as expected due to identical methods):

    trait A {
      def m = "hello"
    }
    
    class AA extends A
    
    sealed trait B[X <: A]{ val x: X }
    
    case class C[X <: A](x: X) extends B[X]
    
    class D[X <: A](val x: X) extends B[X]
    
    object D {
      def apply[X <: A](x: X) = new D(x)
      def unapply[X <: A](d: D[X]): Option[X] =
        if (d eq null) None
        else Some(d.x)
    }
    
    def ext[X <: A](b: B[X]) = b match {
      case C(x) => Some("c:"+x.m)
      case D(x) => Some("d:"+x.m)
      case _ => None
    }
    println(ext(C[AA](new AA())))
    println(ext(D[AA](new AA())))