From 8ea12613f8750bfddafc8b8f2142f191a66d69ef Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Wed, 21 May 2025 10:51:18 +0000 Subject: [PATCH 1/2] Make hashcode of enum items stable Co-authored-by: anna herlihy Co-authored-by: Miguel Trigueira <12471808+mtrigueira@users.noreply.github.com> --- .../tools/dotc/transform/SyntheticMembers.scala | 17 +++++++++-------- .../run/i19177-stable-enum-item-hashcode.check | 1 + .../run/i19177-stable-enum-item-hashcode.scala | 5 +++++ 3 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 tests/run/i19177-stable-enum-item-hashcode.check create mode 100644 tests/run/i19177-stable-enum-item-hashcode.scala diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 4bc9575996d1..4fd33f7e7155 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -68,7 +68,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { myCaseSymbols = defn.caseClassSynthesized myCaseModuleSymbols = myCaseSymbols.filter(_ ne defn.Any_equals) myEnumValueSymbols = List(defn.Product_productPrefix) - myNonJavaEnumValueSymbols = myEnumValueSymbols :+ defn.Any_toString :+ defn.Enum_ordinal + myNonJavaEnumValueSymbols = myEnumValueSymbols :+ defn.Any_toString :+ defn.Enum_ordinal :+ defn.Any_hashCode } def valueSymbols(using Context): List[Symbol] = { initSymbols; myValueSymbols } @@ -116,6 +116,12 @@ class SyntheticMembers(thisPhase: DenotTransformer) { def syntheticDefIfMissing(sym: Symbol): List[Tree] = if (existingDef(sym, clazz).exists) Nil else syntheticDef(sym) :: Nil + def identifierRef: Tree = + if isSimpleEnumValue then // owner is `def $new(_$ordinal: Int, $name: String) = new MyEnum { ... }` + ref(clazz.owner.paramSymss.head.find(_.name == nme.nameDollar).get) + else // assume owner is `val Foo = new MyEnum { def ordinal = 0 }` + Literal(Constant(clazz.owner.name.toString)) + def syntheticDef(sym: Symbol): Tree = { val synthetic = sym.copy( owner = clazz, @@ -135,12 +141,6 @@ class SyntheticMembers(thisPhase: DenotTransformer) { else identifierRef - def identifierRef: Tree = - if isSimpleEnumValue then // owner is `def $new(_$ordinal: Int, $name: String) = new MyEnum { ... }` - ref(clazz.owner.paramSymss.head.find(_.name == nme.nameDollar).get) - else // assume owner is `val Foo = new MyEnum { def ordinal = 0 }` - Literal(Constant(clazz.owner.name.toString)) - def ordinalRef: Tree = if isSimpleEnumValue then // owner is `def $new(_$ordinal: Int, $name: String) = new MyEnum { ... }` ref(clazz.owner.paramSymss.head.find(_.name == nme.ordinalDollar_).get) @@ -357,7 +357,8 @@ class SyntheticMembers(thisPhase: DenotTransformer) { * For case classes with primitive paramters, see [[caseHashCodeBody]]. */ def chooseHashcode(using Context) = - if (accessors.isEmpty) Literal(Constant(ownName.hashCode)) + if (isNonJavaEnumValue) identifierRef.select(nme.hashCode_).appliedToTermArgs(Nil) + else if (accessors.isEmpty) Literal(Constant(ownName.hashCode)) else if (accessors.exists(_.info.finalResultType.classSymbol.isPrimitiveValueClass)) caseHashCodeBody else diff --git a/tests/run/i19177-stable-enum-item-hashcode.check b/tests/run/i19177-stable-enum-item-hashcode.check new file mode 100644 index 000000000000..1479e19b5fdf --- /dev/null +++ b/tests/run/i19177-stable-enum-item-hashcode.check @@ -0,0 +1 @@ +65 diff --git a/tests/run/i19177-stable-enum-item-hashcode.scala b/tests/run/i19177-stable-enum-item-hashcode.scala new file mode 100644 index 000000000000..d643b4be4217 --- /dev/null +++ b/tests/run/i19177-stable-enum-item-hashcode.scala @@ -0,0 +1,5 @@ +enum E: + case A + +@main def Test = + println(E.A.hashCode) From 5c2e39e35d9a884d2669e5e1b22f23b20eee77b0 Mon Sep 17 00:00:00 2001 From: Matt Bovel Date: Thu, 22 May 2025 15:32:28 +0000 Subject: [PATCH 2/2] Add more enum hashcode tests --- .../i19177-stable-enum-item-hashcode.check | 1 - .../i19177-stable-enum-item-hashcode.scala | 5 ---- tests/run/stable-enum-hashcodes.check | 12 ++++++++++ tests/run/stable-enum-hashcodes.scala | 23 +++++++++++++++++++ 4 files changed, 35 insertions(+), 6 deletions(-) delete mode 100644 tests/run/i19177-stable-enum-item-hashcode.check delete mode 100644 tests/run/i19177-stable-enum-item-hashcode.scala create mode 100644 tests/run/stable-enum-hashcodes.check create mode 100644 tests/run/stable-enum-hashcodes.scala diff --git a/tests/run/i19177-stable-enum-item-hashcode.check b/tests/run/i19177-stable-enum-item-hashcode.check deleted file mode 100644 index 1479e19b5fdf..000000000000 --- a/tests/run/i19177-stable-enum-item-hashcode.check +++ /dev/null @@ -1 +0,0 @@ -65 diff --git a/tests/run/i19177-stable-enum-item-hashcode.scala b/tests/run/i19177-stable-enum-item-hashcode.scala deleted file mode 100644 index d643b4be4217..000000000000 --- a/tests/run/i19177-stable-enum-item-hashcode.scala +++ /dev/null @@ -1,5 +0,0 @@ -enum E: - case A - -@main def Test = - println(E.A.hashCode) diff --git a/tests/run/stable-enum-hashcodes.check b/tests/run/stable-enum-hashcodes.check new file mode 100644 index 000000000000..2a3cb493c4c9 --- /dev/null +++ b/tests/run/stable-enum-hashcodes.check @@ -0,0 +1,12 @@ +65 +65 +66 +66 +67 +67 +68 +68 +-1449359058 +-1449359058 +194551161 +194551161 diff --git a/tests/run/stable-enum-hashcodes.scala b/tests/run/stable-enum-hashcodes.scala new file mode 100644 index 000000000000..60b5af11e437 --- /dev/null +++ b/tests/run/stable-enum-hashcodes.scala @@ -0,0 +1,23 @@ +enum Enum: + case A + case B + case C() + case D() + case E(x: Int) + +@main def Test = + // Enum values (were not stable from run to run before #23218) + println(Enum.A.hashCode) + println(Enum.A.hashCode) + println(Enum.B.hashCode) + println(Enum.B.hashCode) + + // Other enum cases (were already stable from run to run) + println(Enum.C().hashCode) + println(Enum.C().hashCode) + println(Enum.D().hashCode) + println(Enum.D().hashCode) + println(Enum.E(1).hashCode) + println(Enum.E(1).hashCode) + println(Enum.E(2).hashCode) + println(Enum.E(2).hashCode)