Why does extracting and assigning individual tuple values cause a recursive implicit search?

100 Views Asked by At

Here's the code to produce this error:

build.sbt

scalaVersion := "2.11.7"

libraryDependencies ++= Seq(
  "ai.x" %% "safe" % "0.1.0"

)

scalacOptions := Seq("-Ytyper-debug") // Only add this if you want to see a bunch of stuff

test.scala

import ai.x.safe._

package object foo {
    final implicit val (enc, dec) = {
        ("x" === "y") -> 0
    }
}

Attempting to compile this will cause this error:

[info] Compiling 1 Scala source to /tmp/test/target/scala-2.11/classes...
[error] /tmp/test/test.scala:4: recursive value x$1 needs type
[error]     final implicit val (enc, dec) = {
[error]                         ^
[error] one error found

With the full debug mode on I can see that it's trying to resolve === and the compiler is looking at the current implicits to determine if any match. Since (enc, dec) are implicit it appears to use them as well, and so it tries to type them, causing this implicit recursion the compiler is complaining about.

|    |    |    |    |    |-- "x".$eq$eq$eq("y") EXPRmode-POLYmode-QUALmode (silent: value x$1 in package) 
|    |    |    |    |    |    |-- "x".$eq$eq$eq BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value x$1 in package) 
|    |    |    |    |    |    |    |-- "x" EXPRmode-POLYmode-QUALmode (silent: value x$1 in package) 
|    |    |    |    |    |    |    |    \-> String("x")
|    |    |    |    |    |    |    |-- x$1._1 EXPRmode (site: value enc  in package) 
|    |    |    |    |    |    |    |    |-- x$1 EXPRmode-POLYmode-QUALmode (site: value enc  in package) 
|    |    |    |    |    |    |    |    |    caught scala.reflect.internal.Symbols$CyclicReference: illegal cyclic reference involving value x$1: while typing x$1
[error] /tmp/test/test.scala:4: recursive value x$1 needs type
[error]     final implicit val (enc, dec) = {
[error]                         ^
|    |    |    |    |    |    |    |    |    \-> <error>
|    |    |    |    |    |    |    |    \-> <error>
|    |    |    |    |    |    |    |-- x$1._2 EXPRmode (site: value dec  in package) 
|    |    |    |    |    |    |    |    |-- x$1 EXPRmode-POLYmode-QUALmode (site: value dec  in package) 
|    |    |    |    |    |    |    |    |    \-> <error>
|    |    |    |    |    |    |    |    \-> <error>
|    |    |    |    |    |    |    |-- SafeEquals BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value x$1 in package) implicits disabled
|    |    |    |    |    |    |    |    |-- ai.x.safe.`package` EXPRmode-POLYmode-QUALmode (silent: value x$1 in package) implicits disabled
|    |    |    |    |    |    |    |    |    \-> ai.x.safe.type
|    |    |    |    |    |    |    |    \-> ai.x.safe.SafeEquals.type <and> [T](l: T)ai.x.safe.SafeEquals[T]
|    |    |    |    |    |    |    solving for (T: ?T)
|    |    |    |    |    |    |    solving for (T: ?T)
|    |    |    |    |    |    |    solving for (T: ?T)
|    |    |    |    |    |    |    [adapt] SafeEquals adapted to [T](l: T)ai.x.safe.package.SafeEquals[T] based on pt String("x") => ?{def ===: ?}
|    |    |    |    |    |    |    |-- [T](l: T)ai.x.safe.package.SafeEquals[T] EXPRmode-POLYmode-QUALmode (silent: value x$1 in package) 
|    |    |    |    |    |    |    |    \-> ai.x.safe.package.SafeEquals[String]
|    |    |    |    |    |    |    |-- ai.x.safe.`package`.SafeEquals[String]("x").$eq$eq$eq BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value x$1 in package) 
|    |    |    |    |    |    |    |    \-> (r: String)Boolean
|    |    |    |    |    |    |    \-> (r: String)Boolean
|    |    |    |    |    |    |-- "y" : pt=String BYVALmode-EXPRmode (silent: value x$1 in package) 
|    |    |    |    |    |    |    \-> String("y")
|    |    |    |    |    |    \-> Boolean

I can of course make it compile by doing something like this:

final val (x,y) = {
    ("x" === "y") -> 0
}
implicit val (f,b) = (x,y)

Since the implicits don't exist when the body of {} is being defined, they don't interfere with the compilers implicit search when locating that === from SafeEquals can apply to String and make the code work. Now I don't really have a problem with this because it does make sense since one can define lazy recursive serializers and other implicit things that use themselves without problems. So of course the compiler should look at the implicit that's being defined as a possible application to make something work.

But the weird thing to me is that this works if you don't extract the tuple directly during the assignment:

final implicit val tuple = {
    ("x" === "y") -> 0
}

Obviously that's not what I want to do since I want both things in the tuple to be implicit, (in my real case it's an encoder/decoder pair from circe). But it's just strange to me that the use of (what I believe to be) an extractor for the Tuple2 causes this compiler error with the implicit being searched. Can anyone tell me why this happens or what's causing the behavior? I'd be interested to know a bit more about what I'm seeing in the debug output. Why does resolving the type of each individual thing inside of the tuple cause the compiler error, but resolving the type of the overall tuple not cause any problems?

0

There are 0 best solutions below