Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explanation for "illegal cyclic reference" involving implicits

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.

like image 561
Feuermurmel Avatar asked Feb 21 '19 10:02

Feuermurmel


1 Answers

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.

like image 179
Travis Brown Avatar answered Nov 15 '22 03:11

Travis Brown