检索Scala宏调用将分配给的值的名称

检索Scala宏调用将分配给的值的名称,scala,macros,scala-2.10,scala-macros,Scala,Macros,Scala 2.10,Scala Macros,我正在尝试编写一个宏,它将包装一个函数,并从其调用将分配给的值中扣除一个参数 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)

我正在尝试编写一个宏,它将包装一个函数,并从其调用将分配给的值中扣除一个参数

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没有宏天堂编译器插件。

这是非常可能的。我知道,因为我做过。诀窍是在封闭树中搜索其右侧与宏应用程序位置相同的值:

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
的名称。当然,如果您希望在运行时应用它,这也是可能的。

当我想简化一些属性初始化时,我遇到了类似的问题。因此,您的代码帮助我找到了这是如何实现的,但我收到了弃用警告。随着scala宏的发展,
enclosingClass
在scala 2.11中被弃用。要改为使用
c.internal.enclosuringowner
的状态。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}")
  }
}

我认为这是不可能的,至少对于标准的
def
宏是不可能的。宏只能检查和修改自己的调用。@ghik:它还知道自己的位置,并且可以检查其封闭类中的位置。有关详细信息,请参见下面的答案。@TravisBrown,没错,宏可以访问其封闭方法和类树。我完全错了。不知何故,这对我来说似乎有点太危险了。@TravisBrown同时,也有得到不连贯树的危险,因为非类型化=>类型化的转换可以极大地改变某些树的形状。在这种情况下,这并不重要,因为它只是一个ValDef的名称,但其他情况可能不那么宽容。@loutre3我理解这种观点,但请注意,宏天堂现在是一个编译器插件,因此您可以轻松地与vanilla Scala一起使用。至于宏注释,我个人保证它们在相当长的一段时间内不会出现在任何地方:。工作完全符合预期!我主要是想简化API的使用,因为这种情况经常发生,而且很快就会变得冗长。非常感谢。外壳现在已去润滑:(
asTerm
似乎不需要?
c.internal.外壳所有者
是一个符号。