From ce14d7c0bfda2241a5f5b527ecca8339a2d5dffb Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 31 Jul 2021 17:38:16 +0100 Subject: [PATCH 1/3] Fix "is not a trait" when extending a Java annotation Java annotations are interfaces ("@interface Foo") but we model them as classes to mimic Scala annotations and avoid special-casing. So we add some special-casing in Namer for the not-actually-classes Java annotations. --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 1 + .../src/dotty/tools/dotc/core/SymDenotations.scala | 4 ++++ .../src/dotty/tools/dotc/parsing/JavaParsers.scala | 12 +++--------- compiler/src/dotty/tools/dotc/typer/Namer.scala | 4 ++-- tests/pos/i12840/A.scala | 3 +++ tests/pos/i12840/Named.java | 5 +++++ 6 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 tests/pos/i12840/A.scala create mode 100644 tests/pos/i12840/Named.java diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 7c1c2494d323..d63c3068a552 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -935,6 +935,7 @@ class Definitions { @tu lazy val TargetNameAnnot: ClassSymbol = requiredClass("scala.annotation.targetName") @tu lazy val VarargsAnnot: ClassSymbol = requiredClass("scala.annotation.varargs") + @tu lazy val JavaAnnotationClass: ClassSymbol = requiredClass("java.lang.annotation.Annotation") @tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable") // A list of meta-annotations that are relevant for fields and accessors diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index df2c195b9e74..0f0835a3a6f1 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -784,6 +784,10 @@ object SymDenotations { def isAnnotation(using Context): Boolean = isClass && derivesFrom(defn.AnnotationClass) + /** Is this a Java annotation ? */ + def isJavaAnnotation(using Context): Boolean = + isClass && derivesFrom(defn.JavaAnnotationClass) + /** Is this symbol a class that extends `java.io.Serializable` ? */ def isSerializable(using Context): Boolean = isClass && derivesFrom(defn.JavaSerializableClass) diff --git a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala index 8322c75e5e2d..3b9f49627013 100644 --- a/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/JavaParsers.scala @@ -91,13 +91,7 @@ object JavaParsers { def scalaAnnotationDot(name: Name): Select = Select(scalaDot(nme.annotation), name) - def javaDot(name: Name): Tree = - Select(rootDot(nme.java), name) - - def javaLangDot(name: Name): Tree = - Select(javaDot(nme.lang), name) - - def javaLangObject(): Tree = javaLangDot(tpnme.Object) + def javaLangObject(): Tree = javaDotLangDot(tpnme.Object) def arrayOf(tpt: Tree): AppliedTypeTree = AppliedTypeTree(scalaDot(tpnme.Array), List(tpt)) @@ -878,7 +872,7 @@ object JavaParsers { } def annotationParents: List[Select] = List( scalaAnnotationDot(tpnme.Annotation), - Select(javaLangDot(nme.annotation), tpnme.Annotation), + Select(javaDotLangDot(nme.annotation), tpnme.Annotation), scalaAnnotationDot(tpnme.ClassfileAnnotation) ) def annotationDecl(start: Offset, mods: Modifiers): List[Tree] = { @@ -942,7 +936,7 @@ object JavaParsers { AppliedTypeTree(javaLangDot(tpnme.Enum), List(enumType)) */ val superclazz = Apply(TypeApply( - Select(New(javaLangDot(tpnme.Enum)), nme.CONSTRUCTOR), List(enumType)), Nil) + Select(New(javaDotLangDot(tpnme.Enum)), nme.CONSTRUCTOR), List(enumType)), Nil) val enumclazz = atSpan(start, nameOffset) { TypeDef(name, makeTemplate(superclazz :: interfaces, body, List(), true)).withMods(mods | Flags.JavaEnumTrait) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index d2264e367f36..dd83ebed5886 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1284,8 +1284,8 @@ class Namer { typer: Typer => val ptype = parentType(parent)(using completerCtx.superCallContext).dealiasKeepAnnots if (cls.isRefinementClass) ptype else { - val pt = checkClassType(ptype, parent.srcPos, - traitReq = parent ne parents.head, stablePrefixReq = true) + val traitReq = (parent ne parents.head) && !(ptype.typeSymbol.isJavaAnnotation) + val pt = checkClassType(ptype, parent.srcPos, traitReq = traitReq, stablePrefixReq = true) if (pt.derivesFrom(cls)) { val addendum = parent match { case Select(qual: Super, _) if Feature.migrateTo3 => diff --git a/tests/pos/i12840/A.scala b/tests/pos/i12840/A.scala new file mode 100644 index 000000000000..e9e05bec4178 --- /dev/null +++ b/tests/pos/i12840/A.scala @@ -0,0 +1,3 @@ +package example + +class A diff --git a/tests/pos/i12840/Named.java b/tests/pos/i12840/Named.java new file mode 100644 index 000000000000..dab111aac046 --- /dev/null +++ b/tests/pos/i12840/Named.java @@ -0,0 +1,5 @@ +package example; + +public @interface Named {} + +abstract class NamedImpl implements Named {} From 5219947e02f9acf6d971647163d8559eb344caf5 Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Sat, 31 Jul 2021 18:20:44 +0100 Subject: [PATCH 2/3] Also fix "Invalid interfaces" when constructing a annotation subclass --- .../tools/backend/jvm/BCodeBodyBuilder.scala | 9 ------- .../tools/backend/jvm/BTypesFromSymbols.scala | 24 +++++++++++++++---- tests/pos/i12840/A.scala | 3 --- tests/pos/i12840/Main.scala | 5 ++++ tests/pos/i12840/Named.java | 2 -- tests/pos/i12840/NamedImpl.java | 9 +++++++ 6 files changed, 34 insertions(+), 18 deletions(-) delete mode 100644 tests/pos/i12840/A.scala create mode 100644 tests/pos/i12840/Main.scala create mode 100644 tests/pos/i12840/NamedImpl.java diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index 334c47f3511d..2dd0bf48db8d 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -1579,15 +1579,6 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { } } - /** Does this symbol actually correspond to an interface that will be emitted? - * In the backend, this should be preferred over `isInterface` because it - * also returns true for the symbols of the fake companion objects we - * create for Java-defined classes as well as for Java annotations - * which we represent as classes. - */ - private def isEmittedInterface(sym: Symbol): Boolean = sym.isInterface || - sym.is(JavaDefined) && (toDenot(sym).isAnnotation || sym.is(ModuleClass) && (sym.companionClass.is(PureInterface)) || sym.companionClass.is(Trait)) - } object BCodeBodyBuilder { diff --git a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala index 78dfd8e0a869..eb2318edcbcd 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala @@ -94,7 +94,8 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes { private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = { val superClassSym: Symbol = { val t = classSym.asClass.superClass - if (t.exists) t + if (toDenot(classSym).isJavaAnnotation) defn.ObjectClass + else if (t.exists) t else if (classSym.is(ModuleClass)) { // workaround #371 @@ -106,7 +107,7 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes { assert( if (classSym == defn.ObjectClass) superClassSym == NoSymbol - else if (classSym.isInterface) + else if (isEmittedInterface(classSym)) superClassSym == defn.ObjectClass else // A ClassBType for a primitive class (scala.Boolean et al) is only created when compiling these classes. @@ -305,7 +306,7 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes { 0 .addFlagIf(privateFlag, ACC_PRIVATE) .addFlagIf(!privateFlag, ACC_PUBLIC) .addFlagIf(sym.is(Deferred) || sym.isOneOf(AbstractOrTrait), ACC_ABSTRACT) - .addFlagIf(sym.isInterface, ACC_INTERFACE) + .addFlagIf(isEmittedInterface(sym), ACC_INTERFACE) .addFlagIf(finalFlag // Primitives are "abstract final" to prohibit instantiation // without having to provide any implementations, but that is an @@ -317,12 +318,13 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes { .addFlagIf(sym.isStaticMember, ACC_STATIC) .addFlagIf(sym.is(Bridge), ACC_BRIDGE | ACC_SYNTHETIC) .addFlagIf(sym.is(Artifact), ACC_SYNTHETIC) - .addFlagIf(sym.isClass && !sym.isInterface, ACC_SUPER) + .addFlagIf(sym.isClass && !isEmittedInterface(sym), ACC_SUPER) .addFlagIf(sym.isAllOf(JavaEnumTrait), ACC_ENUM) .addFlagIf(sym.is(JavaVarargs), ACC_VARARGS) .addFlagIf(sym.is(Synchronized), ACC_SYNCHRONIZED) .addFlagIf(sym.isDeprecated, ACC_DEPRECATED) .addFlagIf(sym.is(Enum), ACC_ENUM) + .addFlagIf(sym.isJavaAnnotation, ACC_ANNOTATION) } def javaFieldFlags(sym: Symbol) = { @@ -333,4 +335,18 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes { .addFlagIf(sym.hasAnnotation(VolatileAttr), ACC_VOLATILE) .addFlagIf(!sym.is(Mutable), ACC_FINAL) } + + /** Does this symbol actually correspond to an interface that will be emitted? + * In the backend, this should be preferred over `isInterface` because it + * also returns true for the symbols of the fake companion objects we + * create for Java-defined classes as well as for Java annotations + * which we represent as classes. + */ + final def isEmittedInterface(sym: Symbol): Boolean = + sym.isInterface || + sym.is(JavaDefined) && ( + toDenot(sym).isAnnotation + || sym.is(ModuleClass) && sym.companionClass.is(PureInterface) + || sym.companionClass.is(Trait) + ) } diff --git a/tests/pos/i12840/A.scala b/tests/pos/i12840/A.scala deleted file mode 100644 index e9e05bec4178..000000000000 --- a/tests/pos/i12840/A.scala +++ /dev/null @@ -1,3 +0,0 @@ -package example - -class A diff --git a/tests/pos/i12840/Main.scala b/tests/pos/i12840/Main.scala new file mode 100644 index 000000000000..9fccfecb00b7 --- /dev/null +++ b/tests/pos/i12840/Main.scala @@ -0,0 +1,5 @@ +package example + +object Main { + new NamedImpl() {} +} diff --git a/tests/pos/i12840/Named.java b/tests/pos/i12840/Named.java index dab111aac046..66750609afda 100644 --- a/tests/pos/i12840/Named.java +++ b/tests/pos/i12840/Named.java @@ -1,5 +1,3 @@ package example; public @interface Named {} - -abstract class NamedImpl implements Named {} diff --git a/tests/pos/i12840/NamedImpl.java b/tests/pos/i12840/NamedImpl.java new file mode 100644 index 000000000000..f23a7c12aff8 --- /dev/null +++ b/tests/pos/i12840/NamedImpl.java @@ -0,0 +1,9 @@ +package example; + +import java.lang.annotation.Annotation; + +public class NamedImpl implements Named { + public Class annotationType() { + return Named.class; + } +} From 01f7cdd7acca61f2b0bb66d00f1c9134756e586f Mon Sep 17 00:00:00 2001 From: Dale Wijnand Date: Mon, 2 Aug 2021 13:54:34 +0100 Subject: [PATCH 3/3] Make i12840 a run test to test the bytecode --- tests/pos/i12840/Main.scala | 5 ----- tests/run/i12840.check | 1 + tests/{pos => run}/i12840/Named.java | 0 tests/{pos => run}/i12840/NamedImpl.java | 2 ++ tests/run/i12840/Test.scala | 1 + 5 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 tests/pos/i12840/Main.scala create mode 100644 tests/run/i12840.check rename tests/{pos => run}/i12840/Named.java (100%) rename tests/{pos => run}/i12840/NamedImpl.java (78%) create mode 100644 tests/run/i12840/Test.scala diff --git a/tests/pos/i12840/Main.scala b/tests/pos/i12840/Main.scala deleted file mode 100644 index 9fccfecb00b7..000000000000 --- a/tests/pos/i12840/Main.scala +++ /dev/null @@ -1,5 +0,0 @@ -package example - -object Main { - new NamedImpl() {} -} diff --git a/tests/run/i12840.check b/tests/run/i12840.check new file mode 100644 index 000000000000..a914077ee9e8 --- /dev/null +++ b/tests/run/i12840.check @@ -0,0 +1 @@ +NamedImpl diff --git a/tests/pos/i12840/Named.java b/tests/run/i12840/Named.java similarity index 100% rename from tests/pos/i12840/Named.java rename to tests/run/i12840/Named.java diff --git a/tests/pos/i12840/NamedImpl.java b/tests/run/i12840/NamedImpl.java similarity index 78% rename from tests/pos/i12840/NamedImpl.java rename to tests/run/i12840/NamedImpl.java index f23a7c12aff8..178eca212b8b 100644 --- a/tests/pos/i12840/NamedImpl.java +++ b/tests/run/i12840/NamedImpl.java @@ -6,4 +6,6 @@ public class NamedImpl implements Named { public Class annotationType() { return Named.class; } + + public String toString() { return "NamedImpl"; } } diff --git a/tests/run/i12840/Test.scala b/tests/run/i12840/Test.scala new file mode 100644 index 000000000000..32106c9ca44d --- /dev/null +++ b/tests/run/i12840/Test.scala @@ -0,0 +1 @@ +@main def Test = println(new example.NamedImpl())