Compiler plugin for detecting usage of Java classes

Hi,

I'm trying to write a compiler plugin, which should basically print the Java classes used in some Scala file.

E. g. when compiling

class A {
  val list = new java.util.ArrayList[Int]
  System.out.println(list)
}

the plugin should print something like:

A.scala:2: new java.util.ArrayList
A.scala:3: java.lang.System.out.println

I used the basic plugin code from http://www.scala-lang.org/node/140 and as far as I have understood I need to implement the apply method to get this done.
This is the part where I am not sure how to go on. Do I need to match against something or is it enough to ask for the type of every child of unit.body? Should I work with trees or syms? Which compiler phase is the most appropriate one? (Currently the plugin runs after the typer.) Is there something like an isJava method? (If not, looking for “java.” is ok too ...)

Any suggestions?

Thanks!


Simon

Re: Compiler plugin for detecting usage of Java classes

Hi!

It doesn't look very nice, but this is what I came up with:

      def apply(unit: CompilationUnit) {
        for (tree <- unit.body) {
          tree match {
            case tree@Apply(fun, args)
              if !(fun.tpe <:< definitions.ScalaObjectClass.tpe) =>
              println(fun.pos.source + ":" + fun.pos.line + ":" + " " + fun.symbol.fullName)
            case tree@DefDef(mods, name, tparams, vparamss, tpt, rhs)
              if !(rhs.tpe <:< definitions.ScalaObjectClass.tpe) && (rhs.symbol != null && !mods.hasAccessorFlag) =>
              println(rhs.pos.source + ":" + rhs.pos.line + ":" + " " + rhs.symbol.fullName)
            case _ =>
          }
        }
      }

Any suggestions?

Thanks and bye,

Simon

Re: Re: Compiler plugin for detecting usage of Java classes



On Wed, Jan 4, 2012 at 7:36 PM, Simon Ochsenreither <simon [dot] ochsenreither [at] googlemail [dot] com> wrote:
              if !(fun.tpe <:< definitions.ScalaObjectClass.tpe) =>

if (fun.symbol.isJavaDefined) ...
For the most part fun.tpe will be a method type, which will not inherit ScalaObject because, well, it's a method.
scala> typer typed (LIT("abc") DOT "length" APPLY ()) res4: $r.intp.global.analyzer.global.Tree = "abc".length()
scala> res4.asInstanceOf[Apply]res5: $r.intp.global.Apply = "abc".length()
scala> res5.fun.tperes6: $r.intp.global.Type = ()Int
scala> res5.fun.tpe <:< ScalaObjectClass.tperes7: Boolean = false
scala> res5.symbol res8: $r.intp.global.Symbol = method length
scala> res5.symbol.isJavaDefinedres9: Boolean = true

Re: Re: Compiler plugin for detecting usage of Java classes

Thanks a lot Paul!

Did you use the REPL :power mode for the stuff above or something different?

With your help it works pretty well now. I have hit one problem though:
I'm interested in the type argument in stuff like

  trait Foo extends Bar[java.lang.Integer]
  val list = List[java.math.MathContext]()

I catch it in Template/ValDef, but I have the problem that I seem to have to work with Types (foo.tpe.typeArgs) instead of Symbols, which makes it difficult to work with and to print line numbers, positions, ...
Is there something I'm missing?

This is the corresponding snippet I wrote currently:

      def apply(unit: CompilationUnit) {
        for (tree <- unit.body) {
          tree match {
            case tree@Apply(fun, args)
              if fun.symbol.isJavaDefined && fun.symbol.isSourceMethod && fun.symbol.fullName != "java.lang.Object.<init>" =>
              printJava(fun, "Apply ")
            case tree@DefDef(mods, name, tparams, vparamss, tpt, rhs)
              if isJavaAndNotAccessor(mods, rhs.symbol) =>
              printJava(rhs, "DefDef")
            case tree@ValDef(mods, name, tpt, rhs) =>
              if (isJavaAndNotAccessor(mods, tpt.symbol))
                printJava(tpt, "ValDef")
              val typeArgs = tpt.tpe.typeArgs
              //FIMXE: Some classes like the numeric wrappers lack the "java.lang." part. Why?
              typeArgs.foreach(typeArg => /*if (typeArg.toLongString.startsWith("java"))*/ println("TpeArgs " + typeArg.toLongString))

            case tree@Template(parents, self, body) =>
              parents
                .foreach {
                p =>
                  if (isJavaAndNotJLObject(p.symbol)) printJava(p, "Templa")
                  p.tpe.typeArgs.foreach {
                    //FIMXE: Some classes like the numeric wrappers lack the "java.lang." part. Why?
                    typeArg => /*if (typeArg.toLongString.startsWith("java"))*/ println("TemplT " + typeArg.toLongString)
                  }
              }

            case _ =>
          }
        }

        println(occurrences + " occurrences found.")
      }

      def isJavaAndNotAccessor(mods: Modifiers, sym: Symbol): Boolean = sym != null && sym.isJavaDefined && !mods.hasAccessorFlag

      def isJavaAndNotJLObject(sym: Symbol): Boolean = sym.isJavaDefined && sym != definitions.ObjectClass

      private def printJava(tree: Tree, source: String) {
        val line = numbersWithThreeDigits(tree.pos.line)
        println(tree.pos.source + ":" + line + ": [" + source + "] " + " " + tree.symbol.fullName)

        occurrences += 1
      }

      var occurrences = 0

      def numbersWithThreeDigits(num: Int): String = {
        val l = num
        if (l < 10) "00" + l
        else if (l < 100) "0" + l
        else "" + l
      }

Maybe you have an idea.

Thanks!


Simon

Re: Re: Compiler plugin for detecting usage of Java classes

package bippy
trait Bar[T]trait Foo extends Bar[java.lang.Integer]
class A {  val list = List[java.math.MathContext]()}
scala> intp("bippy.A")res0: $r.intp.global.Symbol = class A
scala> res0.info.member(newTermName("list"))res3: $r.intp.global.Symbol = value list
scala> res3.tperes4: $r.intp.global.Type = => List[java.math.MathContext]
scala> res3.tpe.finalResultType.typeArgs.headres5: $r.intp.global.Type = java.math.MathContext
scala> res5.typeSymbolres6: $r.intp.global.Symbol = class MathContext
scala> intp("bippy.Foo")res12: $r.intp.global.Symbol = class Foo
scala> res12.info.baseTypeSeq.toList >bippy.Foobippy.Bar[Integer]ObjectAny
scala> res12.info.baseTypeSeq.toList flatMap (_.typeArgs) map (_.typeSymbol.fullName) res14: List[String] = List(java.lang.Integer)
scala> intp("scala.collection.mutable.StringBuilder").info.baseTypeSeq.toList flatMap (_.typeArgs) filter (_.typeSymbol.isJavaDefined) distinct res21: List[$r.intp.global.Type] = List(String)
scala> intp("scala.collection.mutable.StringBuilder").info.baseTypeSeq.toList flatMap (_.typeArgs) filterNot (_.typeSymbol.isJavaDefined) distinct res22: List[$r.intp.global.Type] = List(Char, StringBuilder, scala.collection.mutable.IndexedSeq[Char], scala.collection.mutable.Seq[Char], Int)

Re: Re: Compiler plugin for detecting usage of Java classes

Hi Paul,

Thanks again!

I think I almost got it... I'm only struggling with tree@Select(qual, name), because in e. g. java.awt.Point it matches not only java.awt.Point, but also java and java.awt. Is there a way to check if the selection is "complete"?

Bye,

Simon


Re: Re: Compiler plugin for detecting usage of Java classes

Mhhh, it looks like (tree.symbol.isMethod || tree.symbol.isValue || tree.symbol.isStaticMember) does a pretty good job. Would you suggest something different?

Bye

Re: Compiler plugin for detecting usage of Java classes

On Wed, Jan 4, 2012 at 5:18 PM, Simon Ochsenreither <simon [dot] ochsenreither [at] googlemail [dot] com> wrote:
Is there something like an isJava method? (If not, looking for “java.” is ok too ...)

IIUC anything that doesn't implement the scala.ScalaObject interface was not compiled by scalac.
-0xe1a

Re: Compiler plugin for detecting usage of Java classes

Afaik the interface part of traits doesn't implement ScalaObject ...

Re: Compiler plugin for detecting usage of Java classes

Yes, I know that. I'm wondering more about how to represent it, so that I catch everything exactly once. New instances, field access, static stuff, etc ...

Thanks!

Copyright © 2013 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland