In my project, I have a type A
, used for arguments in a few places, where I want a bunch of types automatically converted to that type. I've implemented this using a few implicit classes in the companion object of A
. I've removed everything not necessary to trigger the problem:
trait A
object A {
implicit class SeqA[T](v: Seq[T])(implicit x: T => A) extends A
implicit class IntA(v: Int) extends A
implicit class TupleA(v: (Int, Int)) extends SeqA(Seq(v._1, v._2))
}
But scalac
rejects this code because of an illegal cyclic reference:
$ scalac -version
Scala compiler version 2.12.8 -- Copyright 2002-2018, LAMP/EPFL and Lightbend, Inc.
$ scalac A.scala
A.scala:5: error: illegal cyclic reference involving class TupleA
implicit class TupleA(v: (Int, Int)) extends SeqA(Seq(v._1, v._2))
^
one error found
What exactly is the problem here? What is the compiler tying to do/infer/resolve that involves an illegal cylce?
Bonus points for a valid way to implement these implicit classes.
You're running into SI-9553, a three-and-a-half-year-old compiler bug.
The reason the bug hasn't been fixed is probably in part because there's an extremely simple workaround—just put an explicit type parameter on the generic class you're extending:
trait A
object A {
implicit class SeqA[T](v: Seq[T])(implicit x: T => A) extends A
implicit class IntA(v: Int) extends A
implicit class TupleA(v: (Int, Int)) extends SeqA[Int](Seq(v._1, v._2))
}
This is probably a good idea anyway, since you're asking for trouble any time you let type parameters get inferred where implicit definitions are involved.
As a side note, you can investigate issues like this with a compiler option like -Xprint:typer
. In this case it shows the following in a REPL:
// ...
implicit class TupleA extends $line6.$read.$iw.$iw.A.SeqA[Int] {
<paramaccessor> private[this] val v: (Int, Int) = _;
def <init>(v: (Int, Int)): $line6.$read.$iw.$iw.A.TupleA = {
TupleA.super.<init>(scala.collection.Seq.apply[Int](v._1, v._2))({
((v: Int) => A.this.IntA(v))
});
()
}
};
implicit <synthetic> def <TupleA: error>(v: (Int, Int)): <error> = new TupleA(v)
Which in this case isn't terribly helpful, but it at least indicates that the problem is happening in the definition of the synthetic conversion method for the TupleA
implicit class, and not at some point before that.