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

Reply via email to