Note: There's an EDIT below! Note: There's another EDIT below!
I have written a Scala annotation macro that is being passed a class and creates (or rather populates) a case object. The name of the case object is the same as the name of the passed class. More importantly, for every field of the passed class, there will be a field in the case object of the same name. The fields of the case object, however, are all of type String
, and their value is the name of the type of the respective field in the passed class. Example:
// 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
This, however, seems to only work with pre-defined classes such as String
. If I define a case class A(...)
and try to do @RegisterClass(classOf[A]) case object A
, I get the following error:
[info] scala.tools.reflect.ToolBoxError: reflective compilation has failed:
[info]
[info] not found: type A
What have I done wrong? The code of my macro can be found below. Also, if someone notices un-idiomatic Scala or bad practices in general, I wouldn't mind a hint. Thank you very much in advance!
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.")
}
}
}
EDIT: As Eugene Burmako pointed out, the error happens because class A
hasn't been compiled yet, so a java.lang.Class
for it doesn't exist. I have now started a bounty of 100 StackOverflow points for everyone who as an idea how one could get this to work!
EDIT 2: Some background on the use case: As part of my bachelor thesis I am working on a Scala DSL for expressing queries for event processing systems. Those queries are traditionally expressed as strings, which induces a lot of problems. A typical query would look like that: "select A.id, B.timestamp from pattern[A -> B]". Meaning: If an event of type A
occurs and after that an event of type B
occurs, too, give me the id
of the A
event and the timestamp
of the B
event. The types A
and B
usually are simple Java classes over which I have no control. id
and timestamp
are fields of those classes. I would like queries of my DSL to look like that: select (A.id, B.timestamp) { /* ... * / }
. This means that for every class representing an event type, e.g., A
, I need a companion object -- ideally of the same name. This companion object should have the same fields as the respective class, so that I can pass its fields to the select
function, like so: select (A.id, B.timestamp) { /* ... * / }
. This way, if I tried to pass A.idd
to the select
function, it would fail at compile-time if there was no such field in the original class -- because then there would not be one in the companion object either.
@RegisterClass case object String { type T = String }
or@RegisterClass case object String { val t : String = _ }
. Then possibly the annotation macro can get access to the typeT
or the type oft
by inspecting the annottee? (I don't know if this works, that's why I don't post an answer, but perhaps the idea helps somehow.) – Dominique Unruhselect
,join
,groupby
(see the examples here). (3) Why not make a compiler plugin for this? Then you can "rewrite" Scala syntax locally. – Alec