注意:下面有一个编辑! 注意:下面还有另一个编辑!
我编写了一个 Scala 注释宏,它被传递一个类并创建(或更确切地说是填充)一个案例对象。案例对象的名称与传递的类的名称相同。更重要的是,对于传递的类的每一个字段,都会有一个同名的case对象中的字段。但是,case 对象的字段都是 type String
,它们的值是传递的类中相应字段的类型名称。例子:
// Using the annotation macro to populate a case object called `String`
@RegisterClass(classOf[String]) case object String
// The class `String` defines a field called `value` of type `char[]`.
// The case object also has a field `value`, containing `"char[]"`.
println(String.value) // Prints `"char[]"` to the console
然而,这似乎只适用于预定义的类,例如String
. 如果我定义 acase class A(...)
并尝试执行@RegisterClass(classOf[A]) case object A
,我会收到以下错误:
[info] scala.tools.reflect.ToolBoxError: reflective compilation has failed:
[info]
[info] not found: type A
我做错了什么?我的宏的代码可以在下面找到。此外,如果有人注意到不惯用的 Scala 或一般的不良做法,我不介意提示。非常感谢您!
class RegisterClass[T](clazz: Class[T]) extends StaticAnnotation {
def macroTransform(annottees: Any*) =
macro RegisterClass.expandImpl[T]
}
object RegisterClass {
def expandImpl[T](c: blackbox.Context)(annottees: c.Expr[Any]*) = {
import c.universe._
val clazz: Class[T] = c.prefix.tree match {
case q"new RegisterClass($clazz)" => c.eval[Class[T]](c.Expr(clazz))
case _ => c.abort(c.enclosingPosition, "RegisterClass: Annotation expects a Class[T] instance as argument.")
}
annottees.map(_.tree) match {
case List(q"case object $caseObjectName") =>
if (caseObjectName.toString != clazz.getSimpleName)
c.abort(c.enclosingPosition, "RegisterClass: Annotated case object and class T of passed Class[T] instance" +
"must have the same name.")
val clazzFields = clazz.getDeclaredFields.map(field => field.getName -> field.getType.getSimpleName).toList
val caseObjectFields = clazzFields.map(field => {
val fieldName: TermName = field._1
val fieldType: String = field._2
q"val $fieldName = $fieldType"
})
c.Expr[Any](q"case object $caseObjectName { ..$caseObjectFields }")
case _ => c.abort(c.enclosingPosition, "RegisterClass: Annotation must be applied to a case object definition.")
}
}
}
编辑:正如 Eugene Burmako 指出的那样,发生错误是因为class A
尚未编译,所以 a java.lang.Class
for 它不存在。我现在已经开始为每个想知道如何让它工作的人提供 100 个 StackOverflow 积分!
编辑 2:用例的一些背景知识:作为我的学士论文的一部分,我正在研究 Scala DSL,用于表达对事件处理系统的查询。这些查询传统上表示为字符串,这会引发很多问题。典型的查询如下所示:“从模式 [A -> B] 中选择 A.id, B.timestamp”。含义:如果一个类型的事件A
发生,然后一个类型的事件也B
发生了,给我事件的和id
事件的。这些类型通常是我无法控制的简单 Java 类。并且是这些类的字段。我希望我的 DSL 查询看起来像这样:. 这意味着对于表示事件类型的每个类,例如,A
timestamp
B
A
B
id
timestamp
select (A.id, B.timestamp) { /* ... * / }
A
,我需要一个伴生对象——最好是同名的。这个伴生对象应该具有与相应类相同的字段,以便我可以将其字段传递给select
函数,如下所示select (A.id, B.timestamp) { /* ... * / }
:这样,如果我试图传递A.idd
给select
函数,如果原始类中没有这样的字段,它将在编译时失败——因为那样的话伴生对象中也不会有这样的字段。