Skip to content

Commit d3070f6

Browse files
authored
Support use-site meta-annotations (#16445)
Fixes #12492 Fixes #15318
2 parents 5929a50 + ff68dc3 commit d3070f6

19 files changed

+246
-53
lines changed

compiler/src/dotty/tools/dotc/core/Annotations.scala

+20-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ package dotty.tools
22
package dotc
33
package core
44

5-
import Symbols._, Types._, Contexts._, Constants._
6-
import dotty.tools.dotc.ast.tpd, tpd.*
5+
import Symbols._, Types._, Contexts._, Constants._, Phases.*
6+
import ast.tpd, tpd.*
77
import util.Spans.Span
88
import printing.{Showable, Printer}
99
import printing.Texts.Text
10-
import annotation.internal.sharable
10+
11+
import scala.annotation.internal.sharable
1112

1213
object Annotations {
1314

@@ -87,6 +88,22 @@ object Annotations {
8788
def sameAnnotation(that: Annotation)(using Context): Boolean =
8889
symbol == that.symbol && tree.sameTree(that.tree)
8990

91+
def hasOneOfMetaAnnotation(metaSyms: Set[Symbol], orNoneOf: Set[Symbol] = Set.empty)(using Context): Boolean = atPhaseNoLater(erasurePhase) {
92+
def go(metaSyms: Set[Symbol]) =
93+
def recTp(tp: Type): Boolean = tp.dealiasKeepAnnots match
94+
case AnnotatedType(parent, metaAnnot) => metaSyms.exists(metaAnnot.matches) || recTp(parent)
95+
case _ => false
96+
def rec(tree: Tree): Boolean = methPart(tree) match
97+
case New(tpt) => rec(tpt)
98+
case Select(qual, _) => rec(qual)
99+
case Annotated(arg, metaAnnot) => metaSyms.exists(metaAnnot.tpe.classSymbol.derivesFrom) || rec(arg)
100+
case t @ Ident(_) => recTp(t.tpe)
101+
case Typed(expr, _) => rec(expr)
102+
case _ => false
103+
metaSyms.exists(symbol.hasAnnotation) || rec(tree)
104+
go(metaSyms) || orNoneOf.nonEmpty && !go(orNoneOf)
105+
}
106+
90107
/** Operations for hash-consing, can be overridden */
91108
def hash: Int = System.identityHashCode(this)
92109
def eql(that: Annotation) = this eq that

compiler/src/dotty/tools/dotc/core/Definitions.scala

+5-1
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,8 @@ class Definitions {
10231023
@tu lazy val UncheckedVarianceAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedVariance")
10241024
@tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile")
10251025
@tu lazy val WithPureFunsAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WithPureFuns")
1026+
@tu lazy val BeanGetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanGetter")
1027+
@tu lazy val BeanSetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanSetter")
10261028
@tu lazy val FieldMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.field")
10271029
@tu lazy val GetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.getter")
10281030
@tu lazy val ParamMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.param")
@@ -1039,8 +1041,10 @@ class Definitions {
10391041
@tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable")
10401042

10411043
// A list of meta-annotations that are relevant for fields and accessors
1042-
@tu lazy val FieldAccessorMetaAnnots: Set[Symbol] =
1044+
@tu lazy val NonBeanMetaAnnots: Set[Symbol] =
10431045
Set(FieldMetaAnnot, GetterMetaAnnot, ParamMetaAnnot, SetterMetaAnnot)
1046+
@tu lazy val MetaAnnots: Set[Symbol] =
1047+
NonBeanMetaAnnots + BeanGetterMetaAnnot + BeanSetterMetaAnnot
10441048

10451049
// A list of annotations that are commonly used to indicate that a field/method argument or return
10461050
// type is not null. These annotations are used by the nullification logic in JavaNullInterop to

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

+9
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,15 @@ object SymDenotations {
252252
final def filterAnnotations(p: Annotation => Boolean)(using Context): Unit =
253253
annotations = annotations.filterConserve(p)
254254

255+
def annotationsCarrying(meta: Set[Symbol], orNoneOf: Set[Symbol] = Set.empty)(using Context): List[Annotation] =
256+
annotations.filterConserve(_.hasOneOfMetaAnnotation(meta, orNoneOf = orNoneOf))
257+
258+
def copyAndKeepAnnotationsCarrying(phase: DenotTransformer, meta: Set[Symbol], orNoneOf: Set[Symbol] = Set.empty)(using Context): Unit =
259+
if annotations.nonEmpty then
260+
val cpy = copySymDenotation()
261+
cpy.annotations = annotationsCarrying(meta, orNoneOf = orNoneOf)
262+
cpy.installAfter(phase)
263+
255264
/** Optionally, the annotation matching the given class symbol */
256265
final def getAnnotation(cls: Symbol)(using Context): Option[Annotation] =
257266
dropOtherAnnotations(annotations, cls) match {

compiler/src/dotty/tools/dotc/transform/BeanProperties.scala

+6-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import core._
55
import ast.tpd._
66
import Annotations._
77
import Contexts._
8-
import Symbols.newSymbol
8+
import Symbols.*
9+
import SymUtils.*
910
import Decorators._
1011
import Flags._
1112
import Names._
@@ -23,8 +24,6 @@ class BeanProperties(thisPhase: DenotTransformer):
2324
} ::: origBody)
2425

2526
def generateAccessors(valDef: ValDef)(using Context): List[Tree] =
26-
import Symbols.defn
27-
2827
def generateGetter(valDef: ValDef, annot: Annotation)(using Context) : Tree =
2928
val prefix = if annot matches defn.BooleanBeanPropertyAnnot then "is" else "get"
3029
val meth = newSymbol(
@@ -34,9 +33,9 @@ class BeanProperties(thisPhase: DenotTransformer):
3433
info = MethodType(Nil, valDef.denot.info),
3534
coord = annot.tree.span
3635
).enteredAfter(thisPhase).asTerm
37-
meth.addAnnotations(valDef.symbol.annotations)
36+
.withAnnotationsCarrying(valDef.symbol, defn.BeanGetterMetaAnnot)
3837
val body: Tree = ref(valDef.symbol)
39-
DefDef(meth, body)
38+
DefDef(meth, body).withSpan(meth.span)
4039

4140
def maybeGenerateSetter(valDef: ValDef, annot: Annotation)(using Context): Option[Tree] =
4241
Option.when(valDef.denot.asSymDenotation.flags.is(Mutable)) {
@@ -48,9 +47,9 @@ class BeanProperties(thisPhase: DenotTransformer):
4847
info = MethodType(valDef.name :: Nil, valDef.denot.info :: Nil, defn.UnitType),
4948
coord = annot.tree.span
5049
).enteredAfter(thisPhase).asTerm
51-
meth.addAnnotations(valDef.symbol.annotations)
50+
.withAnnotationsCarrying(valDef.symbol, defn.BeanSetterMetaAnnot)
5251
def body(params: List[List[Tree]]): Tree = Assign(ref(valDef.symbol), params.head.head)
53-
DefDef(meth, body)
52+
DefDef(meth, body).withSpan(meth.span)
5453
}
5554

5655
def prefixedName(prefix: String, valName: Name) =

compiler/src/dotty/tools/dotc/transform/Memoize.scala

+4-21
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package transform
44
import core._
55
import DenotTransformers._
66
import Contexts._
7-
import Phases.phaseOf
7+
import Phases.*
88
import SymDenotations.SymDenotation
99
import Denotations._
1010
import Symbols._
@@ -114,26 +114,10 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
114114
flags = Private | (if (sym.is(StableRealizable)) EmptyFlags else Mutable),
115115
info = fieldType,
116116
coord = tree.span
117-
).withAnnotationsCarrying(sym, defn.FieldMetaAnnot)
117+
).withAnnotationsCarrying(sym, defn.FieldMetaAnnot, orNoneOf = defn.MetaAnnots)
118118
.enteredAfter(thisPhase)
119119
}
120120

121-
def addAnnotations(denot: Denotation): Unit =
122-
denot match {
123-
case fieldDenot: SymDenotation if sym.annotations.nonEmpty =>
124-
val cpy = fieldDenot.copySymDenotation()
125-
cpy.annotations = sym.annotations
126-
cpy.installAfter(thisPhase)
127-
case _ => ()
128-
}
129-
130-
def removeUnwantedAnnotations(denot: SymDenotation, metaAnnotSym: ClassSymbol): Unit =
131-
if (sym.annotations.nonEmpty) {
132-
val cpy = sym.copySymDenotation()
133-
cpy.filterAnnotations(_.symbol.hasAnnotation(metaAnnotSym))
134-
cpy.installAfter(thisPhase)
135-
}
136-
137121
val NoFieldNeeded = Lazy | Deferred | JavaDefined | Inline
138122

139123
def erasedBottomTree(sym: Symbol) =
@@ -183,8 +167,7 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
183167
if isErasableBottomField(field, rhsClass) then erasedBottomTree(rhsClass)
184168
else transformFollowingDeep(ref(field))(using ctx.withOwner(sym))
185169
val getterDef = cpy.DefDef(tree)(rhs = getterRhs)
186-
addAnnotations(fieldDef.denot)
187-
removeUnwantedAnnotations(sym, defn.GetterMetaAnnot)
170+
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot))
188171
Thicket(fieldDef, getterDef)
189172
else if sym.isSetter then
190173
if (!sym.is(ParamAccessor)) { val Literal(Constant(())) = tree.rhs: @unchecked } // This is intended as an assertion
@@ -210,7 +193,7 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
210193
then Literal(Constant(()))
211194
else Assign(ref(field), adaptToField(field, ref(tree.termParamss.head.head.symbol)))
212195
val setterDef = cpy.DefDef(tree)(rhs = transformFollowingDeep(initializer)(using ctx.withOwner(sym)))
213-
removeUnwantedAnnotations(sym, defn.SetterMetaAnnot)
196+
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.SetterMetaAnnot))
214197
setterDef
215198
else
216199
// Curiously, some accessors from Scala2 have ' ' suffixes.

compiler/src/dotty/tools/dotc/transform/Pickler.scala

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package dotty.tools.dotc
1+
package dotty.tools
2+
package dotc
23
package transform
34

45
import core._
@@ -147,6 +148,7 @@ class Pickler extends Phase {
147148
if unequal then
148149
output("before-pickling.txt", previous)
149150
output("after-pickling.txt", unpickled)
151+
//sys.process.Process("diff -u before-pickling.txt after-pickling.txt").!
150152
report.error(em"""pickling difference for $cls in ${cls.source}, for details:
151153
|
152154
| diff before-pickling.txt after-pickling.txt""")

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

+7-15
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package dotty.tools.dotc
1+
package dotty.tools
2+
package dotc
23
package transform
34

45
import dotty.tools.dotc.ast.{Trees, tpd, untpd, desugar}
@@ -156,12 +157,14 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
156157
checkInferredWellFormed(tree.tpt)
157158
if sym.is(Method) then
158159
if sym.isSetter then
159-
removeUnwantedAnnotations(sym, defn.SetterMetaAnnot, NoSymbol, keepIfNoRelevantAnnot = false)
160+
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.SetterMetaAnnot))
160161
else
161162
if sym.is(Param) then
162-
removeUnwantedAnnotations(sym, defn.ParamMetaAnnot, NoSymbol, keepIfNoRelevantAnnot = true)
163+
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots)
164+
else if sym.is(ParamAccessor) then
165+
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot))
163166
else
164-
removeUnwantedAnnotations(sym, defn.GetterMetaAnnot, defn.FieldMetaAnnot, keepIfNoRelevantAnnot = !sym.is(ParamAccessor))
167+
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots)
165168
if sym.isScala2Macro && !ctx.settings.XignoreScala2Macros.value then
166169
if !sym.owner.unforcedDecls.exists(p => !p.isScala2Macro && p.name == sym.name && p.signature == sym.signature)
167170
// Allow scala.reflect.materializeClassTag to be able to compile scala/reflect/package.scala
@@ -183,17 +186,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
183186
=> Checking.checkAppliedTypesIn(tree)
184187
case _ =>
185188

186-
private def removeUnwantedAnnotations(sym: Symbol, metaAnnotSym: Symbol,
187-
metaAnnotSymBackup: Symbol, keepIfNoRelevantAnnot: Boolean)(using Context): Unit =
188-
def shouldKeep(annot: Annotation): Boolean =
189-
val annotSym = annot.symbol
190-
annotSym.hasAnnotation(metaAnnotSym)
191-
|| annotSym.hasAnnotation(metaAnnotSymBackup)
192-
|| (keepIfNoRelevantAnnot && {
193-
!annotSym.annotations.exists(metaAnnot => defn.FieldAccessorMetaAnnots.contains(metaAnnot.symbol))
194-
})
195-
if sym.annotations.nonEmpty then
196-
sym.filterAnnotations(shouldKeep(_))
197189

198190
private def transformSelect(tree: Select, targs: List[Tree])(using Context): Tree = {
199191
val qual = tree.qualifier

compiler/src/dotty/tools/dotc/transform/SymUtils.scala

+2-5
Original file line numberDiff line numberDiff line change
@@ -270,11 +270,8 @@ object SymUtils:
270270
def isEnumCase(using Context): Boolean =
271271
self.isAllOf(EnumCase, butNot = JavaDefined)
272272

273-
def annotationsCarrying(meta: ClassSymbol)(using Context): List[Annotation] =
274-
self.annotations.filter(_.symbol.hasAnnotation(meta))
275-
276-
def withAnnotationsCarrying(from: Symbol, meta: ClassSymbol)(using Context): self.type = {
277-
self.addAnnotations(from.annotationsCarrying(meta))
273+
def withAnnotationsCarrying(from: Symbol, meta: Symbol, orNoneOf: Set[Symbol] = Set.empty)(using Context): self.type = {
274+
self.addAnnotations(from.annotationsCarrying(Set(meta), orNoneOf))
278275
self
279276
}
280277

compiler/src/dotty/tools/dotc/util/Spans.scala

+1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ object Spans {
182182
assert(isSpan)
183183
if (this == NoCoord) NoSpan else Span(-1 - encoding)
184184
}
185+
override def toString = if isSpan then s"$toSpan" else s"Coord(idx=$toIndex)"
185186
}
186187

187188
/** An index coordinate */

tests/run/beans.check

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22
true
33
10
44
[@beans.LibraryAnnotation_1()]
5+
[]
56
some text
67
other text

tests/run/beans/Test_3.java

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public A run() throws ReflectiveOperationException{
77
System.out.println(a.isY());
88
System.out.println(new T2().getX());
99

10+
System.out.println(Arrays.asList(a.getClass().getDeclaredField("retainingAnnotation").getAnnotations()));
1011
System.out.println(Arrays.asList(a.getClass().getMethod("getRetainingAnnotation").getAnnotations()));
1112

1213
System.out.println(a.getMutableOneWithLongName());

tests/run/i12492.check

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
inspecting constructor MyTable
2+
inspecting param aaaParam1 @MyColumnBase
3+
inspecting param fldParam1
4+
inspecting param getParam1
5+
inspecting param parParam1 @MyColumnBase
6+
inspecting field aaaField1 @MyColumnBase
7+
inspecting field aaaParam1
8+
inspecting field fldField1 @MyColumnBase
9+
inspecting field fldParam1 @MyColumnBase
10+
inspecting field getField1
11+
inspecting field getParam1
12+
inspecting field parField1
13+
inspecting field parParam1
14+
inspecting method aaaField1
15+
inspecting method aaaParam1
16+
inspecting method fldField1
17+
inspecting method fldParam1
18+
inspecting method getField1 @MyColumnBase
19+
inspecting method getParam1 @MyColumnBase
20+
inspecting method parField1
21+
inspecting method parParam1
22+
inspecting constructor MyTable2
23+
inspecting param fldParam2
24+
inspecting param getParam2
25+
inspecting param parParam2 @MyColumnBase
26+
inspecting field fldField2 @MyColumnBase
27+
inspecting field fldParam2 @MyColumnBase
28+
inspecting field getField2
29+
inspecting field getParam2
30+
inspecting field parField2
31+
inspecting field parParam2
32+
inspecting method fldField2
33+
inspecting method fldParam2
34+
inspecting method getField2 @MyColumnBase
35+
inspecting method getParam2 @MyColumnBase
36+
inspecting method parField2
37+
inspecting method parParam2

tests/run/i12492/MyColumnBase.java

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import java.lang.annotation.Retention;
2+
import java.lang.annotation.RetentionPolicy;
3+
4+
@Retention(RetentionPolicy.RUNTIME)
5+
public @interface MyColumnBase {}

tests/run/i12492/MyTable.scala

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import scala.annotation.meta.{ field as fld, getter as get, param as par }
2+
3+
type FldColumn = MyColumnBase @fld
4+
type GetColumn = MyColumnBase @get
5+
type ParColumn = MyColumnBase @par
6+
7+
class MyTable(
8+
@(MyColumnBase ) val aaaParam1: String,
9+
@(MyColumnBase @fld) val fldParam1: String,
10+
@(MyColumnBase @get) val getParam1: String,
11+
@(MyColumnBase @par) val parParam1: String,
12+
) {
13+
@(MyColumnBase ) val aaaField1: String = ""
14+
@(MyColumnBase @fld) val fldField1: String = ""
15+
@(MyColumnBase @get) val getField1: String = ""
16+
@(MyColumnBase @par) val parField1: String = ""
17+
}
18+
19+
class MyTable2(
20+
@FldColumn val fldParam2: String,
21+
@GetColumn val getParam2: String,
22+
@ParColumn val parParam2: String,
23+
) {
24+
@FldColumn val fldField2: String = ""
25+
@GetColumn val getField2: String = ""
26+
@ParColumn val parField2: String = ""
27+
}

tests/run/i12492/Test.scala

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// scalajs: --skip
2+
object Test:
3+
def main(args: Array[String]): Unit =
4+
go(classOf[MyTable])
5+
go(classOf[MyTable2])
6+
7+
def go(cls: Class[?]): Unit =
8+
for c <- cls.getDeclaredConstructors.sortBy(_.getName) do
9+
c.setAccessible(true)
10+
println(s"inspecting constructor ${c.getName}")
11+
for p <- c.getParameters.sortBy(_.getName) do
12+
print(s"inspecting param ${p.getName}")
13+
for a <- p.getAnnotations.sortBy(_.annotationType.toString) do
14+
print(s" @${a.annotationType.getName}")
15+
println()
16+
17+
for (m <- cls.getDeclaredFields.sortBy(_.getName)) {
18+
m.setAccessible(true)
19+
print(s"inspecting field ${m.getName}")
20+
for a <- m.getAnnotations().sortBy(_.annotationType.toString) do
21+
print(s" @${a.annotationType.getName}")
22+
println()
23+
}
24+
25+
for (m <- cls.getDeclaredMethods.sortBy(_.getName)) {
26+
m.setAccessible(true)
27+
print(s"inspecting method ${m.getName}")
28+
for a <- m.getAnnotations().sortBy(_.annotationType.toString) do
29+
print(s" @${a.annotationType.getName}")
30+
println()
31+
}

0 commit comments

Comments
 (0)