Can't figure out why scalac is unhappy here (2.12):
trait A {
type Self <: A
type X <: Self
}
trait B extends A {
override type Self <: B
override type X = C // error: overriding type X in trait A with bounds <: B.this.Self
}
trait C extends B {
override type Self = C
}
Feels like it is because of the path-dependent types, but I don't understand what exactly is wrong and if there's a good way to fix it.
C
is a subtype of B
, B
is a subtype of A
, so C
is a subtype of A
but C
is not a subtype of A
's Self
or B
's Self
. So you can't override (in B
) A
's X
having upper bound Self
(i.e. A
's Self
) with C
not satisfying the bound (i.e. B
's Self
).
trait A {
type Self <: A
type X <: Self
// implicitly[C <:< Self] // doesn't compile
}
trait B extends A {
override type Self <: B
// override type X = C
// implicitly[C <:< Self] // doesn't compile
}
trait C extends B {
override type Self = C
}
C
's Self
equals C
but this doesn't mean that A
's Self
or B
's Self
does.
You can fix compilation with lower bound
trait A {
type Self <: A
type X <: Self
}
trait B extends A {
override type Self >: C <: B // >: C is added
override type X = C
}
trait C extends B {
override type Self = C
}
Or if you mean that A
's X
is a subtype of not A
's Self
but C
's Self
you can specify this with type projection
trait A {
type Self <: A
type X <: C#Self // here
}
trait B extends A {
override type Self <: B
override type X = C
}
trait C extends B {
override type Self = C
}
I guess misunderstanding was because for def
s
trait A {
def foo(): String = "A#foo()"
def bar(): String = s"bar=A#bar(), foo=${foo()}"
}
trait B extends A {
def foo(): String = "A#foo()"
}
trait C extends B {
override def foo(): String = "C#foo()"
}
when we write foo()
inside A
's bar()
we refer actually not to A
's foo()
but to implementation's foo()
. This is possible because method implementations are resolved late, at runtime. But types are resolved early, at compile time. So when you write
trait A {
type Self <: A
type X <: Self
}
Self
in the upper bound of X
is A
's Self
, not implementation's Self
.
OOP principles say that inside A
you can't refer specifically to C
's foo()
(unless you instantiate C
). But you can everywhere refer specifically to A
's Self
, B
's Self
, C
's Self
with type projections A#Self
, B#Self
, C#Self
.