2

我正在尝试为案例类编写一个简单的宏注释,它向伴随对象添加一个方法。问题是新方法必须考虑带注释的案例类的类型参数。

这是我需要通过的测试

package my.macros

import org.scalatest._

class DefaultApplyTest extends FlatSpec with Matchers {

  @defaultApply case class Generic[A, B](a: A, b: B)

  it should "define defaultApply method in companion object" in {
    assert(Generic.defaultApply("str", 1) == Generic("str", 1))
  }
}

这是我为完成此任务而编写的代码

package my.macros

import scala.reflect.macros._
import scala.language.experimental.macros
import scala.annotation.StaticAnnotation

class defaultApply extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro DefaultApply.impl
}

object DefaultApply {

  def impl(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._

    def defaultApplyCompanion(classDecl: ClassDef) = {
      val (name, typeParams, valueParams) = try {
        val q"case class ${name: TypeName}[..${typeParams: Seq[TypeDef]}](..${valueParams: Seq[ValDef]}) extends ..$bases { ..$body }" = classDecl
        (name, typeParams, valueParams)
      } catch {
        case e: MatchError =>
          c.warning(c.enclosingPosition, e.toString)
          c.abort(c.enclosingPosition, "Annotation is only supported on case class")
      }

      val applyDef = q"""${name.toTermName}.apply[..$typeParams]"""
      c.warning(c.enclosingPosition, showRaw(applyDef))

      q"""
        object ${name.toTermName} {
          def defaultApply: (..${valueParams.map(_.tpt)}) => $name[..$typeParams] = $applyDef
        }
      """
    }

    def modifiedDeclaration(classDecl: ClassDef) = {
      val compDecl = defaultApplyCompanion(classDecl)

      c.Expr(q"""
        $classDecl
        $compDecl
      """)
    }

    annottees.map(_.tree) match {
      case (classDecl: ClassDef) :: Nil => modifiedDeclaration(classDecl)
      case _ => c.abort(c.enclosingPosition, "Invalid annottee")
    }
  }
}

据我了解的问题是,当我尝试类型参数列表提升到生成的语法树中时,它们不会被识别为与原始树中相同的类型参数。

所以我关注的是宏的这一部分

  val applyDef = q"""${name.toTermName}.apply[..$typeParams]"""
  c.warning(c.enclosingPosition, showRaw(applyDef))

原始语法树发出为

TypeApply(Select(Ident(TermName("Generic")), TermName("apply")), List(TypeDef(Modifiers(PARAM), TypeName("A"), List(), TypeBoundsTree(EmptyTree, EmptyTree)), TypeDef(Modifiers(PARAM), TypeName("B"), List(), TypeBoundsTree(EmptyTree, EmptyTree))))

但编译器对此不满意

type arguments [<notype>,<notype>] do not conform to method apply's type parameter bounds [A,B]

最终用例是生成涉及超过 1k 行代码的可缓存类型类的实例。非参数化版本已经可以工作了,这只是锦上添花。scalac 的底层有些东西我不明白,但我想。非常感谢您花时间阅读本文。

我正在使用带有宏天堂 2.1.0的 Scala 2.11.8

4

1 回答 1

0

The problem seems to be that you're using type parameters in the place of type arguments. This seems to work (I also had to add the type parameters to the defaultApply method declaration):

  val typeArgs = typeParams.map(_.name)
  val applyDef = q"""${name.toTermName}.apply[..$typeArgs]"""
  c.warning(c.enclosingPosition, showRaw(applyDef))

  q"""
    object ${name.toTermName} {
      def defaultApply[..$typeParams]:
          (..${valueParams.map(_.tpt)}) => $name[..$typeArgs] = $applyDef
    }
  """
于 2016-07-06T21:55:59.803 回答