Skip to content

Commit f915648

Browse files
committed
Require named arguments for java defined annotations
1 parent 6c4eace commit f915648

File tree

11 files changed

+105
-0
lines changed

11 files changed

+105
-0
lines changed

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
214214
case UnusedSymbolID // errorNumber: 198
215215
case TailrecNestedCallID //errorNumber: 199
216216
case FinalLocalDefID // errorNumber: 200
217+
case NonNamedArgumentInJavaAnnotationID // errorNumber: 201
217218

218219
def errorNumber = ordinal - 1
219220

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3288,3 +3288,25 @@ object UnusedSymbol {
32883288
def privateMembers(using Context): UnusedSymbol = new UnusedSymbol(i"unused private member")
32893289
def patVars(using Context): UnusedSymbol = new UnusedSymbol(i"unused pattern variable")
32903290
}
3291+
3292+
class NonNamedArgumentInJavaAnnotation(using Context) extends SyntaxMsg(NonNamedArgumentInJavaAnnotationID):
3293+
3294+
override protected def msg(using Context): String =
3295+
"Named arguments are required for Java defined annotations"
3296+
3297+
override protected def explain(using Context): String =
3298+
i"""Starting from Scala 3.6.0, named arguments are required for Java defined annotations.
3299+
|Java defined annotations don't have an exact constructor representation
3300+
|and we previously relied on the order of the fields to create one.
3301+
|One possible issue with this representation is the reordering of the fields.
3302+
|Lets take the following example:
3303+
|
3304+
| public @interface Annotation {
3305+
| int a() default 41;
3306+
| int b() default 42;
3307+
| }
3308+
|
3309+
|Reordering the fields is binary-compatible but it might affect the meaning of @Annotation(1)
3310+
"""
3311+
3312+
end NonNamedArgumentInJavaAnnotation

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,17 @@ trait Checking {
15271527
// TODO: Add more checks here
15281528
}
15291529

1530+
/** check that parameters of a java defined annotations are all named arguments if we have more than one parameter */
1531+
def checkNamedArgumentForJavaAnnotation(annot: untpd.Tree)(using Context): Unit =
1532+
assert(annot.symbol.is(JavaDefined))
1533+
annot match
1534+
case untpd.Apply(_, params) if params.length > 1 =>
1535+
for param <- params
1536+
if !param.isInstanceOf[untpd.NamedArg]
1537+
do report.error(NonNamedArgumentInJavaAnnotation(), param)
1538+
case _ =>
1539+
end checkNamedArgumentForJavaAnnotation
1540+
15301541
/** Check that symbol's external name does not clash with symbols defined in the same scope */
15311542
def checkNoTargetNameConflict(stats: List[Tree])(using Context): Unit =
15321543
var seen = Set[Name]()

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2737,6 +2737,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27372737
lazy val annotCtx = annotContext(mdef, sym)
27382738
// necessary in order to mark the typed ahead annotations as definitely typed:
27392739
for (annot <- mdef.mods.annotations)
2740+
if (annot.symbol.is(JavaDefined)) then
2741+
checkNamedArgumentForJavaAnnotation(annot)
27402742
val annot1 = typedAnnotation(annot)(using annotCtx)
27412743
checkAnnotApplicable(annot1, sym)
27422744
if Annotations.annotClass(annot1) == defn.NowarnAnnot then

tests/neg/i20554.check

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
-- [E201] Syntax Error: tests/neg/i20554/Test.scala:3:12 ---------------------------------------------------------------
2+
3 |@Annotation(3, 4) // error // error
3+
| ^
4+
| Named arguments are required for Java defined annotations
5+
|---------------------------------------------------------------------------------------------------------------------
6+
| Explanation (enabled by `-explain`)
7+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8+
| Starting from Scala 3.6.0, named arguments are required for Java defined annotations.
9+
| Java defined annotations don't have an exact constructor representation
10+
| and we previously relied on the order of the fields to create one.
11+
| One possible issue with this representation is the reordering of the fields.
12+
| Lets take the following example:
13+
|
14+
| public @interface Annotation {
15+
| int a() default 41;
16+
| int b() default 42;
17+
| }
18+
|
19+
| Reordering the fields is binary-compatible but it might affect the meaning of @Annotation(1)
20+
|
21+
---------------------------------------------------------------------------------------------------------------------
22+
-- [E201] Syntax Error: tests/neg/i20554/Test.scala:3:15 ---------------------------------------------------------------
23+
3 |@Annotation(3, 4) // error // error
24+
| ^
25+
| Named arguments are required for Java defined annotations
26+
|---------------------------------------------------------------------------------------------------------------------
27+
| Explanation (enabled by `-explain`)
28+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
29+
| Starting from Scala 3.6.0, named arguments are required for Java defined annotations.
30+
| Java defined annotations don't have an exact constructor representation
31+
| and we previously relied on the order of the fields to create one.
32+
| One possible issue with this representation is the reordering of the fields.
33+
| Lets take the following example:
34+
|
35+
| public @interface Annotation {
36+
| int a() default 41;
37+
| int b() default 42;
38+
| }
39+
|
40+
| Reordering the fields is binary-compatible but it might affect the meaning of @Annotation(1)
41+
|
42+
---------------------------------------------------------------------------------------------------------------------

tests/neg/i20554/Annotation.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public @interface Annotation {
2+
int a() default 41;
3+
int b() default 42;
4+
int c() default 43;
5+
}

tests/neg/i20554/Test.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//> using options -explain
2+
3+
@Annotation(3, 4) // error // error
4+
class Test

tests/pos/i20554/Annotation.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public @interface Annotation {
2+
int a() default 41;
3+
int b() default 42;
4+
int c() default 43;
5+
}

tests/pos/i20554/Container.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
public @interface Container {
2+
SimpleAnnotation[] value();
3+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import java.lang.annotation.Repeatable;
2+
3+
@Repeatable(Container.class)
4+
public @interface SimpleAnnotation {
5+
int a() default 1;
6+
}

tests/pos/i20554/Test.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
@Container(Array(new SimpleAnnotation(), new SimpleAnnotation(1), new SimpleAnnotation(a = 1)))
3+
@Annotation(a = 1, b = 2)
4+
class Test

0 commit comments

Comments
 (0)