Fix Java enum forwarders to pull from companion on initialization #24361
Merged
tanishiking merged 3 commits intoscala:mainfrom Feb 19, 2026
Merged
Fix Java enum forwarders to pull from companion on initialization #24361tanishiking merged 3 commits intoscala:mainfrom
tanishiking merged 3 commits intoscala:mainfrom
Conversation
Contributor
|
We discussed this during the core meeting and we are ok with taking with this approach. |
|
Hi ;) Any news when this might be merged? |
Contributor
|
It seems it got stuck on review. |
SolalPirelli
approved these changes
Feb 18, 2026
|
|
||
| // Store forwarder symbols for later use in companion initialization | ||
| if forwarderSyms.nonEmpty then | ||
| enumForwarders(clazz) = forwarderSyms.toList |
Contributor
There was a problem hiding this comment.
Checking nonEmpty is just a minor optimization, right?
sjrd
approved these changes
Feb 18, 2026
Contributor
|
@tanishiking I guess we only need to rebase and we should be good to merge |
Fixes scala#12637 Previously, Java enum forwarders were initialized to null when the companion's static initializer was triggered before the Java enum forwarder class's static initializer. For example, when `enum Testme extends java.lang.Enum[Testme]` is accessed from Scala (which accesses `Testme$.Hello`): - `Testme$.<clinit>` is triggered - The static initializer creates enum values by calling `Testme$.new(...)` - It constructs `Testme$$anon$1` (which represents `Hello`), a subtype of `Testme` - Therefore, `Testme.<clinit>` is triggered - `Testme.<clinit>` tries to initialize its `Testme.Hello` field by pulling from `Testme$.Hello` - However, it's still null during the companion's static initialization! See: scala#12637 (comment) ```scala // Testme.scala object TestenumS: def go() = println("Scala: Testme Hello= " + Testme.Hello) enum Testme extends java.lang.Enum[Testme]: case Hello // TestenumJ.java public class TestenumJ { public static void main(String[] args) { TestenumS.go(); System.out.println("Java: Testme Hello= " + Testme.Hello); } } ``` This commit fixes the initialization problem by having the companion object's static initializer push enum values to the forwarders after it finishes initializing the enum value fields. **When the companion is accessed first:** - Companion's `<clinit>` runs and creates enum values - During initialization, the forwarder's `<clinit>` is triggered - Forwarders pull from the companion (value will be null) - Companion's `<clinit>` pushes final values to forwarders at the end **When the forwarder is accessed first:** - Enum class's `<clinit>` tries to initialize the forwarder via `getstatic` from the companion - This triggers the companion's `<clinit>` first - Companion's `<clinit>` pushes values to the forwarders - The original `putstatic` completes (resulting in double assignment, but with the correct value) **Drawbacks:** - We assign the forwarder field twice, making it slightly slower than before - **We changed the Java enum forwarder fields to be non-final**
6d8bc37 to
cc658de
Compare
Member
Author
|
Thanks, rebased on main without conflicts, let's see CI and will merge it :) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #12637
Previously, Java enum forwarders were initialized to null when the companion's static initializer was triggered before the Java enum forwarder class's static initializer.
For example, when
enum Testme extends java.lang.Enum[Testme]is accessed from Scala (which accessesTestme$.Hello):Testme$.<clinit>is triggeredTestme$.new(...)Testme$$anon$1(which representsHello), a subtype ofTestmeTestme.<clinit>is triggeredTestme.<clinit>tries to initialize itsTestme.Hellofield by pulling fromTestme$.HelloSee: #12637 (comment)
full javap result is available here: https://github.com/tanishiking/kitchensink/tree/main/scala3/12637
This commit fixes the initialization problem by having the companion object's static initializer push enum values to the forwarders after it finishes initializing the enum value fields.
When the companion is accessed first:
<clinit>runs and creates enum values<clinit>is triggered<clinit>pushes final values to forwarders at the endWhen the forwarder is accessed first:
<clinit>tries to initialize the forwarder viagetstaticfrom the companion<clinit>first<clinit>pushes values to the forwardersputstaticcompletes (resulting in double assignment, but with the correct value)Drawbacks:
Now it's possible to update the static forwarder like
If making the Java enum forwarder non-final isn't acceptable, other option would be generating a proxy method like for Scala.js, but Java will need to call
Testme.Hello(), instead ofTestme.Hello.Or maybe make Scala access Java forwarder on
Testme.Helloif the forwarder exists? Now sure it's plausible.