From 27bb69a5539cf3d7a961d23825746d7863bd32bb Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 10 Jul 2024 09:06:14 +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 [Cherry-picked 7500b0504747de41f5c3f5ff67d6577c4227d27a][modified] --- .../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 ec23998ccd72..36cffc27ef32 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala @@ -112,11 +112,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 09b80d668af5..ed4ee4a72778 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 2bd469b31a0d..5a8ff2ea9bfc 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -16,10 +16,11 @@ class GenBCode extends Phase { self => override def description: String = GenBCode.description - 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 1a141dbdb978..5388e65d33b7 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 {