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

前端 未结 2 1451
广开言路
广开言路 2021-01-22 19:15

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 {
  de         


        
相关标签:
2条回答
  • 2021-01-22 19:43

    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.

    0 讨论(0)
  • 2021-01-22 19:47

    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}")
      }
    }
    
    0 讨论(0)
提交回复
热议问题