paulk-asert opened a new pull request, #2692: URL: https://github.com/apache/groovy/pull/2692
…edClosures/flag off by default (GEP-27 S1) Add closure packing on top of the SAM-lambda hoisting: an eligible closure literal's body is hoisted into a synthetic method on the enclosing class and the literal is replaced by a shared PackedClosure adapter, removing the per-closure generated class (and the nested $_closure1$_closure2 name explosion) while the value stays a real groovy.lang.Closure. Capability analysis (PackStrategy, ClosureWriter.chooseStrategy): - a single decision point selects FULL_CLASS (S0) vs PACKED_ADAPTER (S1); - triggered by the @PackedClosures annotation (trust path, dynamic or static), or automatically under @CompileStatic when the type checker PROVES the closure delegate-independent -- every implicitly-received name resolved against the owner, read from STC's IMPLICIT_RECEIVER paths (a path ending in "owner" is safe; "delegate", from @DelegatesTo/with, is not). Behind groovy.target.closure.pack. Typed static dispatch (phase 4): the hoisted body reuses the expressions StaticCompilationVisitor already annotated, so under @CompileStatic it compiles statically -- read-only captures pass as typed leading parameters, inferred types (@ClosureParams, implicit it) arrive via metadata, and the method takes the closure's inferred return type. Packed bodies are statically dispatched with zero invokedynamic, often fewer indy sites than a closure class (which Reference-boxes captures as Object). Written captures ride the compiler's holder machinery: the shared Reference is passed as a parameter (originType/closure-shared/UseExistingReference), so the body is not rewritten, compiles statically, and supports every write form (compound ops beyond +=, postfix value semantics). Soundness for the dynamic trust path: - PackedClosure runtime guard fails fast if a delegate / delegate-consulting resolveStrategy is set on a trust-packed closure; a statically proven closure tolerates it as a no-op (as a @CompileStatic closure class does today); - compile-time escape decline keeps closures that visibly escape (stored to a field/property, returned, appended, in a collection literal) as classes. Mechanism fidelity so a packed closure behaves like a generated closure class: correct owner/static dispatch in static methods; getParameterTypes() carries the real declared types; arguments are adapted as reflective dispatch would (vararg collection, single List/Tuple destructuring, per-arg coercion Closure->SAM and GString->String); call() dispatches straight to doCall with invoker-exception unwrapping; dispatch via a cached reflective Method (metaclass coercion would auto-dereference Reference holder arguments). ClassNode.visitMethods: dedup newly-added methods by identity via an IdentityHashMap set (O(1)) instead of ArrayList.removeAll (O(n^2)). Visiting a method during code generation can add more (constructor-ref synthetic, lambda $deserializeLambda$, a hoisted body that hoists a nested one); the O(1) fixpoint visits them all and fixes a compile-time freeze the O(n^2) scan caused against method-generating visitors (e.g. groovy-contracts). Tests: ClosurePackCapabilityTest and PackedClosuresTransformTest (proof, declines, auto-pack, zero-indy typed bodies, write forms, destructuring). Flag-on stress net 0 failures across LambdaTest + all *Closure*/*DelegatesTo* static-compile suites + ReproducibleBytecodeBugs; flag-off byte-identical (778 green); groovy-contracts compiles and tests clean. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
