Consider following code in Scala:
object Test {
class A {}
class B extends A {}
class AI extends A {
def sayHello: String = "Hello from AI"
}
implicit def AtoAI(a: A): AI = a
class BI extends B {
def sayHello: String = "Hello from BI"
}
implicit def BtoBI(b: B): BI = b
def main(args: Array[String]) {
val a = new A
println(a.sayHello)
val b = new B
println(b.sayHello)
}
}
The use of implicits results in looping code. In fact, disassemblying reveals, that generated conversion methods have only a goto 0
inside:
public Test$AI AtoAI(Test$A);
Code:
0: goto 0
public Test$BI BtoBI(Test$B);
Code:
0: goto 0
What causes that behaviour? I understand, that a hierarchy of classes here is a suspect, but implicit conversion should be applied only once.
I use Scala 2.9.1
Not nice but I would definitely not call it a bug.
It boils down to
class A
class B
implicit def aToB(a: A) : B = a
There is no need for the two sides of the conversion to be related in any way. The implicit is just the same thing as writing
implicit def aToB(a: A): B = aToB(a)
because the compiler inserts the aToB
call in order to convert the result a
to the required return type B
.
The goto 0
implementation is just a tail call optimization. The compiler might probably gives a warning when it generates a method that starts this way.
Maybe there could be a rule that implicit methods are not available as implicits inside their own body. But it does not always create an infinite loop
implicit def listAToListB(l: list[A] = l match {
case Nil => Nil
case x:xs => toB(x) :: xs // equivalent to toB(x) :: listAToList[B](xs)
}
(ok this is just a map(toB)
). Anyway the same could happen with two mutually recursive implicits. In my opinion, it's not worthwhile to tweak the spec just to avoid some possibility to write an infinite, do nothing loop, among many others. But a warning when such a loop is detected, irrespective of implicits, would be nice.
I cannot understand why the code compiles at all. I verified that it does compile. Wouldn't you have to downcast inside the implicit conversion method?
Didier points out, that the implicit conversion gets applied recursively, which means the code can compile without a downcast.
The following code adds the downcasts (which should not change the runtime behavior). It fails at runtime with the failing casts. So this looks like a compiler bug to me. As Didier says, the explicit downcast prevents the recursive application of the implicit conversion here.
object Test {
class A {}
class B extends A {}
class AI extends A {
def sayHello: String = "Hello from AI"
}
implicit def AtoAI(a: A): AI = a.asInstanceOf[AI]
class BI extends B {
def sayHello: String = "Hello from BI"
}
implicit def BtoBI(b: B): BI = b.asInstanceOf[BI]
def main(args: Array[String]) {
val a = new A
println(a.sayHello)
val b = new B
println(b.sayHello)
}
}
Finishing by answering the question: you call a method of AI
on an object of class A
that doesn't have this method. Obviously this can't work. What happens is unspecified; in your case it was an infinite loop.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With