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 }
This is a problem with path dependent types.
The
test
method of objecth
does not expect, as you would assume, a subtype ofParent.Inner
. It expects a subtype ofh.Self
which is a slightly different type. Even thoughChild.Inner
is a subtype ofParent.Inner
, it is not a subtype ofh.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:
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, becauseh.Self
is actuallyC
andChild.Inner
is not a subtype ofC
. That's why scalac correctly rejects it.You would expect that in your snippet scalac should remember that the type of
h
is exactlyParent.Inner
, but unfortunately it doesn't keep that information. It only knows thath
is some subtype ofParent.Inner
.It appears that you are thinking of
def inner = new Inner()
as a field instead of thinking of it as aval
. The difference is that a value is determined at the time a class is loaded, while the value using thedef
keyword is determined at run-time. This may not be exactly what you are looking for, as it answers another question, but there seems to be something funky going on here.Since I am curious myself:
val p : Parent.Inner = Child.inner
This is obviously true, with this line:
class Inner extends Parent.Inner
however if we do:
val h = Parent.inner h.test[Parent.Inner]
does not work either with the following error:
type arguments [Parent.Inner] do not conform to method test's type parameter bounds [T <: h.Self] h.test[Parent.Inner]
even more curiously (or maybe not curiously, since this is a subtype of everything) this works:
h.test[Nothing]