简体   繁体   中英

Implicit search inside macro expansion fails

Consider a trait that performs "encoding" of arbitrary objects:

trait Encoder[R] {
  def encode(r: R): Array[Byte]
}

Assuming encoding of primitive types is known and custom types can be encoded by defining up a "serializer":

trait Serializer[T] {
  def serialize(r: T): Array[Byte]
}

we can implement a macro for case class encoding by simply looping the fields and looking up type serializers implicitly. Here's a dummy implementation that looks up the serializer for R itself (in reality we look up serializers for case class field types):

object Encoder {
  implicit def apply[R <: Product]: Encoder[R] = macro applyImpl[R]
  def applyImpl[R: c.WeakTypeTag](c: blackbox.Context): c.Expr[Encoder[R]] = {
    import c.universe._
    c.Expr[Encoder[R]](q"""
      new ${weakTypeOf[Encoder[R]]} {
        override def encode(r: ${weakTypeOf[R]}): Array[Byte] = 
          implicitly[_root_.Serializer[${weakTypeOf[R]}]].serialize(r)
        }
    """)
  }
}

Now define a base "processor":

abstract class BaseProcessor[R: Encoder] {
  def process(r: R): Unit = {
    println(implicitly[Encoder[R]].encode(r).length)
  }
}

And try to use it:

case class Record(i: Int)

object Serializers {
  implicit def recordSerializer: Serializer[Record] = 
    (r: Record) => Array.emptyByteArray
}

import Serializers._

class Processor extends BaseProcessor[Record]

This fails to compile with:

// [error] Loader.scala:10:22: could not find implicit value for parameter e: Serializer[Record]
// [error] class Processor extends BaseProcessor[Record]
// [error]                         ^
// [error] one error found

However the following do compile:

class Processor extends BaseProcessor[Record]()(Encoder[Record]) // Compiles!
object x { class Processor extends BaseProcessor[Record] }       // Compiles!

I can't really understand why this happens, looks like it has something to do with the Processor definition being a top level definition, since as soon as I move it inside a class/object everything works as expected. Here's one more example that fails to compile:

object x {
  import Serializers._ // moving the import here also makes it NOT compile
  class Processor extends BaseProcessor[Record] 
}

Why does this happen and is there any way to make it work?

To make it work you can try one of the following:

1) add context bound

implicit def apply[R <: Product : Serializer]: Encoder[R] = macro applyImpl[R]

def applyImpl[R: c.WeakTypeTag](c: blackbox.Context)(serializer: c.Expr[Serializer[R]]): c.Expr[Encoder[R]] = {
  import c.universe._
  c.Expr[Encoder[R]](q"""
    new Encoder[${weakTypeOf[R]}] {
      override def encode(r: ${weakTypeOf[R]}): Array[Byte] =
        $serializer.serialize(r)
    }
  """)
}

2) make macro whitebox

3) put implicit instance recordSerializer: Serializer[Record] to the companion object of Record rather than some object Serializers to be imported

4) maybe your example is easier than actual use case but now it seems you don't need macros

implicit def apply[R <: Product : Serializer]: Encoder[R] = new Encoder[R] {
  override def encode(r: R): Array[Byte] = implicitly[Serializer[R]].serialize(r)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM