From f22420fea5662f883a5daa5ec00921fea74bb218 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 12 Mar 2023 13:16:09 +0100 Subject: [PATCH] Handle @companionClass and @companionMethod meta-annotations Fixes #17002 --- .../dotty/tools/dotc/core/Definitions.scala | 4 +++- .../tools/dotc/core/SymDenotations.scala | 9 ++++++--- .../dotty/tools/dotc/transform/Memoize.scala | 4 ++-- .../tools/dotc/transform/PostTyper.scala | 16 +++++++++++---- tests/neg/i17002.scala | 20 +++++++++++++++++++ tests/pos/i17002.scala | 10 ++++++++++ 6 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 tests/neg/i17002.scala create mode 100644 tests/pos/i17002.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 20a1d59316eb..148b314220a8 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1028,6 +1028,8 @@ class Definitions { @tu lazy val GetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.getter") @tu lazy val ParamMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.param") @tu lazy val SetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.setter") + @tu lazy val CompanionClassMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.companionClass") + @tu lazy val CompanionMethodMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.companionMethod") @tu lazy val ShowAsInfixAnnot: ClassSymbol = requiredClass("scala.annotation.showAsInfix") @tu lazy val FunctionalInterfaceAnnot: ClassSymbol = requiredClass("java.lang.FunctionalInterface") @tu lazy val TargetNameAnnot: ClassSymbol = requiredClass("scala.annotation.targetName") @@ -1041,7 +1043,7 @@ class Definitions { // A list of meta-annotations that are relevant for fields and accessors @tu lazy val NonBeanMetaAnnots: Set[Symbol] = - Set(FieldMetaAnnot, GetterMetaAnnot, ParamMetaAnnot, SetterMetaAnnot) + Set(FieldMetaAnnot, GetterMetaAnnot, ParamMetaAnnot, SetterMetaAnnot, CompanionClassMetaAnnot, CompanionMethodMetaAnnot) @tu lazy val MetaAnnots: Set[Symbol] = NonBeanMetaAnnots + BeanGetterMetaAnnot + BeanSetterMetaAnnot diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 9d7a3945a1ca..0f94d52f5be9 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -255,10 +255,13 @@ object SymDenotations { def annotationsCarrying(meta: Set[Symbol], orNoneOf: Set[Symbol] = Set.empty)(using Context): List[Annotation] = annotations.filterConserve(_.hasOneOfMetaAnnotation(meta, orNoneOf = orNoneOf)) - def copyAndKeepAnnotationsCarrying(phase: DenotTransformer, meta: Set[Symbol], orNoneOf: Set[Symbol] = Set.empty)(using Context): Unit = - if annotations.nonEmpty then + def keepAnnotationsCarrying(phase: DenotTransformer, meta: Set[Symbol], orNoneOf: Set[Symbol] = Set.empty)(using Context): Unit = + updateAnnotationsAfter(phase, annotationsCarrying(meta, orNoneOf = orNoneOf)) + + def updateAnnotationsAfter(phase: DenotTransformer, annots: List[Annotation])(using Context): Unit = + if annots ne annotations then val cpy = copySymDenotation() - cpy.annotations = annotationsCarrying(meta, orNoneOf = orNoneOf) + cpy.annotations = annots cpy.installAfter(phase) /** Optionally, the annotation matching the given class symbol */ diff --git a/compiler/src/dotty/tools/dotc/transform/Memoize.scala b/compiler/src/dotty/tools/dotc/transform/Memoize.scala index 5a2eda4101a4..1392d00011a2 100644 --- a/compiler/src/dotty/tools/dotc/transform/Memoize.scala +++ b/compiler/src/dotty/tools/dotc/transform/Memoize.scala @@ -167,7 +167,7 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase => if isErasableBottomField(field, rhsClass) then erasedBottomTree(rhsClass) else transformFollowingDeep(ref(field))(using ctx.withOwner(sym)) val getterDef = cpy.DefDef(tree)(rhs = getterRhs) - sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot)) + sym.keepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot)) Thicket(fieldDef, getterDef) else if sym.isSetter then if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs: @unchecked } // This is intended as an assertion @@ -193,7 +193,7 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase => then Literal(Constant(())) else Assign(ref(field), adaptToField(field, ref(tree.termParamss.head.head.symbol))) val setterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(using ctx.withOwner(sym))) - sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.SetterMetaAnnot)) + sym.keepAnnotationsCarrying(thisPhase, Set(defn.SetterMetaAnnot)) setterDef else // Curiously, some accessors from Scala2 have ' ' suffixes. diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 574db18c9c7f..7f3e47c14732 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -157,14 +157,20 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase checkInferredWellFormed(tree.tpt) if sym.is(Method) then if sym.isSetter then - sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.SetterMetaAnnot)) + sym.keepAnnotationsCarrying(thisPhase, Set(defn.SetterMetaAnnot)) + if sym.isOneOf(GivenOrImplicit) then + val cls = sym.info.finalResultType.classSymbol + if cls.isOneOf(GivenOrImplicit) then + sym.updateAnnotationsAfter(thisPhase, + atPhase(thisPhase)(cls.annotationsCarrying(Set(defn.CompanionMethodMetaAnnot))) + ++ sym.annotations) else if sym.is(Param) then - sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) + sym.keepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) else if sym.is(ParamAccessor) then - sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot)) + sym.keepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot)) else - sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) + sym.keepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots) if sym.isScala2Macro && !ctx.settings.XignoreScala2Macros.value then if !sym.owner.unforcedDecls.exists(p => !p.isScala2Macro && p.name == sym.name && p.signature == sym.signature) // Allow scala.reflect.materializeClassTag to be able to compile scala/reflect/package.scala @@ -388,6 +394,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase VarianceChecker.check(tree) annotateExperimental(sym) checkMacroAnnotation(sym) + if sym.isOneOf(GivenOrImplicit) then + sym.keepAnnotationsCarrying(thisPhase, Set(defn.CompanionClassMetaAnnot), orNoneOf = defn.MetaAnnots) tree.rhs match case impl: Template => for parent <- impl.parents do diff --git a/tests/neg/i17002.scala b/tests/neg/i17002.scala new file mode 100644 index 000000000000..c2a21dd3d415 --- /dev/null +++ b/tests/neg/i17002.scala @@ -0,0 +1,20 @@ +import scala.annotation.compileTimeOnly + +sealed trait Test[T] + +object Test: + @compileTimeOnly("Error") + given test0[T]: Test[T] = ??? + + @compileTimeOnly("Error") + given test1[T]: Test[T]() + + @compileTimeOnly("Error") + implicit class ic(x: Int): + def foo = 2 + + test0 // error + + test1 // error + + 2.foo // error \ No newline at end of file diff --git a/tests/pos/i17002.scala b/tests/pos/i17002.scala new file mode 100644 index 000000000000..d33c1bd386d9 --- /dev/null +++ b/tests/pos/i17002.scala @@ -0,0 +1,10 @@ +import scala.annotation.meta.companionMethod + +@companionMethod +class methOnly extends annotation.Annotation + +class Test +object Test: + + @methOnly + given test2[T]: Test with {}