The thing is in scopes.
Simpler example is
trait A { type T }
implicit val a: A { type T = Int } = null
def test(implicit x: A): Unit = {
implicitly[x.T =:= Int] // doesn't compile, cannot prove that x.T =:= Int
}
You assume that x
is a
(aIsBaseTc
is doubleHoldsTypeMember
in your notations). But actually x
is not a
, x
will be resolved when test
is called (in the scope of test
call site) but a
is defined in current scope (scope of test
definition). Similarly, aIsBaseTc
is not doubleHoldsTypeMember
.
When doing implicit resolution with type parameters, why does val placement matter? (See the difference between implicit x: X
and implicitly[X]
.)
As for any anonymous class
implicit val containerOfDoubleIsActual = new IsActualTC[Container[Double], Double] {
def get(self: Self) = self.get // type mismatch;
}
is a shorthand for
class IsActualTCImpl extends IsActualTC[Container[Double], Double] {
def get(self: Self) = self.get // type mismatch;
// aIsBaseTc is not doubleHoldsTypeMember here
}
implicit val containerOfDoubleIsActual =
new IsActualTCImpl // implicit is resolved here
// aIsBaseTc becomes doubleHoldsTypeMember here
And since aIsBaseTc
is not doubleHoldsTypeMember
, aIsBaseTc.Self
is not Double
.
Possible fix is to add one more type parameter S
to IsActualTC
abstract class IsActualTC[A, T, S](implicit val aIsBaseTc: IsBaseTC[T] {type Self = S}) extends IsBaseTC {
type Self = A
def get(self: A): S
}
implicit val containerOfDoubleIsActual = new IsActualTC[Container[Double], Double, Double] {
def get(self: Self) = self.get
}
or to add a type refinement to the implicit parameter of IsActualTC
abstract class IsActualTC[A, T](implicit val aIsBaseTc: IsBaseTC[T] {type Self = T}) extends IsBaseTC {
type Self = A
def get(self: A): aIsBaseTc.Self
}
implicit val containerOfDoubleIsActual = new IsActualTC[Container[Double], Double] {
def get(self: Self) = self.get
}