Ответ 1
Разборка входных выражений и их повторная сборка по-другому - важный сценарий в макрологии (это то, что мы делаем внутри макроса reify
). Но, к сожалению, на данный момент это не особенно легко.
Проблема заключается в том, что входные аргументы макропроцессорной реализации макросов уже проверяются typechecked. Это и благословение, и проклятие.
Особый интерес для нас представляет тот факт, что переменные привязки в деревьях, соответствующие аргументам, уже установлены. Это означает, что все узлы Ident
и Select
имеют заполненные поля sym
, указывая на определения, к которым относятся эти узлы.
Вот пример того, как работают символы. Я скопирую/вставляю распечатку с одного из моих разговоров (я не даю ссылку здесь, потому что большая часть информации на моих переговорах устарела до сих пор, но эта конкретная распечатка имеет вечную полезность):
>cat Foo.scala
def foo[T: TypeTag](x: Any) = x.asInstanceOf[T]
foo[Long](42)
>scalac -Xprint:typer -uniqid Foo.scala
[[syntax trees at end of typer]]// Scala source: Foo.scala
def foo#8339
[T#8340 >: Nothing#4658 <: Any#4657]
(x#9529: Any#4657)
(implicit evidence$1#9530: TypeTag#7861[T#8341])
: T#8340 =
x#9529.asInstanceOf#6023[T#8341];
Test#14.this.foo#8339[Long#1641](42)(scala#29.reflect#2514.`package`#3414.mirror#3463.TypeTag#10351.Long#10361)
Напомним, мы пишем небольшой фрагмент, а затем скомпилируем его с помощью scalac, попросив компилятор выгрузить деревья после фазы typer, распечатав уникальные идентификаторы символов, назначенных деревьям (если они есть).
В полученной распечатке мы видим, что идентификаторы связаны с соответствующими определениями. Например, с одной стороны, ValDef("x", ...)
, представляющий параметр метода foo, определяет символ метода с id = 9529. С другой стороны, Ident("x")
в теле метода получил поле sym
, установленное на тот же символ, который устанавливает привязку.
Хорошо, мы видели, как привязки работают в scalac, и теперь самое время представить фундаментальный факт.
If a symbol has been assigned to an AST node,
then subsequent typechecks will never reassign it.
Вот почему reify является гигиеничным. Вы можете взять результат reify и вставить его в произвольное дерево (которое, возможно, определяет переменные с конфликтующими именами) - исходные привязки останутся неповрежденными. Это работает, потому что reify сохраняет исходные символы, поэтому последующие typechecks не будут перепечатывать восстановленные узлы AST.
Теперь мы все настроены на объяснение ошибки, с которой вы сталкиваетесь:
symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details.
Аргумент макроса transform
содержит как определение, так и ссылку на переменную x
. Как мы только что узнали, это означает, что соответствующие ValDef и Ident будут синхронизированы с полями sym
. Пока что так хорошо.
Однако, к сожалению, макрос искажает установленную привязку. Он воссоздает ValDef, но не очищает поле sym
соответствующего идентификатора. Последующая typecheck присваивает новый символ вновь созданному ValDef, но не затрагивает оригинальный Иден, который скопирован в результирующий результат.
После typecheck исходный идентификатор указывает на символ, который больше не существует (это именно то, что говорило сообщение об ошибке:)), что приводит к сбою во время генерации байт-кода.
Итак, как мы исправим ошибку? К сожалению, нет простого ответа.
Одним из вариантов было бы использовать c.resetLocalAttrs
, который рекурсивно стирает все символы в данном AST node. Последующая typecheck затем восстановит привязки, предоставленные тем, что созданный вами код не возится с ними (если, например, вы переносите xExp в блок, который сам определяет значение с именем x, тогда у вас проблемы).
Другой вариант - играть с символами. Например, вы можете написать свой собственный resetLocalAttrs
, который удаляет только поврежденные привязки и не затрагивает действительные. Вы могли бы также попытаться назначить символы самостоятельно, но это короткий путь к безумию, хотя иногда один из них вынужден ходить по нему.
Не круто, согласен. Мы это осознаем и намерены иногда исправить эту фундаментальную проблему. Однако сейчас наши руки заполнены исправлением ошибок до окончательной версии 2.10.0, поэтому мы не сможем решить проблему в ближайшем будущем. UPD. Для получения дополнительной информации см. https://groups.google.com/forum/#!topic/scala-internals/rIyJ4yHdPDU.
Нижняя линия. Плохие вещи случаются, потому что привязки перепутаны. Сначала попробуйте resetLocalAttrs, и если это не сработает, подготовьтесь к тяжелой работе.