Problem polega na tym, że kod przekazywany pod numer reify
jest zasadniczo umieszczany dosłownie w punkcie, w którym makro jest rozwijane, a fieldMemberType
nic tam nie znaczy.
W niektórych przypadkach można użyć splice
, aby wkraść wyrażenie, które masz w czasie ekspansji makr do kodu, który korygujesz. Na przykład, jeśli staraliśmy się stworzyć instancję tej cechy:
trait Foo { def i: Int }
i miał tę zmienną w czasie makro-rozbudowa:
val myInt = 10
Moglibyśmy napisać następujący:
reify { new Foo { def i = c.literal(myInt).splice } }
To nie zadziała tutaj, co oznacza, że będziesz musiał zapomnieć o miłym małym reify
i napisać AST ręcznie. Niestety często się to zdarza. Moja średnia podejście jest, aby rozpocząć nową rEPL i wpisać coś takiego:
import scala.reflect.runtime.universe._
trait TypeBuilder { type fieldType }
showRaw(reify(new TypeBuilder { type fieldType = String }))
Będzie to wypluć kilka linii AST, które można następnie wyciąć i wkleić do swojej definicji makra jako punkt wyjścia. Potem bawić się z nim, zastępując rzeczy tak:
Ident(TypeBuilder)
z tym:
Ident(newTypeName("TypeBuilder"))
And FINAL
z Flag.FINAL
, i tak dalej. Chciałbym, aby metody typu AST odpowiadały dokładniej kodowi potrzebnemu do ich zbudowania, ale szybko zorientujesz się, co musisz zmienić. Będziesz skończyć z czymś takim:
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)
)
)
Gdzie anon
jest już utworzony z góry za anonimową klasy nazwą typu i constructor
jest metoda wygoda używam do tego typu rzeczy trochę mniej odrażający (możesz znaleźć jego definicję pod koniec this complete working example).
Teraz jeśli to wyrażenie owinąć w coś jak this, możemy napisać następujące:
scala> TypeMemberExample.builderWithType[String]
res0: TypeBuilder{type fieldType = String} = [email protected]
Tak to działa.Wzięliśmy c.universe.Type
(który dostaję tutaj z WeakTypeTag
parametru typu na builderWithType
, ale będzie działać dokładnie w ten sam sposób z każdym starym Type
) i użył go do zdefiniowania elementu typu naszej cechy TypeBuilder
.
Tylko jedno pytanie, w jaki sposób "newTypeName" znajduje typ? Chodzi mi o to, skąd on wie, w jakim to jest pakiecie? – mgonto
Dzięki! Natomiast 'newTypeName' nie wyszukuje samego typu - po prostu kopiuje napis, który podajesz do kodu generowanego przez makro, który zostanie normalnie sprawdzony przez kompilator. Jeśli chcesz określić pakiet, możesz napisać np. 'Wybierz (Ident (" pakiet "), newTypeName (" MyClass "))'. –
Możesz użyć '-Yreify-copypaste' i/lub' showRaw', aby uzyskać kod budujący fragmenty kodu, które Cię interesują. –