Description
In the standard library, =:=
is defined as follow:
// the only instance for <:< and =:=, used to avoid overhead
private val singleton: =:=[Any, Any] = new =:=[Any,Any] {
// ...
}
implicit def refl[A]: A =:= A = singleton.asInstanceOf[A =:= A]
}
I always thought this was perfectly fine, but thanks to Dotty's world-class GADT support, it isn't anymore :)
object Test {
def cast[A, B](elem: A, default: B)(implicit x: A =:= A, y: B =:= B): B = x match {
case x: y.type =>
elem
case _ =>
default
}
def main(args: Array[String]): Unit = {
val x: String = cast(1, "") // ClassCastException
}
}
The same code also compiles and crashes in scalac, but produces an unchecked erasure warning which I believe is incorrect: if it weren't for the cast used to get an instance of =:=
, this code would be perfectly fine.
What can we do about this? In the short-term, either do nothing or special-case the GADT handling of =:=
in the compiler. in the long-term, we should rewrite =:=
and <:<
using an opaque type to some dummy value:
opaque type <:<[-From, +To] = Object
opaque type =:=[From, To] <: <:<[From, To] = Object
private val singleton: =:=[Any, Any] = new Object
implicit def refl[A]: A =:= A = singleton
// ... and a bunch of extension methods on <:< and =:=
There's still a cast (EDIT: actually not needed), but the use of opaque types prevents the bogus inference of GADT bounds:
https://github.com/lampepfl/dotty/blob/b448db2454e003f1448a31746daed11001a2ec86/compiler/src/dotty/tools/dotc/core/TypeComparer.scala#L869-L870
(another solution would be to somehow enforce that values of type =:=
must always be erased at runtime, and therefore do not have a notion of reference equality)