我正在尝试编写一个宏,它将包装一个函数并从其调用的值中扣除一个参数。
object TestMacros {
def foo(name: String): String = name.toUpper
def bar = macro barImpl
def barImpl(c: Context): c.Expr[String] = {
import c.universe._
//TODO extract value name (should be baz)
c.Expr[String](Apply(
Select(newTermName("TestMacros"), newTermName("foo")), // Probably wrong, just typed it quickly for demonstration purposes
List(Literal(Constant("test"))))) // Should replace test by value name
}
}
object TestUsage {
val baz = bar // should be BAZ
}
我不知道这是否足够清楚。我已经调查了c.prefix和c.macroApplication而没有成功。我正在使用没有宏天堂编译器插件的Scala 2.10.2。
答案 0 :(得分:5)
这很有可能。我知道,因为我已经完成了something like it before。诀窍是在封闭树中搜索右侧与宏应用程序具有相同位置的值:
import scala.language.experimental.macros
import scala.reflect.macros.Context
object TestMacros {
def foo(name: String): String = name.toUpperCase
def bar = macro barImpl
def barImpl(c: Context): c.Expr[String] = {
import c.universe._
c.enclosingClass.collect {
case ValDef(_, name, _, rhs)
if rhs.pos == c.macroApplication.pos => c.literal(foo(name.decoded))
}.headOption.getOrElse(
c.abort(c.enclosingPosition, "Not a valid application.")
)
}
}
然后:
scala> object TestUsage { val baz = TestMacros.bar }
defined module TestUsage
scala> TestUsage.baz
res0: String = BAZ
scala> class TestClassUsage { val zab = TestMacros.bar }
defined class TestClassUsage
scala> (new TestClassUsage).zab
res1: String = ZAB
请注意,您可以在编译时应用foo
,因为您在编译时知道val
的名称。如果你想在运行时应用它,那当然也是可能的。
答案 1 :(得分:2)
当我想简化一些属性初始化时,我遇到了类似的问题。所以你的代码帮助我找出了可行的方法,但我得到了弃用警告。随着scala宏的发展,enclosingClass
在Scala 2.11中被弃用了。 documentation状态代替使用c.internal.enclosingOwner
。 quasiquotes功能使事情变得更容易 - 我的样本只检索val baz = TestMacros.getName
中的名称,如下所示:
import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context
object TestMacros {
def getName(): String = macro getNameImpl
def getNameImpl(c: Context)() = {
import c.universe._
val term = c.internal.enclosingOwner.asTerm
val name = term.name.decodedName.toString
// alternatively use term.fullName to get package+class+value
c.Expr(q"${name}")
}
}