paulk-asert commented on PR #15557:
URL: https://github.com/apache/grails-core/pull/15557#issuecomment-4326453660

   I tried to get Claude to build a standalone reproducer for this row:
   
   @Delegate field on a trait silently returns null because Groovy 5's 
DelegateASTTransformation emits direct field-access instead of 
trait-helper-method access
   
   Any help getting a reproducer would be greatly appreciated.
   
   --------
   
   ## Findings
   
   **TL;DR — I cannot reproduce the regression as described, and the bytecode 
evidence contradicts the commit message's diagnosis.** Before filing a Groovy 
issue, I'd want to verify the failure actually reproduces against a clean 
commit.
   
   ### Relevant commits in PR #15557 (3 of the 81)
   
   | SHA          | Date  | Author                             | What           
                                                                                
               |
   | ------------ | ----- | ---------------------------------- | 
-------------------------------------------------------------------------------------------------------------
 |
   | `3608a76a63` | Apr 6 | James Fredley (assisted by Claude) | First attempt: 
add explicit `@CompileDynamic render(Map)` to the trait                         
               |
   | `43f98a1564` | Apr 6 | James Fredley (assisted by Claude) | Second 
attempt: bypass named-arg `render(Map)` by calling 
`templateRenderer.render(...)` with positional args |
   | `a0ee062e89` | Apr 7 | James Fredley (assisted by Claude) | **Final "fix": 
convert `GrailsApplicationCommand` trait → abstract class** (and revert the 
workarounds)       |
   
   All three are the AI-assisted `Assisted-by: Claude Code` commits. The third 
one is the diagnostic source for "Groovy 5's `DelegateASTTransformation` emits 
direct field-access instead of trait-helper-method access".
   
   ### Reproducer
   
   `/tmp/delegate-trait-repro/` — a faithful, standalone reproducer mirroring 
`GrailsApplicationCommand`:
   
   - `src/Service.groovy` — `Named`/`Described` interfaces, `BaseCommand` 
(`@CompileStatic` trait extending them), `ModelBuilder` trait, multi-overload 
`TemplateRenderer` interface + impl, `FileSystemInteraction` interface + impl
   - `src/MyCommand.groovy` — `@CompileStatic trait MyCommand implements 
BaseCommand, ModelBuilder` with **two** `@Delegate` fields and an `init()` that 
assigns them from inside the trait body (mirrors `handle(ExecutionContext)`)
   - `src/GenerateCommand.groovy` — `@CompileStatic class GenerateCommand 
implements MyCommand` with a `run()` method that issues the named-arg call 
`render(template: …, destination: file('…'), overwrite: true)` exactly like 
`GenerateControllerCommand`
   - `Main.groovy` — driver that calls `init()` then `run()`
   
   ### Result
   
   | Groovy | Behaviour                                                         
           |
   | ------ | 
---------------------------------------------------------------------------- |
   | 4.0.27 | ✅ Field set, `render(Map)` correctly dispatched, output produced  
          |
   | 5.0.5  | ✅ **Same** — field set, `render(Map)` correctly dispatched, 
output produced |
   
   I tried every variation I could think of: `@CompileStatic` only on the impl 
class; `@CompileStatic` on both trait and impl; multi-trait composition; 
setting fields from inside the trait body; named-arg vs Map-literal call sites; 
multi-file vs single-script compilation. **All combinations work correctly 
under both Groovy versions.**
   
   ### Bytecode evidence (the reason I'm skeptical)
   
   The `@Delegate`-generated `render(Map)` on the implementing class compiles 
to essentially identical bytecode under both:
   
   ```
   0: aload_0
   1: getfield  MyCommand__templateRenderer:LTemplateRenderer;   // direct 
field access — both versions
   4: aload_1
   5: invokeinterface TemplateRenderer.render:(Ljava/util/Map;)V
   ```
   
   The field name `MyCommand__templateRenderer` is the trait field correctly 
copied onto the impl class, and `getfield` against it works fine on Groovy 5 — 
the field is set by the trait setter in the normal way.
   
   Source-side, `DelegateASTTransformation.java` between `GROOVY_4_0_27` and 
`GROOVY_5_0_5` uses the *same* `delegate.getOp = varX(fieldNode)` line. There's 
no shift from "helper-method access" to "direct field access" in the @Delegate 
transform itself — both versions use `varX`.
   
   ### What I think actually happened
   
   The commit-message rationale was AI-authored ("Assisted-by: Claude Code"). 
The trait→class conversion clearly *did* fix some test failure, but the 
bytecode and source evidence suggests the diagnostic explanation is post-hoc 
speculation, not the real root cause. Plausible alternatives:
   
   - The failing test (`ScaffoldingSpec`) is an integration test that forks a 
Gradle JVM and runs `generate-controller` end-to-end — there are many moving 
parts (Spring Boot 4, classpath, Spock parallel forks, static state in the 
trait helper) that could fail for reasons unrelated to `@Delegate`
   - The real fix may have been the *removal* of the `@CompileDynamic 
render(Map)` workaround that was added in `3608a76a63` and was masking 
something else
   - A genuine Groovy-5 regression may exist somewhere in this neighborhood, 
but it isn't what the commit message describes
   
   ### Recommendation before raising a Groovy issue
   
   1. Revert just `a0ee062e89` on top of HEAD and confirm the test actually 
fails again (verifies the fix's necessity)
   2. If it does fail, drop in `printlns`/breakpoints in the trait helper and 
`TemplateRendererImpl.render(Map)` to determine whether `templateRenderer` is 
actually null, or whether some other dispatch is going wrong
   3. Only then is it worth taking a reproducer to the Groovy team — and the 
reproducer should be the one that actually fails, not the one inferred from the 
commit message
   
   Sources:
   - [Apache Groovy GitHub](https://github.com/apache/groovy)
   - [Delegate (Groovy 
5.0.5)](https://docs.groovy-lang.org/latest/html/api/groovy/lang/Delegate.html)


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