Vấn đề là mã bạn chuyển đến reify
về cơ bản sẽ được đặt đúng nguyên văn tại thời điểm macro đang được mở rộng và fieldMemberType
sẽ không có ý nghĩa gì ở đó.
Trong một số trường hợp, bạn có thể sử dụng splice
để ẩn một biểu thức mà bạn có thời gian mở rộng macro thành mã mà bạn đang xác minh lại. Ví dụ, nếu chúng ta đã cố gắng để tạo ra một thể hiện của đặc điểm này:
trait Foo { def i: Int }
Và đã biến này lúc vĩ mô mở rộng:
val myInt = 10
Chúng ta có thể viết như sau:
reify { new Foo { def i = c.literal(myInt).splice } }
Điều đó sẽ không hoạt động ở đây, có nghĩa là bạn sẽ phải quên đi một chút đẹp reify
và viết ra AST bằng tay. Bạn sẽ thấy điều này xảy ra rất nhiều, thật không may. tiếp cận tiêu chuẩn của tôi là để bắt đầu một REPL mới và gõ một cái gì đó như thế này:
import scala.reflect.runtime.universe._
trait TypeBuilder { type fieldType }
showRaw(reify(new TypeBuilder { type fieldType = String }))
này sẽ nhổ ra một vài dòng AST, sau đó bạn có thể cắt và dán vào định nghĩa macro của bạn như là một điểm khởi đầu. Sau đó, bạn fiddle với nó, thay thế cho những thứ như thế này:
Ident(TypeBuilder)
Với điều này:
Ident(newTypeName("TypeBuilder"))
Và FINAL
với Flag.FINAL
, và vân vân. Tôi muốn các phương thức toString
cho các kiểu AST tương ứng chính xác hơn với mã cần thiết để xây dựng chúng, nhưng bạn sẽ nhanh chóng hiểu được những gì bạn cần thay đổi. Bạn sẽ kết thúc với một cái gì đó như thế này:
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)
)
)
đâu anon
là một cái tên kiểu bạn đã tạo trước cho lớp ẩn danh của bạn, và constructor
là một phương pháp thuận tiện tôi sử dụng để làm loại điều một chút ít ghê tởm hơn (bạn có thể tìm thấy định nghĩa của nó ở cuối this complete working example).
Bây giờ nếu chúng ta quấn biểu thức này trong một cái gì đó giống như this, chúng ta có thể viết như sau:
scala> TypeMemberExample.builderWithType[String]
res0: TypeBuilder{type fieldType = String} = [email protected]
Vì vậy, nó hoạt động.Chúng tôi đã chụp c.universe.Type
(mà tôi nhận được ở đây từ WeakTypeTag
của thông số loại trên builderWithType
, nhưng nó sẽ hoạt động theo cách tương tự với bất kỳ số Type
cũ nào) và sử dụng nó để xác định loại thành viên của đặc điểm TypeBuilder
của chúng tôi.
Chỉ một câu hỏi, cách "newTypeName" tìm thấy Loại? Tôi có nghĩa là làm thế nào nó biết trong đó gói này là? – mgonto
Cảm ơn! Và 'newTypeName' không truy tìm chính nó - nó chỉ sao chép chuỗi mà bạn đưa nó vào mã mà macro đang sinh ra, sau đó nó sẽ được trình biên dịch kiểm tra kiểu như bình thường. Nếu bạn cần chỉ định gói bạn có thể viết, ví dụ: 'Select (Ident (" package "), newTypeName (" MyClass "))'. –
Bạn có thể sử dụng '-Yreify-copypaste' và/hoặc' showRaw' để lấy mã xây dựng đoạn mã bạn quan tâm. –