[
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)