是否可以替换为Scala宏中的案例类生成的默认apply方法?

时间:2014-08-16 00:36:33

标签: scala scala-macros case-class scala-macro-paradise

似乎这不起作用(使用2.11.1和宏天堂2.0.1)。 我希望case类生成的方法要么被抑制,要么在树中,所以我可以摆脱它。这是一个很难的限制吗?

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

object EvisMacro {

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

    def makeApply(tpName: TypeName, parents: List[Tree], params: List[List[ValDef]] ) : List[Tree]= {
      List(q"""def apply(...$params): $tpName = null""")
    }

    val result = annottees map (_.tree) match {
      case (classDef @ q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }")
        :: Nil if mods.hasFlag(Flag.CASE) =>
        c.info(c.enclosingPosition,  s"Eviscerating $tpname !($mods, $parents, $paramss)", true)

        parents match {
          case q"${pname: TypeName}" :: rest =>
            c.info(c.enclosingPosition, s"${pname.decodedName}", true )
            val sc = c.universe.rootMirror.staticClass( pname.decodedName.toString  )
            c.info(c.enclosingPosition, s"${sc}", true )
        }

        val name = tpname.toTermName
        q"""
        $classDef
        object $name {
          ..${makeApply(tpname, parents, paramss)}
        }
        """
      case (classDef @ q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }")
        :: q"object $objName {..$objDefs}"
        :: Nil if mods.hasFlag(Flag.CASE) =>
        q"""
        $classDef
         object $objName {
           ..${makeApply(tpname, parents, paramss)}
           ..$objDefs
         }
         """
      case _ => c.abort(c.enclosingPosition, "Invalid annotation target: must be a case class")
    }
    c.Expr[Any](result)
  }

}

使用它:

trait Thing
@evis
case class Trade(id: Long, notional: Long, comment: String) extends Thing
@evis
case class Pop(name: String) extends Thing

object Pop{

}

object TestTrade extends App{

  val t = Trade (1, 1, "")
  val p : Pop = Pop("")

  println(t)

}

结果:

错误:(2,2)方法apply被定义两次   冲突的符号都起源于文件' core / src / main / scala / Test.scala' @evis  ^

1 个答案:

答案 0 :(得分:3)

问题是由于这样一个事实,即对于编译器,宏注释生成的代码与手工编写的代码没有任何不同。如果您手动编写示例中提供的宏生成的代码,您将获得完全相同的双定义错误,因此它不是错误 - 它是案例类合成的限制。不幸的是,案例类合成是不可扩展的,因此需要解决这个问题。

我建议的一个解决方法是从类的mod中删除CASE标志,然后宏可以完全自由地选择要生成的成员。但是,这意味着宏必须生成案例类通常生成的所有代码,这不会非常令人愉快。另一个需要注意的是,编译器通过发出更高效的代码来处理CASE类上的模式匹配,因此这样的仿真也会失去一些性能(我认为损失将是微不足道的,甚至可能是非 - 与Scala 2.11中基于名称的新模式匹配机制一致 - 但需要对其进行测试。)