1

I would like this plugin to fetch the contents of an annotation (@Typestate(filename)). But at the moment even if I print out the entire tree I can't find the annotation anywhere.

How to get an annotation from source code or alternatively, where to find good documentation on how to do this?

I pulled most of this code from this Scala plugin tutorial.

File with the annotation:

class Typestate(filename:String) extends scala.annotation.StaticAnnotation


@Typestate(filename = "MyProtocol.txt")
class Cat{
  def comeAlive(): Unit = println("The cat is alive")
}

object Main extends App {
  val cat = new Cat()
  cat.comeAlive()
}

Plugin code:

package compilerPlugin

import scala.tools.nsc
import nsc.Global
import nsc.Phase
import nsc.plugins.Plugin
import nsc.plugins.PluginComponent
import scala.collection.mutable.ListBuffer

class GetFileFromAnnotation(val global: Global) extends Plugin {
  import global._

  val name = "GetFileFromAnnotation"
  val description = "gets file from typestate annotation"
  val components: List[PluginComponent] = List[PluginComponent](Component)

  private object Component extends PluginComponent {
    val global: GetFileFromAnnotation.this.global.type = GetFileFromAnnotation.this.global
    val runsAfter: List[String] = List[String]("refchecks")
    val phaseName: String = GetFileFromAnnotation.this.name
    def newPhase(_prev: Phase) = new GetFileFromAnnotationPhase(_prev)

    class GetFileFromAnnotationPhase(prev: Phase) extends StdPhase(prev) {
      override def name: String = GetFileFromAnnotation.this.name

      def apply(unit: CompilationUnit): Unit = {
        printRaw(unit.body)

        for(select @ Select(statements, expr) <- unit.body){
              //global.reporter.error(tree.pos, "file name is here")
        }            
      }
    }
  }
}

Entire tree:

PackageDef(Ident(<empty>), List(ClassDef(Modifiers(), Typestate, List(), Template(List(TypeTree(), TypeTree().setOriginal(Select(Select(Ident(scala), scala.annotation), scala.annotatio
n.StaticAnnotation))), noSelfType, List(ValDef(Modifiers(PRIVATE | LOCAL | PARAMACCESSOR), TermName("filename"), TypeTree().setOriginal(Select(Select(Ident(scala), scala.Predef), TypeN
ame("String"))), EmptyTree), DefDef(Modifiers(), termNames.CONSTRUCTOR, List(), List(List(ValDef(Modifiers(PARAM | PARAMACCESSOR), TermName("filename"), TypeTree().setOriginal(Select(S
elect(Ident(scala), scala.Predef), TypeName("String"))), EmptyTree))), TypeTree(), Block(List(Apply(Select(Super(This(TypeName("Typestate")), typeNames.EMPTY), termNames.CONSTRUCTOR),
List())), Literal(Constant(()))))))), ClassDef(Modifiers(), Cat, List(), Template(List(Select(Ident(scala), TypeName("AnyRef"))), noSelfType, List(DefDef(Modifiers(), termNames.CONSTRU
CTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(TypeName("Cat")), typeNames.EMPTY), termNames.CONSTRUCTOR), List())), Literal(Constant(())))), DefDef(Modifie
rs(), TermName("comeAlive"), List(), List(List()), TypeTree().setOriginal(Select(Ident(scala), scala.Unit)), Apply(Select(Select(Ident(scala), scala.Predef), TermName("println")), List
(Literal(Constant("The cat is alive")))))))), ModuleDef(Modifiers(), Main, Template(List(TypeTree(), TypeTree().setOriginal(Select(Ident(scala), scala.App))), noSelfType, List(DefDef(M
odifiers(), termNames.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(TypeName("Main")), typeNames.EMPTY), termNames.CONSTRUCTOR), List())), Literal(C
onstant(())))), ValDef(Modifiers(PRIVATE | LOCAL), TermName("cat "), TypeTree(), Apply(Select(New(Ident(Cat)), termNames.CONSTRUCTOR), List())), DefDef(Modifiers(METHOD | STABLE | ACCE
SSOR), TermName("cat"), List(), List(), TypeTree(), Select(This(TypeName("Main")), TermName("cat "))), Apply(Select(Select(This(TypeName("Main")), TermName("cat")), TermName("comeAlive
")), List()))))))

1 Answer 1

3

Phase seems to be wrong. If I change refchecks to parser then the plugin with the following component

private object Component extends PluginComponent {
  val global: GetFileFromAnnotation.this.global.type = GetFileFromAnnotation.this.global
  val runsAfter: List[String] = List[String]("parser")
  val phaseName: String = GetFileFromAnnotation.this.name
  def newPhase(_prev: Phase) = new GetFileFromAnnotationPhase(_prev)

  class GetFileFromAnnotationPhase(prev: Phase) extends StdPhase(prev) {
    override def name: String = GetFileFromAnnotation.this.name

    def apply(unit: CompilationUnit): Unit = {
      for(tree@q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" <- unit.body){
        global.reporter.echo(tree.pos, s"tree=$tree, showRaw(tree)=${showRaw(tree)}, mods.annotations=${mods.annotations}")
      }
    }
  }
}

produces output

[info] .../core/src/main/scala/Main.scala:5:7: tree=@new Typestate(filename = "MyProtocol.txt") class Cat extends scala.AnyRef {
[info]   def <init>() = {
[info]     super.<init>();
[info]     ()
[info]   };
[info]   def comeAlive(): Unit = println("The cat is alive")
[info] }, showRaw(tree)=ClassDef(Modifiers(NoFlags, typeNames.EMPTY, List(Apply(Select(New(Ident(TypeName("Typestate"))), termNames.CONSTRUCTOR), List(NamedArg(Ident(TermName("filename")), Literal(Constant("MyProtocol.txt"))))))), TypeName("Cat"), List(), Template(List(Select(Ident(scala), TypeName("AnyRef"))), noSelfType, List(DefDef(Modifiers(), termNames.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(pendingSuperCall), Literal(Constant(())))), DefDef(Modifiers(), TermName("comeAlive"), List(), List(List()), Ident(TypeName("Unit")), Apply(Ident(TermName("println")), List(Literal(Constant("The cat is alive")))))))), mods.annotations=List(new Typestate(filename = "MyProtocol.txt"))
[info] class Cat{
[info]       ^

with correct mods.annotations=List(new Typestate(filename = "MyProtocol.txt")).

But if I change phase even to namer (the next phase after parser) then the plugin produces

[info] .../core/src/main/scala/Main.scala:5:7: tree=@Typestate("MyProtocol.txt") class Cat extends scala.AnyRef {
[info]   def <init>(): Cat = {
[info]     Cat.super.<init>();
[info]     ()
[info]   };
[info]   def comeAlive(): Unit = scala.Predef.println("The cat is alive")
[info] }, showRaw(tree)=ClassDef(Modifiers(), Cat, List(), Template(List(Select(Ident(scala), TypeName("AnyRef"))), noSelfType, List(DefDef(Modifiers(), termNames.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(TypeName("Cat")), typeNames.EMPTY), termNames.CONSTRUCTOR), List())), Literal(Constant(())))), DefDef(Modifiers(), TermName("comeAlive"), List(), List(List()), TypeTree().setOriginal(Select(Ident(scala), scala.Unit)), Apply(Select(Select(Ident(scala), scala.Predef), TermName("println")), List(Literal(Constant("The cat is alive")))))))), mods.annotations=List()
[info] class Cat{
[info]       ^

with already empty mods.annotations=List().

After namer phase you should look for annotations not in a tree but in its symbol.

Indeed,

global.reporter.echo(tree.pos, tree.symbol.annotations.mkString(", "))

produces

[info] .../core/src/main/scala/Main.scala:5:7: Typestate("MyProtocol.txt")
[info] class Cat{
[info]       ^

Learning resource:

Seth Tisue - Scala Compiler Plugins 101

video https://www.youtube.com/watch?v=h5NZjuxS5Qo

slides https://docs.google.com/presentation/d/1KtJMd27yGWmr7E2yxKC_ipMeaP_vr1-6wVJ-QcqS_Vc/edit?usp=sharing

code https://github.com/SethTisue/cloc-plugin

Changes in Dotty: http://dotty.epfl.ch/docs/reference/changed-features/compiler-plugins.html

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks! That was really useful! The "annotations" in mods.annotations came up in red in Intellij (cannot resolve symbol error) but it worked fine anyway.
@aurorarized IntelliJ highlighting is irrelevant. You can try quasiquote q"${mods: Modifiers} class...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.