2013-08-26 5 views
2

Я пытаюсь написать макрос, который бы обернул функцию и вычитал параметр из значения, которому будет назначен его вызов.Получить имя значения a Макро-вызов Scala будет назначен

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

Я не думаю, что это возможно, по крайней мере, не со стандартными 'def' макросов. Макро может только проверять и изменять свой собственный вызов. – ghik

+0

@ghik: Он также знает свое собственное положение и может проверять позиции в своем классе. См. Мой ответ ниже для деталей. –

+0

@TravisBrown Ugh, это правда, макрос имеет доступ к его окружающему методу и деревьям классов. Я был не прав. Каким-то образом это показалось мне слишком опасным. – ghik

ответ

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 при компиляции -time, так как вы знаете имя val во время компиляции. Если вы хотите, чтобы его применяли во время выполнения, что также было бы возможно, конечно.

+0

Работает точно так, как ожидалось! В основном я пытался упростить использование API, так как эта ситуация часто возникала, и была очень быстро сделана.Спасибо! – AlexBergeron

1

У меня была аналогичная проблема, когда я хотел упростить некоторые инициализации свойств. Таким образом, ваш код помог мне узнать, как это возможно, но я получил предупреждения об устаревании. По мере развития макросов 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}") 
    } 
} 
+0

'asTerm' похоже не нужно? 'c.internal.enclosingOwner' является символом. – maow