Search code examples
scalainheritancepolymorphisminner-classesabstract-type

Scala abstract types in classes within objects


If I do this:

object Parent {
    class Inner extends Testable { type Self <: Inner }
    def inner = new Inner()
}

object Child {
    class Inner extends Parent.Inner { type Self <: Inner }
    def inner = new Inner()
}

trait Testable {
    type Self
    def test[T <: Self] = {}
}

object Main {
    // this works
    val p: Parent.Inner = Child.inner
    // this doesn't
    val h = Parent.inner
    h.test[Child.Inner]
}

I get this error:

error: type arguments [Child.Inner] do not conform to method test's type parameter bounds [T <: Main.h.Self]
    h.test[Child.Inner]

Why does this error when I my Self type is Parent.Inner and Child.Inner <: Parent.Inner?


And if I change type Self <: Inner to type Self = Inner and then override type Self = Inner, I get this error:

overriding type Self in class Inner, which equals Parent.Inner;
 type Self has incompatible type
    class Inner extends Parent.Inner { override type Self = Inner }

Solution

  • This is a problem with path dependent types.

    The test method of object h does not expect, as you would assume, a subtype of Parent.Inner. It expects a subtype of h.Self which is a slightly different type. Even though Child.Inner is a subtype of Parent.Inner, it is not a subtype of h.Self and that's why the compiler complains.

    The problem with type members is that they are path dependent - they're bound to their enclosing instance and scalac will not allow you to pass type member of one instance where type member of another instance is expected. Child.Inner is not bound to any instance at all and will also be rejected.

    Why is this needed? Look at this very similar code:

    object Main {
      class C extends Child.Inner { type Self = C }
    
      val h: Parent.Inner = new C
      h.test[Child.Inner]
    }
    

    When looking at types, this code is exactly the same as yours (in particular, the type of h is exactly the same). But this code is clearly incorrect, because h.Self is actually C and Child.Inner is not a subtype of C. That's why scalac correctly rejects it.

    You would expect that in your snippet scalac should remember that the type of h is exactly Parent.Inner, but unfortunately it doesn't keep that information. It only knows that h is some subtype of Parent.Inner.