#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
— в таком случае макрос будет видеть свое происхождение только как своего рода псевдоним, часть контекста, а не что-то, определенное локально с помощью aTree
.4. Конечно, функция ничего не знает о параметре, здесь
x
это часть контекста. Мне кажется, что остановка поиска по всей программе AST для объявления переменной является ограничением реализации системы макросов, а не ограничением в принципе. Макрос уже выполняет поиск по AST за пределами непосредственно переданного AST — в противномextractBody({val x = 1})
случае работал бы только.5. Да, это было сознательное решение, иначе вы могли бы столкнуться с циклическими зависимостями: вам, возможно, придется разрешить макрос, чтобы найти тип, который полагается на макрос и т. Д. А в нециклических случаях вам придется определить порядок, в котором будут выводиться типы-это уже сложно, и макросы дополнительно увеличивают эту сложность. Поэтому потребовалось некоторое сокращение, и авторы языка решили, что в таких случаях, как ваш, не отслеживать деревья внешних идентификаторов-разумный компромисс.