From 414091f75768ae8fe207193420eb6bee4a7e88e3 Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Thu, 28 Apr 2022 17:06:36 +0200 Subject: [PATCH] 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 https://github.com/scala/scala/commit/954c5d32d71a43b141be546877b01183a994a1b2 Fixes #14743 --- .../dotc/transform/RepeatableAnnotations.scala | 12 +++++++++++- tests/pos/Annotations.scala | 8 ++++++++ tests/pos/annotations1/a.scala | 2 ++ tests/pos/annotations1/b.scala | 3 +++ tests/pos/annotationsJava/Annot1.java | 5 +++++ tests/pos/annotationsJava/Annot2.java | 5 +++++ tests/pos/annotationsJava/b.scala | 1 + tests/pos/annotationsJavaRepeatable/Annot1.java | 13 +++++++++++++ tests/pos/annotationsJavaRepeatable/Annot2.java | 6 ++++++ tests/pos/annotationsJavaRepeatable/b.scala | 1 + 10 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/pos/Annotations.scala create mode 100644 tests/pos/annotations1/a.scala create mode 100644 tests/pos/annotations1/b.scala create mode 100644 tests/pos/annotationsJava/Annot1.java create mode 100644 tests/pos/annotationsJava/Annot2.java create mode 100644 tests/pos/annotationsJava/b.scala create mode 100644 tests/pos/annotationsJavaRepeatable/Annot1.java create mode 100644 tests/pos/annotationsJavaRepeatable/Annot2.java create mode 100644 tests/pos/annotationsJavaRepeatable/b.scala diff --git a/compiler/src/dotty/tools/dotc/transform/RepeatableAnnotations.scala b/compiler/src/dotty/tools/dotc/transform/RepeatableAnnotations.scala index 24c46cd98e4c..e8f8a80e1a0d 100644 --- a/compiler/src/dotty/tools/dotc/transform/RepeatableAnnotations.scala +++ b/compiler/src/dotty/tools/dotc/transform/RepeatableAnnotations.scala @@ -11,6 +11,8 @@ import Constants._ import Types._ import Decorators._ +import scala.collection.mutable + class RepeatableAnnotations extends MiniPhase: override def phaseName: String = RepeatableAnnotations.name @@ -28,7 +30,7 @@ class RepeatableAnnotations extends MiniPhase: tree private def aggregateAnnotations(annotations: Seq[Annotation])(using Context): List[Annotation] = - val annsByType = annotations.groupBy(_.symbol) + val annsByType = stableGroupBy(annotations, _.symbol) annsByType.flatMap { case (_, a :: Nil) => a :: Nil case (sym, anns) if sym.derivesFrom(defn.ClassfileAnnotationClass) => @@ -50,6 +52,14 @@ class RepeatableAnnotations extends MiniPhase: case (_, anns) => anns }.toList + private def stableGroupBy[A, K](ins: Seq[A], f: A => K): scala.collection.MapView[K, List[A]] = + val out = new mutable.LinkedHashMap[K, mutable.ListBuffer[A]]() + for (in <- ins) { + val buffer = out.getOrElseUpdate(f(in), new mutable.ListBuffer) + buffer += in + } + out.view.mapValues(_.toList) + object RepeatableAnnotations: val name: String = "repeatableAnnotations" val description: String = "aggregate repeatable annotations" diff --git a/tests/pos/Annotations.scala b/tests/pos/Annotations.scala new file mode 100644 index 000000000000..322cf2eafb06 --- /dev/null +++ b/tests/pos/Annotations.scala @@ -0,0 +1,8 @@ +package foo.bar + +import jdk.jfr.Enabled + +@Enabled +@Deprecated +final class Annotations { +} diff --git a/tests/pos/annotations1/a.scala b/tests/pos/annotations1/a.scala new file mode 100644 index 000000000000..8d0df5a2cac5 --- /dev/null +++ b/tests/pos/annotations1/a.scala @@ -0,0 +1,2 @@ +class Annot1(s: String) extends scala.annotation.StaticAnnotation +class Annot2(s: Class[_]) extends scala.annotation.StaticAnnotation diff --git a/tests/pos/annotations1/b.scala b/tests/pos/annotations1/b.scala new file mode 100644 index 000000000000..03b7d0722dfa --- /dev/null +++ b/tests/pos/annotations1/b.scala @@ -0,0 +1,3 @@ +@Annot1("foo") +@Annot2(classOf[AnyRef]) +class Test diff --git a/tests/pos/annotationsJava/Annot1.java b/tests/pos/annotationsJava/Annot1.java new file mode 100644 index 000000000000..dc75aa3ab772 --- /dev/null +++ b/tests/pos/annotationsJava/Annot1.java @@ -0,0 +1,5 @@ +import java.lang.annotation.*; +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@interface Annot1 { String value() default ""; } diff --git a/tests/pos/annotationsJava/Annot2.java b/tests/pos/annotationsJava/Annot2.java new file mode 100644 index 000000000000..56d4f2168585 --- /dev/null +++ b/tests/pos/annotationsJava/Annot2.java @@ -0,0 +1,5 @@ +import java.lang.annotation.*; +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@interface Annot2 { Class value(); } diff --git a/tests/pos/annotationsJava/b.scala b/tests/pos/annotationsJava/b.scala new file mode 100644 index 000000000000..03be7d0e35fd --- /dev/null +++ b/tests/pos/annotationsJava/b.scala @@ -0,0 +1 @@ +@Annot1("foo") @Annot2(classOf[AnyRef]) class Test diff --git a/tests/pos/annotationsJavaRepeatable/Annot1.java b/tests/pos/annotationsJavaRepeatable/Annot1.java new file mode 100644 index 000000000000..60cf37a21f6b --- /dev/null +++ b/tests/pos/annotationsJavaRepeatable/Annot1.java @@ -0,0 +1,13 @@ +import java.lang.annotation.*; + +@Repeatable(Annot1.Container.class) +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@interface Annot1 { String value() default ""; + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public static @interface Container { + Annot1[] value(); + } +} diff --git a/tests/pos/annotationsJavaRepeatable/Annot2.java b/tests/pos/annotationsJavaRepeatable/Annot2.java new file mode 100644 index 000000000000..fb3c7915b0c9 --- /dev/null +++ b/tests/pos/annotationsJavaRepeatable/Annot2.java @@ -0,0 +1,6 @@ +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@interface Annot2 { Class value(); } diff --git a/tests/pos/annotationsJavaRepeatable/b.scala b/tests/pos/annotationsJavaRepeatable/b.scala new file mode 100644 index 000000000000..6c775fdecf6c --- /dev/null +++ b/tests/pos/annotationsJavaRepeatable/b.scala @@ -0,0 +1 @@ +@Annot1("foo") @Annot2(classOf[String]) @Annot1("bar") class Test