This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-dev-site.git
The following commit(s) were added to refs/heads/asf-site by this push:
new 58fbea87 2026/07/09 13:03:58: Generated dev website from
groovy-website@0a28012
58fbea87 is described below
commit 58fbea87f1ae9bfdbdb250fc0f10cba9ab55006e
Author: jenkins <[email protected]>
AuthorDate: Thu Jul 9 13:03:58 2026 +0000
2026/07/09 13:03:58: Generated dev website from groovy-website@0a28012
---
search/search-index.json | 9 +-
wiki/GEP-27.html | 1142 ++++++++++++++++++++++++++++++++++++++++++++++
wiki/geps.html | 2 +-
3 files changed, 1151 insertions(+), 2 deletions(-)
diff --git a/search/search-index.json b/search/search-index.json
index 10ccf4a9..c6b50349 100644
--- a/search/search-index.json
+++ b/search/search-index.json
@@ -888,6 +888,13 @@
"url": "wiki/GEP-20.html",
"site": "dev"
},
+ {
+ "id": "wiki/GEP-27.html",
+ "title": "The Apache Groovy programming language - Developer docs -
GEP-27",
+ "content": "The Apache Groovy programming language - Developer docs -
GEP-27 Socialize Discuss on the mailing list Groovy on X Groovy on Bluesky
Groovy on Mastodon Groovy on LinkedIn Events and conferences Source code on
GitHub Report issues in Jira Stack Overflow questions Slack Community You are
using an outdated browser. Please upgrade your browser to improve your
experience. Apache Groovy™ Learn Documentation Download Support
Contribute Ecosystem Blog posts Socialize GE [...]
+ "url": "wiki/GEP-27.html",
+ "site": "dev"
+ },
{
"id": "wiki/grails-proposal.html",
"title": "The Apache Groovy programming language - Developer docs -
Grails Project Proposal",
@@ -926,7 +933,7 @@
{
"id": "wiki/geps.html",
"title": "The Apache Groovy programming language - GEPs",
- "content": "The Apache Groovy programming language - GEPs Socialize
Discuss on the mailing list Groovy on X Groovy on Bluesky Groovy on Mastodon
Groovy on LinkedIn Events and conferences Source code on GitHub Report issues
in Jira Stack Overflow questions Slack Community You are using an outdated
browser. Please upgrade your browser to improve your experience. Apache
Groovy™ Learn Documentation Download Support Contribute Ecosystem Blog
posts Socialize GEPs GEP-1 GEP-2 GEP- [...]
+ "content": "The Apache Groovy programming language - GEPs Socialize
Discuss on the mailing list Groovy on X Groovy on Bluesky Groovy on Mastodon
Groovy on LinkedIn Events and conferences Source code on GitHub Report issues
in Jira Stack Overflow questions Slack Community You are using an outdated
browser. Please upgrade your browser to improve your experience. Apache
Groovy™ Learn Documentation Download Support Contribute Ecosystem Blog
posts Socialize GEPs GEP-1 GEP-2 GEP- [...]
"url": "wiki/geps.html",
"site": "dev"
},
diff --git a/wiki/GEP-27.html b/wiki/GEP-27.html
new file mode 100644
index 00000000..ae4140c7
--- /dev/null
+++ b/wiki/GEP-27.html
@@ -0,0 +1,1142 @@
+<!DOCTYPE html>
+<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
+<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
+<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
+<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--><head>
+ <meta charset='utf-8'/><meta http-equiv='X-UA-Compatible'
content='IE=edge'/><meta name='viewport' content='width=device-width,
initial-scale=1'/><title>The Apache Groovy programming language - Developer
docs - GEP-27</title><link href='../img/favicon.ico' type='image/x-ico'
rel='icon'/><script src='../js/matomo.js'></script><link rel='stylesheet'
type='text/css' href='../css/bootstrap.css'/><link rel='stylesheet'
type='text/css' href='../css/fontawesome.min.css'/><link rel='styleshe [...]
+</head><body>
+ <div id='fork-me'>
+ <a href='https://github.com/apache/groovy'>
+ <img style='position: fixed; top: 20px; right: -58px; border: 0;
z-index: 100; transform: rotate(45deg);'
src='../img/horizontal-github-ribbon.png'/>
+ </a>
+ </div><div id='st-container' class='st-container st-effect-9'>
+ <nav class='st-menu st-effect-9' id='menu-12'>
+ <h2 class='icon icon-lab'>Socialize</h2><ul>
+ <li>
+ <a href='https://groovy-lang.org/mailing-lists.html'
class='icon'><span class='fa fa-classic fa-regular fa-envelope'></span> Discuss
on the mailing list</a>
+ </li><li>
+ <a href='https://x.com/ApacheGroovy' class='icon'><span
class='fa fa-brands fa-x-twitter'></span> Groovy on X</a>
+ </li><li>
+ <a href='https://bsky.app/profile/groovy.apache.org'
class='icon'><span class='fa fa-brands fa-bluesky'></span> Groovy on Bluesky</a>
+ </li><li>
+ <a href='https://fosstodon.org/@ApacheGroovy'
class='icon'><span class='fa fa-brands fa-mastodon'></span> Groovy on
Mastodon</a>
+ </li><li>
+ <a
href='https://www.linkedin.com/company/106402668/admin/dashboard/'
class='icon'><span class='fa fa-brands fa-linkedin'></span> Groovy on
LinkedIn</a>
+ </li><li>
+ <a href='https://groovy-lang.org/events.html'
class='icon'><span class='fa fa-classic fa-solid fa-calendar-days'></span>
Events and conferences</a>
+ </li><li>
+ <a href='https://github.com/apache/groovy'
class='icon'><span class='fa fa-brands fa-github'></span> Source code on
GitHub</a>
+ </li><li>
+ <a href='https://groovy-lang.org/reporting-issues.html'
class='icon'><span class='fa fa-classic fa-solid fa-bug'></span> Report issues
in Jira</a>
+ </li><li>
+ <a href='http://stackoverflow.com/questions/tagged/groovy'
class='icon'><span class='fa fa-brands fa-stack-overflow'></span> Stack
Overflow questions</a>
+ </li><li>
+ <a href='http://www.groovycommunity.com/'
class='icon'><span class='fa fa-brands fa-slack'></span> Slack Community</a>
+ </li>
+ </ul>
+ </nav><div class='st-pusher'>
+ <div class='st-content'>
+ <div class='st-content-inner'>
+ <!--[if lt IE 7]>
+ <p class="browsehappy">You are using an
<strong>outdated</strong> browser. Please <a
href="http://browsehappy.com/">upgrade your browser</a> to improve your
experience.</p>
+ <![endif]--><div><div class='navbar navbar-default
navbar-static-top' role='navigation'>
+ <div class='container'>
+ <div class='navbar-header'>
+ <button type='button'
class='navbar-toggle' data-toggle='collapse' data-target='.navbar-collapse'>
+ <span class='sr-only'></span><span
class='icon-bar'></span><span class='icon-bar'></span><span
class='icon-bar'></span>
+ </button><a class='navbar-brand'
href='../index.html'>
+ <i class='fa-classic fa-solid
fa-star'></i> Apache Groovy™
+ </a>
+ </div><div class='navbar-collapse collapse'>
+ <ul class='nav navbar-nav navbar-right'>
+ <li class=''><a
href='https://groovy-lang.org/learn.html'>Learn</a></li><li class=''><a
href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li
class=''><a href='/download.html'>Download</a></li><li class=''><a
href='https://groovy-lang.org/support.html'>Support</a></li><li class=''><a
href='/'>Contribute</a></li><li class=''><a
href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li class=''><a
href='/blog'>Blog pos [...]
+ <a data-effect='st-effect-9'
class='st-trigger' href='#'>Socialize</a>
+ </li><li class=''>
+ <a href='../search.html'>
+ <i class='fa-classic fa-solid
fa-magnifying-glass'></i>
+ </a>
+ </li><li>
+ <button id='theme-switcher'
class='theme-switcher' type='button' title='Toggle theme' aria-label='Toggle
theme'>
+ <span
class='theme-icon'></span>
+ </button>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div><div id='content' class='page-1'><div
class='row'><div class='row-fluid'><div class='col-lg-3'><ul
class='nav-sidebar'><li><a href='geps.html'>GEP index</a></li><li
class='active'><a href='#doc'>GEP-27</a></li><li><a href='#_abstract'
class='anchor-link'>Abstract</a></li><li><a href='#_motivation'
class='anchor-link'>Motivation</a></li><li><a
href='#_background_how_closures_and_lambdas_compile_today'
class='anchor-link'>Background: how closures and lambdas c [...]
+<div class="sectionbody">
+<div class="sidebarblock">
+<div class="content">
+<div class="title">Metadata</div>
+<div class="hdlist">
+<table>
+<tr>
+<td class="hdlist1">
+<strong>Number</strong>
+</td>
+<td class="hdlist2">
+<p>GEP-27</p>
+</td>
+</tr>
+<tr>
+<td class="hdlist1">
+<strong>Title</strong>
+</td>
+<td class="hdlist2">
+<p>Compact Closure and Lambda Compilation</p>
+</td>
+</tr>
+<tr>
+<td class="hdlist1">
+<strong>Version</strong>
+</td>
+<td class="hdlist2">
+<p>1</p>
+</td>
+</tr>
+<tr>
+<td class="hdlist1">
+<strong>Type</strong>
+</td>
+<td class="hdlist2">
+<p>Feature</p>
+</td>
+</tr>
+<tr>
+<td class="hdlist1">
+<strong>Status</strong>
+</td>
+<td class="hdlist2">
+<p>Draft</p>
+</td>
+</tr>
+<tr>
+<td class="hdlist1">
+<strong>Target</strong>
+</td>
+<td class="hdlist2">
+<p>Groovy 6.0 (potential first step) and Groovy 7.0 (remainder)</p>
+</td>
+</tr>
+<tr>
+<td class="hdlist1">
+<strong>Comment</strong>
+</td>
+<td class="hdlist2">
+<p>Reduces the per-closure/per-lambda generated-class explosion by hoisting
bodies onto the enclosing class. A small, sound-by-construction slice
potentially targets Groovy 6; the broader closure work targets Groovy 7.
Strictly additive; gated by a flag for back-out.</p>
+</td>
+</tr>
+<tr>
+<td class="hdlist1">
+<strong>Leader</strong>
+</td>
+<td class="hdlist2">
+<p>Paul King</p>
+</td>
+</tr>
+<tr>
+<td class="hdlist1">
+<strong>Created</strong>
+</td>
+<td class="hdlist2">
+<p>2026-07-09</p>
+</td>
+</tr>
+<tr>
+<td class="hdlist1">
+<strong>Last modification</strong>
+</td>
+<td class="hdlist2">
+<p>2026-07-09</p>
+</td>
+</tr>
+</table>
+</div>
+</div>
+</div>
+<table class="tableblock frame-all grid-all" style="width: 80%;">
+<colgroup>
+<col style="width: 100%;">
+</colgroup>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><div class="content"><div
class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<div class="title">Note</div>
+</td>
+<td class="content">
+<em>WARNING:</em>
+Material on this page is still under development!
+We are currently working on Groovy 6.0. A small, self-contained part of this
proposal targets Groovy 6.0;
+the larger part targets Groovy 7.0.
+The final version of this proposal may differ significantly from the current
draft,
+but having this draft available allows us to gather early feedback, align
design
+decisions in Groovy 6 as best we can, and iterate on the design.
+We welcome feedback and discussion, but please keep in mind that the details
are not yet finalized.
+</td>
+</tr>
+</table>
+</div></div></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_abstract">Abstract</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Every closure literal and (statically compiled) lambda in Groovy is
compiled to its own
+generated class. A method with a handful of closures produces a handful of
extra <code>.class</code>
+files; deeply nested closures produce classes whose names concatenate without
bound
+(<code>Outer$_method_closure1$_closure2$_closure3</code>). This is invisible
at the source level but
+real at the bytecode level: more class files to load, verify, JIT, and hold in
metaspace.</p>
+</div>
+<div class="paragraph">
+<p>This GEP proposes compiling eligible closures and lambdas <strong>more
compactly</strong> by <strong>hoisting the
+body into a method on the enclosing class</strong> instead of generating a
class per closure. This is
+exactly how the JVM compiles Java lambdas, and it unifies two today-separate
code paths behind
+one primitive. The value expressed as an object is then chosen to fit what the
callsite
+actually needs:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>a <strong>SAM-targeted lambda</strong> becomes a
<code>LambdaMetafactory</code> functional-interface instance over the
+hoisted method — no generated class at all, and a zero-allocation singleton
when
+non-capturing (Java parity);</p>
+</li>
+<li>
+<p>a <strong>closure</strong> becomes one shared <code>Closure</code> adapter
that dispatches to the hoisted method — no
+per-closure class, while remaining a real <code>groovy.lang.Closure</code> (so
<code>curry</code>, <code>memoize</code>,
+<code>trampoline</code>, and iteration continue to work);</p>
+</li>
+<li>
+<p>where a value neither escapes nor needs closure identity, the object can be
elided entirely.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>The proposal is deliberately phased. A <strong>small, sound-by-construction
slice — eliminating the
+generated class for non-capturing SAM lambdas — targets Groovy 6.0</strong>.
The <strong>broader closure work,
+which needs a soundness analysis and a performance path, targets Groovy
7.0</strong>. The change is
+strictly additive, defaults can be flipped via a flag for back-out, and it is
gated so that any
+tooling that reflects on generated-class names has an escape hatch.</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_motivation">Motivation</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_the_class_explosion">The class explosion</h3>
+<div class="paragraph">
+<p>Groovy compiles each closure literal to a distinct generated inner class
extending
+<code>groovy.lang.Closure</code>. That class carries a <code>doCall</code>
method, a <code>serialVersionUID</code>, its own
+metaclass initialiser, a constructor taking <code>(owner, thisObject,
…​captured)</code>, and one
+<code>Reference</code>-boxed field per captured mutable local. Nesting
concatenates names:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">class Deep {
+ def render(Map m) {
+ m.each { k, v ->
+ [1, 2].each { i ->
+ [3, 4].each { j -> "$k$v$i$j".toString() }
+ }
+ }
+ }
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>compiles to four classes — <code>Deep</code>,
<code>Deep$_render_closure1</code>,
+<code>Deep$_render_closure1$_closure2</code>, and
<code>Deep$_render_closure1$_closure2$_closure3</code> — one per
+nesting level, names growing with depth.</p>
+</div>
+<div class="paragraph">
+<p>The cost is not correctness but <strong>packaging</strong>: class-file
bytes, classloading, bytecode
+verification, JIT profiling, and metaspace, all multiplied by the number of
closures. For
+closure-heavy code — GSP page compilation, builders, DSL-shaped code — this is
a measurable
+tax paid on code that reads as a one-liner.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_lambdas_are_heavier_than_javas">Lambdas are heavier than
Java’s</h3>
+<div class="paragraph">
+<p>Even for a statically compiled lambda that targets a functional interface,
Groovy generates a
+full <code>groovy.lang.Closure</code> subclass:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">@CompileStatic
+class L {
+ Function<Integer,Integer> nonCap() { (Integer x) -> x * 2 }
// -> L$_nonCap_lambda1 (extends Closure)
+ Function<Integer,Integer> cap(int base) { (Integer x) -> x + base
} // -> L$_cap_lambda2 (extends Closure)
+}</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>Both generated classes extend <code>Closure</code> and implement
<code>GeneratedLambda</code>, each with its own
+metaclass machinery. Groovy already uses
<code>invokedynamic</code>/<code>LambdaMetafactory</code> (so the runtime
+<em>value</em> is a JDK metafactory <code>Function</code>, and the
non-capturing case is already a zero-allocation
+singleton) — but it still drags along a per-lambda class whose only live part
is a <code>static
+doCall</code>. The equivalent Java lambda emits <strong>no</strong> class per
lambda: the body is a <code>private static
+synthetic</code> method on the enclosing class and the metafactory bootstraps
directly against it.</p>
+</div>
+<div class="paragraph">
+<p>The primitive that closes this gap for lambdas — "put the body on the
enclosing class" — is the
+<em>same</em> primitive that fixes the closure explosion. That shared
primitive is the heart of this GEP.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_design_principles">Design principles</h3>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Compile the body once, choose the wrapper to fit.</strong> A single
backend hoists a closure/lambda
+body to a method on the enclosing class. The object exposed at the callsite
(metafactory SAM,
+shared <code>Closure</code> adapter, or nothing at all) is chosen by what the
value must actually satisfy.</p>
+</li>
+<li>
+<p><strong>Never change observable behaviour.</strong> An optimised
closure/lambda computes the same result,
+keeps its per-instance identity where it has one, and preserves the
<code>Closure</code> API where that
+API is reachable. Anything a compact representation cannot provide is a reason
to <em>decline</em>
+the optimisation for that closure, not to change its meaning.</p>
+</li>
+<li>
+<p><strong>Sound by analysis under static, sound by declaration under
dynamic.</strong> Static compilation
+supplies the types needed to prove an optimisation safe and to make the
hoisted body fast;
+dynamic compilation cannot prove it, so there the optimisation is opt-in.</p>
+</li>
+<li>
+<p><strong>Ship the safe corner first.</strong> The functional-interface (SAM)
case is sound by construction —
+a SAM target cannot express the dangerous closure behaviours. It lands first
and proves the
+shared backend before the broader closure work builds on it.</p>
+</li>
+<li>
+<p><strong>Additive and reversible.</strong> Nothing changes for un-optimised
code. Each landing is gated by a
+flag so it can be disabled without recompiling application code.</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_background_how_closures_and_lambdas_compile_today">Background: how
closures and lambdas compile today</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>A closure <code>{ params → body }</code>:</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>becomes a generated <code>InnerClassNode</code>
(<code>ClosureWriter.getOrAddClosureClass</code>) — one per literal;</p>
+</li>
+<li>
+<p>that class extends <code>groovy.lang.Closure implements
GeneratedClosure</code>, with a <code>doCall</code> holding
+the body, captured mutable locals threaded as
<code>groovy.lang.Reference</code> constructor
+arguments/fields, and standard <code>Closure</code> state (<code>owner</code>,
<code>thisObject</code>, <code>delegate</code>,
+<code>resolveStrategy</code>, …​);</p>
+</li>
+<li>
+<p>at the callsite, <code>new Outer$_m_closure1(this, this,
ref…​)</code> is instantiated.</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>A statically compiled lambda <code>(params) → body</code> targeting a
functional interface
+(<code>StaticTypesLambdaWriter</code>):</p>
+</div>
+<div class="olist arabic">
+<ol class="arabic">
+<li>
+<p>still generates a lambda <code>InnerClassNode</code> extending
<code>Closure</code> with a <code>doCall</code> (made <code>static</code>
+when non-capturing);</p>
+</li>
+<li>
+<p>emits <code>invokedynamic</code> bootstrapped through
<code>LambdaMetafactory.metafactory</code>, whose
+implementation-method handle points at that <code>doCall</code>;</p>
+</li>
+<li>
+<p>so the runtime value is a JDK-generated functional-interface instance, and
the generated
+lambda class exists only to <em>hold</em> the implementation method.</p>
+</li>
+</ol>
+</div>
+<div class="paragraph">
+<p>A dynamically compiled lambda is compiled exactly like a closure (no
metafactory).</p>
+</div>
+<div class="paragraph">
+<p>Two facts make hoisting natural. First, methods can already be added to the
enclosing class
+<em>during</em> code generation and get compiled — Groovy’s own
serializable-lambda deserialisation
+helpers do this. Second, for statically compiled code the type checker has
already run, so
+the inferred types needed to type a hoisted method and to prove an
optimisation safe are
+available on the AST.</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_the_design_space">The design space</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>The combinations one could enumerate — dynamic/static, closure/lambda,
annotated/not,
+capturing/not, needs-identity/not, SAM/not — collapse to a small number of
axes that actually
+drive the compilation strategy.</p>
+</div>
+<div class="sect2">
+<h3 id="_the_one_that_matters_required_capability">The one that matters:
required capability</h3>
+<div class="paragraph">
+<p>The question that selects the representation is <strong>what must this
value be able to do at its use
+sites?</strong> Crucially, this is a <strong>short</strong> list. A shared
<code>Closure</code> adapter that dispatches to a
+hoisted method is still a real <code>Closure</code>, so it preserves almost
everything: it is callable, it
+has per-instance identity, and
<code>curry</code>/<code>rcurry</code>/<code>ncurry</code>,
<code>memoize*</code>, <code>trampoline</code>,
+<code>compose</code>, and iteration all continue to work (they operate through
<code>call()</code>).</p>
+</div>
+<div class="paragraph">
+<p>A hoisted representation loses exactly two things relative to a normal
generated closure:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Externally-set
<code>delegate</code>/<code>resolveStrategy</code>.</strong> A hoisted body
binds free names at compile
+time against the owner; a normal closure resolves free names against a
<code>delegate</code> set at
+runtime. This is the one true soundness boundary (see <em>The soundness
boundary</em>).</p>
+</li>
+<li>
+<p><strong>Serialization.</strong> A method-handle/dispatch-backed adapter is
not portably serializable the way
+a generated closure class is.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>So "needs closure identity" is not a broad lattice; it is essentially the
predicate <strong>"does
+externally-set delegate/resolveStrategy or serialization reach this
value?"</strong></p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_the_one_that_flips_the_difficulty_static_vs_dynamic">The one that
flips the difficulty: static vs dynamic</h3>
+<div class="paragraph">
+<p>Compilation mode is not a strategy selector; it is an <strong>information
and dispatch</strong> modifier that
+flips which half of the problem is hard:</p>
+</div>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 14.2857%;">
+<col style="width: 42.8571%;">
+<col style="width: 42.8572%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top"></th>
+<th class="tableblock halign-left valign-top">Dynamic</th>
+<th class="tableblock halign-left valign-top"><code>@CompileStatic</code></th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><strong>Mechanism</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Trivial —
uniform runtime dispatch, no type story</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Harder —
the hoisted body must be typed and the adapter must present the right
type</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><strong>Soundness</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Hard — no
types, so the dangerous delegate case cannot be detected; must be
trusted</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Feasible —
types make escape analysis and <code>@DelegatesTo</code> detection
possible</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><strong>Performance upside</strong></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">~None
beyond class count (dispatch was dynamic anyway)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Real — the
hoisted body can be statically dispatched, and SAM targets reach
<code>LambdaMetafactory</code></p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>The consequence is that the "easy" dynamic case is the <em>low-value</em>
one (unsound without a
+declaration, no speed gain), and the "hard" static case is where the
optimisation is both
+<em>provably safe</em> and <em>valuable</em>.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_the_rest_are_inputs_and_cost_dials">The rest are inputs and cost
dials</h3>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Lambda vs closure syntax</strong> is a capability
<em>declaration</em>, not a separate axis. Arrow syntax
+already routes a SAM-targeted expression to the lighter (metafactory) writer;
it is the
+programmer saying "treat me as a function." A brace closure and an arrow
lambda targeting the
+<em>same</em> functional interface compile differently today for exactly this
reason.</p>
+</li>
+<li>
+<p><strong>An opt-in annotation</strong> (working name <code>@Packed</code>)
is the same kind of thing: a programmer
+assertion that a closure needs no external delegate, supplied where the
compiler cannot prove
+it (i.e. under dynamic compilation).</p>
+</li>
+<li>
+<p><strong>SAM vs not</strong> is the target-type <em>upper bound</em> on
capability: a SAM target permits the
+object-free metafactory path; a <code>Closure</code>/<code>Object</code>
target caps at the adapter.</p>
+</li>
+<li>
+<p><strong>Capturing vs not</strong> is a <em>cost dial</em> (allocation vs
singleton) within whatever strategy is
+chosen, not a selector.</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_the_strategy_lattice">The strategy lattice</h3>
+<div class="paragraph">
+<p>Given the required capability, the compiler picks the lightest
representation that satisfies it:</p>
+</div>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 14.2857%;">
+<col style="width: 42.8571%;">
+<col style="width: 42.8572%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Strategy</th>
+<th class="tableblock halign-left valign-top">When</th>
+<th class="tableblock halign-left valign-top">Result</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><strong>S0</strong> full generated class</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">needs
external delegate / serialization, or capability unprovable</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">today’s behaviour — one class per
closure/lambda</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><strong>S1</strong> shared <code>Closure</code>
adapter</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">closure-shaped, no external delegate/serialization</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">hoisted
body + one shared adapter; no per-closure class;
<code>curry</code>/<code>memoize</code> preserved</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><strong>S2</strong> metafactory SAM</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">functional-interface target, no closure identity
needed</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">hoisted
body + <code>LambdaMetafactory</code>; <strong>no class</strong>; zero-alloc
singleton when non-capturing; static-only</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><strong>S3</strong> elide / inline</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">value
neither escapes nor needs an object (e.g. call-once)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">no object
at all</p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>Capturing modulates each (non-capturing enables the singleton/static-method
form); static mode
+enables typed dispatch of the hoisted body and the S2 path.</p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_architecture">Architecture</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>One backend, one capability front-end, a wrapper choice:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre>closure / lambda body
+ |
+ v
+ hoist to a method on the enclosing class <-- shared backend
+ |
+ capability inference (target type; arrow/@Packed declarations;
+ | static-only escape + @DelegatesTo + serialization
analysis)
+ v
+ +----+----+----------------------+---------------------+
+ | | | |
+ S3 S2 S1 S0
+ elide metafactory SAM PackedClosure adapter generated class
+ (no obj) (no class) (no per-closure class) (unchanged)</pre>
+</div>
+</div>
+<div class="paragraph">
+<p>The elegant consequence is that the closure writer and the lambda writer
stop being two
+separate mechanisms. They share the hoist backend and differ only in which
wrapper the
+capability front-end selects. The existing
<code>StaticTypesLambdaWriter</code> becomes a special case
+of the general design.</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_the_soundness_boundary">The soundness boundary</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>The single behaviour a hoisted representation cannot reproduce is
<strong>delegate resolution set by
+the caller</strong>. This is not hypothetical:</p>
+</div>
+<div class="listingblock">
+<div class="content">
+<pre class="prettyprint highlight"><code data-lang="groovy">class Mk { Closure
c() { return { -> who() } }; def who() { 'OWNER' } }
+def c = new Mk().c()
+c.delegate = [who: { -> 'DELEGATE' }]
+c.resolveStrategy = Closure.DELEGATE_FIRST
+c() // a normal closure resolves who() against the delegate -> 'DELEGATE'
+ // a hoisted body binds who() to the owner at compile time ->
'OWNER'</code></pre>
+</div>
+</div>
+<div class="paragraph">
+<p>An eligibility check that only looks <em>inside</em> a closure for
<code>delegate</code>/<code>owner</code> references does
+not catch this, because the delegate is set by the <em>caller</em> after the
closure escapes. Making
+S1 sound therefore requires an <strong>escape analysis</strong>: pack a
closure only when it can be proven not
+to reach a delegate-setting context (and is not serialized). Under
<code>@CompileStatic</code> this is
+tractable — closures handed to <code>@DelegatesTo</code>-annotated parameters
are the detectable danger
+signal, and the target types are known. Under dynamic compilation it cannot be
proven, which is
+why the closure optimisation is opt-in there.</p>
+</div>
+<div class="paragraph">
+<p>By contrast, everything else survives — verified: <code>curry</code>,
<code>memoize</code>, and <code>trampoline</code> all
+continue to work on an adapter-backed closure, because the adapter <em>is</em>
a real <code>Closure</code>.</p>
+</div>
+<div class="paragraph">
+<p><strong>Why lambdas are the safe corner.</strong> A functional-interface
target <em>cannot express</em> delegate
+resolution — a <code>Function</code> has no <code>delegate</code>. And a
SAM-typed lambda is, by its type, used as
+that SAM. So the entire soundness cliff is defined away for the SAM case,
which is precisely why
+it can land first.</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_groovy_6_0_deliverables_pending_discussion_and_findings">Groovy 6.0
deliverables (pending discussion and findings)</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>The Groovy 6 target is the sound-by-construction slice. One piece is
proposed as the primary
+deliverable, with a second offered as an option.</p>
+</div>
+<div class="sect2">
+<h3
id="_piece_1_primary_eliminate_the_class_for_non_capturing_sam_lambdas">Piece 1
(primary): eliminate the class for non-capturing SAM lambdas</h3>
+<div class="paragraph">
+<p>For a statically compiled, <strong>non-capturing</strong>, non-serializable
lambda targeting a functional
+interface, do what Java does: emit the body as a <code>private static</code>
synthetic method on the
+enclosing class and bootstrap <code>LambdaMetafactory</code> directly against
it — generating no lambda
+class.</p>
+</div>
+<div class="paragraph">
+<p>This is sound by construction: the target is a SAM (no delegate possible),
the runtime value is
+already a metafactory instance (so the lambda class was never the value — it
was a dead
+impl-holder), and non-capturing means no capture threading. Measured on a
proof-of-concept:</p>
+</div>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 60%;">
+<col style="width: 20%;">
+<col style="width: 20%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Case</th>
+<th class="tableblock halign-left valign-top">Before</th>
+<th class="tableblock halign-left valign-top">After</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>L</code> with <code>nonCap</code>, <code>cap</code>,
<code>runnable</code></p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>L</code> + 3 lambda classes</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>L</code> + 1 (only the capturing one)</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>Handlers</code> with 5 non-capturing + 1 capturing
lambda</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>Handlers</code> + 6 lambda classes</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>Handlers</code> + 1 — <strong>5 classes
eliminated</strong>, 5 <code>$lambda$</code> static methods on
<code>Handlers</code></p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>The metafactory singleton is preserved (<code>nonCap().is(nonCap()) ==
true</code>), stack traces name a
+<code>$lambda$…​</code> method on the enclosing class (Java-like),
and behaviour is unchanged across
+<code>Predicate</code>/<code>BiFunction</code>, lambdas in <code>static</code>
methods, lambdas calling static methods, and
+serializable non-capturing round-trips (which fall back to the class-based
path). A lambda that
+reads an instance field (<code>() → count</code>) is correctly
<em>not</em> hoisted and keeps working.</p>
+</div>
+<div class="paragraph">
+<p>The change is localised to <code>StaticTypesLambdaWriter</code> (the
existing <code>doCall</code> construction already
+does the correct non-capturing/instance-member analysis; the hoist re-homes
the resulting static
+method onto the enclosing class). It requires no change to the
<code>invokedynamic</code>/metafactory
+bootstrap other than the implementation method’s owner class, and does
not require the
+class-visitor changes the closure work needs.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_piece_2_option_capturing_sam_lambdas">Piece 2 (option): capturing SAM
lambdas</h3>
+<div class="paragraph">
+<p>A capturing SAM lambda today instantiates its lambda class to hold the
captured values (Groovy
+even <code>Reference</code>-wraps them). The Java-style form uses a
<code>static</code> implementation method taking
+the captured values as leading arguments, with the metafactory capturing them
directly — which
+<em>also</em> removes the <code>Closure</code> allocation and the
<code>Reference</code> wrapping. This is a larger change
+than Piece 1 (the impl signature and bootstrap arguments change), so it is
offered as a Groovy 6
+<strong>stretch</strong> or an early Groovy 7 item rather than a
commitment.</p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_groovy_7_0_deliverables">Groovy 7.0 deliverables</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>The broader, higher-value work — where the soundness analysis and the
performance story live.</p>
+</div>
+<div class="sect2">
+<h3 id="_closure_packing_s1_with_a_soundness_analysis">Closure packing (S1)
with a soundness analysis</h3>
+<div class="paragraph">
+<p>A shared <code>Closure</code> adapter (<code>PackedClosure</code>) that
dispatches to a hoisted body, applied to
+closures rather than lambdas. A proof-of-concept in the code generator already
demonstrates the
+mechanism across the hard cases: no-capture and captured closures (by value;
written captures via
+shared <code>Reference</code>, including <code>=`/`+</code>),
<strong>arbitrary-depth nested closures</strong>, implicit <code>it</code>,
under
+both <code>@CompileStatic</code> and dynamic compilation, correctly declining
closures that use
+<code>delegate</code>/<code>owner</code>/<code>super</code>. What remains for
a real feature is the <strong>capability/escape analysis</strong>
+described above, so packing is chosen only when it is provably sound
(automatic under
+<code>@CompileStatic</code>; opt-in via <code>@Packed</code> under dynamic),
falling back to S0 otherwise.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_typed_static_dispatch_making_it_fast_not_just_small">Typed static
dispatch (making it fast, not just small)</h3>
+<div class="paragraph">
+<p>In the proof-of-concept the hoisted body is compiled dynamically, so a
packed <code>@CompileStatic</code>
+closure is smaller but dispatched dynamically. A production form must type the
hoisted method
+from the inferred types the type checker already computed and dispatch the
body statically (with
+an <code>invokedynamic</code>/<code>MethodHandle</code> callsite), so that
packing under <code>@CompileStatic</code> is a speed
+win and not merely a class-count win.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_object_elision_s3">Object elision (S3)</h3>
+<div class="paragraph">
+<p>For closures/lambdas that neither escape nor need an object — for example
immediately-invoked
+bodies — elide the object entirely. This is the most aggressive step and
depends on escape
+proof and, for iteration intrinsics, knowledge of the callee.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_unify_the_two_writers">Unify the two writers</h3>
+<div class="paragraph">
+<p>Fold the lambda and closure code paths behind the single hoist backend, so
the SAM and
+<code>Closure</code> cases are one mechanism selecting different wrappers.</p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_configuration_and_flags">Configuration and flags</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Each landing is gated so it can be disabled without recompiling application
code, following
+Groovy’s existing
<code>SystemUtil.getBooleanSafe(…​)</code>/<code>static
final</code> convention (as used for
+<code>groovy.target.indy</code> and similar). The gate wraps the single
decision branch, so <strong>disabled</strong>
+means byte-for-byte today’s behaviour.</p>
+</div>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 33.3333%;">
+<col style="width: 33.3334%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Concern</th>
+<th class="tableblock halign-left valign-top">Flag (proposed)</th>
+<th class="tableblock halign-left valign-top">Default</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">Non-capturing SAM lambda hoisting (Groovy 6)</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>groovy.target.lambda.hoist.disabled</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">off (i.e.
hoisting <strong>on</strong>), opt-out</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Closure
packing under <code>@CompileStatic</code> (Groovy 7)</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>groovy.target.closure.pack</code> (+ per-scope
<code>@Packed</code>)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">to be
decided per release</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Closure
packing under dynamic compilation</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock"><code>@Packed</code> opt-in only</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">off unless
annotated</p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>The default <strong>direction</strong> is a policy decision independent of
the mechanism: a feature can ship
+<strong>on</strong> with an opt-out flag (deliver the win immediately, keep a
back-out valve), or <strong>off</strong> with an
+opt-in flag and be flipped a release later (conservative). The same one-line
gate serves either
+way; only the boolean’s default changes.</p>
+</div>
+<div class="admonitionblock note">
+<table>
+<tr>
+<td class="icon">
+<div class="title">Note</div>
+</td>
+<td class="content">
+<div class="paragraph">
+<p>The flag also eases test migration. Groovy’s current tests assert the
<em>old</em> bytecode shape (a
+<code>doCall</code> on a lambda class); with hoisting disabled those
assertions stay valid, while
+new-structure tests run with it enabled — so the codegen change and the test
rewrite need not
+land in the same commit.</p>
+</div>
+</td>
+</tr>
+</table>
+</div>
+<div class="paragraph">
+<p>If per-compilation (rather than JVM-wide) control is wanted, the same
toggle can be promoted to
+a <code>CompilerConfiguration</code> option, mirroring how <code>indy</code>
is both a system property and a config
+option. The system property is proposed as the initial back-out mechanism.</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_ide_and_tooling_considerations">IDE and tooling considerations</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>Because this is a <strong>bytecode-shape</strong> change, behaviour is
preserved but the generated structure a
+tool might reflect on changes. This surface needs an explicit compatibility
pass with IDEs,
+debuggers, and other tooling before the defaults are turned on broadly:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Generated class names.</strong> Tools that assume
<code>Outer$_method_closure1</code> / <code>Outer$_m_lambda1</code>
+classes exist — by name, by <code>Class.forName</code>, by scanning class
files, or by pattern — will not
+find them for hoisted closures/lambdas. This includes coverage tools,
decompilers, bytecode
+viewers, and any reflection keyed on the generated naming scheme.</p>
+</li>
+<li>
+<p><strong>Marker types.</strong>
<code>GeneratedClosure</code>/<code>GeneratedLambda</code> are not present on a
metafactory SAM
+value (they were never on the runtime value for SAM lambdas, but tooling that
inspects the
+<em>generated class</em> is affected).</p>
+</li>
+<li>
+<p><strong>Debuggers and stepping.</strong> The body now lives in a
<code>$lambda$…​</code>/<code>$packed$…​</code> method
on the
+enclosing class rather than a <code>doCall</code> on a nested class. Stack
traces improve (they name the
+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.</p>
+</li>
+<li>
+<p><strong>Serialization.</strong> Serializable closures/lambdas continue on
the class-based path in the first
+step; any future extension to hoist them must carry the
<code>$deserializeLambda$</code> machinery to the
+enclosing class (as Java does) and be validated for round-trip and
versioning.</p>
+</li>
+<li>
+<p><strong>Stub generation / joint compilation and Groovydoc.</strong>
Synthetic hoisted methods must remain
+invisible to public API views (they are <code>private synthetic</code>), and
stub generation must not
+surface them.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>Concretely, before enabling by default we should: build the reference IDE
integrations against a
+snapshot; run the debugger step/breakpoint scenarios for both closures and
lambdas; check the
+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.</p>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_reference_implementation">Reference implementation</h2>
+<div class="sectionbody">
+<div class="paragraph">
+<p>A proof-of-concept exists as a spike in the code generator, exercised
through this proposal’s
+development:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Lambda first step (Groovy 6 candidate).</strong> Implemented in
<code>StaticTypesLambdaWriter</code>:
+non-capturing SAM lambda classes eliminated, impl hoisted to <code>private
static</code> 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.</p>
+</li>
+<li>
+<p><strong>Closure packing (Groovy 7 direction).</strong> A
<code>ClosureWriter</code>-based spike (with a <code>PackedClosure</code>
+runtime adapter) demonstrates hoisting closures — captures, arbitrary-depth
nesting, written
+captures via <code>Reference</code>, implicit <code>it</code> — under both
compilation modes, with correct declines
+for delegate/owner/super. It confirms the writer is the right home (no
<code>@CompileStatic</code>
+boundary, unlike an AST-transform approach) and empirically establishes the
soundness cliff and
+what is preserved. It does <strong>not</strong> yet include the
capability/escape analysis or typed dispatch;
+those are the Groovy 7 work.</p>
+</li>
+</ul>
+</div>
+<div class="paragraph">
+<p>One supporting change surfaced by the closure spike is worth noting:
hoisting nested closures
+requires the class visitor to visit methods added <em>during</em> code
generation to a fixpoint (the
+current two-round <code>visitMethods</code> misses methods added while
compiling a method that was itself
+added during compilation). That change is general and low-risk but must be
validated against the
+full suite before it lands.</p>
+</div>
+<div class="sect2">
+<h3 id="_phased_delivery">Phased delivery</h3>
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 11.1111%;">
+<col style="width: 33.3333%;">
+<col style="width: 44.4444%;">
+<col style="width: 11.1112%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Phase</th>
+<th class="tableblock halign-left valign-top">What ships</th>
+<th class="tableblock halign-left valign-top">What works</th>
+<th class="tableblock halign-left valign-top">Target</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">1</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">Non-capturing SAM lambda class elimination + flag + test
migration + IDE/tooling check</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">Java-parity lambda codegen for the common SAM case; back-out
flag</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Groovy
6.0</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">2</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Capturing
SAM lambdas (metafactory-captured static impl)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Removes
the class, <code>Closure</code> alloc, and <code>Reference</code> wrapping for
capturing SAM lambdas</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Groovy 6.0
stretch / 7.0</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">3</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Closure
packing (S1) + capability/escape analysis + <code>@Packed</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Sound
closure packing: automatic under <code>@CompileStatic</code>, opt-in under
dynamic</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Groovy
7.0</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">4</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Typed
static dispatch of hoisted bodies</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Packed
<code>@CompileStatic</code> closures are faster, not just smaller</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Groovy
7.0</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">5</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Object
elision (S3) + unify the writers</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">No object
for non-escaping call-once bodies; one backend for closures and lambdas</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Groovy
7.0</p></td>
+</tr>
+</tbody>
+</table>
+<div class="paragraph">
+<p>Phase 1 stands alone and delivers value with no dependency on the later
phases. Phases 3–5 build
+on the shared backend proven in phases 1–2.</p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_excluded_and_deferred_features">Excluded and deferred features</h2>
+<div class="sectionbody">
+<table class="tableblock frame-all grid-all stretch">
+<colgroup>
+<col style="width: 33.3333%;">
+<col style="width: 16.6666%;">
+<col style="width: 50.0001%;">
+</colgroup>
+<thead>
+<tr>
+<th class="tableblock halign-left valign-top">Feature</th>
+<th class="tableblock halign-left valign-top">Status</th>
+<th class="tableblock halign-left valign-top">Rationale</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Changing
closure/lambda <em>semantics</em></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Not
planned</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">This is a
codegen/packaging optimisation only. Eligible closures compute identical
results and keep their reachable <code>Closure</code> API; ineligible ones are
compiled exactly as today.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Packing
closures that use externally-set
<code>delegate</code>/<code>resolveStrategy</code></p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Not
planned</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">These
require a real per-closure representation; they are detected (statically) or
declined and fall back to S0.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">Serializable closure/lambda hoisting</p></td>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">Deferred</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Kept on
the class-based path initially; hoisting them requires carrying the
deserialisation machinery to the enclosing class.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Object
elision (S3)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Deferred
(Groovy 7)</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The most
aggressive step; needs escape proof and, for iteration, callee
knowledge.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p
class="tableblock">Dynamic-mode automatic packing</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Not
planned</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Cannot be
proven sound without types; dynamic packing is opt-in via <code>@Packed</code>
only.</p></td>
+</tr>
+<tr>
+<td class="tableblock halign-left valign-top"><p class="tableblock">A
user-visible switch between S1/S2/S3 per closure</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">Not
planned</p></td>
+<td class="tableblock halign-left valign-top"><p class="tableblock">The
compiler picks the lightest sound representation; users influence it only via
target type, arrow syntax, and <code>@Packed</code>.</p></td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_compatibility_and_impact">Compatibility and impact</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_backwards_compatibility">Backwards compatibility</h3>
+<div class="paragraph">
+<p>The feature is additive and reversible:</p>
+</div>
+<div class="ulist">
+<ul>
+<li>
+<p>Un-optimised code is unchanged. Ineligible closures/lambdas (and everything
when the flag is
+off) compile byte-for-byte as today.</p>
+</li>
+<li>
+<p>Observable behaviour of optimised closures/lambdas is preserved, including
per-instance
+identity and the reachable <code>Closure</code> API
(<code>curry</code>/<code>memoize</code>/<code>trampoline</code>).</p>
+</li>
+<li>
+<p>The one behavioural difference a compact representation <em>cannot</em>
provide — caller-set delegate
+resolution — is exactly the case the analysis declines to optimise, falling
back to a normal
+generated closure.</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_binary_compatibility">Binary compatibility</h3>
+<div class="paragraph">
+<p>Generated-class <em>names</em> and the presence of per-closure/per-lambda
classes are not part of any
+public API, but tooling may depend on them (see <em>IDE and tooling
considerations</em>). The flag is
+the compatibility valve while tools are updated.</p>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_performance">Performance</h3>
+<div class="ulist">
+<ul>
+<li>
+<p>Fewer generated classes: less classloading, verification, JIT profiling,
and metaspace.</p>
+</li>
+<li>
+<p>Non-capturing SAM lambdas keep their zero-allocation metafactory singleton
(the class removed
+is dead weight, not an allocation).</p>
+</li>
+<li>
+<p>Closure packing under <code>@CompileStatic</code> is a class-count win
immediately and a dispatch-speed
+win once typed dispatch (phase 4) lands; without it, a packed
<code>@CompileStatic</code> closure trades a
+class for dynamic dispatch, so typed dispatch is a prerequisite for turning it
on for hot code.</p>
+</li>
+</ul>
+</div>
+</div>
+<div class="sect2">
+<h3 id="_security">Security</h3>
+<div class="paragraph">
+<p>No new trust boundary. The optimisation is a local codegen transform; it
introduces no new
+runtime input handling.</p>
+</div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_alternatives_considered">Alternatives considered</h2>
+<div class="sectionbody">
+<div class="ulist">
+<ul>
+<li>
+<p><strong>Do it as an AST transformation rather than in the code
generator.</strong> Rejected for the closure
+case: a phase sweep shows there is no AST-transform window that both packs
closures <em>and</em>
+survives <code>@CompileStatic</code> — early enough to control codegen means
the type checker sees the
+rewritten (untyped) form and fails; late enough to be post-type-check means it
is too late to
+suppress class emission. The code generator is the only place with both the
inferred types and
+control over class emission.</p>
+</li>
+<li>
+<p><strong>Ship closure packing without the escape analysis.</strong>
Rejected: without it, packing silently
+mis-resolves a closure whose delegate is set by the caller. Soundness is a
prerequisite, not a
+follow-up.</p>
+</li>
+<li>
+<p><strong>One aggressive representation for everything.</strong> Rejected: no
single representation satisfies the
+full <code>Closure</code> contract cheaply. The lattice picks the lightest
<em>sound</em> option per closure.</p>
+</li>
+<li>
+<p><strong>Lambda-style (metafactory) for closures too.</strong> Rejected as
the general answer:
+<code>LambdaMetafactory</code> targets interfaces, and
<code>groovy.lang.Closure</code> is an abstract class with
+rich mutable state, so closures need the shared adapter (S1) rather than the
metafactory (S2).
+S2 applies only where the target genuinely is a functional interface.</p>
+</li>
+<li>
+<p><strong>Leave lambdas as they are.</strong> Rejected: Groovy lambdas carry
a per-lambda class that Java does
+not, and the fix is the same primitive the closure work needs. The SAM corner
is the cheapest,
+safest place to introduce that primitive.</p>
+</li>
+<li>
+<p><strong>No flag.</strong> Rejected: a bytecode-shape change should carry a
back-out for tooling that has not
+yet been updated. The optimisation’s all-or-nothing branch makes a
single toggle clean.</p>
+</li>
+</ul>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_references">References</h2>
+<div class="sectionbody">
+<div class="ulist">
+<ul>
+<li>
+<p><a href="GEP-26.html">GEP-26: GINQ SQL Backend</a> — companion GEP in the
same series</p>
+</li>
+<li>
+<p><a
href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/invoke/LambdaMetafactory.html"><code>java.lang.invoke.LambdaMetafactory</code></a>
— the JVM lambda bootstrap this design reuses for the SAM case</p>
+</li>
+<li>
+<p><a href="https://openjdk.org/jeps/126">JEP 126: Lambda Expressions</a> —
Java’s "body as a method on the enclosing class + metafactory" model, the
prior art for the shared primitive</p>
+</li>
+<li>
+<p><code>org.codehaus.groovy.classgen.asm.ClosureWriter</code>,
<code>org.codehaus.groovy.classgen.asm.sc.StaticTypesLambdaWriter</code> — the
code-generation writers this GEP targets</p>
+</li>
+<li>
+<p><code>groovy.lang.Closure</code> — the closure contract whose narrow,
reachable subset
(<code>curry</code>/<code>memoize</code>/<code>trampoline</code>) the shared
adapter preserves</p>
+</li>
+</ul>
+</div>
+</div>
+</div></div></div></div></div><footer id='footer'>
+ <div class='row'>
+ <div class='colset-3-footer'>
+ <div class='col-1'>
+ <h1>Groovy</h1><ul>
+ <li><a
href='https://groovy-lang.org/learn.html'>Learn</a></li><li><a
href='https://groovy-lang.org/documentation.html'>Documentation</a></li><li><a
href='/download.html'>Download</a></li><li><a
href='https://groovy-lang.org/support.html'>Support</a></li><li><a
href='/'>Contribute</a></li><li><a
href='https://groovy-lang.org/ecosystem.html'>Ecosystem</a></li><li><a
href='/blog'>Blog posts</a></li><li><a
href='https://groovy.apache.org/events.ht [...]
+ </ul>
+ </div><div class='col-2'>
+ <h1>About</h1><ul>
+ <li><a
href='https://github.com/apache/groovy'>Source code</a></li><li><a
href='https://groovy-lang.org/security.html'>Security</a></li><li><a
href='https://groovy-lang.org/learn.html#books'>Books</a></li><li><a
href='https://groovy-lang.org/thanks.html'>Thanks</a></li><li><a
href='http://www.apache.org/foundation/sponsorship.html'>Sponsorship</a></li><li><a
href='https://groovy-lang.org/faq.html'>FAQ</a></li><li><a
href='https://groovy-lang.or [...]
+ </ul>
+ </div><div class='col-3'>
+ <h1>Socialize</h1><ul>
+ <li><a
href='https://groovy-lang.org/mailing-lists.html'>Discuss on the mailing
list</a></li><li><a href='https://x.com/ApacheGroovy'>Groovy on
X</a></li><li><a href='https://bsky.app/profile/groovy.apache.org'>Groovy on
Bluesky</a></li><li><a href='https://fosstodon.org/@ApacheGroovy'>Groovy on
Mastodon</a></li><li><a
href='https://www.linkedin.com/company/106402668/admin/dashboard/'>Groovy on
LinkedIn</a></li><li><a href='https://groovy-lang. [...]
+ </ul>
+ </div><div class='col-right'>
+ <p>
+ The Groovy programming language is
supported by the <a href='https://www.apache.org'>Apache Software
Foundation</a> and the Groovy community.
+ </p><div text-align='right'>
+ <img
src='https://www.apache.org/img/asf_logo.png' title='The Apache Software
Foundation' alt='The Apache Software Foundation' style='width:60%'/>
+ </div><p>Apache, Apache Groovy,
Groovy, and the ASF logo are either registered trademarks or trademarks of The
Apache Software Foundation.</p>
+ </div>
+ </div><div class='clearfix'>© 2003-2026
The Apache Software Foundation — Groovy is Open Source: <a
href='https://www.apache.org/licenses/LICENSE-2.0.html'>Apache 2</a> <a
href='https://www.apache.org/licenses/'>License</a>, <a
href='https://privacy.apache.org/policies/privacy-policy-public.html'>privacy
policy</a>.</div>
+ </div>
+ </footer></div>
+ </div>
+ </div>
+ </div>
+ </div><script src='../js/vendor/jquery-1.10.2.min.js'
defer></script><script src='../js/vendor/classie.js' defer></script><script
src='../js/vendor/bootstrap.js' defer></script><script
src='../js/vendor/sidebarEffects.js' defer></script><script
src='../js/vendor/modernizr-2.6.2.min.js' defer></script><script
src='../js/plugins.js' defer></script><script src='../js/theme-switcher.js'
defer></script><script
src='../js/vendor/prettify.min.js'></script><script>document.addEventListener('
[...]
+</body></html>
\ No newline at end of file
diff --git a/wiki/geps.html b/wiki/geps.html
index d672ae61..c697aa5e 100644
--- a/wiki/geps.html
+++ b/wiki/geps.html
@@ -63,7 +63,7 @@
</ul>
</div>
</div>
- </div><div id='content' class='page-1'><div
class='row'><div class='row-fluid'><div class='col-lg-3'><ul
class='nav-sidebar'><li class='active'><a href='#gep'>GEPs</a></li><li><a
href='#GEP-1' class='anchor-link'>GEP-1</a></li><li><a href='#GEP-2'
class='anchor-link'>GEP-2</a></li><li><a href='#GEP-3'
class='anchor-link'>GEP-3</a></li><li><a href='#GEP-4'
class='anchor-link'>GEP-4</a></li><li><a href='#GEP-5'
class='anchor-link'>GEP-5</a></li><li><a href='#GEP-6' [...]
+ </div><div id='content' class='page-1'><div
class='row'><div class='row-fluid'><div class='col-lg-3'><ul
class='nav-sidebar'><li class='active'><a href='#gep'>GEPs</a></li><li><a
href='#GEP-1' class='anchor-link'>GEP-1</a></li><li><a href='#GEP-2'
class='anchor-link'>GEP-2</a></li><li><a href='#GEP-3'
class='anchor-link'>GEP-3</a></li><li><a href='#GEP-4'
class='anchor-link'>GEP-4</a></li><li><a href='#GEP-5'
class='anchor-link'>GEP-5</a></li><li><a href='#GEP-6' [...]
<div class='row'>
<div class='colset-3-footer'>
<div class='col-1'>