jamesfredley opened a new issue, #16126:
URL: https://github.com/apache/grails-core/issues/16126

   ### Summary
   
   A Grails 7 plugin whose AST transformation populates a **private trait 
method** on the annotated class silently produces a class that is missing that 
method under Grails 8 / Groovy 5. Compilation succeeds, the plugin loads, and 
the failure only appears at runtime as a `MissingPropertyException` naming an 
internal member the user never wrote.
   
   Found while auditing released Grails 7 plugins against `8.0.0-M5`, using 
`org.grails.plugins:i18n-enums:7.0.1` (latest release, catalog constraint 
`7.0.0 > *`).
   
   This is **not** #16123. That issue is about a *generic trait with fields* 
failing with `MalformedParameterizedTypeException` through 
`$Trait$FieldHelper`. The trait here is not generic, declares no fields, and 
ships **no** `$Trait$FieldHelper` at all. The mechanism and the symptom are 
different, so I am filing it separately rather than commenting there.
   
   ### Grails Version
   
   8.0.0-M5 (Groovy 5.0.8, Spring Boot 4.1.0, JDK 21.0.11)
   
   ### Steps to Reproduce
   
   In a stock `create-app --profile=web` application on 8.0.0-M5:
   
   ```groovy
   implementation "org.grails.plugins:i18n-enums:7.0.1"
   ```
   
   ```groovy
   package i18nenums
   
   import grails.plugins.i18nEnums.annotations.I18nEnum
   
   @I18nEnum
   enum DeliveryStatus {
       SHIPPED
   }
   ```
   
   with `i18nenums.DeliveryStatus.SHIPPED=Shipped` in `messages.properties`, 
then resolve it through the documented public API:
   
   ```groovy
   messageSource.getMessage(DeliveryStatus.SHIPPED, Locale.ENGLISH)
   ```
   
   ### Actual Behaviour
   
   Compilation succeeds and the plugin loads (`i18nEnums (7.0.1)` appears in 
the load order). The failure happens at runtime:
   
   ```text
   groovy.lang.MissingPropertyException: No such property: i18nEnumASTConfig 
for class: i18nenums.DeliveryStatus
     at 
grails.plugins.i18nEnums.traits.I18nEnumTrait$Trait$Helper.getConfigProperty(I18nEnumTrait.groovy:72)
     at 
grails.plugins.i18nEnums.traits.I18nEnumTrait$Trait$Helper.getCodes(I18nEnumTrait.groovy:18)
     at 
org.springframework.context.support.AbstractMessageSource.getMessage(AbstractMessageSource.java:168)
   ```
   
   No Grails-owned frame appears; the frames are plugin-owned and Spring-owned.
   
   ### Root Cause
   
   The plugin's `I18nEnumTransformation` does not add a new field. It **looks 
up an existing method on the annotated class and replaces its body**:
   
   ```text
   private static void addi18nEnumASTConfig(ClassNode, AnnotationNode);
     ...
     25: aload_0
     26: ldc     // String getI18nEnumASTConfig
     32: invokevirtual  ClassNode.getMethod:(String, Parameter[]) -> MethodNode
     ...
     85: invokevirtual  MethodNode.setCode:(Statement)   // installs the config 
map as the method body
   ```
   
   That method is declared **private in the trait**, so it exists only as a 
helper static:
   
   ```text
   grails.plugins.i18nEnums.traits.I18nEnumTrait$Trait$Helper
     private static java.util.Map 
getI18nEnumASTConfig(java.lang.Class<I18nEnumTrait>);
     private static <T> T getConfigProperty(java.lang.Class<I18nEnumTrait>, 
java.lang.String, java.lang.Class<T>, T);
   ```
   
   The compiled application enum on Grails 8 receives the trait's **public** 
members but **not** the private one:
   
   ```text
   $ javap -p build/classes/groovy/main/i18nenums/DeliveryStatus.class
   public final class i18nenums.DeliveryStatus extends 
java.lang.Enum<i18nenums.DeliveryStatus>
           implements grails.plugins.i18nEnums.traits.I18nEnumTrait, 
groovy.lang.GroovyObject {
     public java.lang.Object[] getArguments();
     public java.lang.String[] getCodes();
     public java.lang.String getDefaultMessage();
     public java.lang.String getName();
     ...
   }
   ```
   
   `getI18nEnumASTConfig` is absent from the emitted class, so the runtime 
property lookup for `i18nEnumASTConfig` finds nothing and every message 
resolution through the plugin fails.
   
   Notably the transformation did **not** fail: `MethodNode.setCode` is invoked 
unconditionally on the result of `getMethod(...)`, so a `null` lookup would 
have produced a `NullPointerException` during compilation. Compilation was 
clean, which means the method node was visible to the transform at AST time but 
is not present in the class that is finally emitted.
   
   ### Expected Behaviour
   
   Either the private trait method remains reachable on the implementing class 
the way it was under the Groovy 4 line, or the mismatch surfaces at **compile** 
time rather than as a runtime `MissingPropertyException` that names an internal 
member.
   
   ### Why this is worth tracking here
   
   The user-visible failure gives no indication of the real problem. `No such 
property: i18nEnumASTConfig` names a member the application author never wrote, 
on a class they declared with a single annotation, and it appears only when the 
message is resolved - potentially far from the annotated enum.
   
   Alongside #16122, #16123, #16124, and #16125, this is a fourth distinct way 
a released Grails 7 plugin breaks on Grails 8, and the second traced to a 
Groovy 4 -> 5 trait ABI change. Even if the fix belongs in the plugin, the 
diagnosis cost here is high and a compile-time signal (or an upgrade-guide note 
about private trait members and AST transformations) would remove most of it.
   
   ### Notes
   
   Part of a compatibility sweep of released Grails 7 plugins against 8.0.0-M5. 
In the same sweep, `greenmail:7.0.1`, `markdown:4.0.0`, `rendering:7.0.1`, 
`x-frame-options:2.0.0`, `grails-csrf:2.0.0`, `joda-time:3.0.0`, 
`taggable:7.0.0`, `cascade-validation:7.0.1`, `grails-cache-guava:7.0.0`, and 
`grails-postgresql-extensions:8.0.0` all passed verification against their 
documented public APIs.
   


-- 
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