Search code examples
reflectionscalatype-systemspath-dependent-typeabstract-type

Path-dependent types vs. "underlying types", which ones are checked?


When using path-dependent types with reflection I am getting a type mismatch error even though I have matching "underlying types". What are these "non-underlying types" and why are they checked instead of the "underlying types"?

In the code below, I want the compare method to accept only same-type subclasses of A as arguments. The error is on the last line.

abstract class A(val a:Int) {
  type Impl <: A
  def compare(other:Impl) {
    if(a==other.a) println("equal") else println("diff")
  }
}
class B(a:Int) extends A(a) {type Impl = B}

object Test {
  def newInst(a: Int, className: String) = {
    val constr = Class.forName(className).getConstructors()(0)
    constr.newInstance(a.asInstanceOf[AnyRef]).asInstanceOf[A]
  }

  def main(args: Array[String]) {
    val b1 = newInst(4, "B")
    val b2 = newInst(5, "B")
    b1.compare(b2)   // type mismatch error here
  }
}

On the last line I get this error:

error: type mismatch;
found   : b2.type (with underlying type A)
required: b1.Impl

Since the type of b2 is the same as the type of b1 (which is A), I expected this to not generate an error. For some reason these path-dependent types are different than the "underlying types" when using reflection. Why?

If I do not use reflection, it works:

val b1 = new B(4)
val b2 = new B(5)
b1.compare(b2)  // no errors

(I do need to use reflection in my case). Can newInst() return the object as class "B", using reflection? Would this help? Is there type erasure when using abstract types?

This is the only reference I found (on this forum) about the same error, but it may not be related.


Solution

  • This doesn't have anything to do with reflection. The type of b1 and b2 is A (since that's the return type of newInst). For call b1.compare(b2) to compile, b2 must have type b1.Impl. The compiler knows only that it is some subtype of A, but it doesn't know which one. Since you can't pass an A where some subtype of A is required, you get an error.

    In the example

    val b1 = new B(4)
    val b2 = new B(5)
    b1.compare(b2)  // no errors
    

    both variables have type B, and B#Impl is B, so everything typechecks.