[
https://issues.apache.org/jira/browse/GROOVY-12151?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18095663#comment-18095663
]
ASF GitHub Bot commented on GROOVY-12151:
-----------------------------------------
blackdrag commented on code in PR #2692:
URL: https://github.com/apache/groovy/pull/2692#discussion_r3565646239
##########
src/main/java/org/codehaus/groovy/runtime/PackedClosure.java:
##########
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.codehaus.groovy.runtime;
+
+import groovy.lang.Closure;
+import groovy.lang.GroovyRuntimeException;
+
+import java.util.Arrays;
+
+/**
+ * A single, shared {@link Closure} adapter used by the {@code
@PackedClosures} prototype.
+ * <p>
+ * Normally the Groovy compiler generates one inner class per closure literal
+ * (e.g. {@code Owner$_method_closure1}). Under {@code @PackedClosures} an
<em>eligible</em>
+ * closure body is instead hoisted into a synthetic method on the enclosing
("owner")
+ * class, and the closure literal is replaced by an instance of this one
adapter class,
+ * which dispatches back to that method. This removes the per-closure
generated class
+ * (and the deeply-nested {@code $_closure1$_closure2$_closure3} name
explosion) while
+ * still yielding a real {@code groovy.lang.Closure} instance, so features
that operate
+ * through {@code call()} (iteration, {@code curry}, {@code memoize}, {@code
trampoline})
+ * continue to work.
+ * <p>
+ * Captured values are stored at construction and prepended to the call
arguments before
+ * dispatch, so the hoisted method has the shape {@code method(captured...,
closureParams...)}.
+ * Dispatch goes through a cached reflective {@link java.lang.reflect.Method}
rather than an
+ * {@code invokedynamic} call site.
+ */
+public final class PackedClosure extends Closure<Object> {
+
+ private static final long serialVersionUID = 1L;
+ private static final Object[] EMPTY = new Object[0];
+
+ private final String method;
+ private final Object[] captured;
+ private final boolean strict;
+ private final org.codehaus.groovy.reflection.ParameterTypes paramInfo;
+
+ /**
+ * @param owner the enclosing instance the hoisted method lives on
(also used as thisObject)
+ * @param method the name of the hoisted synthetic method
+ * @param captured values captured from the enclosing scope, prepended to
each call
+ * @param maxParams the number of parameters the original closure declared
(its visible arity)
+ */
+ public PackedClosure(final Object owner, final String method, final
Object[] captured, final int maxParams) {
+ this(owner, method, captured, maxParams, true);
+ }
+
+ /**
+ * @param owner the enclosing instance the hoisted method lives on
(also used as thisObject)
+ * @param method the name of the hoisted synthetic method
+ * @param captured values captured from the enclosing scope, prepended to
each call
+ * @param maxParams the number of parameters the original closure declared
(its visible arity)
+ * @param strict whether the delegate guard throws. {@code true} for
the dynamic trust path
+ * (an unverifiable {@code @PackedClosures} assertion
must fail fast on misuse);
+ * {@code false} when the type checker PROVED every free
name owner-resolved —
+ * then a caller-set delegate is provably never
consulted, so it is stored and
+ * ignored, exactly like a statically-compiled closure
class behaves today.
+ */
+ public PackedClosure(final Object owner, final String method, final
Object[] captured, final int maxParams, final boolean strict) {
+ super(owner, owner);
+ this.method = method;
+ this.captured = (captured != null) ? captured : EMPTY;
+ this.strict = strict;
+ this.maximumNumberOfParameters = maxParams;
+ Class<?>[] pt = new Class<?>[maxParams];
+ Arrays.fill(pt, Object.class);
+ this.parameterTypes = pt;
+ this.paramInfo = null;
+ }
+
+ /**
+ * @param owner the enclosing instance the hoisted method lives on
(also used as thisObject)
+ * @param method the name of the hoisted synthetic method
+ * @param captured values captured from the enclosing scope, prepended
to each call
+ * @param visibleTypes the closure's declared parameter types, so callers
that key behaviour on
+ * {@code getParameterTypes()} (DGM arity/type
decisions) and argument
+ * adaptation (vararg collection into a trailing array
parameter) behave
+ * exactly as with a generated closure class
+ * @param strict whether the delegate guard throws (see the other
constructor)
+ */
+ public PackedClosure(final Object owner, final String method, final
Object[] captured, final Class[] visibleTypes, final boolean strict) {
+ super(owner, owner);
+ this.method = method;
+ this.captured = (captured != null) ? captured : EMPTY;
+ this.strict = strict;
+ this.maximumNumberOfParameters = visibleTypes.length;
+ this.parameterTypes = visibleTypes;
+ this.paramInfo = new
org.codehaus.groovy.reflection.ParameterTypes(visibleTypes);
+ }
+
+ // GEP-27 runtime guard: a hoisted body binds free names to the owner at
compile time, so it
+ // cannot honour a caller-set delegate. Rather than silently mis-resolve
(returning the owner's
+ // member where a normal closure would reach the delegate), fail fast at
the point of misuse.
+ // Setting the delegate to the owner is the harmless default and stays a
no-op.
+ @Override
+ public void setDelegate(final Object delegate) {
+ if (strict && delegate != null && delegate != getOwner()) {
+ throw new UnsupportedOperationException(
+ "Cannot set a delegate on a @PackedClosures closure
(hoisted method '" + method
+ + "'): its free names were bound to the owner at compile
time, so an external "
+ + "delegate cannot be honoured. Remove @PackedClosures
from this scope, or "
+ + "exclude this closure, if it relies on delegate-based
resolution.");
+ }
+ super.setDelegate(delegate);
+ }
+
+ @Override
+ public void setResolveStrategy(final int resolveStrategy) {
+ // DELEGATE_FIRST/DELEGATE_ONLY need a delegate the hoisted body
cannot consult; TO_SELF
+ // would resolve names against this shared adapter, which has no
user-defined members.
+ // Only owner-based resolution (OWNER_FIRST/OWNER_ONLY) is
reproducible by a hoisted body.
+ if (strict && resolveStrategy != OWNER_FIRST && resolveStrategy !=
OWNER_ONLY) {
+ throw new UnsupportedOperationException(
+ "Cannot set resolveStrategy=" +
strategyName(resolveStrategy)
+ + " on a @PackedClosures closure (hoisted method '" +
method + "'): a hoisted "
+ + "body resolves free names against the owner at compile
time, so only "
+ + "OWNER_FIRST/OWNER_ONLY are supported. Remove
@PackedClosures from this "
+ + "scope, or exclude this closure.");
+ }
+ super.setResolveStrategy(resolveStrategy);
+ }
+
+ private static String strategyName(final int s) {
+ switch (s) {
+ case DELEGATE_FIRST: return "DELEGATE_FIRST";
+ case DELEGATE_ONLY: return "DELEGATE_ONLY";
+ case TO_SELF: return "TO_SELF";
+ default: return String.valueOf(s);
+ }
+ }
+
+ // Dispatch call() straight to doCall: the generic varargs doCall is
otherwise vararg-ambiguous
+ // through the metaclass when a single argument is itself an Object[] (the
metaclass would treat
+ // the element as the whole argument list and spread it). Unwrap invoker
exceptions exactly as
+ // Closure.call does, so user exceptions surface unwrapped.
+ @Override
+ public Object call(final Object... args) {
+ try {
+ return doCall(args);
+ } catch (InvokerInvocationException e) {
+
org.apache.groovy.internal.util.UncheckedThrow.rethrow(e.getCause());
+ return null; // unreachable
+ }
+ }
+
+ public Object doCall(final Object... args) {
+ Object[] provided = (args != null) ? args : EMPTY;
+ int arity = getMaximumNumberOfParameters();
+ // A real closure destructures a single List/Tuple argument across a
multi-parameter
+ // signature ({ a, b -> } called with one Tuple2); mirror that before
arity normalisation.
+ if (provided.length == 1 && arity > 1 && provided[0] instanceof
java.util.List) {
+ provided = ((java.util.List<?>) provided[0]).toArray();
+ }
+ // With the real parameter types available, adapt arguments exactly as
reflective dispatch on
+ // a generated closure class would
> 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)