Skip to content

Add support for repeatable annotations #10751

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 3 commits into from
Mar 18, 2021
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
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ class Compiler {
new RestoreScopes, // Repair scopes rendered invalid by moving definitions in prior phases of the group
new SelectStatic, // get rid of selects that would be compiled into GetStatic
new sjs.JUnitBootstrappers, // Generate JUnit-specific bootstrapper classes for Scala.js (not enabled by default)
new CollectSuperCalls) :: // Find classes that are called with super
new CollectSuperCalls, // Find classes that are called with super
new RepeatableAnnotations) :: // Aggregate repeatable annotations
Nil

/** Generate the output of the compilation */
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 @@ -923,6 +923,8 @@ class Definitions {
@tu lazy val TargetNameAnnot: ClassSymbol = requiredClass("scala.annotation.targetName")
@tu lazy val VarargsAnnot: ClassSymbol = requiredClass("scala.annotation.varargs")

@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] =
Set(FieldMetaAnnot, GetterMetaAnnot, ParamMetaAnnot, SetterMetaAnnot)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package dotty.tools.dotc
package transform

import core._
import ast.tpd._
import Contexts._
import MegaPhase._
import Annotations._
import Symbols.defn
import Constants._
import Types._
import Decorators._

class RepeatableAnnotations extends MiniPhase:
override def phaseName = "repeatableAnnotations"

override def transformTypeDef(tree: TypeDef)(using Context): Tree = transformDef(tree)
override def transformValDef(tree: ValDef)(using Context): Tree = transformDef(tree)
override def transformDefDef(tree: DefDef)(using Context): Tree = transformDef(tree)

private def transformDef(tree: DefTree)(using Context) =
val annotations = tree.symbol.annotations
if (!annotations.isEmpty) then
tree.symbol.annotations = aggregateAnnotations(tree.symbol.annotations)
Copy link
Contributor

@liufengyun liufengyun Mar 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually, we use SymTransformer to change symbol denotations. I think here it's fine to do so as the change is not supposed to be visible in a separately compiled module.

tree

private def aggregateAnnotations(annotations: Seq[Annotation])(using Context): List[Annotation] =
val annsByType = annotations.groupBy(_.symbol)
annsByType.flatMap {
case (_, a :: Nil) => a :: Nil
case (sym, anns) if sym.derivesFrom(defn.ClassfileAnnotationClass) =>
sym.getAnnotation(defn.JavaRepeatableAnnot).flatMap(_.argumentConstant(0)) match
case Some(Constant(containerTpe: Type)) =>
val clashingAnns = annsByType.getOrElse(containerTpe.classSymbol, Nil)
if clashingAnns.nonEmpty then
// this is the same error javac would raise in this case
val pos = clashingAnns.head.tree.srcPos
report.error("Container must not be present at the same time as the element it contains", pos)
Nil
else
val aggregated = JavaSeqLiteral(anns.map(_.tree).toList, TypeTree(sym.typeRef))
Annotation(containerTpe, NamedArg("value".toTermName, aggregated)) :: Nil
case _ =>
val pos = anns.head.tree.srcPos
report.error("Not repeatable annotation repeated", pos)
Nil
case (_, anns) => anns
}.toList
9 changes: 9 additions & 0 deletions tests/neg/repeatable/FirstLevel_0.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package repeatable;

import java.lang.annotation.*;

@Repeatable(SecondLevel_0.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface FirstLevel_0 {
Plain_0[] value();
}
9 changes: 9 additions & 0 deletions tests/neg/repeatable/Plain_0.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package repeatable;

import java.lang.annotation.*;

@Repeatable(FirstLevel_0.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Plain_0 {
public int value();
}
8 changes: 8 additions & 0 deletions tests/neg/repeatable/SecondLevel_0.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package repeatable;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
public @interface SecondLevel_0 {
FirstLevel_0[] value();
}
16 changes: 16 additions & 0 deletions tests/neg/repeatable/Test_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import repeatable._

@Plain_0(1)
@Plain_0(2)
@Plain_0(3)
@FirstLevel_0(Array()) // error
trait U

@FirstLevel_0(Array(Plain_0(4), Plain_0(5)))
@FirstLevel_0(Array(Plain_0(6), Plain_0(7)))
@SecondLevel_0(Array()) // error
trait T

@SecondLevel_0(Array())
@SecondLevel_0(Array()) // error
trait S
6 changes: 6 additions & 0 deletions tests/run/repeatable.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
1
2
3

List(4, 5)
List(6, 7)
9 changes: 9 additions & 0 deletions tests/run/repeatable/FirstLevel_0.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package repeatable;

import java.lang.annotation.*;

@Repeatable(SecondLevel_0.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface FirstLevel_0 {
Plain_0[] value();
}
9 changes: 9 additions & 0 deletions tests/run/repeatable/Plain_0.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package repeatable;

import java.lang.annotation.*;

@Repeatable(FirstLevel_0.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface Plain_0 {
public int value();
}
8 changes: 8 additions & 0 deletions tests/run/repeatable/SecondLevel_0.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package repeatable;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
public @interface SecondLevel_0 {
FirstLevel_0[] value();
}
20 changes: 20 additions & 0 deletions tests/run/repeatable/Test_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import repeatable._

@Plain_0(1)
@Plain_0(2)
@Plain_0(3)
trait U

@FirstLevel_0(Array(Plain_0(4), Plain_0(5)))
@FirstLevel_0(Array(Plain_0(6), Plain_0(7)))
trait T

object Test:
def main(args: Array[String]) =
val annValuesU = classOf[U].getAnnotation(classOf[FirstLevel_0]).value.toList.map(_.value).sorted
annValuesU.foreach(println)

println()

val annValuesT = classOf[T].getAnnotation(classOf[SecondLevel_0]).value.toList.map(_.value.toList.map(_.value).sorted).sorted
annValuesT.foreach(println)