Scala returns no annotations for a field

2019-07-12 01:47发布

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?

2条回答
贪生不怕死
2楼-- · 2019-07-12 02:17

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.

查看更多
够拽才男人
3楼-- · 2019-07-12 02:25

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

查看更多
登录 后发表回答