Ответ 1
Проблема в том, что код, который вы передаете в reify
, по существу, будет размещен дословно в точке, где макрос расширяется, а fieldMemberType
ничего не будет означать.
В некоторых случаях вы можете использовать splice
, чтобы скрывать выражение, которое у вас есть в момент макрорасширения, в код, который вы обновляете. Например, если мы пытаемся создать экземпляр этого признака:
trait Foo { def i: Int }
И эта переменная имела время макрорасширения:
val myInt = 10
Мы могли бы написать следующее:
reify { new Foo { def i = c.literal(myInt).splice } }
Это не будет работать здесь, а это значит, что вам придется забыть о хорошем маленьком reify
и выписать АСТ вручную. Вы обнаружите, что это случается очень много, к сожалению. Мой стандартный подход - начать новый REPL и набрать что-то вроде этого:
import scala.reflect.runtime.universe._
trait TypeBuilder { type fieldType }
showRaw(reify(new TypeBuilder { type fieldType = String }))
Это выплюнет несколько строк AST, которые вы можете вырезать и вставить в определение макроса в качестве отправной точки. Затем вы играете с ним, заменяя такие вещи:
Ident(TypeBuilder)
При этом:
Ident(newTypeName("TypeBuilder"))
И FINAL
с Flag.FINAL
и т.д. Я бы хотел, чтобы методы toString
для типов AST более точно соответствовали коду, который требуется для их создания, но вы довольно быстро получите представление о том, что вам нужно изменить. Вы получите что-то вроде этого:
c.Expr(
Block(
ClassDef(
Modifiers(Flag.FINAL),
anon,
Nil,
Template(
Ident(newTypeName("TypeBuilder")) :: Nil,
emptyValDef,
List(
constructor(c),
TypeDef(
Modifiers(),
newTypeName("fieldType"),
Nil,
TypeTree(fieldMemberType)
)
)
)
),
Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
)
)
Где anon
- это имя типа, которое вы создали заранее для своего анонимного класса, а constructor
- это метод удобства, который я использую, чтобы сделать такое действие менее отвратительным (вы можете найти его определение на end этот полный рабочий пример).
Теперь, если мы обернем это выражение чем-то вроде this, мы можем написать следующее:
scala> TypeMemberExample.builderWithType[String]
res0: TypeBuilder{type fieldType = String} = [email protected]
Так оно и работает. Мы взяли c.universe.Type
(который я получаю здесь из WeakTypeTag
параметра типа на builderWithType
, но он будет работать точно так же, как и любой старый Type
), и использовал его для определения типа член нашей черты TypeBuilder
.