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 e92adb5  updated with recent changes for GROOVY-12151
e92adb5 is described below

commit e92adb5a8dfe358308490020f9cb212b80a838ac
Author: Paul King <[email protected]>
AuthorDate: Tue Jul 21 18:30:08 2026 +1000

    updated with recent changes for GROOVY-12151
---
 site/src/site/wiki/GEP-27.adoc | 46 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 45 insertions(+), 1 deletion(-)

diff --git a/site/src/site/wiki/GEP-27.adoc b/site/src/site/wiki/GEP-27.adoc
index d321181..0b68c0b 100644
--- a/site/src/site/wiki/GEP-27.adoc
+++ b/site/src/site/wiki/GEP-27.adoc
@@ -14,7 +14,7 @@
 *Comment*:: Reduces the per-closure/per-lambda generated-class explosion by 
hoisting bodies onto the enclosing class. The sound-by-construction SAM-lambda 
slice is merged for Groovy 6; closure packing (S1) is implemented and proposed 
as a Groovy 6 stretch goal (GROOVY-12151); object elision and writer 
unification remain for Groovy 7. Strictly additive; gated by a flag for 
back-out.
 *Leader*:: Paul King
 *Created*:: 2026-07-09
-*Last modification*:: 2026-07-16
+*Last modification*:: 2026-07-17
 ****
 
 [width="80%",align="center"]
@@ -604,6 +604,50 @@ which also gives caller-sensitivity-correct semantics. 
Their dispatch models dif
 a fixed table entry versus runtime overload selection — that fully merging 
them into one path is not
 a goal; this is tracked as a separate initiative.
 
+=== OpenClosure: a lexically bound core with a resolver boundary (reference 
spike)
+
+Out of the GROOVY-12151 review discussion came a proposal (Jochen) for where 
this architecture
+should ultimately land: split a closure into a *core* that binds all lexical 
names at creation
+(receiver, captures, body) and a single pluggable *resolver* that answers only 
for free names —
+the MOP reduced to exactly the dynamic part. The classic `groovy.lang.Closure` 
contract
+(owner/delegate/`resolveStrategy`) becomes a resolver implementation plus a 
wrapper over the
+core; under static compilation the resolver is provably empty; and the core — 
"OpenClosure" —
+is a candidate to become the default closure construct, with `Closure` and 
`OpenClosure`
+coexisting (`Closure` wrapping an `OpenClosure` at its heart).
+
+A working *reference spike* for Groovy 7 exists (branch 
`gep27spikeOpenClosure`, behind
+`groovy.spike.openclosure`; not production code):
+
+* *Compiler:* a dynamic closure whose only obstacle is free names — exactly 
what GROOVY-12151
+  must decline — hoists through the unchanged packed pipeline with its free 
reads and
+  unqualified calls rewritten into `resolver.property(name)` / 
`resolver.invoke(name, args)` on
+  a leading `Resolver` parameter; the resolver rides as `captured[0]`, so the 
dispatch tables,
+  ids and capture threading need no modification.
+* *Runtime:* `OpenClosure` (the pure core, not a `Closure`), 
`OpenClosure.Resolver` (the
+  boundary; `EMPTY` for the static case), `ClassicResolver` (the 
owner/delegate/strategy triple
+  as a resolver), and `OpenClosure.AsClosure` — the coexistence wrapper, which 
*extends*
+  `PackedClosure`: `setDelegate`/`setResolveStrategy` reconfigure the resolver 
instead of
+  failing fast.
+* *Validated:* full behavioural parity with classed compilation across the 
delegate/strategy
+  matrix (owner-first, delegate-first with full and partial interception, 
delegate-only
+  including exception parity, owner-only) — checked against the classed run of 
the same source,
+  not hardcoded expectations; one per-class dispatch table serving ordinary 
packed and open
+  bodies side by side; GDK iteration and `curry` on the wrapper; and the 
"template" reading of
+  free names — the same hoisted body executed under two different resolvers 
yields two different
+  meanings, the dynamic part supplied entirely from outside.
+
+The alignment findings the spike was built to test: the GROOVY-12151 machinery 
*is* the
+OpenClosure engine (no dispatch changes were needed); a packed closure is an 
open closure whose
+resolver is statically known to be empty — the strict runtime guard is 
precisely the degenerate
+resolver that forbids anyone supplying meanings; and the classic contract 
reconstructs cleanly
+at the resolver boundary. Free-name <em>writes</em> are covered too: the 
resolver's
+`setProperty(name, value)` leg handles plain and compound assignment (`total 
+= it`) with the
+same read-then-write MOP pair and strategy order as a classic closure, 
preserving
+assignment-expression value semantics. Deliberately out of the spike's scope 
(production work,
+tracked for the design discussion): `++`/`--` on free names, `this`-qualified 
accesses, nested
+literals inside open bodies, the `@DelegatesTo`-typed static variant, 
per-arity class-level
+presentation of the wrapper, and the serialization posture of resolvers.
+
 === Default-on hardening
 
 Flip closure packing from experimental (opt-in `groovy.target.closure.pack`) 
to default-on. This is

Reply via email to