2

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.

AlexBergeron
  • 152
  • 1
  • 6
  • I don't think this is possible, at least not with standard `def` macros. Macro can only inspect and modify its own invocation. – ghik Aug 26 '13 at 18:35
  • @ghik: It also knows its own position and can inspect positions in its enclosing class. See my answer below for the details. – Travis Brown Aug 26 '13 at 19:02
  • @TravisBrown Ugh, that's true, macro has access to its enclosing method and class trees. I was plain wrong. Somehow this seemed a bit too dangerous for me. – ghik Aug 26 '13 at 19:28
  • They are indeed a bit dangerous, because: 1) internally, sometimes enclosing trees get omitted from the typer context chain, potentially leading to distorted results of c.enclosingXXX (I have no concrete counter-example, but I can't guarantee that there's none), 2) c.enclosingXXX methods don't guarantee that their results will be typed, which can lead to a number of unpleasant effects. – Eugene Burmako Aug 26 '13 at 20:35
  • I wonder whether your use case can be reformulated with macro annotations. – Eugene Burmako Aug 26 '13 at 20:35
  • @EugeneBurmako: My understanding is that the "danger" is mitigated by the fact that if we can't find the value definition we're looking for in the enclosing class's tree, we just fail gracefully at compile-time. This may be disappointing but the unpleasantness doesn't leak very far. – Travis Brown Aug 26 '13 at 21:03
  • @EugeneBurmako: If I understand well, I see how this can be dangerous, however, for my current use-case (and what we had before), I still prefer something that fails compile-time than run-time. I haven't taken a good look at macro annotations yet, so I don't really know how it can be applied to it (the fact that it's in macro-paradise kind of scares me more than standard macros). – AlexBergeron Aug 26 '13 at 22:00
  • 2
    @TravisBrown Also, there's danger of getting incoherent trees, because the untyped => typed transition can change the shape of certain trees wildly. In this case it doesn't matter much, because it's just a name of a ValDef, but other scenarios might be less forgiving. – Eugene Burmako Aug 27 '13 at 05:19
  • 1
    @loutre3 I understand this sentiment, but please note that macro paradise is now a compiler plugin, so you can easily use with vanilla Scala. As for macro annotations, there's my personal guarantee that they are not going anywhere for quite a long period of time: http://scalamacros.org/news/2013/08/07/roadmap-for-macro-paradise.html. – Eugene Burmako Aug 27 '13 at 05:21

2 Answers2

5

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.

Travis Brown
  • 138,631
  • 12
  • 375
  • 680
3

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}")
  }
}
michael_s
  • 2,515
  • 18
  • 24