From 7500b0504747de41f5c3f5ff67d6577c4227d27a Mon Sep 17 00:00:00 2001 From: Arnout Engelen Date: Tue, 18 Jun 2024 17:00:49 +0200 Subject: [PATCH] Fix deterministically adding additional interfaces When a class contains calls to 'super' for traits it does not directly implement, these are added to the list of interfaces of the generated class. Previously, because these interfaces were determined using set logic, the ordering of that list was not deterministic. This change makes the order deterministic (assuming the order in which these calls are registered using `registerSuperCall` in the `CollectSuperCalls` phase is deterministic within each class) Fixes #20496 --- .../tools/backend/jvm/BTypesFromSymbols.scala | 7 +++--- .../backend/jvm/DottyBackendInterface.scala | 2 +- .../dotty/tools/backend/jvm/GenBCode.scala | 7 +++--- .../backend/jvm/DottyBytecodeTests.scala | 24 +++++++++++++++++++ 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala index b8d7ee04c870..97934935f352 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala @@ -113,11 +113,12 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I, val frontendAcce val directlyInheritedTraits = sym.directlyInheritedTraits val directlyInheritedTraitsSet = directlyInheritedTraits.toSet val allBaseClasses = directlyInheritedTraits.iterator.flatMap(_.asClass.baseClasses.drop(1)).toSet - val superCalls = superCallsMap.getOrElse(sym, Set.empty) - val additional = (superCalls -- directlyInheritedTraitsSet).filter(_.is(Trait)) + val superCalls = superCallsMap.getOrElse(sym, List.empty) + val superCallsSet = superCalls.toSet + val additional = superCalls.filter(t => !directlyInheritedTraitsSet(t) && t.is(Trait)) // if (additional.nonEmpty) // println(s"$fullName: adding supertraits $additional") - directlyInheritedTraits.filter(t => !allBaseClasses(t) || superCalls(t)) ++ additional + directlyInheritedTraits.filter(t => !allBaseClasses(t) || superCallsSet(t)) ++ additional } val interfaces = classSym.superInterfaces.map(classBTypeFromSymbol) diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 8016c2bfc209..cab17b31c3f3 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -25,7 +25,7 @@ import StdNames.nme import NameKinds.{LazyBitMapName, LazyLocalName} import Names.Name -class DottyBackendInterface(val superCallsMap: ReadOnlyMap[Symbol, Set[ClassSymbol]])(using val ctx: Context) { +class DottyBackendInterface(val superCallsMap: ReadOnlyMap[Symbol, List[ClassSymbol]])(using val ctx: Context) { private val desugared = new java.util.IdentityHashMap[Type, tpd.Select] diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index a616241d9a3e..58daa01e4bdf 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -23,10 +23,11 @@ class GenBCode extends Phase { self => override def isRunnable(using Context) = super.isRunnable && !ctx.usedBestEffortTasty - private val superCallsMap = new MutableSymbolMap[Set[ClassSymbol]] + private val superCallsMap = new MutableSymbolMap[List[ClassSymbol]] def registerSuperCall(sym: Symbol, calls: ClassSymbol): Unit = { - val old = superCallsMap.getOrElse(sym, Set.empty) - superCallsMap.update(sym, old + calls) + val old = superCallsMap.getOrElse(sym, List.empty) + if (!old.contains(calls)) + superCallsMap.update(sym, old :+ calls) } private val entryPoints = new mutable.HashSet[String]() diff --git a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala index f80336646dfd..e92c4c26adb8 100644 --- a/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala @@ -1963,6 +1963,30 @@ class DottyBytecodeTests extends DottyBytecodeTest { assertSameCode(instructions, expected) } } + + /** + * Test 'additional' imports are generated in deterministic order + * https://github.com/scala/scala3/issues/20496 + */ + @Test def deterministicAdditionalImports = { + val source = + """trait Actor: + | def receive() = () + |trait Timers: + | def timers() = () + |abstract class ShardCoordinator extends Actor with Timers + |class PersistentShardCoordinator extends ShardCoordinator: + | def foo = + | super.receive() + | super.timers()""".stripMargin + checkBCode(source) { dir => + val clsIn = dir.lookupName("PersistentShardCoordinator.class", directory = false).input + val clsNode = loadClassNode(clsIn) + + val expected = List("Actor", "Timers") + assertEquals(expected, clsNode.interfaces.asScala) + } + } } object invocationReceiversTestCode {