Skip to content

Commit f7a0667

Browse files
Kordyjanmichelou
authored andcommitted
Fix scala#10788: Parsing arguments for java annotations
* A port of fixes from scala/scala#8781 with improvements. * Some of improvements come from scala/scala#8982. * There are small changes to typer to allow for single elements of any type T be used where array of T is expected inside of java annotations arguments as it is correct in java and is used in 2 files in scala standard library.
1 parent db6f2f5 commit f7a0667

File tree

8 files changed

+258
-18
lines changed

8 files changed

+258
-18
lines changed

compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala

Lines changed: 79 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ object JavaParsers {
246246

247247
def qualId(): RefTree = {
248248
var t: RefTree = atSpan(in.offset) { Ident(ident()) }
249-
while (in.token == DOT) {
249+
while (in.token == DOT && in.lookaheadToken == IDENTIFIER) {
250250
in.nextToken()
251251
t = atSpan(t.span.start, in.offset) { Select(t, ident()) }
252252
}
@@ -346,20 +346,86 @@ object JavaParsers {
346346
/** Annotation ::= TypeName [`(` AnnotationArgument {`,` AnnotationArgument} `)`]
347347
*/
348348
def annotation(): Option[Tree] = {
349-
val id = convertToTypeId(qualId())
350-
// only parse annotations without arguments
351-
if (in.token == LPAREN && in.lookaheadToken != RPAREN) {
352-
skipAhead()
353-
accept(RPAREN)
354-
None
355-
}
356-
else {
357-
if (in.token == LPAREN) {
349+
object LiteralT:
350+
def unapply(token: Token) = Option(token match {
351+
case TRUE => true
352+
case FALSE => false
353+
case CHARLIT => in.name(0)
354+
case INTLIT => in.intVal(false).toInt
355+
case LONGLIT => in.intVal(false)
356+
case FLOATLIT => in.floatVal(false).toFloat
357+
case DOUBLELIT => in.floatVal(false)
358+
case STRINGLIT => in.name.toString
359+
case _ => null
360+
}).map(Constant(_))
361+
362+
def classOrId(): Tree =
363+
val id = qualId()
364+
if in.lookaheadToken == CLASS then
358365
in.nextToken()
359-
accept(RPAREN)
366+
accept(CLASS)
367+
TypeApply(
368+
Select(
369+
scalaDot(nme.Predef),
370+
nme.classOf),
371+
convertToTypeId(id) :: Nil
372+
)
373+
else id
374+
375+
def array(): Tree =
376+
accept(LBRACE)
377+
val buffer = ListBuffer[Tree]()
378+
while (in.token != RBRACE) {
379+
buffer += argValue()
380+
if (in.token == COMMA) in.nextToken() // using this instead of repsep allows us to handle trailing commas
360381
}
361-
Some(ensureApplied(Select(New(id), nme.CONSTRUCTOR)))
362-
}
382+
val ok = !buffer.contains(EmptyTree)
383+
in.token match {
384+
case RBRACE if ok =>
385+
accept(RBRACE)
386+
Apply(scalaDot(nme.Array), buffer.toList)
387+
case _ =>
388+
skipTo(RBRACE)
389+
EmptyTree
390+
}
391+
392+
def argValue(): Tree =
393+
in.token match {
394+
case LiteralT(c) =>
395+
val tree = atSpan(in.offset)(Literal(c))
396+
in.nextToken()
397+
tree
398+
case AT =>
399+
in.nextToken()
400+
annotation().get
401+
case IDENTIFIER => classOrId()
402+
case LBRACE => array()
403+
case _ => EmptyTree
404+
}
405+
406+
def annArg(): Tree =
407+
if (in.token == IDENTIFIER && in.lookaheadToken == EQUALS)
408+
val name = ident()
409+
accept(EQUALS)
410+
val argv = argValue()
411+
NamedArg(name, argv)
412+
413+
else
414+
NamedArg(nme.value, argValue())
415+
416+
417+
val id = convertToTypeId(qualId())
418+
val args = if in.token == LPAREN then
419+
in.nextToken()
420+
val args = repsep(annArg, COMMA)
421+
accept(RPAREN)
422+
args
423+
else Nil
424+
425+
Some(Apply(
426+
Select(New(id), nme.CONSTRUCTOR),
427+
args
428+
))
363429
}
364430

365431
def modifiers(inInterface: Boolean): Modifiers = {

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ object Typer {
8181
*/
8282
private val DroppedEmptyArgs = new Property.Key[Unit]
8383

84+
85+
/** Marker context property that indicates that typer is resolving types for arguments of
86+
* an annotation defined in Java. This means that value of any type T can appear in positions where
87+
* Array[T] is expected.
88+
* For example, both `@Annot(5)` and `@Annot({5, 6}) are viable calls of the constructor
89+
* of annotation defined as `@interface Annot { int[] value() }`
90+
*/
91+
private[typer] val JavaAnnotationArg = Property.Key[Unit]()
92+
8493
/** An attachment that indicates a failed conversion or extension method
8594
* search was tried on a tree. This will in some cases be reported in error messages
8695
*/
@@ -855,7 +864,14 @@ class Typer extends Namer
855864
}
856865

857866
def typedNamedArg(tree: untpd.NamedArg, pt: Type)(using Context): NamedArg = {
858-
val arg1 = typed(tree.arg, pt)
867+
val arg1 = if (ctx.property(JavaAnnotationArg).isDefined) {
868+
pt match {
869+
case AppliedType(a, typ :: Nil) if (a.isRef(defn.ArrayClass)) =>
870+
tryAlternatively { typed(tree.arg, pt) } { typed(untpd.JavaSeqLiteral(tree.arg :: Nil, TypeTree(typ)), pt) }
871+
case _ => typed(tree.arg, pt)
872+
}
873+
} else typed(tree.arg, pt)
874+
859875
assignType(cpy.NamedArg(tree)(tree.name, arg1), arg1)
860876
}
861877

@@ -1977,11 +1993,13 @@ class Typer extends Namer
19771993
*/
19781994
def annotContext(mdef: untpd.Tree, sym: Symbol)(using Context): Context = {
19791995
def isInner(owner: Symbol) = owner == sym || sym.is(Param) && owner == sym.owner
1980-
val c = ctx.outersIterator.dropWhile(c => isInner(c.owner)).next()
1981-
c.property(ExprOwner) match {
1982-
case Some(exprOwner) if c.owner.isClass => c.exprContext(mdef, exprOwner)
1983-
case _ => c
1996+
val outer = ctx.outersIterator.dropWhile(c => isInner(c.owner)).next()
1997+
val c = outer.property(ExprOwner) match {
1998+
case Some(exprOwner) if outer.owner.isClass => outer.exprContext(mdef, exprOwner)
1999+
case _ => outer
19842000
}
2001+
if (c.isJava) c.fresh.setProperty(JavaAnnotationArg, ())
2002+
else c
19852003
}
19862004

19872005
def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(using Context): Unit = {

tests/run/java-annot-params.check

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class annots.A
2+
class annots.A
3+
SOME STRING
4+
VALUE OF CONST
5+
false
6+
13.7
7+
VALUE
8+
List(a, b, c)
9+
List()
10+
List(SINGLE)
11+
List(ABC)
12+
13+
class annots.A
14+
class annots.A
15+
SOME STRING
16+
VALUE OF CONST
17+
false
18+
13.7
19+
VALUE
20+
List(a, b, c)
21+
List()
22+
List(SINGLE)
23+
List(ABC)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package annots;
2+
3+
import java.lang.annotation.*;
4+
5+
@Retention(RetentionPolicy.RUNTIME)
6+
@interface WithClass {
7+
Class<?> arg();
8+
}
9+
10+
@Retention(RetentionPolicy.RUNTIME)
11+
@interface WithClassDefaultName {
12+
Class<?> value();
13+
}
14+
15+
@Retention(RetentionPolicy.RUNTIME)
16+
@interface WithString {
17+
String arg();
18+
}
19+
20+
@Retention(RetentionPolicy.RUNTIME)
21+
@interface WithReference {
22+
String arg();
23+
}
24+
25+
@Retention(RetentionPolicy.RUNTIME)
26+
@interface WithBoolean {
27+
boolean arg();
28+
}
29+
30+
@Retention(RetentionPolicy.RUNTIME)
31+
@interface WithFloat {
32+
float arg();
33+
}
34+
35+
@Retention(RetentionPolicy.RUNTIME)
36+
@interface WithNested {
37+
Nested arg();
38+
}
39+
40+
@Retention(RetentionPolicy.RUNTIME)
41+
@interface Nested {
42+
String value();
43+
}
44+
45+
@Retention(RetentionPolicy.RUNTIME)
46+
@interface WithArray {
47+
String[] value();
48+
}
49+
50+
@Retention(RetentionPolicy.RUNTIME)
51+
@interface WithEmptyArray {
52+
String[] value();
53+
}
54+
55+
@Retention(RetentionPolicy.RUNTIME)
56+
@interface WithSingleElement {
57+
String[] value();
58+
}
59+
60+
@Retention(RetentionPolicy.RUNTIME)
61+
@interface WithMultipleArgs {
62+
int[] ints();
63+
float floatVal();
64+
Nested[] annots();
65+
Class<?> clazz();
66+
String[] value();
67+
}
68+
class A {
69+
static final String CONST = "VALUE OF CONST";
70+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object Test:
2+
def main(args: Array[String]): Unit =
3+
annots.runTest(classOf[annots.Use_0])
4+
println()
5+
annots.runTest(classOf[annots.Use_1])
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package annots;
2+
3+
@WithClass(arg = A.class)
4+
@WithClassDefaultName(A.class)
5+
@WithString(arg = "SOME STRING")
6+
@WithReference(arg = A.CONST)
7+
@WithBoolean(arg = false)
8+
@WithFloat(arg = 13.7f)
9+
@WithNested(arg = @Nested("VALUE"))
10+
@WithArray({ "a", "b", "c" })
11+
@WithEmptyArray({})
12+
@WithSingleElement("SINGLE")
13+
@WithMultipleArgs(
14+
ints = {1, 2, 3, },
15+
annots = { @Nested("Value"), @Nested(A.CONST) },
16+
floatVal = 13.7f,
17+
value = "ABC",
18+
clazz = A.class
19+
)
20+
public class Use_0 {}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package annots;
2+
3+
@WithClass(arg = A.class)
4+
@WithClassDefaultName(A.class)
5+
@WithString(arg = "SOME STRING")
6+
@WithReference(arg = A.CONST)
7+
@WithBoolean(arg = false)
8+
@WithFloat(arg = 13.7f)
9+
@WithNested(arg = @Nested("VALUE"))
10+
@WithArray({"a", "b", "c"})
11+
@WithEmptyArray({})
12+
@WithSingleElement("SINGLE")
13+
@WithMultipleArgs(
14+
ints = { 1, 2, 3, },
15+
annots = { @Nested("Value"),
16+
@Nested(A.CONST) },
17+
floatVal = 13.7f,
18+
value = "ABC",
19+
clazz = A.class
20+
)
21+
public class Use_1 {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package annots
2+
3+
def runTest(cls: Class[_]): Unit =
4+
val params =
5+
Option(cls.getAnnotation(classOf[WithClass])).map(_.arg) ::
6+
Option(cls.getAnnotation(classOf[WithClassDefaultName])).map(_.value) ::
7+
Option(cls.getAnnotation(classOf[WithString])).map(_.arg) ::
8+
Option(cls.getAnnotation(classOf[WithReference])).map(_.arg) ::
9+
Option(cls.getAnnotation(classOf[WithBoolean])).map(_.arg) ::
10+
Option(cls.getAnnotation(classOf[WithFloat])).map(_.arg) ::
11+
Option(cls.getAnnotation(classOf[WithNested])).map(_.arg.value) ::
12+
Option(cls.getAnnotation(classOf[WithArray])).map(_.value.toList) ::
13+
Option(cls.getAnnotation(classOf[WithEmptyArray])).map(_.value.toList) ::
14+
Option(cls.getAnnotation(classOf[WithSingleElement])).map(_.value.toList) ::
15+
Option(cls.getAnnotation(classOf[WithMultipleArgs])).map(_.value.toList) ::
16+
Nil
17+
params.flatten.foreach(println)

0 commit comments

Comments
 (0)