This is an automated email from the ASF dual-hosted git repository. paulk-asert pushed a commit to branch asf-site in repository https://gitbox.apache.org/repos/asf/groovy-website.git
commit 9a811130973f6b53a57a4d3da892749a404cfbc0 Author: Paul King <[email protected]> AuthorDate: Fri Jul 10 09:25:36 2026 +1000 draft "Compact Closure and Lambda Compilation" GEP --- site/src/site/wiki/GEP-27.adoc | 49 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/site/src/site/wiki/GEP-27.adoc b/site/src/site/wiki/GEP-27.adoc index ceebd39..b7cca8f 100644 --- a/site/src/site/wiki/GEP-27.adoc +++ b/site/src/site/wiki/GEP-27.adoc @@ -435,17 +435,54 @@ snapshot; run the debugger step/breakpoint scenarios for both closures and lambd common coverage and decompiler tools; and document the flag as the back-out for any tool not yet updated. This tooling verification is a first-class deliverable of the GEP, not an afterthought. +=== Codegen-level verification (done for the SAM-lambda first step) + +The prototype hoisted method carries the debug metadata a debugger relies on, verified both by +inspecting `javap -p -l` output and by an automated test (`LambdaHoistTest`) that reads the class +bytes with ASM and asserts the following on every hoisted `$lambda$...` method: + +* *`LineNumberTable` with one entry per body line.* A multi-line lambda body yields a line-number + entry for each source statement, so line breakpoints, single-step, and "step into" land on the + correct source lines — and stack frames report accurate line numbers. +* *Populated `LocalVariableTable` with the original names.* The captured values (now leading + parameters, e.g. `base`), the SAM parameters (e.g. `x`), and locals declared inside the body + (e.g. `a`, `b`) all appear by name, so the debugger's variables view is complete. This is on par + with — arguably better than — the old `doCall` frame, because the frame now names the enclosing + class. +* *`ACC_SYNTHETIC` on the impl method.* The hoisted method is `private static synthetic`, so it is + correctly excluded from public-API views (Groovydoc, IDE member lists, stub generation). + +Because the hoisted body reuses the original AST statements, source positions propagate +unchanged; there is no separate line-mapping step that could drift. + +=== Remaining human-only verification (cannot be scripted) + +The following must be exercised interactively in the reference IDEs and cannot be asserted from a +build, so they remain open items before the flag defaults on: + +* IntelliJ IDEA and Eclipse: set a breakpoint inside a hoisted lambda body, confirm it binds and + stops; "step into" a call whose target is a hoisted lambda; inspect captured values, parameters, + and body locals in the variables view; confirm the frame names the enclosing class sensibly. +* JDWP / remote-debug parity with the above against a stock JDK debugger. +* Coverage tools (JaCoCo) and popular decompilers/bytecode viewers: confirm they attribute the + hoisted method to the enclosing class without error and do not rely on the old nested-class name. + == Reference implementation A proof-of-concept exists as a spike in the code generator, exercised through this proposal's development: -* *Lambda first step (Groovy 6 candidate).* Implemented in `StaticTypesLambdaWriter`: - non-capturing SAM lambda classes eliminated, impl hoisted to `private static` methods on the - enclosing class, metafactory singleton and behaviour verified, class-count reductions measured - as above. The existing lambda tests that assert the old bytecode shape need updating to the new - structure (behaviour is unchanged); that test migration is the bulk of the remaining first-step - work. +* *Lambda first step (Groovy 6 candidate).* Implemented in `StaticTypesLambdaWriter`: both + non-capturing *and* read-only-capturing SAM lambda classes are eliminated, with the impl hoisted + to `private static` methods on the enclosing class (captured values become leading parameters) + and the metafactory bootstrapped directly against them — the non-capturing case yielding a + zero-alloc singleton. Behaviour and class-count reductions are verified as above, and the hoisted + methods are confirmed to carry full debug metadata (see _IDE and tooling considerations_). Cases + that need the shared `Reference` or closure identity — mutated captures, instance access, + serializable and nested-function lambdas — correctly decline and keep their class. The whole + first step sits behind the `groovy.target.lambda.hoist` flag. The existing lambda tests that + assert the old bytecode shape need updating to the new structure (behaviour is unchanged); that + test migration is the bulk of the remaining first-step work. * *Closure packing (Groovy 7 direction).* A `ClosureWriter`-based spike (with a `PackedClosure` runtime adapter) demonstrates hoisting closures — captures, arbitrary-depth nesting, written captures via `Reference`, implicit `it` — under both compilation modes, with correct declines
