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!
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.
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.
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. :-)
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]]