On 7/15/26 08:39, Paul King wrote:
AI read below - I will push for further free names support.
----
You're right and my sentence was sloppy. The real split is:
* Closure with no free names (the { x -> x+1 } class): provably
delegate-independent
by inspection. Types irrelevant.
* Closure WITH a free name (foo(), or a bare bar): you have to know
whether that name
resolves to the owner (safe to bind at compile time) or could be a
delegate member
(unsafe). @CompileStatic's type checker resolves each one and
records where it landed
(the IMPLICIT_RECEIVER path) — so it can *prove* the answer
automatically. Dynamic
compilation doesn't resolve those names at compile time, so it
cannot prove it — which
is why the dynamic path uses the @PackedClosures trust assertion
plus the runtime
guard for that subset.
That is the actual role of types: they let the compiler auto-*prove*
soundness for the
free-name subset. For the trivial subset there is nothing to prove.
Let me express it like this:
(A) variables
(A1) no free variables like { x -> x+1 }. Easy, already mentioned
(A2) x is from lexical scope of method. {x+1}, where x is a local
variable or parameter. Here Closure already uses its reference based
logic. These are also possible PackedClosure candidates.
(A3) x is from field or property. Here only static compilation
guarantees that the name "x" actually means the field or property and
will not be intercepted or redirected by some MetaClass or Closure strategy
(A4) x is not associated with a field or property, nor from the lexical
scope of the method. x is then a truly free variable that gets its
meaning only by setting a Closure strategy or by manipulating meta
classes. If this is used as method argument, we can in static
compilation still see if the matching method provides a delegatesTo or
something like that to give the free variable a meaning after all.
Otherwise it will fail compilation in static mode. Still works in
dynamic mode. But yes, here only static compilation can find if that is
a possible PackedClosure. But then it would still have to be called
differently to actually work.
For case A4 it would mean that the PackedClosure still could work, but
needs the free variables as input. That deviates from the Closure idea
and makes it more like a template - meaning the dynamic part is kept out
of the construct and becomes part of the delegate or it becomes directly
part of the call/doCall method. It is a question of how far we want to
go here.
But imagine we can split a Closure into something that binds the lexical
names directly and then has a mechanism to deal with the free names
only, then we have a concept Closure is actually using already today,
but maybe we can migrate that concept and make the MOP part for only the
resolution of the free variables. Then there would be only a single
delegate, now owner and this, and no resolution strategy. All that would
move out of the Closure. This new Closure could then become the core for
the old Closure maybe. And maybe we would have a compiler option that
will switch from the old to the new Closure... just ideas
(B) methods
Besides free names for variables/fields/properties/dynamic, there are
also unqualified method calls. Here we have less variants
(B1) dynamic mode: a method call is resolved according to the strategy
and MOP, no direct binding. Example {foo()}, here foo is resolved
dynamically and the meta class system goes to quite some lengths to try
to resolve it.
(B2) static mode: foo is resolved by the compiler, no MOP involved.
PackedClosure could be possible. "this" is captured I think already,
which means the more advanced cases where the parameter annotation
determines the target of such a method call is maybe still open. Again
this would probably require a delegate and thus maybe a different way to
make the call. Similar to A4.
To sum it up. My ambition is to make something like PackedClosure the
core of modern Groovy. With a split of a static part and a dynamic part,
with only one instance per Closure and no inner classes at all. With
only one delegate, that is something like an MOP adapter, which takes
care of the dynamic part. In case of static compilation that is empty.
How exactly that MOP adapter looks like is not fully clear to me yet. I
needs the relevant dynamic data and the metaclass. Just to give a
temporary name let me call it OpenClosure (which is a bad name since it
is kind of a contraction). A classic Closure would then consist of the
OpenClosure and the adapter and inside reroute the calls to them. A
PackedClosure would be almost identical to OpenClosure and can probably
be replaced by it.
bye Jochen