Как получить тело инициализации переменной из внешней области в макросах Scala 3?

#scala #macros #scala-macros #scala-3

Вопрос:

Предположим, у меня есть этот код для извлечения кода, инициализирующего переменную:

 def extractBodyImpl[T: Type](expr: Expr[T])(using Quotes) =  import quotes.reflect._  expr.asTerm.underlyingArgument match  case ident @ Ident(_) =gt;  ident.symbol.tree match  case ValDef(_,_,rhs) =gt; println(rhs)  case DefDef(_,_,_,rhs) =gt; println(rhs)  '{ () }  inline def extractBody[T](inline expr: T) = ${ extractBodyImpl('expr) }  

При вызове переменной, объявленной в той же области, она работает так, как требуется:

 @main def hello() =  val x = 1  extractBody(x)  

С принтами Some(Literal(Constant(1))) .

Однако в переменной из внешней области он выводит None :

 val x = 1 @main def hello() =  extractBody(x)  

Как я могу заставить это работать во втором случае?

Ответ №1:

Вы не можете сделать это в макросе. Функция, получившая аргумент, могла быть вызвана отовсюду. Как статический анализ будет получать доступ к информации, доступной только во время выполнения? Единственным надежным решением было бы заставить пользователя развернуть этот extractBody макрос сразу после определения значения и передачи результата в некоторой оболочке, объединяющей как значение, так и его происхождение.

Комментарии:

1. Спасибо за ваш ответ. Какая информация в моем примере недоступна во время компиляции? Чтобы быть ясным, я не прошу способа извлечь какой-либо возможный символ, что, конечно, было бы невозможно (например, символ, определенный вне кодовой базы). Просто символ из одной области в моей кодовой базе

2. Макрос (упрощающий) получает доступ к информации из AST, включая типы и области действия. Начало функции-это горизонт того, что она знает: где вызывается эта функция, неизвестно во время компиляции, поэтому недоступно для макросов. Если вы это сделаете val x = 1; foo(x) , foo знает только то, что он получил некоторое значение известного типа в качестве параметра. Откуда взялось это значение и как оно появилось — оно неизвестно внутри функции и вообще не может быть известно.

3. Я считаю, что здесь, когда у вас есть x функция, она воспринимается как нечто, переданное из внешней области, где x может рассматриваться, например, как псевдоним _root_.x — в таком случае макрос будет видеть свое происхождение только как своего рода псевдоним, часть контекста, а не что-то, определенное локально с помощью a Tree .

4. Конечно, функция ничего не знает о параметре, здесь x это часть контекста. Мне кажется, что остановка поиска по всей программе AST для объявления переменной является ограничением реализации системы макросов, а не ограничением в принципе. Макрос уже выполняет поиск по AST за пределами непосредственно переданного AST — в противном extractBody({val x = 1}) случае работал бы только.

5. Да, это было сознательное решение, иначе вы могли бы столкнуться с циклическими зависимостями: вам, возможно, придется разрешить макрос, чтобы найти тип, который полагается на макрос и т. Д. А в нециклических случаях вам придется определить порядок, в котором будут выводиться типы-это уже сложно, и макросы дополнительно увеличивают эту сложность. Поэтому потребовалось некоторое сокращение, и авторы языка решили, что в таких случаях, как ваш, не отслеживать деревья внешних идентификаторов-разумный компромисс.