Skip to content
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

Support use-site meta-annotations #16445

Merged
merged 16 commits into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from 9 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
13 changes: 13 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,19 @@ object Annotations {
def sameAnnotation(that: Annotation)(using Context): Boolean =
symbol == that.symbol && tree.sameTree(that.tree)

def hasOneOfMetaAnnotation(metaSyms: Symbol*)(using Context): Boolean =
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)
sjrd marked this conversation as resolved.
Show resolved Hide resolved
case t @ Ident(_) => recTp(t.tpe)
case Typed(expr, _) => rec(expr)
case _ => false
metaSyms.exists(symbol.hasAnnotation) || rec(tree)

/** Operations for hash-consing, can be overridden */
def hash: Int = System.identityHashCode(this)
def eql(that: Annotation) = this eq that
Expand Down
2 changes: 2 additions & 0 deletions 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 Down
14 changes: 10 additions & 4 deletions compiler/src/dotty/tools/dotc/transform/BeanProperties.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,12 @@ class BeanProperties(thisPhase: DenotTransformer):
info = MethodType(Nil, valDef.denot.info),
coord = annot.tree.span
).enteredAfter(thisPhase).asTerm
meth.addAnnotations(valDef.symbol.annotations)
val annots = valDef.symbol.annotations.filter { a =>
a.hasOneOfMetaAnnotation(defn.BeanGetterMetaAnnot) || !a.hasOneOfMetaAnnotation(defn.BeanSetterMetaAnnot)
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
}
meth.addAnnotations(annots)
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 +51,12 @@ 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)
val annots = valDef.symbol.annotations.filter { a =>
a.hasOneOfMetaAnnotation(defn.BeanSetterMetaAnnot) || !a.hasOneOfMetaAnnotation(defn.BeanGetterMetaAnnot)
}
meth.addAnnotations(annots)
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
6 changes: 3 additions & 3 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 @@ -122,15 +122,15 @@ class Memoize extends MiniPhase with IdentityDenotTransformer { thisPhase =>
denot match {
case fieldDenot: SymDenotation if sym.annotations.nonEmpty =>
val cpy = fieldDenot.copySymDenotation()
cpy.annotations = sym.annotations
cpy.annotations = atPhase(typerPhase)(sym.annotationsCarrying(defn.FieldMetaAnnot))
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
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.filterAnnotations(annot => atPhase(typerPhase)(annot.hasOneOfMetaAnnotation(metaAnnotSym)))
cpy.installAfter(thisPhase)
}

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
sjrd marked this conversation as resolved.
Show resolved Hide resolved
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
11 changes: 4 additions & 7 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
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
package transform

import dotty.tools.dotc.ast.{Trees, tpd, untpd, desugar}
Expand Down Expand Up @@ -186,12 +187,8 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
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))
})
annot.hasOneOfMetaAnnotation(metaAnnotSym, metaAnnotSymBackup)
|| keepIfNoRelevantAnnot && !annot.hasOneOfMetaAnnotation(defn.FieldAccessorMetaAnnots.toList*)
if sym.annotations.nonEmpty then
sym.filterAnnotations(shouldKeep(_))

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ object SymUtils:
self.isAllOf(EnumCase, butNot = JavaDefined)

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

def withAnnotationsCarrying(from: Symbol, meta: ClassSymbol)(using Context): self.type = {
self.addAnnotations(from.annotationsCarrying(meta))
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
13 changes: 13 additions & 0 deletions tests/run/i12492.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
inspecting field fieldName1 @MyColumnBase
inspecting field fieldName2 @MyColumnBase
inspecting field getterName1
inspecting field getterName2
inspecting method fieldName1
inspecting method fieldName2
inspecting method getterName1 @MyColumnBase
inspecting method getterName2 @MyColumnBase
inspecting constructor MyTable
inspecting param fieldName1
inspecting param fieldName2
inspecting param getterName1
inspecting param getterName2
7 changes: 7 additions & 0 deletions tests/run/i12492/MyColumnBase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface MyColumnBase {
String name() default "";
}
16 changes: 16 additions & 0 deletions tests/run/i12492/MyTable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import scala.annotation.meta.*

type FieldColumn = MyColumnBase @field
type GetterColumn = MyColumnBase @getter

class MyTable(
@(MyColumnBase @field)(name="FIELD_NAME1")
val fieldName1: String,
@FieldColumn(name="FIELD_NAME2")
val fieldName2: String,

@(MyColumnBase @getter)(name="GETTER_NAME1")
val getterName1: String,
@GetterColumn(name="GETTER_NAME2")
val getterName2: String,
)
29 changes: 29 additions & 0 deletions tests/run/i12492/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// scalajs: --skip
object Test:
def main(args: Array[String]): Unit =
val cls = classOf[MyTable]

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()
}

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()
11 changes: 11 additions & 0 deletions tests/run/i15318.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
inspecting field value
inspecting field value2
inspecting method getValue @JsonProperty
inspecting method getValue2 @JsonProperty
inspecting method setValue
inspecting method setValue2
inspecting method value
inspecting method value2
inspecting method value2_$eq
inspecting method value_$eq
inspecting constructor TestBeanProperty
7 changes: 7 additions & 0 deletions tests/run/i15318/JsonProperty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface JsonProperty {
String value() default "";
}
29 changes: 29 additions & 0 deletions tests/run/i15318/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// scalajs: --skip
object Test:
def main(args: Array[String]): Unit =
val cls = classOf[TestBeanProperty]

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()
}

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()
16 changes: 16 additions & 0 deletions tests/run/i15318/TestBeanProperty.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import scala.annotation.meta.beanGetter
import scala.beans.BeanProperty

type BeanGetterJsonProperty = JsonProperty @beanGetter

class TestBeanProperty {

@(JsonProperty @beanGetter)(value = "REAL_VALUE")
@BeanProperty
var value: String = _

@BeanGetterJsonProperty(value = "REAL_VALUE2")
@BeanProperty
var value2: String = _

}