Skip to content

Support use-site meta-annotations #16445

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package dotty.tools
package dotc
package core

import Symbols._, Types._, Contexts._, Constants._
import dotty.tools.dotc.ast.tpd, tpd.*
import Symbols._, Types._, Contexts._, Constants._, Phases.*
import ast.tpd, tpd.*
import util.Spans.Span
import printing.{Showable, Printer}
import printing.Texts.Text
import annotation.internal.sharable

import scala.annotation.internal.sharable

object Annotations {

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

def hasOneOfMetaAnnotation(metaSyms: Set[Symbol], orNoneOf: Set[Symbol] = Set.empty)(using Context): Boolean = atPhaseNoLater(erasurePhase) {
def go(metaSyms: Set[Symbol]) =
def recTp(tp: Type): Boolean = tp.dealiasKeepAnnots match
case AnnotatedType(parent, metaAnnot) => metaSyms.exists(metaAnnot.matches) || recTp(parent)
case _ => false
def rec(tree: Tree): Boolean = methPart(tree) match
case New(tpt) => rec(tpt)
case Select(qual, _) => rec(qual)
case Annotated(arg, metaAnnot) => metaSyms.exists(metaAnnot.tpe.classSymbol.derivesFrom) || rec(arg)
case t @ Ident(_) => recTp(t.tpe)
case Typed(expr, _) => rec(expr)
case _ => false
metaSyms.exists(symbol.hasAnnotation) || rec(tree)
go(metaSyms) || orNoneOf.nonEmpty && !go(orNoneOf)
}

/** Operations for hash-consing, can be overridden */
def hash: Int = System.identityHashCode(this)
def eql(that: Annotation) = this eq that
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,8 @@ class Definitions {
@tu lazy val UncheckedVarianceAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedVariance")
@tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile")
@tu lazy val WithPureFunsAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WithPureFuns")
@tu lazy val BeanGetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanGetter")
@tu lazy val BeanSetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanSetter")
@tu lazy val FieldMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.field")
@tu lazy val GetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.getter")
@tu lazy val ParamMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.param")
Expand All @@ -1037,8 +1039,10 @@ class Definitions {
@tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable")

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

// A list of annotations that are commonly used to indicate that a field/method argument or return
// type is not null. These annotations are used by the nullification logic in JavaNullInterop to
Expand Down
9 changes: 9 additions & 0 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,15 @@ object SymDenotations {
final def filterAnnotations(p: Annotation => Boolean)(using Context): Unit =
annotations = annotations.filterConserve(p)

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
val cpy = copySymDenotation()
cpy.annotations = annotationsCarrying(meta, orNoneOf = orNoneOf)
cpy.installAfter(phase)

/** Optionally, the annotation matching the given class symbol */
final def getAnnotation(cls: Symbol)(using Context): Option[Annotation] =
dropOtherAnnotations(annotations, cls) match {
Expand Down
13 changes: 6 additions & 7 deletions compiler/src/dotty/tools/dotc/transform/BeanProperties.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import core._
import ast.tpd._
import Annotations._
import Contexts._
import Symbols.newSymbol
import Symbols.*
import SymUtils.*
import Decorators._
import Flags._
import Names._
Expand All @@ -23,8 +24,6 @@ class BeanProperties(thisPhase: DenotTransformer):
} ::: origBody)

def generateAccessors(valDef: ValDef)(using Context): List[Tree] =
import Symbols.defn

def generateGetter(valDef: ValDef, annot: Annotation)(using Context) : Tree =
val prefix = if annot matches defn.BooleanBeanPropertyAnnot then "is" else "get"
val meth = newSymbol(
Expand All @@ -34,9 +33,9 @@ class BeanProperties(thisPhase: DenotTransformer):
info = MethodType(Nil, valDef.denot.info),
coord = annot.tree.span
).enteredAfter(thisPhase).asTerm
meth.addAnnotations(valDef.symbol.annotations)
.withAnnotationsCarrying(valDef.symbol, defn.BeanGetterMetaAnnot)
val body: Tree = ref(valDef.symbol)
DefDef(meth, body)
DefDef(meth, body).withSpan(meth.span)

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

def prefixedName(prefix: String, valName: Name) =
Expand Down
25 changes: 4 additions & 21 deletions compiler/src/dotty/tools/dotc/transform/Memoize.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package transform
import core._
import DenotTransformers._
import Contexts._
import Phases.phaseOf
import Phases.*
import SymDenotations.SymDenotation
import Denotations._
import Symbols._
Expand Down Expand Up @@ -114,26 +114,10 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
flags = Private | (if (sym.is(StableRealizable)) EmptyFlags else Mutable),
info = fieldType,
coord = tree.span
).withAnnotationsCarrying(sym, defn.FieldMetaAnnot)
).withAnnotationsCarrying(sym, defn.FieldMetaAnnot, orNoneOf = defn.MetaAnnots)
.enteredAfter(thisPhase)
}

def addAnnotations(denot: Denotation): Unit =
denot match {
case fieldDenot: SymDenotation if sym.annotations.nonEmpty =>
val cpy = fieldDenot.copySymDenotation()
cpy.annotations = sym.annotations
cpy.installAfter(thisPhase)
case _ => ()
}

def removeUnwantedAnnotations(denot: SymDenotation, metaAnnotSym: ClassSymbol): Unit =
if (sym.annotations.nonEmpty) {
val cpy = sym.copySymDenotation()
cpy.filterAnnotations(_.symbol.hasAnnotation(metaAnnotSym))
cpy.installAfter(thisPhase)
}

val NoFieldNeeded = Lazy | Deferred | JavaDefined | Inline

def erasedBottomTree(sym: Symbol) =
Expand Down Expand Up @@ -183,8 +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)
addAnnotations(fieldDef.denot)
removeUnwantedAnnotations(sym, defn.GetterMetaAnnot)
sym.copyAndKeepAnnotationsCarrying(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
Expand All @@ -210,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)))
removeUnwantedAnnotations(sym, defn.SetterMetaAnnot)
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.SetterMetaAnnot))
setterDef
else
// Curiously, some accessors from Scala2 have ' ' suffixes.
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/Pickler.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package dotty.tools.dotc
package dotty.tools
package dotc
package transform

import core._
Expand Down Expand Up @@ -147,6 +148,7 @@ class Pickler extends Phase {
if unequal then
output("before-pickling.txt", previous)
output("after-pickling.txt", unpickled)
//sys.process.Process("diff -u before-pickling.txt after-pickling.txt").!
report.error(em"""pickling difference for $cls in ${cls.source}, for details:
|
| diff before-pickling.txt after-pickling.txt""")
Expand Down
22 changes: 7 additions & 15 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package dotty.tools.dotc
package dotty.tools
package dotc
package transform

import dotty.tools.dotc.ast.{Trees, tpd, untpd, desugar}
Expand Down Expand Up @@ -156,12 +157,14 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
checkInferredWellFormed(tree.tpt)
if sym.is(Method) then
if sym.isSetter then
removeUnwantedAnnotations(sym, defn.SetterMetaAnnot, NoSymbol, keepIfNoRelevantAnnot = false)
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.SetterMetaAnnot))
else
if sym.is(Param) then
removeUnwantedAnnotations(sym, defn.ParamMetaAnnot, NoSymbol, keepIfNoRelevantAnnot = true)
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots)
else if sym.is(ParamAccessor) then
sym.copyAndKeepAnnotationsCarrying(thisPhase, Set(defn.GetterMetaAnnot, defn.FieldMetaAnnot))
else
removeUnwantedAnnotations(sym, defn.GetterMetaAnnot, defn.FieldMetaAnnot, keepIfNoRelevantAnnot = !sym.is(ParamAccessor))
sym.copyAndKeepAnnotationsCarrying(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
Expand All @@ -183,17 +186,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
=> Checking.checkAppliedTypesIn(tree)
case _ =>

private def removeUnwantedAnnotations(sym: Symbol, metaAnnotSym: Symbol,
metaAnnotSymBackup: Symbol, keepIfNoRelevantAnnot: Boolean)(using Context): Unit =
def shouldKeep(annot: Annotation): Boolean =
val annotSym = annot.symbol
annotSym.hasAnnotation(metaAnnotSym)
|| annotSym.hasAnnotation(metaAnnotSymBackup)
|| (keepIfNoRelevantAnnot && {
!annotSym.annotations.exists(metaAnnot => defn.FieldAccessorMetaAnnots.contains(metaAnnot.symbol))
})
if sym.annotations.nonEmpty then
sym.filterAnnotations(shouldKeep(_))

private def transformSelect(tree: Select, targs: List[Tree])(using Context): Tree = {
val qual = tree.qualifier
Expand Down
7 changes: 2 additions & 5 deletions compiler/src/dotty/tools/dotc/transform/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -270,11 +270,8 @@ object SymUtils:
def isEnumCase(using Context): Boolean =
self.isAllOf(EnumCase, butNot = JavaDefined)

def annotationsCarrying(meta: ClassSymbol)(using Context): List[Annotation] =
self.annotations.filter(_.symbol.hasAnnotation(meta))

def withAnnotationsCarrying(from: Symbol, meta: ClassSymbol)(using Context): self.type = {
self.addAnnotations(from.annotationsCarrying(meta))
def withAnnotationsCarrying(from: Symbol, meta: Symbol, orNoneOf: Set[Symbol] = Set.empty)(using Context): self.type = {
self.addAnnotations(from.annotationsCarrying(Set(meta), orNoneOf))
self
}

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/util/Spans.scala
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ object Spans {
assert(isSpan)
if (this == NoCoord) NoSpan else Span(-1 - encoding)
}
override def toString = if isSpan then s"$toSpan" else s"Coord(idx=$toIndex)"
}

/** An index coordinate */
Expand Down
1 change: 1 addition & 0 deletions tests/run/beans.check
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
true
10
[@beans.LibraryAnnotation_1()]
[]
some text
other text
1 change: 1 addition & 0 deletions tests/run/beans/Test_3.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public A run() throws ReflectiveOperationException{
System.out.println(a.isY());
System.out.println(new T2().getX());

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

System.out.println(a.getMutableOneWithLongName());
Expand Down
37 changes: 37 additions & 0 deletions tests/run/i12492.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
inspecting constructor MyTable
inspecting param aaaParam1 @MyColumnBase
inspecting param fldParam1
inspecting param getParam1
inspecting param parParam1 @MyColumnBase
inspecting field aaaField1 @MyColumnBase
inspecting field aaaParam1
inspecting field fldField1 @MyColumnBase
inspecting field fldParam1 @MyColumnBase
inspecting field getField1
inspecting field getParam1
inspecting field parField1
inspecting field parParam1
inspecting method aaaField1
inspecting method aaaParam1
inspecting method fldField1
inspecting method fldParam1
inspecting method getField1 @MyColumnBase
inspecting method getParam1 @MyColumnBase
inspecting method parField1
inspecting method parParam1
inspecting constructor MyTable2
inspecting param fldParam2
inspecting param getParam2
inspecting param parParam2 @MyColumnBase
inspecting field fldField2 @MyColumnBase
inspecting field fldParam2 @MyColumnBase
inspecting field getField2
inspecting field getParam2
inspecting field parField2
inspecting field parParam2
inspecting method fldField2
inspecting method fldParam2
inspecting method getField2 @MyColumnBase
inspecting method getParam2 @MyColumnBase
inspecting method parField2
inspecting method parParam2
5 changes: 5 additions & 0 deletions tests/run/i12492/MyColumnBase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface MyColumnBase {}
27 changes: 27 additions & 0 deletions tests/run/i12492/MyTable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import scala.annotation.meta.{ field as fld, getter as get, param as par }

type FldColumn = MyColumnBase @fld
type GetColumn = MyColumnBase @get
type ParColumn = MyColumnBase @par

class MyTable(
@(MyColumnBase ) val aaaParam1: String,
@(MyColumnBase @fld) val fldParam1: String,
@(MyColumnBase @get) val getParam1: String,
@(MyColumnBase @par) val parParam1: String,
) {
@(MyColumnBase ) val aaaField1: String = ""
@(MyColumnBase @fld) val fldField1: String = ""
@(MyColumnBase @get) val getField1: String = ""
@(MyColumnBase @par) val parField1: String = ""
}

class MyTable2(
@FldColumn val fldParam2: String,
@GetColumn val getParam2: String,
@ParColumn val parParam2: String,
) {
@FldColumn val fldField2: String = ""
@GetColumn val getField2: String = ""
@ParColumn val parField2: String = ""
}
31 changes: 31 additions & 0 deletions tests/run/i12492/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// scalajs: --skip
object Test:
def main(args: Array[String]): Unit =
go(classOf[MyTable])
go(classOf[MyTable2])

def go(cls: Class[?]): Unit =
for c <- cls.getDeclaredConstructors.sortBy(_.getName) do
c.setAccessible(true)
println(s"inspecting constructor ${c.getName}")
for p <- c.getParameters.sortBy(_.getName) do
print(s"inspecting param ${p.getName}")
for a <- p.getAnnotations.sortBy(_.annotationType.toString) do
print(s" @${a.annotationType.getName}")
println()

for (m <- cls.getDeclaredFields.sortBy(_.getName)) {
m.setAccessible(true)
print(s"inspecting field ${m.getName}")
for a <- m.getAnnotations().sortBy(_.annotationType.toString) do
print(s" @${a.annotationType.getName}")
println()
}

for (m <- cls.getDeclaredMethods.sortBy(_.getName)) {
m.setAccessible(true)
print(s"inspecting method ${m.getName}")
for a <- m.getAnnotations().sortBy(_.annotationType.toString) do
print(s" @${a.annotationType.getName}")
println()
}
Loading