[ 
https://issues.apache.org/jira/browse/GROOVY-12151?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18097258#comment-18097258
 ] 

ASF GitHub Bot commented on GROOVY-12151:
-----------------------------------------

paulk-asert commented on code in PR #2709:
URL: https://github.com/apache/groovy/pull/2709#discussion_r3607812445


##########
src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java:
##########
@@ -158,6 +253,1030 @@ public void writeClosure(final ClosureExpression 
expression) {
         controller.getOperandStack().replace(ClassHelper.CLOSURE_TYPE, 
localVariableParams.length);
     }
 
+    /**
+     * GEP-27 capability analysis: choose the compilation strategy for a 
closure literal.
+     * <p>
+     * A closure is packed (S1, {@link PackStrategy#PACKED_ADAPTER}) when it 
is <em>triggered</em> —
+     * either the enclosing scope opts in via {@code @PackedClosures} (the 
dynamic trust path), or the
+     * capability analysis <em>proves</em> it delegate-independent under 
{@code @CompileStatic} (the
+     * automatic path, see {@link #isDelegateIndependent}) — and it is in a 
packable position:
+     * it needs no real {@code Closure} semantics we cannot reproduce ({@link 
#isPackable}), it is
+     * written directly in a method rather than nested in another closure 
(owner-retargeting for
+     * nested closures is later work), and it does not visibly escape its 
method. Otherwise it stays a
+     * full generated class (S0, {@link PackStrategy#FULL_CLASS}) exactly as 
today.
+     */
+    private PackStrategy chooseStrategy(final ClosureExpression expression) {
+        // The complete packability decision, in order (GEP-27 "The 
packability decision procedure";
+        // any decline keeps the closure a generated class, exactly as today):
+        // 1. needs real Closure semantics 
(owner/delegate/thisObject/resolveStrategy/metaClass/
+        //    super, default parameter values, anonymous inner class)?
+        if (!isPackable(expression)) return PackStrategy.FULL_CLASS;
+        // 2. scope opted out? The most-specific @PackedClosures mode wins 
(method over class), and
+        //    DISABLED beats an enclosing opt-in AND the flag.
+        PackMode mode = annotatedMode();
+        if (mode == PackMode.DISABLED) return PackStrategy.FULL_CLASS;
+        boolean annotated = (mode != null); // LENIENT/WARN/STRICT all opt in 
(DISABLED handled above)
+        // 3. triggered and sound? annotation (dynamic trust), flag + 
syntactic no-free-name proof
+        //    (dynamic), or flag/annotation + the type checker's 
delegate-independence proof (CS,
+        //    which also declines mixed-mode dynamic islands and Map-owner 
property semantics).
+        if (!isPackTriggered(expression, annotated)) return 
PackStrategy.FULL_CLASS;
+        // 4. structural position: directly in a method of the owner class 
(nested closures await
+        //    owner-retargeting)?
+        if (controller.isInGeneratedFunction()) return PackStrategy.FULL_CLASS;
+        // 5. visibly escapes (field/property/index store, return, <<, 
collection literal)?
+        if (escapesEnclosingMethod(expression)) return PackStrategy.FULL_CLASS;
+        // 6. visibly serialization-bound (Serializable cast/coercion, 
writeObject directly or via
+        //    a literal-holding local)?
+        if (serializationBound(expression)) return PackStrategy.FULL_CLASS;
+        // 7. a field initializer (a field store the escape walk cannot see)?
+        if (isFieldInitializer(expression)) return PackStrategy.FULL_CLASS;
+        // 8.-10. a context the adapter cannot inhabit: intersection cast 
(per-literal marker
+        //    interfaces), trait body ($Trait$Helper's synthetic receiver), or 
a this(...)/super(...)
+        //    argument (uninitializedThis cannot be the dispatch receiver)?
+        if (isIntersectionTyped(expression)) return PackStrategy.FULL_CLASS;
+        if (isInTraitContext()) return PackStrategy.FULL_CLASS;
+        if (controller.getCompileStack().isInSpecialConstructorCall()) return 
PackStrategy.FULL_CLASS;
+        return PackStrategy.PACKED_ADAPTER;
+    }
+
+    /**
+     * A closure in a field initializer ({@code def action = { ... }} at class 
level) is stored
+     * into a field by definition — the same escape the escape gate declines 
for in-method stores —
+     * but field initializers compile outside the enclosing method's visible 
code, so the escape
+     * walk cannot see them. Detected directly against the class's fields 
(identity match).
+     */
+    private boolean isFieldInitializer(final ClosureExpression expression) {
+        for (FieldNode fn : controller.getClassNode().getFields()) {
+            if (fn.getInitialValueExpression() == expression) return true;
+        }
+        return false;
+    }
+
+    /**
+     * A closure literal cast to an intersection type (e.g. {@code (Runnable & 
Serializable) { }})
+     * needs its <em>generated class</em> to declare the marker interfaces (see
+     * {@code StaticTypesClosureWriter#addIntersectionMarkers}), which the one 
shared
+     * {@code PackedClosure} adapter cannot do per-literal — so keep it a 
class. The marker list is
+     * recorded by the type checker; a non-empty one is the signal.
+     */
+    private static boolean isIntersectionTyped(final ClosureExpression 
expression) {
+        Object markers = 
expression.getNodeMetaData(org.codehaus.groovy.transform.stc.StaticTypesMarker.LAMBDA_MARKERS);
+        return markers instanceof List && !((List<?>) markers).isEmpty();
+    }
+
+    /**
+     * Whether the closure is being compiled inside a trait. A trait's method 
bodies (and their
+     * closures) are moved into a static {@code $Trait$Helper} nested class 
whose {@code this} is a
+     * synthetic {@code $self} parameter, not an instance receiver — a shape 
the packed dispatch
+     * codegen does not reproduce (it produced invalid bytecode). Decline and 
keep the closure as a
+     * class until the trait context is supported. Detected by walking the 
enclosing class's outer
+     * chain to the {@code @Trait}-annotated interface.
+     */
+    private boolean isInTraitContext() {
+        for (ClassNode c = controller.getClassNode(); c != null; c = 
c.getOuterClass()) {
+            if (org.codehaus.groovy.transform.trait.Traits.isTrait(c)) return 
true;
+        }
+        return false;
+    }
+
+    /**
+     * Whether a packable, non-escaping closure literal should actually be 
packed. Dynamic compilation
+     * has no proof of delegate-independence, so the only trigger is the 
{@code @PackedClosures} trust
+     * assertion; {@code StaticTypesClosureWriter} overrides this to add the
+     * {@code groovy.target.closure.pack} flag and the delegate-independence 
proof.
+     *
+     * @param annotated whether a non-DISABLED {@code @PackedClosures} is in 
scope
+     */
+    protected boolean isPackTriggered(final ClosureExpression expression, 
final boolean annotated) {
+        if (annotated) return true;
+        // Dynamic compilation has no delegate-independence proof from the 
type checker, so the
+        // annotation is normally the only trigger. But a closure with NO free 
names 

> hoist eligible closure bodies to a shared adapter @PackedClosures/flag 
> (GEP-27 S1)
> ----------------------------------------------------------------------------------
>
>                 Key: GROOVY-12151
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12151
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Paul King
>            Assignee: Paul King
>            Priority: Major
>




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to