diff --git a/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala b/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala index 5740f359cb77..44759ec9a412 100644 --- a/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala +++ b/compiler/src/dotty/tools/dotc/transform/CompleteJavaEnums.scala @@ -106,7 +106,9 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => val moduleRef = ref(clazz.companionModule) val enums = moduleCls.info.decls.filter(member => member.isAllOf(EnumValue)) - for { enumValue <- enums } + val forwarderSyms = scala.collection.mutable.ListBuffer[Symbol]() + + val result = for { enumValue <- enums } yield { def forwarderSym(flags: FlagSet, info: Type): Symbol { type ThisName = TermName } = val sym = newSymbol(clazz, enumValue.name.asTermName, flags, info) @@ -119,8 +121,36 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => // we achieve the right contract with static forwarders instead. DefDef(forwarderSym(EnumValue | Method | JavaStatic, MethodType(Nil, enumValue.info)), body) else - ValDef(forwarderSym(EnumValue | JavaStatic, enumValue.info), body) + val sym = forwarderSym(EnumValue | JavaStatic | Mutable, enumValue.info) + forwarderSyms += sym + ValDef(sym, body) } + + // Store forwarder symbols for later use in companion initialization + if forwarderSyms.nonEmpty then + enumForwarders(clazz) = forwarderSyms.toList + + result + } + + /** Generate assignment to initialize enum forwarders in the companion object, + * so that forwarders are initialized when comapnion object is touched first. + * For each enum value, generates: EnumClass.enumValue = Module.enumValue + * see: https://github.com/scala/scala3/issues/12637 + */ + private def enumForwarderInitializers(moduleCls: Symbol)(using Context): List[Tree] = { + if ctx.settings.scalajs.value then + Nil // Scala.js uses methods, no initialization needed + else + val enumClass = moduleCls.linkedClass + val forwarderSyms = enumForwarders.get(enumClass).getOrElse(Nil) + val enums = moduleCls.info.decls.filter(member => member.isAllOf(EnumValue)).toList + + forwarderSyms.zip(enums).map { case (forwarderSym, enumValue) => + val lhs = ref(forwarderSym) + val rhs = ref(enumValue) + Assign(lhs, rhs) + } } private def isJavaEnumValueImpl(cls: Symbol)(using Context): Boolean = @@ -129,6 +159,7 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => && cls.owner.owner.linkedClass.derivesFromJavaEnum private val enumCaseOrdinals = MutableSymbolMap[Int]() + private val enumForwarders = MutableSymbolMap[List[Symbol]]() private def registerEnumClass(cls: Symbol)(using Context): Unit = cls.children.zipWithIndex.foreach(enumCaseOrdinals.update) @@ -181,10 +212,17 @@ class CompleteJavaEnums extends MiniPhase with InfoTransformer { thisPhase => ) else if cls.linkedClass.derivesFromJavaEnum then enumCaseOrdinals.clear() // remove simple cases // invariant: companion is visited after cases - templ + // Add initialization code for enum forwarders + val initializers = enumForwarderInitializers(cls) + enumForwarders.remove(cls.linkedClass) // Clear cache after use + if initializers.isEmpty then + templ + else + cpy.Template(templ)(body = templ.body ++ initializers) else templ } override def checkPostCondition(tree: Tree)(using Context): Unit = assert(enumCaseOrdinals.isEmpty, "Java based enum ordinal cache was not cleared") + assert(enumForwarders.isEmpty, "Java based enum forwarder cache was not cleared") } diff --git a/compiler/test/dotc/run-test-pickling.excludelist b/compiler/test/dotc/run-test-pickling.excludelist index 0122276739c0..d0efb4ba4bd5 100644 --- a/compiler/test/dotc/run-test-pickling.excludelist +++ b/compiler/test/dotc/run-test-pickling.excludelist @@ -4,6 +4,7 @@ derive-generic.scala eff-dependent.scala enum-java +enum-java-scala i5257.scala i7212 i7868.scala diff --git a/tests/run/enum-java-scala.check b/tests/run/enum-java-scala.check new file mode 100644 index 000000000000..daa498773f34 --- /dev/null +++ b/tests/run/enum-java-scala.check @@ -0,0 +1,2 @@ +Scala: Testme Hello= Hello +Java: Testme Hello= Hello \ No newline at end of file diff --git a/tests/run/enum-java-scala/Test.java b/tests/run/enum-java-scala/Test.java new file mode 100644 index 000000000000..7d059e592098 --- /dev/null +++ b/tests/run/enum-java-scala/Test.java @@ -0,0 +1,9 @@ +// scalajs: --skip + +// see: https://github.com/scala/scala3/issues/12637 +public class Test { + public static void main(String[] args) { + TestenumS.go(); + System.out.println("Java: Testme Hello= " + Testme.Hello); + } +} diff --git a/tests/run/enum-java-scala/Testme.scala b/tests/run/enum-java-scala/Testme.scala new file mode 100644 index 000000000000..cf8b1e0abe36 --- /dev/null +++ b/tests/run/enum-java-scala/Testme.scala @@ -0,0 +1,5 @@ +object TestenumS: + def go() = println("Scala: Testme Hello= " + Testme.Hello) + +enum Testme extends java.lang.Enum[Testme]: + case Hello