From 954c5d32d71a43b141be546877b01183a994a1b2 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Wed, 14 Nov 2018 16:41:09 +1000 Subject: [PATCH] Stabilize order of annotations in the class file Regressed in #6846, which added support for encoding repeated annotations. Test failure before replacing `groupBy` with `LinkedHashMap`: ``` $ sbt junit/testOnly scala.tools.nsc.DeterminismTest ... java.lang.AssertionError: assertion failed: Difference detected between recompiling List(b.scala, Annot1.java) Run: jardiff -r /var/folders/tz/p8vd07wn7wxck3b9v54grlzw0000gp/T/reference814657788418452571 /var/folders/tz/p8vd07wn7wxck3b9v54grlzw0000gp/T/recompileOutput4882243280168823330 $ jardiff -r /var/folders/tz/p8vd07wn7wxck3b9v54grlzw0000gp/T/reference814657788418452571 /var/folders/tz/p8vd07wn7wxck3b9v54grlzw0000gp/T/recompileOutput4882243280168823330 diff --git a/Test.class.asm b/Test.class.asm index 98bfd80..a056f9a 100644 --- a/Test.class.asm +++ b/Test.class.asm @@ -4,10 +4,10 @@ // compiled from: b.scala - @LAnnot2;(value=java.lang.Object.class) - @LAnnot1;(value="foo") + @LAnnot2;(value=java.lang.Object.class) + @Lscala/reflect/ScalaSignature;(bytes="\u0006\u0001u1AAA\u0002\u0001\r!)Q\u0002\u0001C\u0001\u001d\u0009!A+Z:u\u0015\u0005!\u0011a\u0002\u001ff[B$\u0018PP\u0002\u0001'\u0009\u0001q\u0001\u0005\u0002\u0009\u00175\u0009\u0011BC\u0001\u000b\u0003\u0015\u00198-\u00197b\u0013\u0009a\u0011B\u0001\u0004B]f\u0014VMZ\u0001\u0007y%t\u0017\u000e\u001e \u0015\u0003=\u0001\"\u0001\u0005\u0001\u000e\u0003\rAC\u0001\u0001\n\u0016-A\u0011\u0001cE\u0005\u0003)\r\u0011a!\u00118o_R\u0014\u0014!\u0002 groupRepeatableAnnotations(x._1, x._2.toList)).toList } // assumes non-empty `anns` diff --git a/test/junit/scala/tools/nsc/DeterminismTest.scala b/test/junit/scala/tools/nsc/DeterminismTest.scala index 8651f23dcf0f..fabd2eb9e87f 100644 --- a/test/junit/scala/tools/nsc/DeterminismTest.scala +++ b/test/junit/scala/tools/nsc/DeterminismTest.scala @@ -1,12 +1,16 @@ package scala.tools.nsc +import java.io.{File, OutputStreamWriter} +import java.nio.charset.Charset import java.nio.file.attribute.BasicFileAttributes import java.nio.file.{FileVisitResult, Files, Path, SimpleFileVisitor} import java.util +import javax.tools.ToolProvider import org.junit.Test -import scala.collection.JavaConverters.asScalaIteratorConverter +import scala.collection.JavaConverters.{asScalaIteratorConverter, seqAsJavaListConverter} +import scala.collection.immutable import scala.language.implicitConversions import scala.reflect.internal.util.{BatchSourceFile, SourceFile} import scala.reflect.io.PlainNioFile @@ -187,6 +191,78 @@ class DeterminismTest { test(List(code)) } + @Test def testAnnotations1(): Unit = { + def code = List[SourceFile]( + source("a.scala", + """ + |class Annot1(s: String) extends scala.annotation.StaticAnnotation + |class Annot2(s: Class[_]) extends scala.annotation.StaticAnnotation + | + """.stripMargin), + source("b.scala", + """ + |@Annot1("foo") + |@Annot2(classOf[AnyRef]) + |class Test + """.stripMargin) + ) + test(List(code)) + } + + @Test def testAnnotationsJava(): Unit = { + def code = List[SourceFile]( + source("Annot1.java", + """ + |import java.lang.annotation.*; + |@Retention(RetentionPolicy.RUNTIME) + |@Target(ElementType.TYPE) + |@Inherited + |@interface Annot1 { String value() default ""; } + | + |@Retention(RetentionPolicy.RUNTIME) + |@Target(ElementType.TYPE) + |@Inherited + |@interface Annot2 { Class value(); } + | + """.stripMargin), + source("b.scala", + """ + |@Annot1("foo") @Annot2(classOf[AnyRef]) class Test + """.stripMargin) + ) + test(List(code)) + } + + @Test def testAnnotationsJavaRepeatable(): Unit = { + val javaAnnots = source("Annot1.java", + """ + |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(); + | } + |} + | + |@Retention(RetentionPolicy.RUNTIME) + |@Target(ElementType.TYPE) + |@Inherited + |@interface Annot2 { Class value(); } + """.stripMargin) + def code = + List(source("dummy.scala", ""), source("b.scala", + """ + |@Annot1("foo") @Annot2(classOf[String]) @Annot1("bar") class Test + """.stripMargin) + ) + test(List(javaAnnots) :: code :: Nil) + } + def source(name: String, code: String): SourceFile = new BatchSourceFile(name, code) private def test(groups: List[List[SourceFile]]): Unit = { val referenceOutput = Files.createTempDirectory("reference") @@ -202,7 +278,22 @@ class DeterminismTest { val r = new Run // println("scalac " + files.mkString(" ")) r.compileSources(files) - assert(!storeReporter.hasErrors, storeReporter.infos.mkString("\n")) + Predef.assert(!storeReporter.hasErrors, storeReporter.infos.mkString("\n")) + files.filter(_.file.name.endsWith(".java")) match { + case Nil => + case javaSources => + def tempFileFor(s: SourceFile): Path = { + val f = output.resolve(s.file.name) + Files.write(f, new String(s.content).getBytes(Charset.defaultCharset())) + } + val options = List("-d", output.toString) + val javac = ToolProvider.getSystemJavaCompiler + val fileMan = javac.getStandardFileManager(null, null, null) + val javaFileObjects = fileMan.getJavaFileObjects(javaSources.map(s => tempFileFor(s).toAbsolutePath.toString): _*) + val task = javac.getTask(new OutputStreamWriter(System.out), fileMan, null, options.asJava, Nil.asJava, javaFileObjects) + val result = task.call() + Predef.assert(result) + } } for (group <- groups.init) {