You are right, this.type
need to be used, but with some limitation:
trait EnforceIdType {
def id: Id[_ >: this.type <: EnforceIdType]
}
case class A(id: Id[A]) extends EnforceIdType
case class B(id: Id[B]) extends EnforceIdType
//case class C(id: Id[B]) extends EnforceIdType // Won't compile, as expected
Updated:
About second limitation (shown by Alexey Romanov). It can be eliminated but with long way:
//Embeded polymorphism used
class Id(val value: Long) {
type M
}
// Factory method for shift type variable to type parameter field
object Id {
def apply[T](value : Long) = new Id(value) { type M = T }
}
trait EnforceIdType {
type This = this.type // store this.type for use inside Id { type M = .. }
val id: Id { type M >: This <: EnforceIdType } // need to be val
def show(x : id.M) { println (x) } // dependent method
}
case class A(id: Id { type M = A }) extends EnforceIdType
case class B(id: Id { type M = B }) extends EnforceIdType
// case class C(id: Id { type M = B }) extends EnforceIdType // Won't compile, as expected
val a = A( Id[A](10))
val b = B( Id[B](10))
a.show(a)
// a.show(b) // Won't compile, as expected