diff --git a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala index e3613e3f783a..cb5e8a7b314c 100644 --- a/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala +++ b/compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala @@ -214,6 +214,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe case UnusedSymbolID // errorNumber: 198 case TailrecNestedCallID //errorNumber: 199 case FinalLocalDefID // errorNumber: 200 + case NonNamedArgumentInJavaAnnotationID // errorNumber: 201 def errorNumber = ordinal - 1 diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index 1d906130d4e4..f112b6fb5aa7 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -3288,3 +3288,25 @@ object UnusedSymbol { def privateMembers(using Context): UnusedSymbol = new UnusedSymbol(i"unused private member") def patVars(using Context): UnusedSymbol = new UnusedSymbol(i"unused pattern variable") } + +class NonNamedArgumentInJavaAnnotation(using Context) extends SyntaxMsg(NonNamedArgumentInJavaAnnotationID): + + override protected def msg(using Context): String = + "Named arguments are required for Java defined annotations" + + override protected def explain(using Context): String = + i"""Starting from Scala 3.6.0, named arguments are required for Java defined annotations. + |Java defined annotations don't have an exact constructor representation + |and we previously relied on the order of the fields to create one. + |One possible issue with this representation is the reordering of the fields. + |Lets take the following example: + | + | public @interface Annotation { + | int a() default 41; + | int b() default 42; + | } + | + |Reordering the fields is binary-compatible but it might affect the meaning of @Annotation(1) + """ + +end NonNamedArgumentInJavaAnnotation diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 421f00e61584..cbeaf95b91fc 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1527,6 +1527,17 @@ trait Checking { // TODO: Add more checks here } + /** check that parameters of a java defined annotations are all named arguments if we have more than one parameter */ + def checkNamedArgumentForJavaAnnotation(annot: untpd.Tree)(using Context): Unit = + assert(annot.symbol.is(JavaDefined)) + annot match + case untpd.Apply(_, params) if params.length > 1 => + for param <- params + if !param.isInstanceOf[untpd.NamedArg] + do report.error(NonNamedArgumentInJavaAnnotation(), param) + case _ => + end checkNamedArgumentForJavaAnnotation + /** Check that symbol's external name does not clash with symbols defined in the same scope */ def checkNoTargetNameConflict(stats: List[Tree])(using Context): Unit = var seen = Set[Name]() diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 32f04c13a3d6..dea675cef71c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2737,6 +2737,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer lazy val annotCtx = annotContext(mdef, sym) // necessary in order to mark the typed ahead annotations as definitely typed: for (annot <- mdef.mods.annotations) + if (annot.symbol.is(JavaDefined)) then + checkNamedArgumentForJavaAnnotation(annot) val annot1 = typedAnnotation(annot)(using annotCtx) checkAnnotApplicable(annot1, sym) if Annotations.annotClass(annot1) == defn.NowarnAnnot then diff --git a/tests/neg/i20554.check b/tests/neg/i20554.check new file mode 100644 index 000000000000..4faa4da6fd09 --- /dev/null +++ b/tests/neg/i20554.check @@ -0,0 +1,42 @@ +-- [E201] Syntax Error: tests/neg/i20554/Test.scala:3:12 --------------------------------------------------------------- +3 |@Annotation(3, 4) // error // error + | ^ + | Named arguments are required for Java defined annotations + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Starting from Scala 3.6.0, named arguments are required for Java defined annotations. + | Java defined annotations don't have an exact constructor representation + | and we previously relied on the order of the fields to create one. + | One possible issue with this representation is the reordering of the fields. + | Lets take the following example: + | + | public @interface Annotation { + | int a() default 41; + | int b() default 42; + | } + | + | Reordering the fields is binary-compatible but it might affect the meaning of @Annotation(1) + | + --------------------------------------------------------------------------------------------------------------------- +-- [E201] Syntax Error: tests/neg/i20554/Test.scala:3:15 --------------------------------------------------------------- +3 |@Annotation(3, 4) // error // error + | ^ + | Named arguments are required for Java defined annotations + |--------------------------------------------------------------------------------------------------------------------- + | Explanation (enabled by `-explain`) + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Starting from Scala 3.6.0, named arguments are required for Java defined annotations. + | Java defined annotations don't have an exact constructor representation + | and we previously relied on the order of the fields to create one. + | One possible issue with this representation is the reordering of the fields. + | Lets take the following example: + | + | public @interface Annotation { + | int a() default 41; + | int b() default 42; + | } + | + | Reordering the fields is binary-compatible but it might affect the meaning of @Annotation(1) + | + --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/neg/i20554/Annotation.java b/tests/neg/i20554/Annotation.java new file mode 100644 index 000000000000..3f8389517eae --- /dev/null +++ b/tests/neg/i20554/Annotation.java @@ -0,0 +1,5 @@ +public @interface Annotation { + int a() default 41; + int b() default 42; + int c() default 43; +} diff --git a/tests/neg/i20554/Test.scala b/tests/neg/i20554/Test.scala new file mode 100644 index 000000000000..ee4adc2bbbea --- /dev/null +++ b/tests/neg/i20554/Test.scala @@ -0,0 +1,4 @@ +//> using options -explain + +@Annotation(3, 4) // error // error +class Test \ No newline at end of file diff --git a/tests/pos/i20554/Annotation.java b/tests/pos/i20554/Annotation.java new file mode 100644 index 000000000000..3f8389517eae --- /dev/null +++ b/tests/pos/i20554/Annotation.java @@ -0,0 +1,5 @@ +public @interface Annotation { + int a() default 41; + int b() default 42; + int c() default 43; +} diff --git a/tests/pos/i20554/Container.java b/tests/pos/i20554/Container.java new file mode 100644 index 000000000000..047363d52bac --- /dev/null +++ b/tests/pos/i20554/Container.java @@ -0,0 +1,3 @@ +public @interface Container { + SimpleAnnotation[] value(); +} diff --git a/tests/pos/i20554/SimpleAnnotation.java b/tests/pos/i20554/SimpleAnnotation.java new file mode 100644 index 000000000000..33c114c2dfad --- /dev/null +++ b/tests/pos/i20554/SimpleAnnotation.java @@ -0,0 +1,6 @@ +import java.lang.annotation.Repeatable; + +@Repeatable(Container.class) +public @interface SimpleAnnotation { + int a() default 1; +} diff --git a/tests/pos/i20554/Test.scala b/tests/pos/i20554/Test.scala new file mode 100644 index 000000000000..427700a963c4 --- /dev/null +++ b/tests/pos/i20554/Test.scala @@ -0,0 +1,4 @@ + +@Container(Array(new SimpleAnnotation(), new SimpleAnnotation(1), new SimpleAnnotation(a = 1))) +@Annotation(a = 1, b = 2) +class Test \ No newline at end of file