From 569b3e341db3e12d08fdafb62a6bfa2ffbdc5540 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sun, 9 Oct 2022 00:55:19 +0200 Subject: [PATCH 1/4] JavaParsers: Java interfaces should not have the Abstract flag The Trait and Abstract flags are not supposed to be set together, and we don't set Abstract when deserializing Java interfaces from classfiles so we shouldn't do it when reading them from source code either. While we're at it, this commit also drops Flags.Trait which is implied by Flags.JavaInterface. --- compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 4611554a01a3..20e1a4b27e58 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -822,7 +822,7 @@ object JavaParsers { val iface = atSpan(start, nameOffset) { TypeDef( name, - makeTemplate(parents, body, tparams, false)).withMods(mods | Flags.Trait | Flags.JavaInterface | Flags.Abstract) + makeTemplate(parents, body, tparams, false)).withMods(mods | Flags.JavaInterface) } addCompanionObject(statics, iface) } From 5c93bdbdd6542a88289f4967b62904461082ce5e Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 5 Oct 2022 11:18:49 +0200 Subject: [PATCH 2/4] Java annotations should not extend scala.annotation.{ClassfileAnnotation,Annotation} This compiler fiction isn't necessary to handle them as annotations and could lead to runtime crashes when upcasting an annotation value. --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 2 +- compiler/src/dotty/tools/dotc/core/StdNames.scala | 1 - compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- .../dotty/tools/dotc/core/classfile/ClassfileParser.scala | 3 +-- compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala | 7 +++---- .../dotty/tools/dotc/transform/RepeatableAnnotations.scala | 3 ++- compiler/src/dotty/tools/dotc/typer/Applications.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Namer.scala | 2 +- compiler/src/dotty/tools/dotc/typer/Typer.scala | 4 ++-- tests/neg/java-ann-extends-separate/Ann_1.java | 3 +++ tests/neg/java-ann-extends-separate/Test_2.scala | 2 ++ tests/neg/java-ann-extends/Ann.java | 3 +++ tests/neg/java-ann-extends/Test.scala | 2 ++ 13 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 tests/neg/java-ann-extends-separate/Ann_1.java create mode 100644 tests/neg/java-ann-extends-separate/Test_2.scala create mode 100644 tests/neg/java-ann-extends/Ann.java create mode 100644 tests/neg/java-ann-extends/Test.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 104a81ec9977..be08738f15cd 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -968,8 +968,8 @@ class Definitions { @tu lazy val Caps_unsafeBoxFunArg: Symbol = CapsUnsafeModule.requiredMethod("unsafeBoxFunArg") // Annotation base classes + @tu lazy val JavaAnnotationClass: ClassSymbol = requiredClass("java.lang.annotation.Annotation") @tu lazy val AnnotationClass: ClassSymbol = requiredClass("scala.annotation.Annotation") - @tu lazy val ClassfileAnnotationClass: ClassSymbol = requiredClass("scala.annotation.ClassfileAnnotation") @tu lazy val StaticAnnotationClass: ClassSymbol = requiredClass("scala.annotation.StaticAnnotation") @tu lazy val RefiningAnnotationClass: ClassSymbol = requiredClass("scala.annotation.RefiningAnnotation") diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index c0aca9d8abf4..50c96191143c 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -243,7 +243,6 @@ object StdNames { final val ToString: N = "ToString" final val Xor: N = "^" - final val ClassfileAnnotation: N = "ClassfileAnnotation" final val ClassManifest: N = "ClassManifest" final val Enum: N = "Enum" final val Group: N = "Group" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index f0ab8253da72..a81705a81aae 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -808,7 +808,7 @@ object SymDenotations { /** Is this a Scala or Java annotation ? */ def isAnnotation(using Context): Boolean = - isClass && derivesFrom(defn.AnnotationClass) + isClass && (derivesFrom(defn.AnnotationClass) || derivesFrom(defn.JavaAnnotationClass)) /** Is this symbol a class that extends `java.io.Serializable` ? */ def isSerializable(using Context): Boolean = diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 4763cd25ff41..2e3dca9cd04a 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -167,7 +167,7 @@ class ClassfileParser( val superType = if (isAnnotation) { in.nextChar - defn.AnnotationClass.typeRef + defn.ObjectType } else if (classRoot.symbol == defn.ComparableClass || classRoot.symbol == defn.JavaCloneableClass || @@ -186,7 +186,6 @@ class ClassfileParser( // Consequently, no best implicit for the "Integral" evidence parameter of "range" // is found. Previously, this worked because of weak conformance, which has been dropped. - if (isAnnotation) ifaces = defn.ClassfileAnnotationClass.typeRef :: ifaces superType :: ifaces } diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 20e1a4b27e58..ce9cb6f86506 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -858,10 +858,9 @@ object JavaParsers { } (statics.toList, members.toList) } - def annotationParents: List[Select] = List( - scalaAnnotationDot(tpnme.Annotation), - Select(javaLangDot(nme.annotation), tpnme.Annotation), - scalaAnnotationDot(tpnme.ClassfileAnnotation) + def annotationParents: List[Tree] = List( + javaLangObject(), + Select(javaLangDot(nme.annotation), tpnme.Annotation) ) def annotationDecl(start: Offset, mods: Modifiers): List[Tree] = { accept(AT) diff --git a/compiler/src/dotty/tools/dotc/transform/RepeatableAnnotations.scala b/compiler/src/dotty/tools/dotc/transform/RepeatableAnnotations.scala index e8f8a80e1a0d..1cf687187eeb 100644 --- a/compiler/src/dotty/tools/dotc/transform/RepeatableAnnotations.scala +++ b/compiler/src/dotty/tools/dotc/transform/RepeatableAnnotations.scala @@ -10,6 +10,7 @@ import Symbols.defn import Constants._ import Types._ import Decorators._ +import Flags._ import scala.collection.mutable @@ -33,7 +34,7 @@ class RepeatableAnnotations extends MiniPhase: val annsByType = stableGroupBy(annotations, _.symbol) annsByType.flatMap { case (_, a :: Nil) => a :: Nil - case (sym, anns) if sym.derivesFrom(defn.ClassfileAnnotationClass) => + case (sym, anns) if sym.is(JavaDefined) => sym.getAnnotation(defn.JavaRepeatableAnnot).flatMap(_.argumentConstant(0)) match case Some(Constant(containerTpe: Type)) => val clashingAnns = annsByType.getOrElse(containerTpe.classSymbol, Nil) diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index bfebb7dbdabb..c24eac187169 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -547,7 +547,7 @@ trait Applications extends Compatibility { /** Is `sym` a constructor of a Java-defined annotation? */ def isJavaAnnotConstr(sym: Symbol): Boolean = - sym.is(JavaDefined) && sym.isConstructor && sym.owner.derivesFrom(defn.AnnotationClass) + sym.is(JavaDefined) && sym.isConstructor && sym.owner.derivesFrom(defn.JavaAnnotationClass) /** Match re-ordered arguments against formal parameters * @param n The position of the first parameter in formals in `methType`. diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index ad8d0e50d348..5c1c8d6debdf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1619,7 +1619,7 @@ class Namer { typer: Typer => typedAhead(tree, typer.typedExpr(_, pt)) def typedAheadAnnotation(tree: Tree)(using Context): tpd.Tree = - typedAheadExpr(tree, defn.AnnotationClass.typeRef) + typedAheadExpr(tree, defn.AnnotationClass.typeRef | defn.JavaAnnotationClass.typeRef) def typedAheadAnnotationClass(tree: Tree)(using Context): Symbol = tree match { case Apply(fn, _) => typedAheadAnnotationClass(fn) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 33638df54fb1..71a8dedf5a32 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2259,7 +2259,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } def typedAnnotation(annot: untpd.Tree)(using Context): Tree = - checkAnnotArgs(typed(annot, defn.AnnotationClass.typeRef)) + checkAnnotArgs(typed(annot, defn.AnnotationClass.typeRef | defn.JavaAnnotationClass.typeRef)) def registerNowarn(tree: Tree, mdef: untpd.Tree)(using Context): Unit = val annot = Annotations.Annotation(tree) @@ -2669,7 +2669,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer end typedPackageDef def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree = { - val annot1 = typedExpr(tree.annot, defn.AnnotationClass.typeRef) + val annot1 = typedExpr(tree.annot, defn.AnnotationClass.typeRef | defn.JavaAnnotationClass.typeRef) val annotCls = Annotations.annotClass(annot1) if annotCls == defn.NowarnAnnot then registerNowarn(annot1, tree) diff --git a/tests/neg/java-ann-extends-separate/Ann_1.java b/tests/neg/java-ann-extends-separate/Ann_1.java new file mode 100644 index 000000000000..97184df24c83 --- /dev/null +++ b/tests/neg/java-ann-extends-separate/Ann_1.java @@ -0,0 +1,3 @@ +public @interface Ann_1 { + int value(); +} diff --git a/tests/neg/java-ann-extends-separate/Test_2.scala b/tests/neg/java-ann-extends-separate/Test_2.scala new file mode 100644 index 000000000000..4e73b71679f6 --- /dev/null +++ b/tests/neg/java-ann-extends-separate/Test_2.scala @@ -0,0 +1,2 @@ +def test(x: Ann_1) = + val y: scala.annotation.Annotation = x // error diff --git a/tests/neg/java-ann-extends/Ann.java b/tests/neg/java-ann-extends/Ann.java new file mode 100644 index 000000000000..9ae845a8af63 --- /dev/null +++ b/tests/neg/java-ann-extends/Ann.java @@ -0,0 +1,3 @@ +public @interface Ann { + int value(); +} diff --git a/tests/neg/java-ann-extends/Test.scala b/tests/neg/java-ann-extends/Test.scala new file mode 100644 index 000000000000..629f1daa9acc --- /dev/null +++ b/tests/neg/java-ann-extends/Test.scala @@ -0,0 +1,2 @@ +def test(x: Ann) = + val y: scala.annotation.Annotation = x // error From 3125665da2e9a15d88b1a6006d60e6b14e1bd34a Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 5 Oct 2022 16:11:49 +0200 Subject: [PATCH 3/4] Represent Java annotations as interfaces so they can be extended Previously we treated Java annotations as if they were classes, like Scala annotations. For example, given @interface Ann { int foo(); } we pretended it was defined as: abstract class Ann(foo: Int) extends java.lang.annotation.Annotation { def foo(): Int } We take advantage of this to type annotation trees as if they were new calls, for example `@Ann(1)` is typed as `new Ann(1)`. Pretending that annotations are classes is fine most of the time and matches what Scala 2.12 did, but it's problematic because the JVM treats annotations as interfaces. In practice this was only an issue with code trying to extend Java annotations, which would either be rejected at compile-time or miscompiled before this commit. This commit switches our representation of annotations to be trait-based instead: trait Ann(foo: Int) extends java.lang.annotation.Annotation { def foo(): Int } Classes are then free to extend annotations using the same pattern as in Scala 2.13: class Foo extends Ann {val annotationType = classOf[Retention]; def foo(): Int = 1} Notice that we still pretend these traits have constructors, this lets us type annotation trees in much the same way as before, and crucially it means that macros that depended on the exact tree shape of annotation trees can continue to work, as demonstrated by the annot-java-tree test extracted from wartremover. To prevent miscompilation issues, we disallow passing arguments to the annotation constructor in `extends` clause. This change is not fully backwards source compatible: this is illustrated by the diffs in tests/run/repeatable/Test_1.scala: -@FirstLevel_0(Array(Plain_0(4), Plain_0(5))) +@FirstLevel_0(Array(new Plain_0(4), new Plain_0(5))) Here, FirstLevel_0 takes an array of `Plain_0` annotations as arguments, and in previous releases of Scala 3 we could put `Plain_0(4)` in this array without `new`. This is because the compiler generates a "constructor proxy" apply method for classes, but this no longer works since `Plain_0` is now a trait. While we could potentially tweak the constructor proxy logic to handle this case, it seems simpler to require a `new` here, both because Scala 2 does it too and because it ensures that user code that inspects the annotation tree does not have to deal with constructor proxies. The treatment of default arguments to annotations stays unchanged from 85cd1cf3f2e5. Fixes #5690. Fixes #12840. Fixes #14199. --- .../core/classfile/ClassfileConstants.scala | 19 +++++------- .../dotc/core/classfile/ClassfileParser.scala | 8 ++--- .../tools/dotc/parsing/JavaParsers.scala | 2 +- .../src/dotty/tools/dotc/typer/Checking.scala | 2 ++ .../src/dotty/tools/dotc/typer/Typer.scala | 3 ++ tests/{pending => }/neg/i5690.scala | 0 tests/neg/java-ann-super-class/Ann.java | 3 ++ tests/neg/java-ann-super-class/Test.scala | 9 ++++++ tests/neg/java-ann-super-class2/Ann.java | 3 ++ tests/neg/java-ann-super-class2/Test.scala | 3 ++ tests/neg/java-ann-super-class3/Ann.java | 3 ++ tests/neg/java-ann-super-class3/Test.scala | 3 ++ tests/neg/repeatable/Test_1.scala | 6 ++-- tests/pos/i5690.scala | 8 ----- .../annot-java-tree/AnnoMacro.scala | 29 +++++++++++++++++++ tests/run-macros/annot-java-tree/S.scala | 4 +++ .../java-ann-super-class-separate/Ann_1.java | 4 +++ .../Test_2.scala | 21 ++++++++++++++ tests/run/java-ann-super-class/Ann.java | 4 +++ tests/run/java-ann-super-class/Test.scala | 21 ++++++++++++++ tests/run/repeatable/Test_1.scala | 4 +-- tests/run/t9400.scala | 28 ++++++++++++++++++ 22 files changed, 156 insertions(+), 31 deletions(-) rename tests/{pending => }/neg/i5690.scala (100%) create mode 100644 tests/neg/java-ann-super-class/Ann.java create mode 100644 tests/neg/java-ann-super-class/Test.scala create mode 100644 tests/neg/java-ann-super-class2/Ann.java create mode 100644 tests/neg/java-ann-super-class2/Test.scala create mode 100644 tests/neg/java-ann-super-class3/Ann.java create mode 100644 tests/neg/java-ann-super-class3/Test.scala delete mode 100644 tests/pos/i5690.scala create mode 100644 tests/run-macros/annot-java-tree/AnnoMacro.scala create mode 100644 tests/run-macros/annot-java-tree/S.scala create mode 100644 tests/run/java-ann-super-class-separate/Ann_1.java create mode 100644 tests/run/java-ann-super-class-separate/Test_2.scala create mode 100644 tests/run/java-ann-super-class/Ann.java create mode 100644 tests/run/java-ann-super-class/Test.scala create mode 100644 tests/run/t9400.scala diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala index 3b05ee351b86..f50033165c0d 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala @@ -353,18 +353,15 @@ object ClassfileConstants { if (jflag == 0) base else base | translateFlag(jflag) private def translateFlags(jflags: Int, baseFlags: FlagSet): FlagSet = { - val nflags = - if ((jflags & JAVA_ACC_ANNOTATION) == 0) jflags - else jflags & ~(JAVA_ACC_ABSTRACT | JAVA_ACC_INTERFACE) // annotations are neither abstract nor interfaces var res: FlagSet = baseFlags | JavaDefined - res = addFlag(res, nflags & JAVA_ACC_PRIVATE) - res = addFlag(res, nflags & JAVA_ACC_PROTECTED) - res = addFlag(res, nflags & JAVA_ACC_FINAL) - res = addFlag(res, nflags & JAVA_ACC_SYNTHETIC) - res = addFlag(res, nflags & JAVA_ACC_STATIC) - res = addFlag(res, nflags & JAVA_ACC_ENUM) - res = addFlag(res, nflags & JAVA_ACC_ABSTRACT) - res = addFlag(res, nflags & JAVA_ACC_INTERFACE) + res = addFlag(res, jflags & JAVA_ACC_PRIVATE) + res = addFlag(res, jflags & JAVA_ACC_PROTECTED) + res = addFlag(res, jflags & JAVA_ACC_FINAL) + res = addFlag(res, jflags & JAVA_ACC_SYNTHETIC) + res = addFlag(res, jflags & JAVA_ACC_STATIC) + res = addFlag(res, jflags & JAVA_ACC_ENUM) + res = addFlag(res, jflags & JAVA_ACC_ABSTRACT) + res = addFlag(res, jflags & JAVA_ACC_INTERFACE) res } diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 2e3dca9cd04a..33a1e1dd6e73 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -165,11 +165,7 @@ class ClassfileParser( * Updates the read pointer of 'in'. */ def parseParents: List[Type] = { val superType = - if (isAnnotation) { - in.nextChar - defn.ObjectType - } - else if (classRoot.symbol == defn.ComparableClass || + if (classRoot.symbol == defn.ComparableClass || classRoot.symbol == defn.JavaCloneableClass || classRoot.symbol == defn.JavaSerializableClass) { // Treat these interfaces as universal traits @@ -844,7 +840,7 @@ class ClassfileParser( class AnnotConstructorCompleter(classInfo: TempClassInfoType) extends LazyType { def complete(denot: SymDenotation)(using Context): Unit = { - val attrs = classInfo.decls.toList.filter(sym => sym.isTerm && sym != denot.symbol) + val attrs = classInfo.decls.toList.filter(sym => sym.isTerm && sym != denot.symbol && sym.name != nme.CONSTRUCTOR) val paramNames = attrs.map(_.name.asTermName) val paramTypes = attrs.map(_.info.resultType) denot.info = MethodType(paramNames, paramTypes, classRoot.typeRef) diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index ce9cb6f86506..d8739c55ebb7 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -876,7 +876,7 @@ object JavaParsers { List(constructorParams), TypeTree(), EmptyTree).withMods(Modifiers(Flags.JavaDefined)) val templ = makeTemplate(annotationParents, constr :: body, List(), true) val annot = atSpan(start, nameOffset) { - TypeDef(name, templ).withMods(mods | Flags.Abstract) + TypeDef(name, templ).withMods(mods | Flags.JavaInterface) } addCompanionObject(statics, annot) } diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index ed9cce9ca0eb..287a07a6ce3e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1110,6 +1110,8 @@ trait Checking { def checkParentCall(call: Tree, caller: ClassSymbol)(using Context): Unit = if (!ctx.isAfterTyper) { val called = call.tpe.classSymbol + if (called.derivesFrom(defn.JavaAnnotationClass)) + report.error(i"${called.name} must appear without any argument to be a valid class parent because it is a Java annotation", call.srcPos) if (caller.is(Trait)) report.error(i"$caller may not call constructor of $called", call.srcPos) else if (called.is(Trait) && !caller.mixins.contains(called)) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 71a8dedf5a32..d89a274a9eea 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2595,6 +2595,9 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer */ def ensureConstrCall(cls: ClassSymbol, parent: Tree, psym: Symbol)(using Context): Tree = if parent.isType && !cls.is(Trait) && !cls.is(JavaDefined) && psym.isClass + // Annotations are represented as traits with constructors, but should + // never be called as such outside of annotation trees. + && !psym.derivesFrom(defn.JavaAnnotationClass) && (!psym.is(Trait) || psym.primaryConstructor.info.takesParams && !cls.superClass.isSubClass(psym)) then typed(untpd.New(untpd.TypedSplice(parent), Nil)) diff --git a/tests/pending/neg/i5690.scala b/tests/neg/i5690.scala similarity index 100% rename from tests/pending/neg/i5690.scala rename to tests/neg/i5690.scala diff --git a/tests/neg/java-ann-super-class/Ann.java b/tests/neg/java-ann-super-class/Ann.java new file mode 100644 index 000000000000..9ae845a8af63 --- /dev/null +++ b/tests/neg/java-ann-super-class/Ann.java @@ -0,0 +1,3 @@ +public @interface Ann { + int value(); +} diff --git a/tests/neg/java-ann-super-class/Test.scala b/tests/neg/java-ann-super-class/Test.scala new file mode 100644 index 000000000000..cf2f72d2f633 --- /dev/null +++ b/tests/neg/java-ann-super-class/Test.scala @@ -0,0 +1,9 @@ +class Bar extends Ann(1) { // error + def value = 1 + def annotationType = classOf[Ann] +} + +def test = + // Typer errors + new Ann // error + new Ann(1) {} // error diff --git a/tests/neg/java-ann-super-class2/Ann.java b/tests/neg/java-ann-super-class2/Ann.java new file mode 100644 index 000000000000..9ae845a8af63 --- /dev/null +++ b/tests/neg/java-ann-super-class2/Ann.java @@ -0,0 +1,3 @@ +public @interface Ann { + int value(); +} diff --git a/tests/neg/java-ann-super-class2/Test.scala b/tests/neg/java-ann-super-class2/Test.scala new file mode 100644 index 000000000000..d5c22860899c --- /dev/null +++ b/tests/neg/java-ann-super-class2/Test.scala @@ -0,0 +1,3 @@ +def test = + // Posttyper errors + new Ann(1) // error diff --git a/tests/neg/java-ann-super-class3/Ann.java b/tests/neg/java-ann-super-class3/Ann.java new file mode 100644 index 000000000000..9ae845a8af63 --- /dev/null +++ b/tests/neg/java-ann-super-class3/Ann.java @@ -0,0 +1,3 @@ +public @interface Ann { + int value(); +} diff --git a/tests/neg/java-ann-super-class3/Test.scala b/tests/neg/java-ann-super-class3/Test.scala new file mode 100644 index 000000000000..8fd9791e6fe3 --- /dev/null +++ b/tests/neg/java-ann-super-class3/Test.scala @@ -0,0 +1,3 @@ +def test = + // Refchecks error + new Ann {} // error diff --git a/tests/neg/repeatable/Test_1.scala b/tests/neg/repeatable/Test_1.scala index 3779b6ffa4a8..6466da95dfa8 100644 --- a/tests/neg/repeatable/Test_1.scala +++ b/tests/neg/repeatable/Test_1.scala @@ -6,11 +6,11 @@ import repeatable._ @FirstLevel_0(Array()) // error trait U -@FirstLevel_0(Array(Plain_0(4), Plain_0(5))) -@FirstLevel_0(Array(Plain_0(6), Plain_0(7))) +@FirstLevel_0(Array(new Plain_0(4), new Plain_0(5))) +@FirstLevel_0(Array(new Plain_0(6), new Plain_0(7))) @SecondLevel_0(Array()) // error trait T @SecondLevel_0(Array()) @SecondLevel_0(Array()) // error -trait S \ No newline at end of file +trait S diff --git a/tests/pos/i5690.scala b/tests/pos/i5690.scala deleted file mode 100644 index 25c81b72d0b6..000000000000 --- a/tests/pos/i5690.scala +++ /dev/null @@ -1,8 +0,0 @@ -// TODO: this should be a compilation error -// program fails at runtime with java.lang.InstantiationError -// see tests/pending/neg/i5690.scala -object AnnotInst{ - def main(a: Array[String]) = { - new java.lang.annotation.Inherited - } -} diff --git a/tests/run-macros/annot-java-tree/AnnoMacro.scala b/tests/run-macros/annot-java-tree/AnnoMacro.scala new file mode 100644 index 000000000000..3dae57868eab --- /dev/null +++ b/tests/run-macros/annot-java-tree/AnnoMacro.scala @@ -0,0 +1,29 @@ +import scala.quoted.* + +inline def checkSuppressWarnings[T]: Unit = ${ checkSuppressWarningsImpl[T] } + +def checkSuppressWarningsImpl[T: Type](using Quotes): Expr[Unit] = + import quotes.reflect.* + val SuppressWarningsSymbol = TypeTree.of[SuppressWarnings].symbol + val sym = TypeRepr.of[T].typeSymbol + // Imitate what wartremover does, so we can avoid unintentionally breaking it: + // https://github.com/wartremover/wartremover/blob/fb18e6eafe9a47823e04960aaf4ec7a9293719ef/core/src/main/scala-3/org/wartremover/WartUniverse.scala#L63-L77 + val actualArgs = sym + .getAnnotation(SuppressWarningsSymbol) + .collect { + case Apply( + Select(_, ""), + Apply(Apply(_, Typed(Repeated(values, _), _) :: Nil), Apply(_, _ :: Nil) :: Nil) :: Nil + ) => + // "-Yexplicit-nulls" + // https://github.com/wartremover/wartremover/issues/660 + values.collect { case Literal(StringConstant(str)) => + str + } + } + .toList + .flatten + val expectedArgs = List("a", "b") + assert(actualArgs == expectedArgs, + s"Expected $expectedArgs arguments for SuppressWarnings annotation of $sym but got $actualArgs") + '{} diff --git a/tests/run-macros/annot-java-tree/S.scala b/tests/run-macros/annot-java-tree/S.scala new file mode 100644 index 000000000000..07065cde46d1 --- /dev/null +++ b/tests/run-macros/annot-java-tree/S.scala @@ -0,0 +1,4 @@ +@SuppressWarnings(Array("a", "b")) class Foo + +@main def Test = + checkSuppressWarnings[Foo] diff --git a/tests/run/java-ann-super-class-separate/Ann_1.java b/tests/run/java-ann-super-class-separate/Ann_1.java new file mode 100644 index 000000000000..b7fcb6952273 --- /dev/null +++ b/tests/run/java-ann-super-class-separate/Ann_1.java @@ -0,0 +1,4 @@ +public @interface Ann_1 { + int bar() default 1; + int baz() default 2; +} diff --git a/tests/run/java-ann-super-class-separate/Test_2.scala b/tests/run/java-ann-super-class-separate/Test_2.scala new file mode 100644 index 000000000000..77da86b4f057 --- /dev/null +++ b/tests/run/java-ann-super-class-separate/Test_2.scala @@ -0,0 +1,21 @@ +// scalajs: --skip + +class Foo extends Ann_1 { + override def bar = 3 + override def baz = 4 + def annotationType = classOf[Ann_1] +} + +object Test { + def main(args: Array[String]): Unit = { + val x = new Foo + val y: Ann_1 = x + val z: Int @Ann_1(1) = 1 + val zz: Int @Ann_1() = 1 + val ann: java.lang.annotation.Annotation = new Ann_1 { + def bar = 3 + def baz = 4 + def annotationType = classOf[Ann_1] + } + } +} diff --git a/tests/run/java-ann-super-class/Ann.java b/tests/run/java-ann-super-class/Ann.java new file mode 100644 index 000000000000..8c40c14fcb59 --- /dev/null +++ b/tests/run/java-ann-super-class/Ann.java @@ -0,0 +1,4 @@ +public @interface Ann { + int bar() default 1; + int baz() default 2; +} diff --git a/tests/run/java-ann-super-class/Test.scala b/tests/run/java-ann-super-class/Test.scala new file mode 100644 index 000000000000..7c5040552cc4 --- /dev/null +++ b/tests/run/java-ann-super-class/Test.scala @@ -0,0 +1,21 @@ +// scalajs: --skip + +class Foo extends Ann { + override def bar = 3 + override def baz = 4 + def annotationType = classOf[Ann] +} + +object Test { + def main(args: Array[String]): Unit = { + val x = new Foo + val y: Ann = x + val z: Int @Ann(1) = 1 + val zz: Int @Ann() = 1 + val ann: java.lang.annotation.Annotation = new Ann { + override def bar = 3 + override def baz = 4 + def annotationType = classOf[Ann] + } + } +} diff --git a/tests/run/repeatable/Test_1.scala b/tests/run/repeatable/Test_1.scala index 2a8654dea6ba..281f91262cce 100644 --- a/tests/run/repeatable/Test_1.scala +++ b/tests/run/repeatable/Test_1.scala @@ -7,8 +7,8 @@ import repeatable._ @Plain_0(3) trait U -@FirstLevel_0(Array(Plain_0(4), Plain_0(5))) -@FirstLevel_0(Array(Plain_0(6), Plain_0(7))) +@FirstLevel_0(Array(new Plain_0(4), new Plain_0(5))) +@FirstLevel_0(Array(new Plain_0(6), new Plain_0(7))) trait T object Test: diff --git a/tests/run/t9400.scala b/tests/run/t9400.scala new file mode 100644 index 000000000000..9f6fda026810 --- /dev/null +++ b/tests/run/t9400.scala @@ -0,0 +1,28 @@ +// scalajs: --skip + +class Deprecation extends Deprecated { + final val annotationType = classOf[Deprecated] + + def forRemoval(): Boolean = false + def since(): String = "" +} + +class Suppression extends SuppressWarnings { + final val annotationType = classOf[SuppressWarnings] + + def value = Array("unchecked") +} + +class Retention(runtime: Boolean) extends java.lang.annotation.Retention { + final val annotationType = classOf[Retention] + + def value = + if (runtime) java.lang.annotation.RetentionPolicy.RUNTIME + else java.lang.annotation.RetentionPolicy.SOURCE +} + +object Test extends App { + new Deprecation + new Suppression + new Retention(true) +} From a685a4f7c97ba448a9a1ac98e039f68542df8b06 Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Thu, 27 Oct 2022 18:15:06 +0200 Subject: [PATCH 4/4] Use a dedicated flag to represent Java annotations A Java interface that extends java.lang.annotation.Annotation might not be a Java annotation, so to prevent false positives we need to keep track of whether an interface is also an annotation when parsing Java sources and bytecode. --- .../tools/backend/jvm/BCodeHelpers.scala | 3 +-- .../dotty/tools/dotc/core/Definitions.scala | 1 - .../src/dotty/tools/dotc/core/Flags.scala | 6 +++--- .../tools/dotc/core/SymDenotations.scala | 2 +- .../core/classfile/ClassfileConstants.scala | 2 ++ .../tools/dotc/parsing/JavaParsers.scala | 2 +- .../dotty/tools/dotc/typer/Applications.scala | 2 +- .../src/dotty/tools/dotc/typer/Checking.scala | 19 ++++++++++++++++++- .../src/dotty/tools/dotc/typer/Namer.scala | 5 +---- .../src/dotty/tools/dotc/typer/Typer.scala | 6 +++--- .../quoted/runtime/impl/QuotesImpl.scala | 1 + library/src/scala/quoted/Quotes.scala | 3 +++ .../neg/java-fake-ann-separate/FakeAnn_1.java | 1 + tests/neg/java-fake-ann-separate/Test_2.scala | 3 +++ tests/neg/java-fake-ann/FakeAnn.java | 1 + tests/neg/java-fake-ann/Test.scala | 2 ++ .../stdlibExperimentalDefinitions.scala | 1 + 17 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 tests/neg/java-fake-ann-separate/FakeAnn_1.java create mode 100644 tests/neg/java-fake-ann-separate/Test_2.scala create mode 100644 tests/neg/java-fake-ann/FakeAnn.java create mode 100644 tests/neg/java-fake-ann/Test.scala diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala index b6d898b3b221..c49b7d9556c9 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala @@ -61,7 +61,6 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { @threadUnsafe lazy val AnnotationRetentionSourceAttr: TermSymbol = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("SOURCE") @threadUnsafe lazy val AnnotationRetentionClassAttr: TermSymbol = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("CLASS") @threadUnsafe lazy val AnnotationRetentionRuntimeAttr: TermSymbol = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("RUNTIME") - @threadUnsafe lazy val JavaAnnotationClass: ClassSymbol = requiredClass("java.lang.annotation.Annotation") val bCodeAsmCommon: BCodeAsmCommon[int.type] = new BCodeAsmCommon(int) @@ -415,7 +414,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { arrAnnotV.visitEnd() } // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape. */ - case t @ Apply(constr, args) if t.tpe.derivesFrom(JavaAnnotationClass) => + case t @ Apply(constr, args) if t.tpe.classSymbol.is(JavaAnnotation) => val typ = t.tpe.classSymbol.denot.info val assocs = assocsFromApply(t) val desc = innerClasesStore.typeDescriptor(typ) // the class descriptor of the nested annotation class diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index be08738f15cd..b43857b7d28c 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -968,7 +968,6 @@ class Definitions { @tu lazy val Caps_unsafeBoxFunArg: Symbol = CapsUnsafeModule.requiredMethod("unsafeBoxFunArg") // Annotation base classes - @tu lazy val JavaAnnotationClass: ClassSymbol = requiredClass("java.lang.annotation.Annotation") @tu lazy val AnnotationClass: ClassSymbol = requiredClass("scala.annotation.Annotation") @tu lazy val StaticAnnotationClass: ClassSymbol = requiredClass("scala.annotation.StaticAnnotation") @tu lazy val RefiningAnnotationClass: ClassSymbol = requiredClass("scala.annotation.RefiningAnnotation") diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index a91da1cf5f96..f23dce020f10 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -350,8 +350,8 @@ object Flags { /** Symbol is a method which should be marked ACC_SYNCHRONIZED */ val (_, Synchronized @ _, _) = newFlags(36, "") - /** Symbol is a Java-style varargs method */ - val (_, JavaVarargs @ _, _) = newFlags(37, "") + /** Symbol is a Java-style varargs method / a Java annotation */ + val (_, JavaVarargs @ _, JavaAnnotation @ _) = newFlags(37, "", "") /** Symbol is a Java default method */ val (_, DefaultMethod @ _, _) = newFlags(38, "") @@ -477,7 +477,7 @@ object Flags { */ val AfterLoadFlags: FlagSet = commonFlags( FromStartFlags, AccessFlags, Final, AccessorOrSealed, - Abstract, LazyOrTrait, SelfName, JavaDefined, Transparent) + Abstract, LazyOrTrait, SelfName, JavaDefined, JavaAnnotation, Transparent) /** A value that's unstable unless complemented with a Stable flag */ val UnstableValueFlags: FlagSet = Mutable | Method diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index a81705a81aae..a4f1bf3c5e80 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -808,7 +808,7 @@ object SymDenotations { /** Is this a Scala or Java annotation ? */ def isAnnotation(using Context): Boolean = - isClass && (derivesFrom(defn.AnnotationClass) || derivesFrom(defn.JavaAnnotationClass)) + isClass && (derivesFrom(defn.AnnotationClass) || is(JavaAnnotation)) /** Is this symbol a class that extends `java.io.Serializable` ? */ def isSerializable(using Context): Boolean = diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala index f50033165c0d..4aa60d973264 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala @@ -346,6 +346,7 @@ object ClassfileConstants { case JAVA_ACC_ENUM => Enum case JAVA_ACC_ABSTRACT => if (isClass) Abstract else Deferred case JAVA_ACC_INTERFACE => PureInterfaceCreationFlags | JavaDefined + case JAVA_ACC_ANNOTATION => JavaAnnotation case _ => EmptyFlags } @@ -362,6 +363,7 @@ object ClassfileConstants { res = addFlag(res, jflags & JAVA_ACC_ENUM) res = addFlag(res, jflags & JAVA_ACC_ABSTRACT) res = addFlag(res, jflags & JAVA_ACC_INTERFACE) + res = addFlag(res, jflags & JAVA_ACC_ANNOTATION) res } diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index d8739c55ebb7..183845fcf3ec 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -876,7 +876,7 @@ object JavaParsers { List(constructorParams), TypeTree(), EmptyTree).withMods(Modifiers(Flags.JavaDefined)) val templ = makeTemplate(annotationParents, constr :: body, List(), true) val annot = atSpan(start, nameOffset) { - TypeDef(name, templ).withMods(mods | Flags.JavaInterface) + TypeDef(name, templ).withMods(mods | Flags.JavaInterface | Flags.JavaAnnotation) } addCompanionObject(statics, annot) } diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index c24eac187169..448c66838164 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -547,7 +547,7 @@ trait Applications extends Compatibility { /** Is `sym` a constructor of a Java-defined annotation? */ def isJavaAnnotConstr(sym: Symbol): Boolean = - sym.is(JavaDefined) && sym.isConstructor && sym.owner.derivesFrom(defn.JavaAnnotationClass) + sym.is(JavaDefined) && sym.isConstructor && sym.owner.is(JavaAnnotation) /** Match re-ordered arguments against formal parameters * @param n The position of the first parameter in formals in `methType`. diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 287a07a6ce3e..99399832085f 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -1110,7 +1110,7 @@ trait Checking { def checkParentCall(call: Tree, caller: ClassSymbol)(using Context): Unit = if (!ctx.isAfterTyper) { val called = call.tpe.classSymbol - if (called.derivesFrom(defn.JavaAnnotationClass)) + if (called.is(JavaAnnotation)) report.error(i"${called.name} must appear without any argument to be a valid class parent because it is a Java annotation", call.srcPos) if (caller.is(Trait)) report.error(i"$caller may not call constructor of $called", call.srcPos) @@ -1265,6 +1265,23 @@ trait Checking { if !Inlines.inInlineMethod && !ctx.isInlineContext then report.error(em"$what can only be used in an inline method", pos) + /** Check that the class corresponding to this tree is either a Scala or Java annotation. + * + * @return The original tree or an error tree in case `tree` isn't a valid + * annotation or already an error tree. + */ + def checkAnnotClass(tree: Tree)(using Context): Tree = + if tree.tpe.isError then + return tree + val cls = Annotations.annotClass(tree) + if cls.is(JavaDefined) then + if !cls.is(JavaAnnotation) then + errorTree(tree, em"$cls is not a valid Java annotation: it was not declared with `@interface`") + else tree + else if !cls.derivesFrom(defn.AnnotationClass) then + errorTree(tree, em"$cls is not a valid Scala annotation: it does not extend `scala.annotation.Annotation`") + else tree + /** Check arguments of compiler-defined annotations */ def checkAnnotArgs(tree: Tree)(using Context): tree.type = val cls = Annotations.annotClass(tree) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 5c1c8d6debdf..6aab561c44b7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -833,7 +833,7 @@ class Namer { typer: Typer => if (cls eq sym) report.error("An annotation class cannot be annotated with iself", annotTree.srcPos) else { - val ann = Annotation.deferred(cls)(typedAheadAnnotation(annotTree)(using annotCtx)) + val ann = Annotation.deferred(cls)(typedAheadExpr(annotTree)(using annotCtx)) sym.addAnnotation(ann) } } @@ -1618,9 +1618,6 @@ class Namer { typer: Typer => def typedAheadExpr(tree: Tree, pt: Type = WildcardType)(using Context): tpd.Tree = typedAhead(tree, typer.typedExpr(_, pt)) - def typedAheadAnnotation(tree: Tree)(using Context): tpd.Tree = - typedAheadExpr(tree, defn.AnnotationClass.typeRef | defn.JavaAnnotationClass.typeRef) - def typedAheadAnnotationClass(tree: Tree)(using Context): Symbol = tree match { case Apply(fn, _) => typedAheadAnnotationClass(fn) case TypeApply(fn, _) => typedAheadAnnotationClass(fn) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index d89a274a9eea..182351bfe7c4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2259,7 +2259,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer } def typedAnnotation(annot: untpd.Tree)(using Context): Tree = - checkAnnotArgs(typed(annot, defn.AnnotationClass.typeRef | defn.JavaAnnotationClass.typeRef)) + checkAnnotClass(checkAnnotArgs(typed(annot))) def registerNowarn(tree: Tree, mdef: untpd.Tree)(using Context): Unit = val annot = Annotations.Annotation(tree) @@ -2597,7 +2597,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer if parent.isType && !cls.is(Trait) && !cls.is(JavaDefined) && psym.isClass // Annotations are represented as traits with constructors, but should // never be called as such outside of annotation trees. - && !psym.derivesFrom(defn.JavaAnnotationClass) + && !psym.is(JavaAnnotation) && (!psym.is(Trait) || psym.primaryConstructor.info.takesParams && !cls.superClass.isSubClass(psym)) then typed(untpd.New(untpd.TypedSplice(parent), Nil)) @@ -2672,7 +2672,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer end typedPackageDef def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree = { - val annot1 = typedExpr(tree.annot, defn.AnnotationClass.typeRef | defn.JavaAnnotationClass.typeRef) + val annot1 = checkAnnotClass(typedExpr(tree.annot)) val annotCls = Annotations.annotClass(annot1) if annotCls == defn.NowarnAnnot then registerNowarn(annot1, tree) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index f8e439baeb0e..3cab93f247ae 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -2765,6 +2765,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def Invisible: Flags = dotc.core.Flags.Invisible def JavaDefined: Flags = dotc.core.Flags.JavaDefined def JavaStatic: Flags = dotc.core.Flags.JavaStatic + def JavaAnnotation: Flags = dotc.core.Flags.JavaAnnotation def Lazy: Flags = dotc.core.Flags.Lazy def Local: Flags = dotc.core.Flags.Local def Macro: Flags = dotc.core.Flags.Macro diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 3e2863f2260b..309e49be1370 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -4278,6 +4278,9 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Is implemented as a Java static */ def JavaStatic: Flags + /** Is this an annotation defined in Java */ + @experimental def JavaAnnotation: Flags + /** Is this symbol `lazy` */ def Lazy: Flags diff --git a/tests/neg/java-fake-ann-separate/FakeAnn_1.java b/tests/neg/java-fake-ann-separate/FakeAnn_1.java new file mode 100644 index 000000000000..597ea980585d --- /dev/null +++ b/tests/neg/java-fake-ann-separate/FakeAnn_1.java @@ -0,0 +1 @@ +interface FakeAnn_1 extends java.lang.annotation.Annotation { } diff --git a/tests/neg/java-fake-ann-separate/Test_2.scala b/tests/neg/java-fake-ann-separate/Test_2.scala new file mode 100644 index 000000000000..becc8babdaa0 --- /dev/null +++ b/tests/neg/java-fake-ann-separate/Test_2.scala @@ -0,0 +1,3 @@ +@FakeAnn_1 def test = // error + (1: @FakeAnn_1) // error + diff --git a/tests/neg/java-fake-ann/FakeAnn.java b/tests/neg/java-fake-ann/FakeAnn.java new file mode 100644 index 000000000000..2b055f782d42 --- /dev/null +++ b/tests/neg/java-fake-ann/FakeAnn.java @@ -0,0 +1 @@ +interface FakeAnn extends java.lang.annotation.Annotation { } diff --git a/tests/neg/java-fake-ann/Test.scala b/tests/neg/java-fake-ann/Test.scala new file mode 100644 index 000000000000..827527cb80bf --- /dev/null +++ b/tests/neg/java-fake-ann/Test.scala @@ -0,0 +1,2 @@ +@FakeAnn def test = // error + (1: @FakeAnn) // error diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index 8901ba53b605..a7c437242ebe 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -62,6 +62,7 @@ val experimentalDefinitionInLibrary = Set( //// New APIs: Quotes // Can be stabilized in 3.3.0 (unsure) or later "scala.quoted.Quotes.reflectModule.CompilationInfoModule.XmacroSettings", + "scala.quoted.Quotes.reflectModule.FlagsModule.JavaAnnotation", // Cant be stabilized yet. // Need newClass variant that can add constructor parameters. // Need experimental annotation macros to check that design works.