codeconsole opened a new pull request, #3024:
URL: https://github.com/apache/groovy/pull/3024

   GROOVY-12240 made enum constants without arguments call the `(String,int)` 
constructor directly. Every constant with arguments still goes through the 
synthetic `$INIT(Object[])` helper. Its body is a spread `this(*para)`, which 
compiles to `ScriptBytecodeAdapter.despreadList` plus 
`selectConstructorAndTransformArguments`, so `MetaClassImpl` selects the 
constructor by reflecting over the declared constructors. `@CompileStatic` 
doesn't change that: the static initializer then calls `$INIT` with 
`invokestatic`, but `$INIT`'s own body stays dynamic. 
`EnumVisitor.canInitDirectly` disabled the direct path for any constant with 
arguments.
   
   As a result, an ordinary enum fails to initialize in a GraalVM native image 
built without reflection metadata for it:
   
   ```groovy
   enum AccessModifier {
       PRIVATE('private'), PROTECTED('protected'), PUBLIC('public')
       final String keyword
       AccessModifier(String keyword) { this.keyword = keyword }
   }
   ```
   
   ```
   groovy.lang.GroovyRuntimeException: Could not find matching constructor for: 
AccessModifier(String, Integer, String)
   ```
   
   This change emits a direct constructor call when the compiler can prove what 
the runtime would do. That requires all three of:
   
   - every argument is a literal: a constant, `null` or a class literal;
   - exactly one non-synthetic constructor has the matching arity, and no 
constructor could take the arguments as varargs;
   - each parameter is the literal's own type, the primitive of a wrapper 
literal, or a type other than `Boolean` that the literal extends or implements 
(`null` goes only to a non-primitive parameter).
   
   Under those conditions `MetaClassImpl.chooseMethod` has a single valid 
candidate. `ParameterTypes.isValidMethod` rejects every other arity unless the 
last parameter is an array, and `CachedClass` does not list synthetic 
constructors. The cast `$INIT` applies before the call returns the argument 
unchanged: `xxxUnbox` for a primitive, the indy cast otherwise. The indy cast 
to `Boolean` goes through `asBoolean`, which is why `Boolean` is excluded.
   
   Everything else keeps `$INIT`:
   
   - named arguments, class bodies and abstract enums, as before;
   - same-arity overloads and varargs;
   - a GString for a String parameter;
   - a literal the runtime would convert, such as an int literal for a long 
parameter or a BigDecimal literal for a double;
   - `null` for a primitive, and any argument that isn't a literal.
   
   The choice is per enum, as in GROOVY-12240, so the documented opt-out (an 
explicit empty argument list such as `ONE()`) still applies to the whole enum. 
An enum's static initializer either needs constructor reflection or it doesn't.
   
   Implementation:
   
   - The decision is made at bytecode generation in `EnumConstantInit`, because 
only then are the constructors (enum completion, default-argument expansion, 
AST transforms) and the transformed arguments final.
   - A `Constants` object shared by an enum's constants tracks the latest 
wrapper of each constant as transforms replace it. It decides once, and each 
constant re-checks itself.
   - The arguments are read from the `$INIT` call as the transforms left it, 
and the bytecode generator loads them exactly as it would for that call.
   - `$INIT` is still generated, and every visitor other than the class 
generator sees the same tree.
   - Node-metadata reads that the wrapper doesn't answer itself fall through to 
the wrapped `$INIT` call. That keeps what the type checker records for it 
(GROOVY-10845) readable from the right-hand side of the constant's assignment; 
`MiscSTCTest.testEnumConstructorChecks` reads it there. If reviewers would 
rather adapt that test than give the wrapper this behavior, it is easy to 
change.
   
   As with GROOVY-12240, a meta class registered before the enum initializes no 
longer sees these constants being created. Where every constant has arguments, 
passing one argument as a non-literal expression keeps the enum on `$INIT`. 
`EnumTest` pins both the narrowing and the opt-out.
   
   Tests:
   
   - `EnumConstantInitBytecodeTest` gains direct-call assertions for String, 
int, every literal kind, `@CompileStatic`, mixed constants and arity-selected 
constructors. It also asserts that the following stay on `$INIT`: same-arity 
overloads, `null` with overloads, varargs (alone and beside an exact match), a 
GString, converting literals, non-literal arguments, a mix of qualifying and 
non-qualifying constants, arguments with a body, the `ONE()` opt-out, and the 
`@CompileStatic` fallback.
   - `EnumTest` gains behavior tests: values, ordinals, field values and 
runtime classes, `valueOf`, `switch`, static fields read from the constructor 
(including initialization order), default arguments, arity and 
`@CompileStatic`. It also tests meta-class interception and the opt-out.
   
   Seven of the new tests fail on master. The enum-related suites and the 
`groovy.transform.stc.*`, `org.codehaus.groovy.classgen.*`, `gls.*` and 
`bugs.*` packages pass (7,591 tests), as do the Checkstyle and CodeNarc gates. 
A differential probe compiled 26 enums with this branch's compiler and with 
master's and found identical runtime values. Of the 26, 18 now take the direct 
call; 8 stay on `$INIT` by design: BigDecimal→double, int→long, a `Boolean` 
parameter, a GString, varargs, a list literal, `'c' as char` and `ONE()`.
   
   The same gap exists on 6.0.x, so a backport to `GROOVY_6_0_X` would let 
Grails 9, which builds with Groovy 6, initialize enums like the one above in a 
native image without metadata.
   
   There is no JIRA issue for this yet.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to