Skip to content

Commit 793304e

Browse files
committed
Keep annotation order
This change makes sure non-repeated annotations are kept in the order they were found in the source code. The motivation is not necessarily to have them in the original order, but to have them in an order that is deterministic across rebuilds (potentially even across different machines), for reasons discussed further in #7661 and the corresponding scala/scala-dev#405 Some integration tests were added in `tests/pos` to be picked up by `IdempotencyCheck.scala`, but unfortunately I haven't successfully reproduced the nondeterminism that way. I didn't see an obvious place for a 'unit test' of this code, I'd be happy to add one when someone can recommend a good place to put it. This is basically the dotty equivalent of scala/scala@954c5d3 Fixes #14743
1 parent 9d6e87a commit 793304e

File tree

10 files changed

+54
-1
lines changed

10 files changed

+54
-1
lines changed

compiler/src/dotty/tools/dotc/transform/RepeatableAnnotations.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import Constants._
1111
import Types._
1212
import Decorators._
1313

14+
import scala.collection.mutable
15+
1416
class RepeatableAnnotations extends MiniPhase:
1517

1618
override def phaseName: String = RepeatableAnnotations.name
@@ -28,7 +30,7 @@ class RepeatableAnnotations extends MiniPhase:
2830
tree
2931

3032
private def aggregateAnnotations(annotations: Seq[Annotation])(using Context): List[Annotation] =
31-
val annsByType = annotations.groupBy(_.symbol)
33+
val annsByType = stableGroupBy(annotations, _.symbol)
3234
annsByType.flatMap {
3335
case (_, a :: Nil) => a :: Nil
3436
case (sym, anns) if sym.derivesFrom(defn.ClassfileAnnotationClass) =>
@@ -50,6 +52,14 @@ class RepeatableAnnotations extends MiniPhase:
5052
case (_, anns) => anns
5153
}.toList
5254

55+
private def stableGroupBy[A, K](ins: Seq[A], f: A => K): scala.collection.MapView[K, List[A]] =
56+
val out = new mutable.LinkedHashMap[K, mutable.ListBuffer[A]]()
57+
for (in <- ins) {
58+
val buffer = out.getOrElseUpdate(f(in), new mutable.ListBuffer)
59+
buffer += in
60+
}
61+
out.view.mapValues(_.toList)
62+
5363
object RepeatableAnnotations:
5464
val name: String = "repeatableAnnotations"
5565
val description: String = "aggregate repeatable annotations"

tests/annotations1/a.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class Annot1(s: String) extends scala.annotation.StaticAnnotation
2+
class Annot2(s: Class[_]) extends scala.annotation.StaticAnnotation

tests/annotations1/b.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@Annot1("foo")
2+
@Annot2(classOf[AnyRef])
3+
class Test

tests/annotationsJava/Annot1.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import java.lang.annotation.*;
2+
@Retention(RetentionPolicy.RUNTIME)
3+
@Target(ElementType.TYPE)
4+
@Inherited
5+
@interface Annot1 { String value() default ""; }

tests/annotationsJava/Annot2.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@Retention(RetentionPolicy.RUNTIME)
2+
@Target(ElementType.TYPE)
3+
@Inherited
4+
@interface Annot2 { Class value(); }

tests/annotationsJava/b.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@Annot1("foo") @Annot2(classOf[AnyRef]) class Test

tests/pos/Annotations.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package foo.bar
2+
3+
import jdk.jfr.Enabled
4+
5+
@Enabled
6+
@Deprecated
7+
final class Annotations {
8+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import java.lang.annotation.*;
2+
3+
@Repeatable(Annot1.Container.class)
4+
@Retention(RetentionPolicy.RUNTIME)
5+
@Target(ElementType.TYPE)
6+
@interface Annot1 { String value() default "";
7+
8+
@Retention(RetentionPolicy.RUNTIME)
9+
@Target(ElementType.TYPE)
10+
public static @interface Container {
11+
Annot1[] value();
12+
}
13+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import java.lang.annotation.*;
2+
3+
@Retention(RetentionPolicy.RUNTIME)
4+
@Target(ElementType.TYPE)
5+
@Inherited
6+
@interface Annot2 { Class value(); }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@Annot1("foo") @Annot2(classOf[String]) @Annot1("bar") class Test

0 commit comments

Comments
 (0)