Можно ли заменить метод применения по умолчанию, сгенерированный для классов в макросе Scala?

Кажется, что это не работает (с использованием 2.11.1 и macro paradise 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 ответ

Проблема вызвана тем, что для компилятора код, сгенерированный аннотациями макросов, ничем не отличается от кода, написанного вручную. Если вы вручную напишите код, созданный макросом, представленным в примере, вы получите точно такую ​​же ошибку двойного определения, так что это не ошибка - это ограничение синтеза класса случая. К сожалению, синтез классов случаев не является расширяемым, поэтому это необходимо обойти.

Один из обходных путей, который я бы предложил, это стереть CASE флаг из модов класса, а затем макрос может быть полностью свободен в выборе членов для генерации. Это, однако, означает, что макрос должен будет генерировать весь код, который обычно генерируют классы case, что не будет очень приятным. Еще одна оговорка: компилятор обрабатывает сопоставление с образцом CASE классы, в частности, испуская несколько более эффективный код, так что такая эмуляция также потеряет некоторую производительность (я считаю, что потеря будет незначительной, и, возможно, даже не существующей с новым механизмом сопоставления с шаблоном на основе имен из Scala 2.11 - но это должно быть проверено).

Другие вопросы по тегам