Retrieve the name of the value a Scala macro invocation will be assigned to

一世执手 提交于 2019-12-24 00:21:26

问题


I'm attempting to write a macro that would wrap a function and deducting a parameter from the value its invocation will be assigned to.

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
}

I don't know if this is clear enough. I've investigated both c.prefix and c.macroApplication without success. I'm using Scala 2.10.2 without the macro-paradise compiler plugin.


回答1:


This is very possible. I know, because I've done something like it before. The trick is to search the enclosing tree for a value whose right-hand side has the same position as the macro application:

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.")
    )
  }
}

And then:

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

Note that you can apply foo at compile-time, since you know the name of the val at compile-time. If you wanted it to be applied at runtime that would also be possible, of course.




回答2:


I had a similar problem when I wanted to simplify some property initializations. So your code helped me to find out how that is possible, but I got deprecation warnings. As scala macros evolve the enclosingClass got deprecated in Scala 2.11. The documentation states to use c.internal.enclosingOwner instead. The quasiquotes feature makes things easier now - my sample to retrieve just the name as in val baz = TestMacros.getName looks like this:

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


来源:https://stackoverflow.com/questions/18450203/retrieve-the-name-of-the-value-a-scala-macro-invocation-will-be-assigned-to

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!