How to turn a Java class literal into a Literal(Constant) in namer?

123 views
Skip to first unread message

Simon Ochsenreither

unread,
Nov 21, 2014, 5:58:10 PM11/21/14
to scala-i...@googlegroups.com
 Hi,

I'm trying to fix a few annotation issues https://github.com/soc/scala/compare/scala:2.11.x...soc:SI-8928?expand=1 and I have hit the question of how to turn a Java class literal (foo.bar.Baz.class) into a Constant(Literal(tpe)), which is what AnnotationInfo expects when converting the annotation tree into AnnotationInfos.

I'm pretty sure I'm missing the obvious solution, but all things I checked (Tree -> Type) like mkClassOf call tree.tpe internally, which won't work because typer is not run for Java sources and therefore tpe is not set. Other things I checked, like methods in Types all seem to expect to work with later trees, not trees coming straight out of namer.

The last commit shows the crucial code where this issue comes up: https://github.com/soc/scala/commit/394df80bf027989a4381dc56b2ee22c4ec24eeaa#diff-226e876495a438b9b5c6d407d5997368R250 and https://github.com/soc/scala/commit/394df80bf027989a4381dc56b2ee22c4ec24eeaa#diff-226e876495a438b9b5c6d407d5997368R407.

(It's all a bit more messy, because if we encounter something like ident.ident.ident, it's not known until the last ident whether the whole thing is a standard identifier or a class literal, but that issue is not important here ...)

I'd appreciate any hints!

Thanks and bye,

Simon

Jason Zaugg

unread,
Nov 21, 2014, 9:15:39 PM11/21/14
to scala-i...@googlegroups.com

I believe you should instead turn it into a untyped tree, q"_root_.scala.Predef.classOf[$c]". The typechecker will be run on the signatures of Java methods by virtue of the type completers, even though the typer phase itself doesn’t run Java compilation units.

The signatures includes untyped annotations found in DefDef.mods.annotations.

Here’s a test case will let you know if its working before you add support for parsing the syntax for Java class literals:

% tail sandbox/{macro.scala,Test.java,test.scala}
==> sandbox/macro.scala <==
  def annotsImpl(c: Context): c.Tree = {
    import c.universe._

    val t = rootMirror.staticClass("Test")
    println(t.info.member(TypeName("X")).annotations)
    q"()"
  }
  def annots: Unit = macro annotsImpl
}

==> sandbox/Test.java <==
class Test {
    public static @interface A {
        public Class<?> value();
    }

    @A(scala.Predef.classOf[Test])
    static class X {}
}

==> sandbox/test.scala <==
object Main {
  def main(args: Array[String]): Unit = {
    new Test.X
    Macro.annots
  }
}
% scalac-hash v2.11.4 sandbox/macro.scala && scalac-hash v2.11.4 sandbox/{Test.java,test.scala}
warning: there was one deprecation warning; re-run with -deprecation for details
one warning found
List() // should be non empty!

-jason

Simon Ochsenreither

unread,
Nov 22, 2014, 8:19:36 AM11/22/14
to scala-i...@googlegroups.com
Wow, thanks Jason!

It seems to work! https://github.com/soc/scala/commit/7bb4ea256ebb7915871102f8147d3b842ccb0455

Another question ... Java let's users declare an annotation argument as an array, but allows passing a single value:

// Java
@Target(ElementType.TYPE)
@interface Val {
  Class clazz();
}


// Scala
@Val(clazz = classOf[String]) class Baz // necessary to trigger:

Foo.java:1: error: type mismatch;
 found   : java.lang.annotation.ElementType(TYPE)
 required: Array[java.lang.annotation.ElementType]
@Target(ElementType.TYPE)
                    ^


In Scala this of course fails to typecheck, because a pretty standard typechecker is used. What would be the best way to approach this?

Simon Ochsenreither

unread,
Nov 22, 2014, 8:25:55 AM11/22/14
to scala-i...@googlegroups.com
Mhhh, might be a misleading example. The "clazz" method doesn't matter, it's the @Target which triggers the issue here. Target is defined to take ElementValue[], but is called with a single ElementValue.

Jason Zaugg

unread,
Nov 22, 2014, 8:36:34 AM11/22/14
to scala-i...@googlegroups.com
(
On Sat, Nov 22, 2014 at 11:25 PM, Simon Ochsenreither <simon.och...@gmail.com> wrote:
Mhhh, might be a misleading example. The "clazz" method doesn't matter, it's the @Target which triggers the issue here. Target is defined to take ElementValue[], but is called with a single ElementValue.

I would try something like the following in adapt:

@@ -1040,6 +1040,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
           if (hasUndets)
             return instantiate(tree, mode, pt)

+          if (context.unit.isJava /* && context.isAnnotationArgument */) {
+            if (pt.typeSymbol == ArrayClass)
+              return typedPos(tree.pos, mode, pt)(gen.mkWrapArray(tree, tree.tpe))
+          }
+

You can probably omit isAnnotationArgument for now the only term trees we should be typechecking in .java files are annotation arguments.

This is essentially the same as an implicit from T => Array[T] for Java files.


-jason

Jason Zaugg

unread,
Nov 22, 2014, 8:53:10 AM11/22/14
to scala-i...@googlegroups.com
Be sure to read the Java spec to understand the way that Java typechecks annotations. If we start typechecking Java annotations in scalac, we must be really sure that we don't trip over on valid programs, and pretty sure that we handle erroneous programs gracefully. OTOH, it isn't a requirement to issue precise errors for erroneous Java annotations, we can defer to javac.

-jason

Simon Ochsenreither

unread,
Nov 22, 2014, 2:16:30 PM11/22/14
to scala-i...@googlegroups.com
Hi Jason,


Be sure to read the Java spec to understand the way that Java typechecks annotations. If we start typechecking Java annotations in scalac, we must be really sure that we don't trip over on valid programs, and pretty sure that we handle erroneous programs gracefully. OTOH, it isn't a requirement to issue precise errors for erroneous Java annotations, we can defer to javac.

Absolutely! I don't have the intention to start typechecking those things more than necessary, it was just that when I started to read those annotations, things started to fail and I wanted to figure out how to handle these things as gracefully as possible.

Bye,

Simon

Simon Ochsenreither

unread,
Nov 24, 2014, 10:10:29 PM11/24/14
to scala-i...@googlegroups.com
That's causing a StackOverflowError https://gist.github.com/soc/02740d95f0e83bfcd609, but I keep looking into it. About time that I learn a bit more about the typer. :-)

Jason Zaugg

unread,
Nov 24, 2014, 11:19:55 PM11/24/14
to scala-i...@googlegroups.com
On Tue, Nov 25, 2014 at 1:10 PM, Simon Ochsenreither <simon.och...@gmail.com> wrote:
That's causing a StackOverflowError https://gist.github.com/soc/02740d95f0e83bfcd609, but I keep looking into it. About time that I learn a bit more about the typer. :-)

 You'll have to go for something more like:

if (context.unit.isJava /* && context.isAnnotationArgument */
) {
  if (pt.typeSymbol == ArrayClass && tree.tpe =:= definitions.elementType(ArrayClass, pt)) {
    val adapted = typedPos(tree.pos, mode, pt)(gen.mkWrapArray(tree, tree.tpe))
    assert(adapted.tpe <:< pt, (adapted.tpe, pt))
    return adapted
}

You will soon face another a problem: when typechecking Java code, Array should act covariantly. You might need another adaptation that applies something like the following to the type of the tree, (again, iff it would result it it conforming to the expected type.)

scala>   /**
     |    * Converts arguments of the Array type constructor to covariant existentials.
     |    * Example: `arrayToExistential(Array[String]) = Array[_ <: String]`
     |    */
     |    def arrayToExistential(tp: Type, owner: Symbol = NoSymbol): Type = {
     |      tp map {
     |        case TypeRef(pre, ArrayClass, args) =>
     |          val syms = mapWithIndex(args) { (a: Type, i: Int) =>
     |            val name = tpnme.WILDCARD.append('$').append(i.toString)
     |            owner.newExistential(name).setInfo(TypeBounds.upper(a))
     |          }
     |          val args1 = syms map (sym => typeRef(NoPrefix, sym, Nil))
     |          newExistentialType(syms, TypeRef(pre, ArrayClass, args1))
     |        case t => t
     |      }
     |    }
arrayToExistential: (tp: $r.intp.global.Type, owner: $r.intp.global.Symbol)$r.intp.global.Type

scala>

scala> arrayToExistential(typeOf[Array[String]])
res1: $r.intp.global.Type = Array[_ <: String]

scala> arrayToExistential(typeOf[Array[Array[String]]])
res2: $r.intp.global.Type = Array[_ <: Array[_ <: String]]
-jason
Reply all
Reply to author
Forward
0 new messages