`Coder[P <: Product]` 的 Scala 2 宏类型 class 推导以错误 `P does not take parameters` 结束

Scala 2 macro type class derivation for `Coder[P <: Product]` ends with error `P does not take parameters`

我是 Scala 2 宏的初学者(在我切换到 Dotty 之前),在尝试了无形类型 class 推导之后,我想更进一步并编写一个可以生成类型的宏 class 没有它的任何 scala.Product 实例。

(为了举例,让我们忽略嵌套递归类型,所以我的目标是扁平化 classes。)

我的类型 class 是抽象的 class Coder[T](例如具有 encode() / decode() 的特征)。

因此生成的代码为:

case class Pojo(
  s: String,
  i: Int,
  l: List[Int]
)

应该是这样的:

import com.github.fpopic.scalamacros.Pojo
import org.apache.beam.sdk.coders.Coder
import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
import java.util

class PojoCoder extends Coder[Pojo] {

  import com.github.fpopic.scalamacros.beam.DefMacroCoder.{
    stringCoder,
    intCoder,
    listCoder
  }

  override def encode(value: Pojo, os: OutputStream): Unit = {
    stringCoder.encode(value.s, os)
    intCoder.encode(value.i, os)
    listCoder(intCoder).encode(value.l, os)
  }

  override def decode(is: InputStream): Pojo = {
    Pojo(
      s = stringCoder.decode(is),
      i = intCoder.decode(is),
      l = listCoder(intCoder).decode(is)
    )
  }

  override def getCoderArguments: util.List[_ <: Coder[_]] = {
    Collections.emptyList()
  }

  override def verifyDeterministic(): Unit =  ()
}

(删除了完全指定的 class 名称以提高可读性)

在宏中我尝试:

def materializeProductCoder[P: c.WeakTypeTag](c: blackbox.Context): c.Expr[Coder[P]] = {
    import c.universe._
    val tpe = c.weakTypeOf[P]
    val helper = new MacrosHelper[c.type](c)

    val expressions =
      helper.getPrimaryConstructorMembers(tpe).map { field =>
        val fieldTerm = field.asTerm.name // e.g. value.s (for now just s)
        val fieldType = field.typeSignature.finalResultType // e.g. String

        val fieldCoderName = c.freshName(TermName("coder")) // e.g. give friendly name coder$...
        val fieldCoderInstance = // e.g. finds instance of Coder[String]
          c.typecheck(
            tree = q"""_root_.scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[${fieldType}]]""",
            silent = false
          )

        val fieldCoderExpression =
          q"private val ${fieldCoderName}: org.apache.beam.sdk.coders.Coder[${fieldType}] = ${fieldCoderInstance}"

        val fieldEncodeExpression =
          q"${fieldCoderName}.encode(value.${fieldTerm}, os)" // replace with full relative name (with dots) instead of value

        val fieldDecodeExpression =
          q"${field.asTerm} = ${fieldCoderName}.decode(is)"

        (fieldCoderExpression, fieldEncodeExpression, fieldDecodeExpression)
      }

    val fieldCodersExpression = expressions.map(_._1).distinct
    val coderEncodeExpresions = expressions.map(_._2)
    val coderDecodeExpresions = expressions.map(_._3)

    val coderExpression =
      q"""{
            new org.apache.beam.sdk.coders.Coder[${tpe}] {

              {import ${c.prefix}._}

              ..${fieldCodersExpression}

              override def encode(value: ${tpe}, os: java.io.OutputStream): _root_.scala.Unit = {
                ..${coderEncodeExpresions}
              }

              override def decode(is: java.io.InputStream): ${tpe} = {
                ${tpe.typeConstructor}(
                  ..${coderDecodeExpresions}
                )
              }

              override def getCoderArguments: java.util.List[_ <: org.apache.beam.sdk.coders.Coder[_]] = {
                java.util.Collections.emptyList
              }

              override def verifyDeterministic(): _root_.scala.Unit = ()
            }
          }
      """

    val ret = coderExpression
    c.Expr[Coder[P]](ret)
  }

但是调用后报错sbt Test / compile:

(在导入和隐式搜索方面有点挣扎,所以现在有中间 private vals,而 distinct 是没用的)

{
  final class $anon extends org.apache.beam.sdk.coders.Coder[com.github.fpopic.scalamacros.beam.Pojo] {
    def <init>() = {
      super.<init>();
      ()
    };
    {
      import DefMacroCoder._;
      ()
    };
    private val coder$macro: org.apache.beam.sdk.coders.Coder[String] = scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[String]](DefMacroCoder.stringCoder);
    private val coder$macro: org.apache.beam.sdk.coders.Coder[Int] = scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[Int]](DefMacroCoder.intCoder);
    private val coder$macro: org.apache.beam.sdk.coders.Coder[List[Int]] = scala.Predef.implicitly[org.apache.beam.sdk.coders.Coder[List[Int]]](DefMacroCoder.listCoder[Int](DefMacroCoder.intCoder));
    override def encode(value: com.github.fpopic.scalamacros.beam.Pojo, os: java.io.OutputStream): _root_.scala.Unit = {
      coder$macro.encode(value.s, os);
      coder$macro.encode(value.i, os);
      coder$macro.encode(value.l, os)
    };
    override def decode(is: java.io.InputStream): com.github.fpopic.scalamacros.beam.Pojo = com.github.fpopic.scalamacros.beam.Pojo(s = coder$macro.decode(is), i = coder$macro.decode(is), l = coder$macro.decode(is));
    override def getCoderArguments: java.util.List[_] forSome { 
      <synthetic> type _ <: org.apache.beam.sdk.coders.Coder[_] forSome { 
        <synthetic> type _
      }
    } = java.util.Collections.emptyList;
    override def verifyDeterministic(): _root_.scala.Unit = ()
  };
  new $anon()
}
[error] .../DefMacroCoderSpec.scala:17:56: com.github.fpopic.scalamacros.beam.Pojo does not take parameters
[error]     val coder: Coder[Pojo] = DefMacroCoder.productCoder[Pojo]
[error]                                                        ^
[error] one error found

我相信这来自 here 但不完全理解编译器试图告诉我什么?

Link 可以找到完整的代码示例 here
Link到CI错误可以找到here.

您实例化 class 的方式是错误的:

${tpe.typeConstructor}(...)

应该是

new $tpe(...)

或者如果你想用 case class 伴生对象的 apply 而不是普通构造函数来做到这一点:

${tpe.typeSymbol.companion}(...)

注意:类型构造函数(也称为更高级类型)与class构造函数[=无关14=]