This page is no longer maintained — Please continue to the home page at www.scala-lang.org

Compiler plugin for detecting usage of Java classes

9 replies
Simon Ochsenreither
Joined: 2011-07-17,
User offline. Last seen 42 years 45 weeks ago.
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
Alex Cruise
Joined: 2008-12-17,
User offline. Last seen 2 years 26 weeks ago.
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
Simon Ochsenreither
Joined: 2011-07-17,
User offline. Last seen 42 years 45 weeks ago.
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!
Simon Ochsenreither
Joined: 2011-07-17,
User offline. Last seen 42 years 45 weeks ago.
Re: Compiler plugin for detecting usage of Java classes
Afaik the interface part of traits doesn't implement ScalaObject ...
Simon Ochsenreither
Joined: 2011-07-17,
User offline. Last seen 42 years 45 weeks ago.
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
extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
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
Simon Ochsenreither
Joined: 2011-07-17,
User offline. Last seen 42 years 45 weeks ago.
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
extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
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)
Simon Ochsenreither
Joined: 2011-07-17,
User offline. Last seen 42 years 45 weeks ago.
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


Simon Ochsenreither
Joined: 2011-07-17,
User offline. Last seen 42 years 45 weeks ago.
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

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