You can do this with implicits like so. Keep in mind that you will always be able to
tricky.trickyMethod((new Banana): Fruit, (new Apple): Fruit)
since disallowing that would break the subtyping relationship. (If really necessary, you could use Miles Sabin's encoding of "not this type" (see comment #38) to reject Fruit
.)
Anyway, one way to achieve what you want is as follows:
class SamePair[T,U] {}
object SamePair extends SamePair[Nothing,Nothing] {
def as[A] = this.asInstanceOf[SamePair[A,A]]
}
implicit def weAreTheSame[A] = SamePair.as[A]
Now we have an implicit that will give us a dummy object that verifies that two types are the same.
So now we change TrickyClass
to
class TrickyClass[T <: Fruit] {
def trickyMethod[A <: T, B <: T](p1: A, p2: B)(implicit same: SamePair[A,B]) = println("!")
}
And it does what you want:
scala> val tricky=new TrickyClass[Fruit]()
tricky: TrickyClass[Fruit] = TrickyClass@1483ce25
scala> tricky.trickyMethod(new Apple,new Apple) //this should be OK
!
scala> tricky.trickyMethod(new Banana,new Banana) //this should be OK
!
scala> tricky.trickyMethod(new Banana,new Apple) //this should NOT compile
<console>:16: error: could not find implicit value for parameter same: SamePair[Banana,Apple]
tricky.trickyMethod(new Banana,new Apple) //this should NOT compile