Scott Murphy Heiberg created GROOVY-12240:
---------------------------------------------

             Summary: Initialize argument-less enum constants with a direct 
constructor call
                 Key: GROOVY-12240
                 URL: https://issues.apache.org/jira/browse/GROOVY-12240
             Project: Groovy
          Issue Type: Improvement
          Components: Compiler
    Affects Versions: 5.0.8
            Reporter: Scott Murphy Heiberg


{{EnumVisitor}} creates every enum constant through a synthetic helper:

{code:groovy}
def $INIT(Object[] para) {
    return this(*para)
}
{code}

{{this(*para)}} is a spread constructor call, so 
{{InvocationWriter.makeDirectConstructorCall}} refuses it — it bails on 
{{SpreadExpression}}, and again on {{!controller.isConstructor()}} — and the 
body compiles to {{ScriptBytecodeAdapter.despreadList}} plus 
{{selectConstructorAndTransformArguments}}. The meta class then picks the 
constructor at run time by reflecting over {{getDeclaredConstructors()}}. The 
static initializer reaches {{$INIT}} itself through a dynamic call site.

For {{enum Colors { RED, GREEN, BLUE }}} that is the entire constant-creation 
path, even though the only arguments are the compiler-supplied name and 
ordinal, both known at compile time.

Where reflection over the enum is not available, the class cannot initialize at 
all. In a GraalVM native image built without reachability metadata for the 
enum, {{getDeclaredConstructors()}} returns nothing and class initialization 
throws:

{noformat}
groovy.lang.GroovyRuntimeException: Could not find matching constructor for: 
com.example.MyEnum(String, Integer)
{noformat}

(note the boxed {{Integer}} — the ordinal has been through {{Object[]}}). This 
kills the application in a static initializer before any user code runs.

{{@CompileStatic}} does not help. Groovy already compiles the *call site* 
statically (StaticTypeCheckingVisitor, GROOVY-10845); it is {{$INIT}}'s own 
body that is necessarily dynamic.

h3. Proposal

When every constant of an enum is a plain identifier, the arguments are 
provably {{[name, ordinal]}}, and the static initializer can call the enum's 
{{(String, int)}} constructor directly:

{noformat}
static {};                                   static {};
 0: ldc           // class Colors             0: new           // class Colors
 2: ldc           // String RED               3: dup
 4: iconst_0                                  4: ldc           // String RED
 5: invokestatic  Integer.valueOf             6: iconst_0
 8: invokedynamic invoke:(Class;String;       7: invokespecial 
"<init>":(Ljava/lang/String;I)V
                  Integer;)Object;           10: putstatic     Field 
RED:LColors;
13: invokedynamic cast:(Object;)LColors;
18: putstatic     Field RED:LColors;
{noformat}

The same shape javac emits for a Java enum: 21 bytes and two invokedynamic call 
sites per constant become 13 bytes and none.

Anything else — constants with arguments, named-argument form, anonymous 
constant bodies, or a mixture — keeps the existing {{$INIT}} path, as does any 
enum lacking a {{(String, int)}} constructor (checked at bytecode generation, 
after {{Verifier}} has run). {{$INIT}} itself is unchanged and still generated 
for every enum.

h3. Scope, stated honestly

This is a *compile-time* change. It only helps code compiled by a Groovy that 
carries the fix; bytecode already compiled by an earlier Groovy keeps its 
{{$INIT}} path whichever Groovy runs it. Confirmed by building a GraalVM native 
image of an application against a patched Groovy: the framework's own enums, 
compiled by an earlier Groovy, still failed with {{Could not find matching 
constructor}} until reachability metadata was restored.

Enums whose constants take arguments ({{RED(255, 0, 0)}}) are not addressed and 
still require reachability metadata in a native image. Fixing those would mean 
relaxing {{InvocationWriter.makeDirectConstructorCall}} to work outside a 
constructor, which is a much wider change.

Related to GROOVY-12234.

I have a patch with tests ({{./gradlew :test}} passes in full: 16,548 tests, 0 
failures) and will open a pull request against this issue.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to