scala macros companion-object

scala - Cree o amplíe un objeto complementario, utilizando una anotación de macro en la clase



macros companion-object (1)

Aquí está mi solución actual:

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(())))) } }

Prueba:

object Bar @mkCompanion class Bar @mkCompanion class Baz implicitly[Foo[Bar]] implicitly[Foo[Baz]]

Usando una macro macro de macroanálisis Scala 2.10 / 2.11, ¿cómo puedo agregar o extender el objeto complementario de una clase anotada? Esqueleto:

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] = ??? }

Tal que, dado

trait Foo[A]

la siguiente entrada

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

se expandirá como:

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

Aquí hay un primer intento ingenuo, simplemente agregando def hasFoo = 33 por ahora:

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(())))) }

Esto funciona cuando el objeto complementario ya existe:

object Foo @mkCompanion class Foo assert(Foo.hasFoo == 33)

Pero no cuando se crea:

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

Así que todavía tengo que averiguar cómo proporcionar el constructor del módulo ...