Skip to content

Commit e60ef35

Browse files
authored
Merge pull request #10751 from Kordyjan/repeatable-annotations
Add support for repeatable annotations
2 parents 2e05d9e + 7406c30 commit e60ef35

File tree

12 files changed

+146
-1
lines changed

12 files changed

+146
-1
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ class Compiler {
127127
new RestoreScopes, // Repair scopes rendered invalid by moving definitions in prior phases of the group
128128
new SelectStatic, // get rid of selects that would be compiled into GetStatic
129129
new sjs.JUnitBootstrappers, // Generate JUnit-specific bootstrapper classes for Scala.js (not enabled by default)
130-
new CollectSuperCalls) :: // Find classes that are called with super
130+
new CollectSuperCalls, // Find classes that are called with super
131+
new RepeatableAnnotations) :: // Aggregate repeatable annotations
131132
Nil
132133

133134
/** Generate the output of the compilation */

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,8 @@ class Definitions {
923923
@tu lazy val TargetNameAnnot: ClassSymbol = requiredClass("scala.annotation.targetName")
924924
@tu lazy val VarargsAnnot: ClassSymbol = requiredClass("scala.annotation.varargs")
925925

926+
@tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable")
927+
926928
// A list of meta-annotations that are relevant for fields and accessors
927929
@tu lazy val FieldAccessorMetaAnnots: Set[Symbol] =
928930
Set(FieldMetaAnnot, GetterMetaAnnot, ParamMetaAnnot, SetterMetaAnnot)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core._
5+
import ast.tpd._
6+
import Contexts._
7+
import MegaPhase._
8+
import Annotations._
9+
import Symbols.defn
10+
import Constants._
11+
import Types._
12+
import Decorators._
13+
14+
class RepeatableAnnotations extends MiniPhase:
15+
override def phaseName = "repeatableAnnotations"
16+
17+
override def transformTypeDef(tree: TypeDef)(using Context): Tree = transformDef(tree)
18+
override def transformValDef(tree: ValDef)(using Context): Tree = transformDef(tree)
19+
override def transformDefDef(tree: DefDef)(using Context): Tree = transformDef(tree)
20+
21+
private def transformDef(tree: DefTree)(using Context) =
22+
val annotations = tree.symbol.annotations
23+
if (!annotations.isEmpty) then
24+
tree.symbol.annotations = aggregateAnnotations(tree.symbol.annotations)
25+
tree
26+
27+
private def aggregateAnnotations(annotations: Seq[Annotation])(using Context): List[Annotation] =
28+
val annsByType = annotations.groupBy(_.symbol)
29+
annsByType.flatMap {
30+
case (_, a :: Nil) => a :: Nil
31+
case (sym, anns) if sym.derivesFrom(defn.ClassfileAnnotationClass) =>
32+
sym.getAnnotation(defn.JavaRepeatableAnnot).flatMap(_.argumentConstant(0)) match
33+
case Some(Constant(containerTpe: Type)) =>
34+
val clashingAnns = annsByType.getOrElse(containerTpe.classSymbol, Nil)
35+
if clashingAnns.nonEmpty then
36+
// this is the same error javac would raise in this case
37+
val pos = clashingAnns.head.tree.srcPos
38+
report.error("Container must not be present at the same time as the element it contains", pos)
39+
Nil
40+
else
41+
val aggregated = JavaSeqLiteral(anns.map(_.tree).toList, TypeTree(sym.typeRef))
42+
Annotation(containerTpe, NamedArg("value".toTermName, aggregated)) :: Nil
43+
case _ =>
44+
val pos = anns.head.tree.srcPos
45+
report.error("Not repeatable annotation repeated", pos)
46+
Nil
47+
case (_, anns) => anns
48+
}.toList
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package repeatable;
2+
3+
import java.lang.annotation.*;
4+
5+
@Repeatable(SecondLevel_0.class)
6+
@Retention(RetentionPolicy.RUNTIME)
7+
public @interface FirstLevel_0 {
8+
Plain_0[] value();
9+
}

tests/neg/repeatable/Plain_0.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package repeatable;
2+
3+
import java.lang.annotation.*;
4+
5+
@Repeatable(FirstLevel_0.class)
6+
@Retention(RetentionPolicy.RUNTIME)
7+
public @interface Plain_0 {
8+
public int value();
9+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package repeatable;
2+
3+
import java.lang.annotation.*;
4+
5+
@Retention(RetentionPolicy.RUNTIME)
6+
public @interface SecondLevel_0 {
7+
FirstLevel_0[] value();
8+
}

tests/neg/repeatable/Test_1.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import repeatable._
2+
3+
@Plain_0(1)
4+
@Plain_0(2)
5+
@Plain_0(3)
6+
@FirstLevel_0(Array()) // error
7+
trait U
8+
9+
@FirstLevel_0(Array(Plain_0(4), Plain_0(5)))
10+
@FirstLevel_0(Array(Plain_0(6), Plain_0(7)))
11+
@SecondLevel_0(Array()) // error
12+
trait T
13+
14+
@SecondLevel_0(Array())
15+
@SecondLevel_0(Array()) // error
16+
trait S

tests/run/repeatable.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
1
2+
2
3+
3
4+
5+
List(4, 5)
6+
List(6, 7)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package repeatable;
2+
3+
import java.lang.annotation.*;
4+
5+
@Repeatable(SecondLevel_0.class)
6+
@Retention(RetentionPolicy.RUNTIME)
7+
public @interface FirstLevel_0 {
8+
Plain_0[] value();
9+
}

tests/run/repeatable/Plain_0.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package repeatable;
2+
3+
import java.lang.annotation.*;
4+
5+
@Repeatable(FirstLevel_0.class)
6+
@Retention(RetentionPolicy.RUNTIME)
7+
public @interface Plain_0 {
8+
public int value();
9+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package repeatable;
2+
3+
import java.lang.annotation.*;
4+
5+
@Retention(RetentionPolicy.RUNTIME)
6+
public @interface SecondLevel_0 {
7+
FirstLevel_0[] value();
8+
}

tests/run/repeatable/Test_1.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import repeatable._
2+
3+
@Plain_0(1)
4+
@Plain_0(2)
5+
@Plain_0(3)
6+
trait U
7+
8+
@FirstLevel_0(Array(Plain_0(4), Plain_0(5)))
9+
@FirstLevel_0(Array(Plain_0(6), Plain_0(7)))
10+
trait T
11+
12+
object Test:
13+
def main(args: Array[String]) =
14+
val annValuesU = classOf[U].getAnnotation(classOf[FirstLevel_0]).value.toList.map(_.value).sorted
15+
annValuesU.foreach(println)
16+
17+
println()
18+
19+
val annValuesT = classOf[T].getAnnotation(classOf[SecondLevel_0]).value.toList.map(_.value.toList.map(_.value).sorted).sorted
20+
annValuesT.foreach(println)

0 commit comments

Comments
 (0)