Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing an Annotation Value in Scala

TL;DR: Basically, I'm looking for the Scala equivalent of the Java:

(MyAnnotation) Thing.getClass().getAnnotations()[0]

Despite the fact that I can happily discover annotations and query based on their type, I can't seem to get from a scala.reflect.runtime.universe.Annotation into my actual type.

scala> // Declare an annotation (it seems StaticAnnotation means runtime
scala> // retention)
scala> case class MyAnnotation(x: Int, y: String) extends scala.annotation.StaticAnnotation
defined class MyAnnotation

scala> // Make a thing decorated with MyAnnotation
scala> @MyAnnotation(x=5, y="cool") case class Thing()
defined class Thing

scala> // Look at the annotation on the Thing...the runtime clearly
scala> // understands the values on it
scala> val annotation = scala.reflect.runtime.universe.typeOf[Thing].typeSymbol.asClass.annotations(0)
annotation: reflect.runtime.universe.Annotation = MyAnnotation(5, "cool")

scala> // I can sort of get at the values by index, which isn't terribly
scala> // safe
scala> annotation.scalaArgs(0)
res0: reflect.runtime.universe.Tree = 5

scala> // And what is a Tree here anyway? It certainly isn't a String (or
scala> // Int). I just want the value!
scala> annotation.scalaArgs(1)
res1: reflect.runtime.universe.Tree = "cool"

scala> // But how do I get at those values programatically?
scala> annotation.asInstanceOf[MyAnnotation]
java.lang.ClassCastException: scala.reflect.internal.AnnotationInfos$CompleteAnnotationInfo cannot be cast to MyAnnotation
        at .<init>(<console>:13)
        at .<clinit>(<console>)
        at .<init>(<console>:7)
        at .<clinit>(<console>)
        at $print(<console>)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:734)
        at scala.tools.nsc.interpreter.IMain$Request.loadAndRun(IMain.scala:983)
        at scala.tools.nsc.interpreter.IMain.loadAndRunReq$1(IMain.scala:573)
        at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:604)
        at scala.tools.nsc.interpreter.IMain.interpret(IMain.scala:568)
        at scala.tools.nsc.interpreter.ILoop.reallyInterpret$1(ILoop.scala:760)
        at scala.tools.nsc.interpreter.ILoop.interpretStartingWith(ILoop.scala:805)
        at scala.tools.nsc.interpreter.ILoop.command(ILoop.scala:717)
        at scala.tools.nsc.interpreter.ILoop.processLine$1(ILoop.scala:581)
        at scala.tools.nsc.interpreter.ILoop.innerLoop$1(ILoop.scala:588)
        at scala.tools.nsc.interpreter.ILoop.loop(ILoop.scala:591)
        at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply$mcZ$sp(ILoop.scala:882)
        at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:837)
        at scala.tools.nsc.interpreter.ILoop$$anonfun$process$1.apply(ILoop.scala:837)
        at scala.tools.nsc.util.ScalaClassLoader$.savingContextLoader(ScalaClassLoader.scala:135)
        at scala.tools.nsc.interpreter.ILoop.process(ILoop.scala:837)
        at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:83)
        at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:96)
        at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:105)
        at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)

The joyous part about all this is I can't even use the traditional Java method even if I wanted, as Scala doesn't bother to populate the array from getAnnotations() anymore.

scala> Thing.getClass.getAnnotations.length
res2: Int = 0

Related

I think what I want is sort of in "Reflection Overview" in the section "Instantiating a Type at Runtime," but I don't see why I would have to jump through so many hoops just to get the values in an annotation. In Java, the value is just a cast away.

The question "See annotations in Scala reflection" seems related, but the question is from 2011 and applies to Scala 2.9. I'm using 2.10 and from what I can tell, a huge amount of how reflection works has changed since then.

like image 673
Travis Gockel Avatar asked Apr 13 '14 18:04

Travis Gockel


People also ask

Does Scala have annotations?

Scala Annotations are metadata added to the program source code. Annotations are allowed on any kind of definition or declaration including vals, vars, classes, objects, traits, defs and types. Annotations are used to associate meta-information with definitions.

Does order of annotations matter?

An annotation clause applies to the first definition or declaration following it. More than one annotation clause may precede a definition and declaration. The order in which these clauses are given does not matter.

What does volatile mean in Scala?

@volatile Marks a field which can change its value outside the control of the program; this is equivalent to the volatile modifier in Java.

What is reflection in Scala?

Scala reflection enables a form of metaprogramming which makes it possible for programs to modify themselves at compile time. This compile-time reflection is realized in the form of macros, which provide the ability to execute methods that manipulate abstract syntax trees at compile-time.


2 Answers

Without having to depend on scala-compiler, this is my version:

  def fetchAnnotations[T <: Annotation](cls : Class[_]) : List[T]= {
    import scala.reflect.runtime.universe._
    val mirror = runtimeMirror(cls.getClassLoader)
    val clsSymbol = mirror.staticClass(cls.getCanonicalName)
    val annotations = clsSymbol.annotations

    val res = ListBuffer[T]()
    for(annt : Annotation <- annotations) {
      val anntCls = annt.tree.tpe.typeSymbol.asClass
      val classMirror = mirror.reflectClass(anntCls);
      val anntType = annt.tree.tpe
      val constructor = anntType.decl(termNames.CONSTRUCTOR).asMethod;
      val constructorMirror = classMirror.reflectConstructor(constructor);

      val instance = annt.tree match {
        case Apply(c, args : List[Tree])   =>
          val res = args.collect({
            case i: Tree =>
              i match {
                case Literal(Constant(value)) =>
                  value
              }
          })
          constructorMirror(res: _*).asInstanceOf[T]
      }

      res+=(instance)
    }
    res.toList
  }

The previous code will only work when the arguments are primitive I suspect.

If we can afford depending on scala-compiler, then we can do something like:

def fetchAnnotations_toolBox[T <: Annotation](cls : Class[_]) : List[T]= {
    import scala.reflect.runtime.universe._
    val mirror = runtimeMirror(cls.getClassLoader)
    val clsSymbol = mirror.staticClass(cls.getCanonicalName)
    val annotations = clsSymbol.annotations

    val res = ListBuffer[T]()
    for(annt : Annotation <- annotations) {
      import scala.tools.reflect.ToolBox

      val toolbox = mirror.mkToolBox()
      val instance = toolbox.eval(toolbox.untypecheck(toolbox.typecheck(annt.tree))).asInstanceOf[T]
      res+=(instance)
    }
    res.toList
  }
like image 76
lqbweb Avatar answered Oct 12 '22 06:10

lqbweb


In their current form, Scala annotations try to combine Java compatibility (which implies constant arguments only and a very limited number of language constructs allowed in annotations) and ultimate flexibility (which implies allowing anything one could imagine in annotations).

This is manifested by the ClassfileAnnotation (compatibility) vs StaticAnnotation (flexibility) distinction. According to this idea, one can choose between Java-style reflection with limited annotations being available as objects and Scala-style reflection with fully flexible annotations being available only as abstract syntax trees (note that we can't automatically convert static annotations to runtime objects, because code stored in such annotations might contain arbitrary Scala expressions, which makes it very hard to evaluate them).

Unfortunately, this idealistic picture is broken by the fact that classfile annotations don't support runtime retention: https://issues.scala-lang.org/browse/SI-32, which means that one doesn't actually get to choose - only Scala-style reflection is supported at the moment. Hopefully, some day we'll get SI-32 fixed, but I'm unaware of any ongoing effort to make it work.

A couple months ago, I wanted to implement something like scala.reflect.Annotation.eval that would take a Scala-style annotation and evaluate it if possible. However, after a discussion during one of our reflection meetings we decided against it, because apart from an unfortunate non-generality, this API would also bring troubles to compile-time reflection (which in Scala is unified with runtime reflection).

This has been logged as an issue at https://issues.scala-lang.org/browse/SI-6423 and also discussed at https://groups.google.com/forum/#!topic/scala-internals/8v2UL-LR9yY, but with no concrete plans for improvement at the moment. Hopefully Project Palladium is going to help here, since one of its core components is a Scala interpreter, which will provide necessary generality to support Annotation.eval.

like image 31
Eugene Burmako Avatar answered Oct 12 '22 08:10

Eugene Burmako