I have this:
class Max(val value : Int) extends StaticAnnotation{}
class Child() extends Parent {
@Max(5) val myMember= register("myMember")
}
abstract class Parent {
def register(fieldName : String) = {
val cls = getClass
import scala.reflect.runtime.universe._
val mirror = runtimeMirror(cls.getClassLoader)
val clsSymbol = mirror.staticClass(cls.getCanonicalName)
val fieldSymbol = clsSymbol.typeSignature.member(TermName(fieldName))
println(s"${fieldSymbol.fullName} " + fieldSymbol.annotations.size)
}
}
this does not work, somehow, it returns 0 annotations, if instead, I put the annotation on the class, then I can read it fine. Why?
Discovered that the previous line:
clsSymbol.typeSignature.member(TermName(fieldName))
was returning the symbol of the auto generated getter for the "val" (which of course does not have any annotation), instead of the symbol from the val itself. If instead I do:
clsSymbol.toType.decl(TermName(s"${fieldName} "))
that seems to work fine. For any reason that I do not know, if I write a space at the end of the TermName, then it returns the field symbol with the annotations.
Adding a bit of additional information to your answer to demonstrate and ilustrate the problem:
scala> import scala.annotation.StaticAnnotation
import scala.annotation.StaticAnnotation
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> class Max(val value : Int) extends StaticAnnotation
defined class Max
scala> class Child {
| @Max(5) val myMember = 2
| }
defined class Child
scala> val cls = classOf[Child]
cls: Class[Child] = class Child
scala> val mirror = runtimeMirror(cls.getClassLoader)
mirror: reflect.runtime.universe.Mirror = JavaMirror with... (I truncated this part which was super long and not useful)
scala> mirror.classSymbol(cls).selfType.decls
res0: reflect.runtime.universe.MemberScope = SynchronizedOps(constructor Child, value myMember, value myMember)
scala> println(mirror.classSymbol(cls).selfType.decls)
Scope{
def <init>: <?>;
val myMember: <?>;
private[this] val myMember: scala.Int
}
scala> mirror.classSymbol(cls).selfType.decls.map(_.annotations)
res2: Iterable[List[reflect.runtime.universe.Annotation]] = List(List(), List(), List(Max(5)))
scala> mirror.classSymbol(cls).selfType.decls.map(_.isMethod)
res4: Iterable[Boolean] = List(true, true, false)
scala> mirror.classSymbol(cls).selfType.decls.map(_.asTerm.name)
res15: Iterable[reflect.runtime.universe.TermName] = List(<init>, myMember, myMember )
Only one of them include the annotation, and we can see that the last one which is the actual attribute you defined and not the synthetic getter that the compiler defined automatically, has a space at the end of its name ! I really wonder who thought it was a good idea to do such horrible thing, but it seems to be the reality. I am no Scala expert but this whole API seems very complex to me and unpractical to work with. It probably suffers from the complexity of Scala as a language itself, which under appearances of simplicity and "magic" features, actually has some very complex mechanisms.
To me, a better API should propose one method to get def
declarations and another one for getting val
and var
declarations. Also, the names should probably not be dedupes by a completely unexpected space at the end of the name !
PS: Martin Odersky explained this design choice on the following thread: https://contributors.scala-lang.org/t/design-choice-reflection-valdef-and-synthetic-getter/565