使用 ToolBox 时隐式解析失败

Implicit resolution fail in reflection with ToolBox

我正在尝试根据 class 路径在反射中生成 Avro4s 的 RecordFormat。以下代码会引发错误。

case class MyCaseClass(a: Int)
println(toolBox.compile {
  toolBox.parse(
    s"""
       |import com.sksamuel.avro4s._
       |import mypackage.MyCaseClass
       |RecordFormat[MyCaseClass]
       |""".stripMargin
  )
}())

could not find implicit value for evidence parameter of type com.sksamuel.avro4s.Decoder[mypackage.MyCaseClass]

记录格式就像

object RecordFormat {

  def apply[T <: Product : Encoder : Decoder : SchemaFor]: RecordFormat[T] = apply(AvroSchema[T])

  def apply[T <: Product : Encoder : Decoder](schema: Schema): RecordFormat[T] = new RecordFormat[T] {
    private val fromRecord = FromRecord[T](schema)
    private val toRecord = ToRecord[T](schema)
    override def from(record: GenericRecord): T = fromRecord.from(record)
    override def to(t: T): Record = toRecord.to(t)
  }
}

参考:https://github.com/sksamuel/avro4s/blob/release/2.0.x/avro4s-core/src/main/scala/com/sksamuel/avro4s/RecordFormat.scala

我明白了,它可以解析 Encoder[MyCaseClass]SchemaFor[MyCaseClass] 但无法解析 Decoder[MyCaseClass]

相同的代码可以解析 RecordFormat[MyCaseClass] 而无需反射。

我可以看到 Decoder 是用类似于 Encoder 的宏实现的。

implicit def applyMacro[T <: Product]: Decoder[T] = macro applyMacroImpl[T]

为什么反射不能解决隐含证据?

avro4s 4.x 使用 Magnolia but avro4s 2.x uses raw implicit macros + Shapeless.

通常 there shouldn't be significant problems 在运行时使用反射工具箱实现类型 class,即使类型 class 是用宏定义的。

现在的问题是定义 com.sksamuel.avro4s.Decoder 的宏有一个错误。 Decoder.scala#L404

c.Expr[Decoder[T]](
  q"""
  new _root_.com.sksamuel.avro4s.Decoder[$tpe] {
    private[this] val decoders = Array(..$decoders)

    override def decode(value: Any, schema: _root_.org.apache.avro.Schema): $tpe = {
      val fullName = $fullName
      value match {
        case record: _root_.org.apache.avro.generic.GenericRecord => $companion.apply(..$fields)
        case _ => sys.error("This decoder decodes GenericRecord => " + fullName + " but has been invoked with " + value)
      }
    }
  }
  """
)

指的是sys.error而不是hygienic _root_.scala.sys.error.

如果您修复此行,Decoder[MyCaseClass]RecordFormat[MyCaseClass] 将在工具箱中工作

println(toolBox.compile {
  toolBox.parse(
    s"""
       |import com.sksamuel.avro4s._
       |import mypackage.MyCaseClass
       |RecordFormat[MyCaseClass]
       |""".stripMargin
  )
}()) //com.sksamuel.avro4s.RecordFormat$$anon@25109d84

所以一个快速的解决方法是删除行

libraryDependencies += "com.sksamuel.avro4s" %% "avro4s-core" % "2........."

build.sbt中添加

libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.3"
libraryDependencies += "org.apache.avro" % "avro" % "1.8.2"

(否则你会得到 NoClassDefFoundError)并将以下修补的 jar 放入 lib

https://github.com/DmytroMitin/avro4s-2.0.5-2.11-patched

avro4s-core_2.11-2.0.5-SNAPSHOT.jar
avro4s-macros_2.11-2.0.5-SNAPSHOT.jar

您始终可以调试使用工具箱生成的基于隐式或基于宏的代码,如果您像这样创建它的话

val toolBox = runtimeMirror.mkToolBox(
  frontEnd = new FrontEnd {
    override def display(info: Info): Unit = println(info)
    override def interactive(): Unit = ???
  },
  options = "-Xlog-implicits" // or "-Xlog-implicits -Ymacro-debug-lite"
)

如果你这样做

println(reify{
  Decoder[MyCaseClass]
}.tree)

它打印

Decoder.apply[MyCaseClass](Decoder.applyMacro)

所以隐式 Decoder[MyCaseClass] 被解析为 Decoder.applyMacro[MyCaseClass]

使用原始未打补丁的 jar

toolBox.compile {
  toolBox.parse(
    s"""
       |import com.sksamuel.avro4s._
       |import mypackage.MyCaseClass
       |Decoder.applyMacro[MyCaseClass]
       |""".stripMargin
  )
}()

抛出

scala.tools.reflect.ToolBoxError: reflective compilation has failed:
object error is not a member of package sys