Создайте или расширьте сопутствующий объект, используя аннотацию макроса в классе.

Используя макрос аннотации макросов Scala 2.10/2.11, как я могу добавить или расширить объект-компаньон аннотированного класса? Скелет:

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

class foo extends StaticAnnotation {
  def macroTransform(annottees: Any*) = macro fooMacro.impl
}
object fooMacro {
  def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = ???
}

Такой, что, учитывая

trait Foo[A]

следующий ввод

@foo class Bar

object Baz {
  def baz = 33
}
@foo class Baz

будет расширен как:

object Bar {
  implicit def hasFoo: Foo[Bar] = ???
}
class Bar

object Baz {
  def baz = 33

  implicit def hasFoo: Foo[Baz] = ???
}
class Baz

Вот первая наивная попытка, пока просто добавляем def hasFoo = 33:

def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
  import c.universe._
  val inputs : List[Tree] = annottees.map(_.tree)(collection.breakOut)
  val outputs: List[Tree] = inputs match {
    case (cd @ ClassDef(_, cName, _, _)) :: tail =>
      val mod0: ModuleDef = tail match {
        case (md @ ModuleDef(_, mName, _)) :: Nil
             if cName.decoded == mName.decoded => md
        case Nil =>
          val cMod = cd.mods
          var mModF = NoFlags
          if (cMod hasFlag Flag.PRIVATE  ) mModF |= Flag.PRIVATE
          if (cMod hasFlag Flag.PROTECTED) mModF |= Flag.PROTECTED
          if (cMod hasFlag Flag.LOCAL    ) mModF |= Flag.LOCAL
          val mMod = Modifiers(mModF, cMod.privateWithin, Nil)
          // or should we have parents = List(AnyRef) and body = List(DefDef(???))
          val mTemp = Template(parents = Nil, self = noSelfType, body = Nil)
          val mName = TermName(cName.decoded) // or encoded?
          ModuleDef(mMod, mName, mTemp)
        case _ => c.abort(c.enclosingPosition, "Expected a companion object")
      }
      val Template(mTempParents, mTempSelf, mTempBody0) = mod0.impl
      val fooDef = DefDef(NoMods, TermName("hasFoo"), Nil, Nil, 
        TypeTree(typeOf[Int]), Literal(Constant(33)))
      val mTempBody1 = fooDef :: mTempBody0
      val mTemp1 = Template(mTempParents, mTempSelf, mTempBody1)
      val mod1 = ModuleDef(mod0.mods, mod0.name, mTemp1)
      cd :: mod1 :: Nil

    case _ => c.abort(c.enclosingPosition, "Must annotate a class or trait")
  }
  c.Expr[Any](Block(outputs, Literal(Constant(()))))
}

Это работает, когда объект-компаньон уже существует:

object Foo
@mkCompanion class Foo

assert(Foo.hasFoo == 33)

Но не тогда, когда он создан:

@mkCompanion class Foo

[error] no constructor in template: impl = Object {
[error]   def hasFoo(): Int = 33
[error] }

Так что мне все еще нужно выяснить, как предоставить конструктор модуля...


person 0__    schedule 09.01.2014    source источник
comment
Следующая проблема заключается в получении типа cd   -  person 0__    schedule 10.01.2014
comment
Да, вам нужно создать конструктор вручную.   -  person Eugene Burmako    schedule 14.01.2014


Ответы (1)


Вот мое текущее решение:

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

trait Foo[A]

class mkCompanion extends StaticAnnotation {
  def macroTransform(annottees: Any*) = macro mkCompanionMacro.impl
}
object mkCompanionMacro {
  def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._

    val inputs : List[Tree] = annottees.map(_.tree)(collection.breakOut)
    val outputs: List[Tree] = inputs match {
      case (cd @ ClassDef(_, cName, _, _)) :: tail =>
        val mod0: ModuleDef = tail match {
          case (md @ ModuleDef(_, mName, mTemp)) :: Nil 
               if cName.decoded == mName.decoded => md

          case Nil =>
            val cMod  = cd.mods
            var mModF = NoFlags
            if (cMod hasFlag Flag.PRIVATE  ) mModF |= Flag.PRIVATE
            if (cMod hasFlag Flag.PROTECTED) mModF |= Flag.PROTECTED
            if (cMod hasFlag Flag.LOCAL    ) mModF |= Flag.LOCAL
            val mMod = Modifiers(mModF, cMod.privateWithin, Nil)

            // XXX TODO: isn't there a shortcut for creating the constructor?
            val mkSuperSelect = Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), 
                                       nme.CONSTRUCTOR)
            val superCall     = Apply(mkSuperSelect, Nil)
            val constr        = DefDef(NoMods, nme.CONSTRUCTOR, Nil, List(Nil), 
              TypeTree(), Block(List(superCall), Literal(Constant())))

            val mTemp = Template(parents = List(TypeTree(typeOf[AnyRef])), 
              self = noSelfType, body = constr :: Nil)
            val mName = TermName(cName.decoded) // or encoded?

            ModuleDef(mMod, mName, mTemp)

          case _ => c.abort(c.enclosingPosition, "Expected a companion object")
        }

        val Template(mTempParents, mTempSelf, mTempBody0) = mod0.impl

        // cf. http://stackoverflow.com/questions/21044957/type-of-a-macro-annottee
        val cTpe        = Ident(TypeName(cd.name.decoded))
        val fooName     = TermName("hasFoo")
        val fooDef      = q"implicit def $fooName: Foo[$cTpe] = ???"
        val mTempBody1  = fooDef :: mTempBody0
        val mTemp1      = Template(mTempParents, mTempSelf, mTempBody1)
        val mod1        = ModuleDef(mod0.mods, mod0.name, mTemp1)

        cd :: mod1 :: Nil

      case _ => c.abort(c.enclosingPosition, "Must annotate a class or trait")
    }

    c.Expr[Any](Block(outputs, Literal(Constant(()))))
  }
}

Тестовое задание:

object Bar
@mkCompanion class Bar

@mkCompanion class Baz

implicitly[Foo[Bar]]
implicitly[Foo[Baz]]
person 0__    schedule 11.01.2014
comment
Вы смотрели на квазикотировки? Они выполняют за вас большую часть тяжелой работы по созданию внутренних представлений дерева. Я, например, уже давно перестал строить деревья вручную и делаю это только при взломе компилятора, где мы пока не можем использовать qqs :) - person Eugene Burmako; 14.01.2014
comment
@EugeneBurmako - да, я хотел. Я еще не нашел времени для глубокого погружения, чтобы понять, как они на самом деле работают в деталях. Впрочем, деревья мне не так уж неудобны. - person 0__; 14.01.2014