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

Похоже, это не работает (используя 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 ^


person rjw    schedule 16.08.2014    source источник


Ответы (1)


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

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

person Eugene Burmako    schedule 16.08.2014