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 5 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
15 changes: 10 additions & 5 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,11 @@ 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)
def body(params: List[List[Tree]]): Tree = Assign(ref(valDef.symbol), params.head.head)
DefDef(meth, body)
val annots = valDef.symbol.annotations.filter { a =>
a.hasOneOfMetaAnnotation(defn.BeanSetterMetaAnnot) | !a.hasOneOfMetaAnnotation(defn.BeanGetterMetaAnnot)
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
}
meth.addAnnotations(annots)
DefDef(meth, (params: List[List[Tree]]) => Assign(ref(valDef.symbol), params.head.head)).withSpan(meth.span)
}

def prefixedName(prefix: String, valName: Name) =
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
9 changes: 9 additions & 0 deletions tests/run/i12492.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
inspecting field brandName
interface MyColumnBase
inspecting field companyName
interface MyColumnBase
inspecting method brandName
inspecting method companyName
inspecting constructor MyTable
inspecting param brandName
inspecting param companyName
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 "";
}
10 changes: 10 additions & 0 deletions tests/run/i12492/MyTable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import scala.annotation.meta.field

type MyColumn = MyColumnBase @field

class MyTable(
@(MyColumnBase @field)(name="BRAND_NAME")
val brandName: String,
@MyColumn(name="COMPANY_NAME")
val companyName: String,
)
26 changes: 26 additions & 0 deletions tests/run/i12492/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// scalajs: --skip
object Test:
def main(args: Array[String]): Unit =
val cls = classOf[MyTable]

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

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

for c <- cls.getDeclaredConstructors.sortBy(_.getName) do
c.setAccessible(true)
println(s"inspecting constructor ${c.getName}")
for p <- c.getParameters.sortBy(_.getName) do
println(s"inspecting param ${p.getName}")
for a <- p.getAnnotations.sortBy(_.annotationType.toString) do
println(a.annotationType)
8 changes: 8 additions & 0 deletions tests/run/i15318.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
inspecting field value
interface JsonProperty
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
inspecting method getValue
interface JsonProperty
inspecting method setValue
inspecting method value
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 "";
}
26 changes: 26 additions & 0 deletions tests/run/i15318/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// scalajs: --skip
object Test:
def main(args: Array[String]): Unit =
val cls = classOf[TestBeanProperty]

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

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

for c <- cls.getDeclaredConstructors.sortBy(_.getName) do
c.setAccessible(true)
println(s"inspecting constructor ${c.getName}")
for p <- c.getParameters.sortBy(_.getName) do
println(s"inspecting param ${p.getName}")
for a <- p.getAnnotations.sortBy(_.annotationType.toString) do
println(a.annotationType)
10 changes: 10 additions & 0 deletions tests/run/i15318/TestBeanProperty.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import scala.annotation.meta.beanGetter
import scala.beans.BeanProperty

class TestBeanProperty {

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

}