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


The following commit(s) were added to refs/heads/asf-site by this push:
     new 841c849  updated with recent changes for GROOVY-12151
841c849 is described below

commit 841c849e2b4f740cf0dde9761fd15f30aad8d556
Author: Paul King <[email protected]>
AuthorDate: Wed Jul 15 13:54:41 2026 +1000

    updated with recent changes for GROOVY-12151
---
 site/src/site/wiki/GEP-27.adoc | 22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/site/src/site/wiki/GEP-27.adoc b/site/src/site/wiki/GEP-27.adoc
index d520e66..e81fee2 100644
--- a/site/src/site/wiki/GEP-27.adoc
+++ b/site/src/site/wiki/GEP-27.adoc
@@ -342,6 +342,7 @@ modes:
 |Mechanism |When |Catches
 |`@PackedClosures(mode = WARN or STRICT)` |compile time |_visible_ declines — 
"did I pack what I intended?"
 |`PackedClosure` throws on `setDelegate`/`setResolveStrategy` |runtime |the 
_invisible_ wrong assumption — "did anyone misuse what I packed?"
+|`PackedClosure` fails fast on serialization (`writeObject`), naming the 
hoisted body and the opt-out |runtime |the _invisible_ serialization route (a 
local later serialized, a callee that serializes its argument); visible routes 
decline at compile time instead
 |===
 
 A conservative *escape gate* shrinks the runtime surface further and is 
implemented in the S1 slice: a
@@ -349,6 +350,11 @@ closure literal that visibly escapes its method — stored 
into a field/property
 `static constraints = { … }` shape), returned, appended with `<<`, or placed 
in a collection literal
 — is declined at compile time and kept as a class. This moves the clearest 
delegate-risk cases from
 a runtime throw to a compile-time non-event; those declines are also what 
`WARN`/`STRICT` report.
+A sibling gate applies the same discipline to the serialization boundary: a 
literal that is visibly
+serialization-bound — cast or coerced to a `Serializable` type, or passed 
directly to a
+`writeObject` call — declines and keeps its class (so it serializes as 
before), with transitive
+routes left to the adapter's fail-fast `writeObject`, exactly as transitive 
escapes are left to the
+delegate guard.
 Because syntactic escape analysis cannot prove that, say, a custom `each` does 
not set a delegate,
 the runtime guard remains the essential backstop under dynamic compilation. (A 
closure that is
 fully self-contained — every free name binds to the owner or its own 
parameters — is
@@ -569,9 +575,11 @@ debuggers, and other tooling before the defaults are 
turned on broadly:
   enclosing class), but "step into closure", breakpoints on a closure body, 
and lambda/closure
   display in variable views should be checked in IntelliJ IDEA and Eclipse, 
and against JDWP
   expectations.
-* *Serialization.* Serializable closures/lambdas continue on the class-based 
path in the first
-  step; any future extension to hoist them must carry the 
`$deserializeLambda$` machinery to the
-  enclosing class (as Java does) and be validated for round-trip and 
versioning.
+* *Serialization.* Serializable lambdas and visibly serialization-bound 
closures continue on the
+  class-based path (and so serialize as before); a packed closure serialized 
via a transitive route
+  fails fast with a message naming the closure and the opt-out. Any future 
extension to hoist the
+  serializable cases must carry the `$deserializeLambda$` machinery to the 
enclosing class (as Java
+  does) and be validated for round-trip and versioning.
 * *Stub generation / joint compilation and Groovydoc.* Synthetic hoisted 
methods must remain
   invisible to public API views (they are `private synthetic`), and stub 
generation must not
   surface them.
@@ -601,6 +609,12 @@ bytes with ASM and asserts the following on every hoisted 
`$lambda$...` method:
 Because the hoisted body reuses the original AST statements, source positions 
propagate
 unchanged; there is no separate line-mapping step that could drift.
 
+The packed-closure leg has the equivalent automated coverage 
(`PackedClosureDebugMetadataTest`):
+line-number entries per body line and named locals — including captured values 
(a written capture
+threaded as a shared `Reference` still appears under its original name), typed 
and implicit-`it`
+parameters, and body locals — on the `$packed$closure$` methods under both 
compilation modes, plus
+`ACC_SYNTHETIC` on the hoisted bodies and on all generated dispatch machinery.
+
 === Remaining human-only verification (cannot be scripted)
 
 The following must be exercised interactively in the reference IDEs and cannot 
be asserted from a
@@ -745,7 +759,7 @@ stretch goal) builds on the shared backend proven in phases 
1–2; phases 4–5
 
 |Changing closure/lambda _semantics_ |Not planned |This is a codegen/packaging 
optimisation only. Eligible closures compute identical results and keep their 
reachable `Closure` API; ineligible ones are compiled exactly as today.
 |Packing closures that use externally-set `delegate`/`resolveStrategy` |Not 
planned |These require a real per-closure representation; they are detected 
(statically) or declined and fall back to S0.
-|Serializable closure/lambda hoisting |Deferred |Kept on the class-based path 
initially; hoisting them requires carrying the deserialisation machinery to the 
enclosing class.
+|Serializable closure/lambda hoisting |Deferred |Serializable lambdas and 
_visibly_ serialization-bound closures (cast/coerced to a `Serializable` type, 
or passed directly to `writeObject`) are kept on the class-based path; a packed 
closure reached by a transitive serialization route fails fast at runtime with 
an actionable message. Hoisting the serializable cases would require carrying 
the deserialisation machinery to the enclosing class.
 |Packing default-parameter (multi-arity) closures |Deferred |A single hoisted 
method cannot reproduce the arity-overloaded dispatch a default parameter 
generates, so these decline to S0 today. A future slice would hoist one method 
per arity (reusing the existing default-fill codegen) and claim one 
dispatch-table id per arity — the per-arity tables the shipped adapter already 
uses, and the same primitive the _MethodClosure_ alignment above would share.
 |Object elision (S3) |Deferred (Groovy 7) |The most aggressive step; needs 
escape proof and, for iteration, callee knowledge.
 |Dynamic-mode automatic packing |Not planned |Cannot be proven sound without 
types; dynamic packing is opt-in via `@PackedClosures` only.

Reply via email to