This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch GROOVY-11993
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 764bf8058a5bb4456dd94d4266c95d6698742a94
Author: Daniel Sun <[email protected]>
AuthorDate: Tue May 5 02:33:46 2026 +0900

    GROOVY-11993: Support serializable method reference
---
 .../asm/sc/AbstractFunctionalInterfaceWriter.java  | 332 +++++++++++++----
 .../StaticTypesFunctionalInterfaceMetadataKey.java |  51 +++
 .../classgen/asm/sc/StaticTypesLambdaAnalyzer.java |   9 +-
 .../classgen/asm/sc/StaticTypesLambdaWriter.java   |  98 ++---
 ...StaticTypesMethodReferenceExpressionWriter.java | 116 +++++-
 .../groovy/groovy/transform/stc/LambdaTest.groovy  |   2 +-
 .../transform/stc/MethodReferenceTest.groovy       | 415 +++++++++++++++++++++
 7 files changed, 872 insertions(+), 151 deletions(-)

diff --git 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java
 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java
index ceb280c43b..9a4a5d2118 100644
--- 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java
+++ 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java
@@ -22,45 +22,191 @@ import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.ast.tools.GeneralUtils;
+import org.codehaus.groovy.classgen.asm.BytecodeHelper;
+import org.codehaus.groovy.classgen.asm.WriterController;
 import org.codehaus.groovy.syntax.RuntimeParserException;
+import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys;
 import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
-import java.util.List;
+import java.util.Arrays;
 
+import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.SERIALIZEDLAMBDA_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper;
 import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
 import static org.codehaus.groovy.ast.ClassHelper.isDynamicTyped;
 import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.eqX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
 import static 
org.codehaus.groovy.ast.tools.GenericsUtils.hasUnresolvedGenerics;
 import static 
org.codehaus.groovy.classgen.asm.BytecodeHelper.getClassInternalName;
 import static 
org.codehaus.groovy.classgen.asm.BytecodeHelper.getMethodDescriptor;
+import static 
org.codehaus.groovy.classgen.asm.sc.StaticTypesFunctionalInterfaceMetadataKey.DESERIALIZE_LAMBDA_DISPATCHER;
+import static 
org.codehaus.groovy.transform.stc.StaticTypesMarker.DIRECT_METHOD_CALL_TARGET;
+import static org.objectweb.asm.Opcodes.CHECKCAST;
 
 /**
- * Represents functional interface writer which contains some common methods 
to complete generating bytecode
+ * Shared bytecode and deserialization support for statically-compiled 
functional interface implementations,
+ * including both lambdas and method references.
+ *
  * @since 3.0.0
  */
 public interface AbstractFunctionalInterfaceWriter {
 
+    default void writeFunctionalInterfaceIndy(final MethodVisitor 
methodVisitor,
+                                              final String samMethodName, 
final String invokedTypeDescriptor,
+                                              final String 
samMethodDescriptor, final int implMethodKind,
+                                              final ClassNode implClassNode, 
final MethodNode implMethodNode,
+                                              final Parameter[] 
implMethodParameters, final boolean serializable) {
+        methodVisitor.visitInvokeDynamicInsn(
+                samMethodName,
+                invokedTypeDescriptor,
+                createBootstrapMethod(serializable),
+                createBootstrapMethodArguments(samMethodDescriptor, 
implMethodKind, implClassNode, implMethodNode, implMethodParameters, 
serializable)
+        );
+        if (serializable) {
+            methodVisitor.visitTypeInsn(CHECKCAST, 
BytecodeHelper.getClassInternalName(ClassHelper.SERIALIZABLE_TYPE));
+        }
+    }
+
     default String createMethodDescriptor(final MethodNode method) {
         return getMethodDescriptor(method.getReturnType(), 
method.getParameters());
     }
 
-    default Handle createBootstrapMethod(final boolean isInterface, final 
boolean serializable) {
+    default ClassNode convertParameterType(final ClassNode targetType, final 
ClassNode parameterType, final ClassNode inferredType) {
+        if 
(!getWrapper(inferredType).isDerivedFrom(getWrapper(parameterType))) {
+            throw new RuntimeParserException("The inferred type[" + 
inferredType.redirect() + "] is not compatible with the parameter type[" + 
parameterType.redirect() + "]", parameterType);
+        }
+
+        ClassNode type;
+        if (isPrimitiveType(parameterType)) {
+            if (!isPrimitiveType(inferredType)) {
+                // The non-primitive type and primitive type are not allowed 
to mix since Java 9+
+                // java.lang.invoke.LambdaConversionException: Type mismatch 
for instantiated parameter 0: class java.lang.Integer is not a subtype of int
+                type = getUnwrapper(inferredType).getPlainNodeReference(false);
+            } else {
+                type = inferredType.getPlainNodeReference(false);
+            }
+        } else if (isPrimitiveType(inferredType)) {
+            // GROOVY-9790: bootstrap method initialization exception raised 
when lambda parameter type is wrong
+            // (1) java.lang.invoke.LambdaConversionException: Type mismatch 
for instantiated parameter 0: class java.lang.Integer is not a subtype of int
+            // (2) java.lang.BootstrapMethodError: bootstrap method 
initialization exception
+            if (!(isDynamicTyped(parameterType) && 
isPrimitiveType(targetType)) // (1)
+                && (parameterType.equals(getUnwrapper(parameterType)) || 
inferredType.equals(getWrapper(inferredType)))) { // (2)
+                // The non-primitive type and primitive type are not allowed 
to mix since Java 9+
+                // java.lang.invoke.LambdaConversionException: Type mismatch 
for instantiated parameter 0: int is not a subtype of class java.lang.Object
+                type = getWrapper(inferredType).getPlainNodeReference();
+            } else {
+                type = inferredType.getPlainNodeReference(false);
+            }
+        } else {
+            type = inferredType;
+            // GROOVY-11304: no placeholders
+            if (hasUnresolvedGenerics(type)) type = type.redirect();
+            // GROOVY-11479: mutable for node metadata or type annotations
+            if (type.toString(false).equals(parameterType.toString(false))) {
+                type = parameterType;
+            } else {
+                // TODO: deep copy if type args set
+                type = type.getPlainNodeReference();
+            }
+        }
+        return type;
+    }
+
+    default SerializedLambdaFingerprint 
createSerializedLambdaFingerprint(final String samMethodDescriptor, final 
ClassNode capturingClass,
+                                                                          
final int implMethodKind, final ClassNode implClassNode,
+                                                                          
final MethodNode implMethodNode, final Parameter[] implMethodParameters,
+                                                                          
final ClassNode functionalType, final MethodNode abstractMethod,
+                                                                          
final int capturedArgumentCount) {
+        return new SerializedLambdaFingerprint(
+            getClassInternalName(capturingClass),
+            implMethodKind,
+            getClassInternalName(implClassNode),
+            implMethodNode.getName(),
+            getMethodDescriptor(implMethodNode),
+            getClassInternalName(functionalType.redirect()),
+            abstractMethod.getName(),
+            samMethodDescriptor,
+            createInstantiatedMethodType(samMethodDescriptor, implMethodNode, 
implMethodParameters).getDescriptor(),
+            capturedArgumentCount
+        );
+    }
+
+    default void addDeserializeDispatcherEntry(final WriterController 
controller, final Parameter[] deserializeMethodParameters,
+                                               final 
SerializedLambdaFingerprint serializedLambdaFingerprint,
+                                               final MethodNode helperMethod) {
+        BlockStatement dispatcherGuards = 
getOrAddDeserializeDispatcherGuards(controller, deserializeMethodParameters);
+        MethodCallExpression helperCall = 
callX(classX(controller.getClassNode()), helperMethod.getName(), 
args(varX(deserializeMethodParameters[0])));
+        helperCall.setImplicitThis(false);
+        helperCall.setMethodTarget(helperMethod);
+
+        dispatcherGuards.addStatement(
+            // Keep this guard strict: deserialization must route to exactly 
one synthetic helper
+            // whose serialized-lambda fingerprint fully matches the incoming 
SerializedLambda.
+            
ifS(boolX(matchesSerializedFunctionalInterface(varX(deserializeMethodParameters[0]),
 serializedLambdaFingerprint)),
+                returnS(helperCall)
+            )
+        );
+    }
+
+    default Parameter[] createDeserializeMethodParameters() {
+        return new Parameter[] { new Parameter(SERIALIZEDLAMBDA_TYPE, 
"serializedLambda") };
+    }
+
+    private Handle createBootstrapMethod(final boolean serializable) {
         return new Handle(
-                Opcodes.H_INVOKESTATIC,
-                "java/lang/invoke/LambdaMetafactory",
-                serializable ? "altMetafactory" : "metafactory",
-                serializable ? 
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"
-                             : 
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
-                false // GROOVY-8299, GROOVY-8989, GROOVY-11265
+            Opcodes.H_INVOKESTATIC,
+            "java/lang/invoke/LambdaMetafactory",
+            serializable ? "altMetafactory" : "metafactory",
+            serializable ? 
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"
+                : 
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
+            false // GROOVY-8299, GROOVY-8989, GROOVY-11265
         );
     }
 
-    default Object[] createBootstrapMethodArguments(final String 
abstractMethodDesc, final int insn, final ClassNode methodOwner, final 
MethodNode methodNode, final Parameter[] parameters, final boolean 
serializable) {
-        ClassNode returnType = methodNode.getReturnType();
-        switch (Type.getReturnType(abstractMethodDesc).getSort()) {
+    private Object[] createBootstrapMethodArguments(final String 
samMethodDescriptor, final int implMethodKind,
+                                                    final ClassNode 
implClassNode, final MethodNode implMethodNode,
+                                                    final Parameter[] 
implMethodParameters, final boolean serializable) {
+        Object[] arguments = !serializable ? new Object[3] : new 
Object[]{null, null, null, 5, 0};
+
+        arguments[0] = Type.getMethodType(samMethodDescriptor);
+
+        arguments[1] = new Handle(
+            implMethodKind, // H_INVOKESTATIC or H_INVOKEVIRTUAL or 
H_INVOKEINTERFACE (GROOVY-9853)
+            getClassInternalName(implClassNode.getName()),
+            implMethodNode.getName(),
+            getMethodDescriptor(implMethodNode),
+            implClassNode.isInterface());
+
+        arguments[2] = createInstantiatedMethodType(samMethodDescriptor, 
implMethodNode, implMethodParameters);
+
+        return arguments;
+    }
+
+    private Type createInstantiatedMethodType(final String 
samMethodDescriptor, final MethodNode implMethodNode, final Parameter[] 
implMethodParameters) {
+        ClassNode returnType = implMethodNode.getReturnType();
+        switch (Type.getReturnType(samMethodDescriptor).getSort()) {
           case Type.BOOLEAN:
             if (returnType.isGenericsPlaceHolder()) returnType = 
ClassHelper.Boolean_TYPE; // GROOVY-10975
             break;
@@ -89,71 +235,125 @@ public interface AbstractFunctionalInterfaceWriter {
             returnType = ClassHelper.VOID_TYPE; // GROOVY-10933
         }
 
-        Object[] arguments = !serializable ? new Object[3] : new 
Object[]{null, null, null, 5, 0};
+        return Type.getMethodType(getMethodDescriptor(returnType, 
implMethodParameters));
+    }
 
-        arguments[0] = Type.getMethodType(abstractMethodDesc);
+    private BlockStatement getOrAddDeserializeDispatcherGuards(final 
WriterController controller, final Parameter[] deserializeMethodParameters) {
+        ClassNode enclosingClass = controller.getClassNode();
+        BlockStatement dispatcherGuards = 
enclosingClass.getNodeMetaData(DESERIALIZE_LAMBDA_DISPATCHER);
+        if (dispatcherGuards != null) {
+            return dispatcherGuards;
+        }
 
-        arguments[1] = new Handle(
-                insn, // H_INVOKESTATIC or H_INVOKEVIRTUAL or 
H_INVOKEINTERFACE (GROOVY-9853)
-                getClassInternalName(methodOwner.getName()),
-                methodNode.getName(),
-                getMethodDescriptor(methodNode),
-                methodOwner.isInterface());
+        dispatcherGuards = new BlockStatement();
+        BlockStatement dispatcher = new BlockStatement();
+        dispatcher.addStatement(dispatcherGuards);
+        dispatcher.addStatement(createInvalidDeserializationStatement());
 
-        arguments[2] = Type.getMethodType(getMethodDescriptor(returnType, 
parameters));
+        MethodNode deserializeLambda = enclosingClass.addSyntheticMethod(
+            "$deserializeLambda$",
+                Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
+                OBJECT_TYPE,
+                deserializeMethodParameters,
+                ClassNode.EMPTY_ARRAY,
+                dispatcher);
+        
deserializeLambda.putNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE,
 Boolean.TRUE);
+        enclosingClass.putNodeMetaData(DESERIALIZE_LAMBDA_DISPATCHER, 
dispatcherGuards);
+        return dispatcherGuards;
+    }
 
-        return arguments;
+    private Statement createInvalidDeserializationStatement() {
+        final ClassNode cn = ClassHelper.make(IllegalArgumentException.class);
+        ConstructorCallExpression exception = ctorX(cn, constX("Invalid 
serialized functional interface"));
+        exception.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, 
cn.getDeclaredConstructor(
+                params(param(ClassHelper.STRING_TYPE, "message"))
+        ));
+        return throwS(exception);
     }
 
-    default ClassNode convertParameterType(final ClassNode parameterType, 
final ClassNode inferredType) {
-        return convertParameterType(parameterType, parameterType, 
inferredType);
+    /**
+     * Builds the identity check used by {@code $deserializeLambda$} to select 
the
+     * correct synthetic helper for a serialized lambda/method reference.
+     * <p>
+     * The generated expression is a conjunction over all stable
+     * {@link java.lang.invoke.SerializedLambda} identity fields we emit via
+     * {@link #createSerializedLambdaFingerprint(String, ClassNode, int, 
ClassNode, MethodNode, Parameter[], ClassNode, MethodNode, int)}:
+     * capturing class, implementation kind/class/name/signature, functional 
interface class/SAM method/signature,
+     * instantiated method type, and captured argument count.
+     * <p>
+     * Do not weaken this predicate (for example, by checking only method 
name/class or
+     * by returning a constant). Doing so can misroute deserialization to the 
wrong helper,
+     * while overly strict constant-false behavior breaks valid 
deserialization.
+     *
+     * @param serializedForm the deserialized {@code SerializedLambda} 
expression
+     * @param serializedLambdaFingerprint compile-time fingerprint of one 
serialized lambda target
+     * @return expression that evaluates to {@code true} only for this exact 
target
+     */
+    private Expression matchesSerializedFunctionalInterface(final Expression 
serializedForm, final SerializedLambdaFingerprint serializedLambdaFingerprint) {
+        return allMatch(
+            matchesSerializedFormInt(serializedForm, "getCapturedArgCount", 
serializedLambdaFingerprint.capturedArgCount()),
+            matchesSerializedFormString(serializedForm, "getCapturingClass", 
serializedLambdaFingerprint.capturingClass()),
+            matchesSerializedFormInt(serializedForm, "getImplMethodKind", 
serializedLambdaFingerprint.implMethodKind()),
+            matchesSerializedFormString(serializedForm, "getImplClass", 
serializedLambdaFingerprint.implClass()),
+            matchesSerializedFormString(serializedForm, "getImplMethodName", 
serializedLambdaFingerprint.implMethodName()),
+            matchesSerializedFormString(serializedForm, 
"getImplMethodSignature", serializedLambdaFingerprint.implMethodSignature()),
+            matchesSerializedFormString(serializedForm, 
"getFunctionalInterfaceClass", 
serializedLambdaFingerprint.functionalInterfaceClass()),
+            matchesSerializedFormString(serializedForm, 
"getFunctionalInterfaceMethodName", 
serializedLambdaFingerprint.functionalInterfaceMethodName()),
+            matchesSerializedFormString(serializedForm, 
"getFunctionalInterfaceMethodSignature", 
serializedLambdaFingerprint.functionalInterfaceMethodSignature()),
+            matchesSerializedFormString(serializedForm, 
"getInstantiatedMethodType", 
serializedLambdaFingerprint.instantiatedMethodType())
+        );
     }
 
-    default ClassNode convertParameterType(final ClassNode targetType, final 
ClassNode parameterType, final ClassNode inferredType) {
-        if 
(!getWrapper(inferredType).isDerivedFrom(getWrapper(parameterType))) {
-            throw new RuntimeParserException("The inferred type[" + 
inferredType.redirect() + "] is not compatible with the parameter type[" + 
parameterType.redirect() + "]", parameterType);
-        }
+    /**
+     * Combines predicates with logical AND.
+     *
+     * @param expressions match predicates to combine
+     * @return conjunction of all predicates
+     * @throws IllegalArgumentException if no predicates are supplied
+     */
+    private Expression allMatch(final Expression... expressions) {
+        return Arrays.stream(expressions)
+                .reduce(GeneralUtils::andX)
+                .orElseThrow(() -> new IllegalArgumentException("expressions 
must not be empty"));
+    }
 
-        ClassNode type;
-        if (isPrimitiveType(parameterType)) {
-            if (!isPrimitiveType(inferredType)) {
-                // The non-primitive type and primitive type are not allowed 
to mix since Java 9+
-                // java.lang.invoke.LambdaConversionException: Type mismatch 
for instantiated parameter 0: class java.lang.Integer is not a subtype of int
-                type = getUnwrapper(inferredType).getPlainNodeReference(false);
-            } else {
-                type = inferredType.getPlainNodeReference(false);
-            }
-        } else if (isPrimitiveType(inferredType)) {
-            // GROOVY-9790: bootstrap method initialization exception raised 
when lambda parameter type is wrong
-            // (1) java.lang.invoke.LambdaConversionException: Type mismatch 
for instantiated parameter 0: class java.lang.Integer is not a subtype of int
-            // (2) java.lang.BootstrapMethodError: bootstrap method 
initialization exception
-            if (!(isDynamicTyped(parameterType) && 
isPrimitiveType(targetType)) // (1)
-                    && (parameterType.equals(getUnwrapper(parameterType)) || 
inferredType.equals(getWrapper(inferredType)))) { // (2)
-                // The non-primitive type and primitive type are not allowed 
to mix since Java 9+
-                // java.lang.invoke.LambdaConversionException: Type mismatch 
for instantiated parameter 0: int is not a subtype of class java.lang.Object
-                type = getWrapper(inferredType).getPlainNodeReference();
-            } else {
-                type = inferredType.getPlainNodeReference(false);
-            }
-        } else {
-            type = inferredType;
-            // GROOVY-11304: no placeholders
-            if (hasUnresolvedGenerics(type)) type = type.redirect();
-            // GROOVY-11479: mutable for node metadata or type annotations
-            if (type.toString(false).equals(parameterType.toString(false))) {
-                type = parameterType;
-            } else {
-                // TODO: deep copy if type args set
-                type = type.getPlainNodeReference();
-            }
+    private Expression matchesSerializedFormInt(final Expression 
serializedForm, final String accessorName, final int expectedValue) {
+        return eqX(serializedLambdaAccessorCall(serializedForm, accessorName), 
constX(expectedValue, true));
+    }
+
+    private Expression matchesSerializedFormString(final Expression 
serializedForm, final String accessorName, final String expectedValue) {
+        return eqX(serializedLambdaAccessorCall(serializedForm, accessorName), 
constX(expectedValue));
+    }
+
+    /**
+     * Creates a direct {@link java.lang.invoke.SerializedLambda} accessor 
call for the generated
+     * deserialization dispatcher.
+     * <p>
+     * The dispatcher is emitted during bytecode generation rather than type 
checking, so it must
+     * resolve accessor targets explicitly instead of relying on a later 
static-type-checking pass.
+     * This keeps the generated bytecode on the direct invocation path and 
prevents accidental
+     * fallback to Groovy's dynamic method dispatch for JDK accessors.
+     *
+     * @param serializedForm the deserialized {@code SerializedLambda} 
expression
+     * @param accessorName the zero-argument accessor to invoke
+     * @return method call expression with a resolved direct-call target
+     * @throws IllegalArgumentException if the accessor does not exist on 
{@code SerializedLambda}
+     */
+    private MethodCallExpression serializedLambdaAccessorCall(final Expression 
serializedForm, final String accessorName) {
+        MethodNode accessor = SERIALIZEDLAMBDA_TYPE.getMethod(accessorName, 
Parameter.EMPTY_ARRAY);
+        if (accessor == null) {
+            throw new IllegalArgumentException("Unknown SerializedLambda 
accessor: " + accessorName);
         }
-        return type;
+
+        MethodCallExpression accessorCall = callX(serializedForm, 
accessorName);
+        accessorCall.setMethodTarget(accessor);
+        accessorCall.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, accessor);
+        return accessorCall;
     }
 
-    default Parameter prependParameter(final List<Parameter> parameterList, 
final String parameterName, final ClassNode parameterType) {
-        Parameter parameter = new Parameter(parameterType, parameterName);
-        parameter.setClosureSharedVariable(false);
-        parameterList.add(0, parameter);
-        return parameter;
+    record SerializedLambdaFingerprint(String capturingClass, int 
implMethodKind, String implClass, String implMethodName,
+                                       String implMethodSignature, String 
functionalInterfaceClass,
+                                       String functionalInterfaceMethodName, 
String functionalInterfaceMethodSignature,
+                                       String instantiatedMethodType, int 
capturedArgCount) {
     }
 }
diff --git 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesFunctionalInterfaceMetadataKey.java
 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesFunctionalInterfaceMetadataKey.java
new file mode 100644
index 0000000000..7b2dc26e24
--- /dev/null
+++ 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesFunctionalInterfaceMetadataKey.java
@@ -0,0 +1,51 @@
+/*
+ *  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.classgen.asm.sc;
+
+/**
+ * Internal AST node metadata keys used by the statically-compiled lambda and 
method-reference pipeline.
+ *
+ * @since 6.0.0
+ */
+enum StaticTypesFunctionalInterfaceMetadataKey {
+    /**
+     * Stores the shared {@code $deserializeLambda$} guard block on the 
enclosing class node.
+     */
+    DESERIALIZE_LAMBDA_DISPATCHER,
+    /**
+     * Marks the synthetic constructor created for a generated lambda class.
+     */
+    LAMBDA_GENERATED_CONSTRUCTOR,
+    /**
+     * Stores the captured shared variables prepared for a lambda expression.
+     */
+    LAMBDA_SHARED_VARIABLES,
+    /**
+     * Marks the deserialize helper method that preloads a captured lambda 
receiver.
+     */
+    LAMBDA_PRELOADED_RECEIVER,
+    /**
+     * Caches whether the analyzed lambda method touches enclosing-instance 
state.
+     */
+    LAMBDA_ACCESSES_INSTANCE_MEMBERS,
+    /**
+     * Stores the synthetic deserialize helper name allocated for a 
method-reference expression.
+     */
+    METHOD_REFERENCE_DESERIALIZE_METHOD_NAME
+}
diff --git 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaAnalyzer.java
 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaAnalyzer.java
index 5509899d86..eca48e99e7 100644
--- 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaAnalyzer.java
+++ 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaAnalyzer.java
@@ -40,6 +40,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
+import static 
org.codehaus.groovy.classgen.asm.sc.StaticTypesFunctionalInterfaceMetadataKey.LAMBDA_ACCESSES_INSTANCE_MEMBERS;
 import static org.apache.groovy.util.BeanUtils.capitalize;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
 import static 
org.codehaus.groovy.transform.stc.StaticTypesMarker.DIRECT_METHOD_CALL_TARGET;
@@ -68,14 +69,14 @@ class StaticTypesLambdaAnalyzer {
     }
 
     boolean accessesInstanceMembers(final MethodNode lambdaMethod) {
-        Boolean accessingInstanceMembers = 
lambdaMethod.getNodeMetaData(MetaDataKey.ACCESSES_INSTANCE_MEMBERS);
+        Boolean accessingInstanceMembers = 
lambdaMethod.getNodeMetaData(LAMBDA_ACCESSES_INSTANCE_MEMBERS);
         if (accessingInstanceMembers != null) return accessingInstanceMembers;
 
         InstanceMemberAccessFinder finder = new 
InstanceMemberAccessFinder(getOrCreateResolver(lambdaMethod));
         lambdaMethod.getCode().visit(finder);
 
         accessingInstanceMembers = finder.isAccessingInstanceMembers();
-        lambdaMethod.putNodeMetaData(MetaDataKey.ACCESSES_INSTANCE_MEMBERS, 
accessingInstanceMembers);
+        lambdaMethod.putNodeMetaData(LAMBDA_ACCESSES_INSTANCE_MEMBERS, 
accessingInstanceMembers);
         return accessingInstanceMembers;
     }
 
@@ -394,10 +395,6 @@ class StaticTypesLambdaAnalyzer {
         }
     }
 
-    private enum MetaDataKey {
-        ACCESSES_INSTANCE_MEMBERS
-    }
-
     private final SourceUnit sourceUnit;
     private final Map<MethodNode, OuterStaticMemberResolver> resolverCache = 
new HashMap<>();
 }
diff --git 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
index b61dc6c930..bbcdc01482 100644
--- 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
+++ 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
@@ -25,11 +25,8 @@ import org.codehaus.groovy.ast.ConstructorNode;
 import org.codehaus.groovy.ast.InnerClassNode;
 import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.ast.Parameter;
-import org.codehaus.groovy.ast.builder.AstStringCompiler;
 import org.codehaus.groovy.ast.expr.ClosureExpression;
-import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.LambdaExpression;
-import org.codehaus.groovy.ast.stmt.BlockStatement;
 import org.codehaus.groovy.ast.stmt.Statement;
 import org.codehaus.groovy.classgen.BytecodeInstruction;
 import org.codehaus.groovy.classgen.BytecodeSequence;
@@ -46,17 +43,17 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import static 
org.codehaus.groovy.classgen.asm.sc.StaticTypesFunctionalInterfaceMetadataKey.LAMBDA_GENERATED_CONSTRUCTOR;
+import static 
org.codehaus.groovy.classgen.asm.sc.StaticTypesFunctionalInterfaceMetadataKey.LAMBDA_PRELOADED_RECEIVER;
+import static 
org.codehaus.groovy.classgen.asm.sc.StaticTypesFunctionalInterfaceMetadataKey.LAMBDA_SHARED_VARIABLES;
 import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.GENERATED_LAMBDA_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.SERIALIZABLE_TYPE;
-import static org.codehaus.groovy.ast.ClassHelper.SERIALIZEDLAMBDA_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE;
 import static org.codehaus.groovy.ast.tools.ClosureUtils.getParametersSafe;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
 import static 
org.codehaus.groovy.transform.stc.StaticTypesMarker.CLOSURE_ARGUMENTS;
 import static 
org.codehaus.groovy.transform.stc.StaticTypesMarker.PARAMETER_TYPE;
@@ -107,10 +104,6 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
         writeLambdaFactoryInvocation(functionalType.redirect(), 
abstractMethod, generatedLambda, serializable);
     }
 
-    private static Parameter[] createDeserializeLambdaMethodParams() {
-        return new Parameter[]{new Parameter(SERIALIZEDLAMBDA_TYPE, 
"serializedLambda")};
-    }
-
     private static MethodNode resolveFunctionalInterfaceMethod(final ClassNode 
functionalType) {
         if (functionalType == null || !functionalType.isInterface()) {
             return null;
@@ -130,23 +123,32 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
             return;
         }
 
-        addDeserializeLambdaMethodForLambdaExpression(expression, 
generatedLambda);
-        addDeserializeLambdaMethod();
+        MethodNode helperMethod = 
addDeserializeLambdaMethodForLambdaExpression(expression, generatedLambda);
+        addDeserializeDispatcherEntry(controller, 
createDeserializeMethodParameters(), createSerializedLambdaFingerprint(
+                
createMethodDescriptor(ClassHelper.findSAM(expression.getNodeMetaData(PARAMETER_TYPE))),
+                controller.getClassNode(),
+                generatedLambda.getImplMethodKind(),
+                generatedLambda.lambdaClass,
+                generatedLambda.lambdaMethod,
+                generatedLambda.lambdaMethod.getParameters(),
+                expression.getNodeMetaData(PARAMETER_TYPE),
+                
ClassHelper.findSAM(expression.getNodeMetaData(PARAMETER_TYPE)),
+                generatedLambda.isCapturing() ? 1 : 0
+        ), helperMethod);
     }
 
     private void writeLambdaFactoryInvocation(final ClassNode functionalType, 
final MethodNode abstractMethod, final GeneratedLambda generatedLambda, final 
boolean serializable) {
-        MethodVisitor mv = controller.getMethodVisitor();
-        mv.visitInvokeDynamicInsn(
+        writeFunctionalInterfaceIndy(
+            controller.getMethodVisitor(),
             abstractMethod.getName(),
             createLambdaFactoryMethodDescriptor(functionalType, 
generatedLambda),
-            createBootstrapMethod(controller.getClassNode().isInterface(), 
serializable),
-            
createBootstrapMethodArguments(createMethodDescriptor(abstractMethod),
-                generatedLambda.getMethodHandleKind(),
-                generatedLambda.lambdaClass, generatedLambda.lambdaMethod, 
generatedLambda.lambdaMethod.getParameters(), serializable)
+            createMethodDescriptor(abstractMethod),
+            generatedLambda.getImplMethodKind(),
+            generatedLambda.lambdaClass,
+            generatedLambda.lambdaMethod,
+            generatedLambda.lambdaMethod.getParameters(),
+            serializable
         );
-        if (serializable) {
-            mv.visitTypeInsn(CHECKCAST, "java/io/Serializable");
-        }
 
         if (generatedLambda.nonCapturing()) {
             controller.getOperandStack().push(functionalType);
@@ -156,7 +158,7 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
     }
 
     private boolean hasDeserializeLambdaMethod(final ClassNode lambdaClass) {
-        return 
controller.getClassNode().hasMethod(createDeserializeLambdaMethodName(lambdaClass),
 createDeserializeLambdaMethodParams());
+        return 
controller.getClassNode().hasMethod(createDeserializeLambdaMethodName(lambdaClass),
 createDeserializeMethodParameters());
     }
 
     private static MethodNode getLambdaMethod(final ClassNode lambdaClass) {
@@ -169,7 +171,7 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
 
     private static ConstructorNode getGeneratedConstructor(final ClassNode 
lambdaClass) {
         for (ConstructorNode constructorNode : 
lambdaClass.getDeclaredConstructors()) {
-            if 
(Boolean.TRUE.equals(constructorNode.getNodeMetaData(MetaDataKey.GENERATED_CONSTRUCTOR)))
 {
+            if 
(Boolean.TRUE.equals(constructorNode.getNodeMetaData(LAMBDA_GENERATED_CONSTRUCTOR)))
 {
                 return constructorNode;
             }
         }
@@ -179,7 +181,7 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
     private boolean isPreloadedLambdaReceiver(final GeneratedLambda 
generatedLambda) {
         MethodNode enclosingMethod = controller.getMethodNode();
         return enclosingMethod != null
-            && 
enclosingMethod.getNodeMetaData(MetaDataKey.PRELOADED_LAMBDA_RECEIVER) == 
generatedLambda.lambdaClass;
+            && enclosingMethod.getNodeMetaData(LAMBDA_PRELOADED_RECEIVER) == 
generatedLambda.lambdaClass;
     }
 
     private void loadLambdaReceiver(final GeneratedLambda generatedLambda) {
@@ -279,7 +281,7 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
         addFieldsForLocalVariables(lambdaClass, localVariableParameters);
 
         ConstructorNode constructorNode = addConstructor(expression, 
localVariableParameters, lambdaClass, 
createBlockStatementForConstructor(expression, outermostClass, enclosingClass));
-        constructorNode.putNodeMetaData(MetaDataKey.GENERATED_CONSTRUCTOR, 
Boolean.TRUE);
+        constructorNode.putNodeMetaData(LAMBDA_GENERATED_CONSTRUCTOR, 
Boolean.TRUE);
 
         syntheticLambdaMethodNode.getCode().visit(new 
CorrectAccessedVariableVisitor(lambdaClass));
 
@@ -297,7 +299,7 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
         Parameter[] localVariableParameters = 
getLambdaSharedVariables(expression);
         removeInitialValues(localVariableParameters);
 
-        expression.putNodeMetaData(MetaDataKey.STORED_LAMBDA_SHARED_VARIABLES, 
localVariableParameters);
+        expression.putNodeMetaData(LAMBDA_SHARED_VARIABLES, 
localVariableParameters);
 
         MethodNode doCallMethod = lambdaClass.addMethod(
             DO_CALL,
@@ -327,36 +329,11 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
         return lambdaParameters;
     }
 
-    private void addDeserializeLambdaMethod() {
-        ClassNode enclosingClass = controller.getClassNode();
-        Parameter[] parameters = createDeserializeLambdaMethodParams();
-        if (enclosingClass.hasMethod("$deserializeLambda$", parameters)) {
-            return;
-        }
-
-        Statement code = block(
-            declS(localVarX("enclosingClass", OBJECT_TYPE), 
classX(enclosingClass)),
-            ((BlockStatement) new AstStringCompiler().compile(
-                "return enclosingClass" +
-                    
".getDeclaredMethod(\"\\$deserializeLambda_${serializedLambda.getImplClass().replace('/',
 '$')}\\$\", serializedLambda.getClass())" +
-                    ".invoke(null, serializedLambda)"
-            ).get(0)).getStatements().get(0)
-        );
-
-        enclosingClass.addSyntheticMethod(
-            "$deserializeLambda$",
-            ACC_PRIVATE | ACC_STATIC,
-            OBJECT_TYPE,
-            parameters,
-            ClassNode.EMPTY_ARRAY,
-            code);
-    }
-
     private static boolean requiresLambdaInstance(final MethodNode 
lambdaMethod) {
         return 0 == (lambdaMethod.getModifiers() & ACC_STATIC);
     }
 
-    private void addDeserializeLambdaMethodForLambdaExpression(final 
LambdaExpression expression, final GeneratedLambda generatedLambda) {
+    private MethodNode addDeserializeLambdaMethodForLambdaExpression(final 
LambdaExpression expression, final GeneratedLambda generatedLambda) {
         ClassNode enclosingClass = controller.getClassNode();
         Statement code;
         if (generatedLambda.nonCapturing()) {
@@ -385,15 +362,16 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
 
         MethodNode deserializeLambdaMethod = enclosingClass.addSyntheticMethod(
             createDeserializeLambdaMethodName(generatedLambda.lambdaClass),
-            ACC_PUBLIC | ACC_STATIC,
+            ACC_PRIVATE | ACC_STATIC,
             OBJECT_TYPE,
-            createDeserializeLambdaMethodParams(),
+            createDeserializeMethodParameters(),
             ClassNode.EMPTY_ARRAY,
             code);
         if (generatedLambda.isCapturing()) {
             // The deserialize helper preloads the captured receiver before it 
reuses the original lambda expression.
-            
deserializeLambdaMethod.putNodeMetaData(MetaDataKey.PRELOADED_LAMBDA_RECEIVER, 
generatedLambda.lambdaClass);
+            deserializeLambdaMethod.putNodeMetaData(LAMBDA_PRELOADED_RECEIVER, 
generatedLambda.lambdaClass);
         }
+        return deserializeLambdaMethod;
     }
 
     private static String createDeserializeLambdaMethodName(final ClassNode 
lambdaClass) {
@@ -401,19 +379,13 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
     }
 
     private static Parameter[] getStoredLambdaSharedVariables(final 
LambdaExpression expression) {
-        Parameter[] sharedVariables = 
expression.getNodeMetaData(MetaDataKey.STORED_LAMBDA_SHARED_VARIABLES);
+        Parameter[] sharedVariables = 
expression.getNodeMetaData(LAMBDA_SHARED_VARIABLES);
         if (sharedVariables == null) {
             throw new GroovyBugError("Failed to find shared variables for 
lambda expression");
         }
         return sharedVariables;
     }
 
-    private enum MetaDataKey {
-        GENERATED_CONSTRUCTOR,
-        STORED_LAMBDA_SHARED_VARIABLES,
-        PRELOADED_LAMBDA_RECEIVER
-    }
-
     private record GeneratedLambda(ClassNode lambdaClass, MethodNode 
lambdaMethod, ConstructorNode constructor,
                                    Parameter[] sharedVariables, boolean 
nonCapturing,
                                    boolean accessingInstanceMembers) {
@@ -422,7 +394,7 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
             return !nonCapturing;
         }
 
-        private int getMethodHandleKind() {
+        private int getImplMethodKind() {
             return nonCapturing ? H_INVOKESTATIC : H_INVOKEVIRTUAL;
         }
     }
diff --git 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
index c526caa7b4..4ee73639d8 100644
--- 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
+++ 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
@@ -31,6 +31,8 @@ import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.MethodCallExpression;
 import org.codehaus.groovy.ast.expr.MethodReferenceExpression;
 import org.codehaus.groovy.ast.tools.GeneralUtils;
+import org.codehaus.groovy.classgen.BytecodeInstruction;
+import org.codehaus.groovy.classgen.BytecodeSequence;
 import org.codehaus.groovy.classgen.AsmClassGenerator;
 import org.codehaus.groovy.classgen.asm.BytecodeHelper;
 import org.codehaus.groovy.classgen.asm.MethodReferenceExpressionWriter;
@@ -40,7 +42,7 @@ import org.codehaus.groovy.syntax.RuntimeParserException;
 import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys;
 import org.codehaus.groovy.transform.stc.ExtensionMethodNode;
 import org.codehaus.groovy.transform.stc.StaticTypesMarker;
-import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
 import java.util.Arrays;
@@ -51,6 +53,7 @@ import java.util.Set;
 
 import static java.util.Comparator.comparingInt;
 import static java.util.stream.Collectors.joining;
+import static 
org.codehaus.groovy.classgen.asm.sc.StaticTypesFunctionalInterfaceMetadataKey.METHOD_REFERENCE_DESERIALIZE_METHOD_NAME;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
@@ -60,6 +63,7 @@ import static 
org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.extractPlaceholders;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.makeClassSafe0;
+import static 
org.codehaus.groovy.classgen.asm.BytecodeHelper.getClassInternalName;
 import static org.codehaus.groovy.ast.tools.ParameterUtils.isVargs;
 import static 
org.codehaus.groovy.ast.tools.ParameterUtils.parametersCompatible;
 import static org.codehaus.groovy.runtime.ArrayGroovyMethods.last;
@@ -68,6 +72,13 @@ import static 
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.filter
 import static 
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsForClassNode;
 import static 
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isAssignableTo;
 import static 
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.resolveClassNodeGenerics;
+import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.CHECKCAST;
+import static org.objectweb.asm.Opcodes.ICONST_0;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 
 /**
  * Generates bytecode for method reference expressions in statically-compiled 
code.
@@ -180,29 +191,35 @@ public class StaticTypesMethodReferenceExpressionWriter 
extends MethodReferenceE
             }
         }
 
-        int referenceKind;
+        int implMethodKind;
         if (isConstructorReference || methodRefMethod.isStatic()) {
-            referenceKind = Opcodes.H_INVOKESTATIC;
+            implMethodKind = Opcodes.H_INVOKESTATIC;
         } else if (methodRefMethod.getDeclaringClass().isInterface()) {
-            referenceKind = Opcodes.H_INVOKEINTERFACE; // GROOVY-9853
+            implMethodKind = Opcodes.H_INVOKEINTERFACE; // GROOVY-9853
         } else {
-            referenceKind = Opcodes.H_INVOKEVIRTUAL;
+            implMethodKind = Opcodes.H_INVOKEVIRTUAL;
         }
 
-        String methodName = abstractMethod.getName();
-        String methodDesc = 
BytecodeHelper.getMethodDescriptor(functionalType.redirect(),
+        String samMethodName = abstractMethod.getName();
+        String invokedTypeDescriptor = 
BytecodeHelper.getMethodDescriptor(functionalType.redirect(),
                 isClassExpression ? Parameter.EMPTY_ARRAY : new 
Parameter[]{new Parameter(typeOrTargetRefType, "__METHODREF_EXPR_INSTANCE")});
+        boolean serializable = 
functionalType.implementsInterface(ClassHelper.SERIALIZABLE_TYPE);
+        if (serializable) {
+            ensureDeserializeLambdaSupport(methodReferenceExpression, 
functionalType, abstractMethod, methodRefMethod, parametersWithExactType,
+                    implMethodKind, !isClassExpression, typeOrTargetRefType, 
invokedTypeDescriptor);
+        }
 
-        Handle bootstrapMethod = 
createBootstrapMethod(classNode.isInterface(), false);
-        Object[] bootstrapArgs = createBootstrapMethodArguments(
+        writeFunctionalInterfaceIndy(
+            controller.getMethodVisitor(),
+                samMethodName,
+                invokedTypeDescriptor,
                 createMethodDescriptor(abstractMethod),
-                referenceKind,
+                implMethodKind,
                 methodRefMethod.getDeclaringClass(),
                 methodRefMethod,
                 parametersWithExactType,
-                false
+                serializable
         );
-        controller.getMethodVisitor().visitInvokeDynamicInsn(methodName, 
methodDesc, bootstrapMethod, bootstrapArgs);
 
         if (isClassExpression) {
             controller.getOperandStack().push(functionalType);
@@ -255,7 +272,7 @@ public class StaticTypesMethodReferenceExpressionWriter 
extends MethodReferenceE
         methodCall.setMethodTarget(mn);
         
methodCall.putNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET, mn);
 
-        String methodName = "class$" + classType.getNameWithoutPackage() + "$" 
+ mn.getName() + "$" + System.nanoTime();
+        String methodName = createSyntheticMethodName("class", classType, 
mn.getName());
 
         ClassNode returnType = resolveClassNodeGenerics(Map.of(new 
GenericsType.GenericsTypeName("T"), new GenericsType(classType)), null, 
mn.getReturnType());
 
@@ -297,7 +314,7 @@ public class StaticTypesMethodReferenceExpressionWriter 
extends MethodReferenceE
         methodCall.setMethodTarget(mn);
         
methodCall.putNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET, mn);
 
-        String methodName = "adapt$" + 
mn.getDeclaringClass().getNameWithoutPackage() + "$" + mn.getName() + "$" + 
System.nanoTime();
+        String methodName = createSyntheticMethodName("adapt", 
mn.getDeclaringClass(), mn.getName());
 
         MethodNode delegateMethod = addSyntheticMethod(methodName, 
mn.getReturnType(), methodCall, parameters, mn.getExceptions());
         if (!isStaticTarget && !mn.isStatic()) 
delegateMethod.setModifiers(delegateMethod.getModifiers() & 
~Opcodes.ACC_STATIC);
@@ -346,7 +363,7 @@ public class StaticTypesMethodReferenceExpressionWriter 
extends MethodReferenceE
                 if (inferredParamType == null) continue;
                 Parameter parameter = parameters[i];
 
-                ClassNode type = convertParameterType(parameter.getType(), 
inferredParamType);
+                ClassNode type = convertParameterType(parameter.getType(), 
parameter.getType(), inferredParamType);
                 parameter.setOriginType(type);
                 parameter.setType(type);
             }
@@ -454,6 +471,75 @@ public class StaticTypesMethodReferenceExpressionWriter 
extends MethodReferenceE
         throw new 
MultipleCompilationErrorsException(controller.getSourceUnit().getErrorCollector());
     }
 
+    private void ensureDeserializeLambdaSupport(final 
MethodReferenceExpression methodReferenceExpression, final ClassNode 
functionalType,
+                                                 final MethodNode 
abstractMethod, final MethodNode methodRefMethod, final Parameter[] 
parametersWithExactType,
+                                                 final int implMethodKind, 
final boolean capturing,
+                                                 final ClassNode 
capturedTargetType, final String invokedTypeDescriptor) {
+        String helperName = 
methodReferenceExpression.getNodeMetaData(METHOD_REFERENCE_DESERIALIZE_METHOD_NAME);
+        if (helperName == null) {
+            helperName = createDeserializeLambdaMethodName();
+            
methodReferenceExpression.putNodeMetaData(METHOD_REFERENCE_DESERIALIZE_METHOD_NAME,
 helperName);
+        }
+
+        Parameter[] parameters = createDeserializeMethodParameters();
+        if (controller.getClassNode().hasMethod(helperName, parameters)) {
+            return;
+        }
+
+        String samMethodDescriptor = createMethodDescriptor(abstractMethod);
+        MethodNode helperMethod = 
addDeserializeLambdaMethodForMethodReference(helperName, abstractMethod, 
methodRefMethod,
+            parametersWithExactType, implMethodKind, capturing, 
capturedTargetType, invokedTypeDescriptor, samMethodDescriptor);
+        addDeserializeDispatcherEntry(controller, parameters,
+            createSerializedLambdaFingerprint(samMethodDescriptor, 
controller.getClassNode(), implMethodKind,
+                methodRefMethod.getDeclaringClass(), methodRefMethod,
+                parametersWithExactType, functionalType, abstractMethod, 
capturing ? 1 : 0),
+            helperMethod);
+    }
+
+    private MethodNode addDeserializeLambdaMethodForMethodReference(final 
String methodName, final MethodNode abstractMethod,
+                                                                    final 
MethodNode methodRefMethod, final Parameter[] parametersWithExactType,
+                                                                    final int 
implMethodKind, final boolean capturing,
+                                                                    final 
ClassNode capturedTargetType, final String invokedTypeDescriptor,
+                                                                    final 
String samMethodDescriptor) {
+        return controller.getClassNode().addSyntheticMethod(
+                methodName,
+                ACC_PRIVATE | ACC_STATIC,
+                ClassHelper.OBJECT_TYPE,
+                createDeserializeMethodParameters(),
+                ClassNode.EMPTY_ARRAY,
+                new BytecodeSequence(new BytecodeInstruction() {
+                    @Override
+                    public void visit(final MethodVisitor mv) {
+                        if (capturing) {
+                            mv.visitVarInsn(ALOAD, 0);
+                            mv.visitInsn(ICONST_0);
+                            mv.visitMethodInsn(INVOKEVIRTUAL, 
"java/lang/invoke/SerializedLambda", "getCapturedArg", "(I)Ljava/lang/Object;", 
false);
+                            mv.visitTypeInsn(CHECKCAST, 
getClassInternalName(capturedTargetType));
+                        }
+                        writeFunctionalInterfaceIndy(
+                            mv,
+                            abstractMethod.getName(),
+                            invokedTypeDescriptor,
+                            samMethodDescriptor,
+                            implMethodKind,
+                            methodRefMethod.getDeclaringClass(),
+                            methodRefMethod,
+                            parametersWithExactType,
+                            true
+                        );
+                        mv.visitInsn(ARETURN);
+                    }
+                }));
+    }
+
+    private String createSyntheticMethodName(final String prefix, final 
ClassNode owner, final String name) {
+        return prefix + "$" + owner.getNameWithoutPackage() + "$" + name + "$" 
+ controller.getNextHelperMethodIndex();
+    }
+
+    private String createDeserializeLambdaMethodName() {
+        return "$deserializeLambda_methodref$" + 
controller.getNextHelperMethodIndex() + "$";
+    }
+
     
//--------------------------------------------------------------------------
 
     private static boolean isBridgeMethod(final MethodNode mn) {
diff --git a/src/test/groovy/groovy/transform/stc/LambdaTest.groovy 
b/src/test/groovy/groovy/transform/stc/LambdaTest.groovy
index 37eab1575c..d449f7f3f4 100644
--- a/src/test/groovy/groovy/transform/stc/LambdaTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/LambdaTest.groovy
@@ -3423,6 +3423,6 @@ final class LambdaTest {
             import java.util.function.IntUnaryOperator
             import java.util.function.Supplier
             '''.stripIndent()
-        private static final String SERIALIZED_LAMBDA_GET_CAPTURED_ARG = 
'INVOKEVIRTUAL java/lang/invoke/SerializedLambda.getCapturedArg'
+        private static final String SERIALIZED_LAMBDA_GET_CAPTURED_ARG = 
'INVOKEVIRTUAL java/lang/invoke/SerializedLambda.getCapturedArg 
(I)Ljava/lang/Object;'
     }
 }
diff --git a/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy 
b/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy
index 3c6b5e1b90..6b60ac7bf4 100644
--- a/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy
@@ -1158,6 +1158,144 @@ final class MethodReferenceTest {
         '''
     }
 
+    @Test // class::new
+    void testSerializableConstructorReference() {
+        assertScript shell, '''
+            import java.io.ByteArrayInputStream
+            import java.io.ByteArrayOutputStream
+            import java.io.Serializable
+
+            @CompileStatic
+            class C {
+                interface SerFunc<I, O> extends Serializable, Function<I, O> {}
+
+                static class Box {
+                    final String value
+
+                    Box(String value) {
+                        this.value = value.trim()
+                    }
+                }
+
+                static SerFunc<String, Box> create() {
+                    Box::new
+                }
+
+                static byte[] serialize(Serializable value) {
+                    def out = new ByteArrayOutputStream()
+                    out.withObjectOutputStream { it.writeObject(value) }
+                    out.toByteArray()
+                }
+
+                static <T> T deserialize(byte[] bytes) {
+                    new 
ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) {
+                        (T) it.readObject()
+                    }
+                }
+            }
+
+            assert C.declaredMethods.count { it.name == '$deserializeLambda$' 
} == 1
+
+            C.SerFunc<String, C.Box> factory = 
C.deserialize(C.serialize(C.create()))
+            assert factory instanceof Serializable
+            assert factory.apply('  ok  ').value == 'ok'
+        '''
+    }
+
+    @Test // arrayClass::new
+    void testSerializableArrayConstructorReference() {
+        assertScript shell, '''
+            import java.io.ByteArrayInputStream
+            import java.io.ByteArrayOutputStream
+            import java.io.Serializable
+
+            @CompileStatic
+            class C {
+                interface SerIntFunc<T> extends Serializable, IntFunction<T> {}
+
+                static SerIntFunc<String[]> create() {
+                    String[]::new
+                }
+
+                static byte[] serialize(Serializable value) {
+                    def out = new ByteArrayOutputStream()
+                    out.withObjectOutputStream { it.writeObject(value) }
+                    out.toByteArray()
+                }
+
+                static <T> T deserialize(byte[] bytes) {
+                    new 
ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) {
+                        (T) it.readObject()
+                    }
+                }
+            }
+
+            C.SerIntFunc<String[]> factory = 
C.deserialize(C.serialize(C.create()))
+            String[] values = factory.apply(3)
+            assert values.length == 3
+            assert values.toList() == [null, null, null]
+        '''
+    }
+
+    @Test
+    void 
testSerializableConstructorReferencesShareDeserializeDispatcherWithLambdas() {
+        assertScript shell, '''
+            import java.io.ByteArrayInputStream
+            import java.io.ByteArrayOutputStream
+            import java.io.Serializable
+
+            @CompileStatic
+            class C {
+                interface SerFunc<I, O> extends Serializable, Function<I, O> {}
+                interface SerIntFunc<T> extends Serializable, IntFunction<T> {}
+
+                static class Box {
+                    final String value
+
+                    Box(String value) {
+                        this.value = value
+                    }
+                }
+
+                static SerFunc<String, Box> createConstructorReference() {
+                    Box::new
+                }
+
+                static SerIntFunc<Box[]> createArrayConstructorReference() {
+                    Box[]::new
+                }
+
+                static SerFunc<Integer, String> createLambda() {
+                    (Integer i) -> 'L' + i
+                }
+
+                static byte[] serialize(Serializable value) {
+                    def out = new ByteArrayOutputStream()
+                    out.withObjectOutputStream { it.writeObject(value) }
+                    out.toByteArray()
+                }
+
+                static <T> T deserialize(byte[] bytes) {
+                    new 
ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) {
+                        (T) it.readObject()
+                    }
+                }
+            }
+
+            assert C.declaredMethods.count { it.name == '$deserializeLambda$' 
} == 1
+            assert C.declaredMethods.findAll { 
it.name.startsWith('$deserializeLambda') && it.name != '$deserializeLambda$' }
+                .every { java.lang.reflect.Modifier.isPrivate(it.modifiers) && 
java.lang.reflect.Modifier.isStatic(it.modifiers) }
+
+            C.SerFunc<String, C.Box> ctor = 
C.deserialize(C.serialize(C.createConstructorReference()))
+            C.SerIntFunc<C.Box[]> arrayCtor = 
C.deserialize(C.serialize(C.createArrayConstructorReference()))
+            C.SerFunc<Integer, String> lambda = 
C.deserialize(C.serialize(C.createLambda()))
+
+            assert ctor.apply('box').value == 'box'
+            assert arrayCtor.apply(2).length == 2
+            assert lambda.apply(4) == 'L4'
+        '''
+    }
+
     @Test // class::staticMethod
     void testFunctionCS() {
         assertScript shell, '''
@@ -1662,6 +1800,283 @@ final class MethodReferenceTest {
         '''
     }
 
+    @Test
+    void testSerializableNonCapturingMethodReference() {
+        assertScript shell, '''
+            import java.io.ByteArrayInputStream
+            import java.io.ByteArrayOutputStream
+            import java.io.Serializable
+
+            @CompileStatic
+            class C {
+                interface SerFunc<I, O> extends Serializable, Function<I, O> {}
+
+                static SerFunc<Integer, String> create() {
+                    Integer::toString
+                }
+
+                static byte[] serialize(Serializable value) {
+                    def out = new ByteArrayOutputStream()
+                    out.withObjectOutputStream { it.writeObject(value) }
+                    out.toByteArray()
+                }
+
+                static <T> T deserialize(byte[] bytes) {
+                    new 
ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) {
+                        (T) it.readObject()
+                    }
+                }
+            }
+
+            assert C.declaredMethods.count { it.name == '$deserializeLambda$' 
} == 1
+
+            C.SerFunc<Integer, String> fn = 
C.deserialize(C.serialize(C.create()))
+            assert fn instanceof Serializable
+            assert fn.apply(7) == '7'
+        '''
+    }
+
+    @Test
+    void testSerializableCapturingMethodReference() {
+        assertScript shell, '''
+            import java.io.ByteArrayInputStream
+            import java.io.ByteArrayOutputStream
+            import java.io.Serializable
+
+            @CompileStatic
+            class C {
+                interface SerSupplier<T> extends Serializable, Supplier<T> {}
+
+                private final String text
+
+                C(String text) {
+                    this.text = text
+                }
+
+                SerSupplier<String> create() {
+                    text::trim
+                }
+
+                static byte[] serialize(Serializable value) {
+                    def out = new ByteArrayOutputStream()
+                    out.withObjectOutputStream { it.writeObject(value) }
+                    out.toByteArray()
+                }
+
+                static <T> T deserialize(byte[] bytes) {
+                    new 
ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) {
+                        (T) it.readObject()
+                    }
+                }
+            }
+
+            C.SerSupplier<String> supplier = C.deserialize(C.serialize(new C(' 
 answer  ').create()))
+            assert supplier instanceof Serializable
+            assert supplier.get() == 'answer'
+        '''
+    }
+
+    @Test
+    void 
testSerializableMethodReferencesShareDeserializeDispatcherWithLambdas() {
+        assertScript shell, '''
+            import java.io.ByteArrayInputStream
+            import java.io.ByteArrayOutputStream
+            import java.io.Serializable
+
+            @CompileStatic
+            class C {
+                interface SerFunc<I, O> extends Serializable, Function<I, O> {}
+                interface SerSupplier<T> extends Serializable, Supplier<T> {}
+
+                private final String text
+
+                C(String text) {
+                    this.text = text
+                }
+
+                static SerFunc<Integer, String> createMethodReference() {
+                    Integer::toString
+                }
+
+                static SerFunc<Integer, String> createLambda() {
+                    (Integer i) -> 'L' + i
+                }
+
+                SerSupplier<String> createBoundMethodReference() {
+                    text::trim
+                }
+
+                static byte[] serialize(Serializable value) {
+                    def out = new ByteArrayOutputStream()
+                    out.withObjectOutputStream { it.writeObject(value) }
+                    out.toByteArray()
+                }
+
+                static <T> T deserialize(byte[] bytes) {
+                    new 
ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) {
+                        (T) it.readObject()
+                    }
+                }
+            }
+
+            assert C.declaredMethods.count { it.name == '$deserializeLambda$' 
} == 1
+            assert C.declaredMethods.findAll { 
it.name.startsWith('$deserializeLambda') && it.name != '$deserializeLambda$' }
+                .every { java.lang.reflect.Modifier.isPrivate(it.modifiers) && 
java.lang.reflect.Modifier.isStatic(it.modifiers) }
+
+            C.SerFunc<Integer, String> methodRef = 
C.deserialize(C.serialize(C.createMethodReference()))
+            C.SerFunc<Integer, String> lambda = 
C.deserialize(C.serialize(C.createLambda()))
+            C.SerSupplier<String> bound = C.deserialize(C.serialize(new C('  x 
 ').createBoundMethodReference()))
+
+            assert methodRef.apply(3) == '3'
+            assert lambda.apply(4) == 'L4'
+            assert bound.get() == 'x'
+        '''
+    }
+
+    @Test
+    void 
testDeserializeDispatcherReturnsMatchingMethodReferenceAndLambdaBeforeFallback()
 {
+        assertScript shell, '''
+            import java.io.Serializable
+            import java.lang.invoke.SerializedLambda
+
+            @CompileStatic
+            class C {
+                interface SerFunc<I, O> extends Serializable, Function<I, O> {}
+
+                static SerFunc<Integer, String> createMethodReference() {
+                    Integer::toString
+                }
+
+                static SerFunc<Integer, String> createLambda() {
+                    (Integer i) -> 'L' + i
+                }
+
+                @CompileDynamic
+                static SerializedLambda serialized(Serializable value) {
+                    def writeReplace = 
value.class.getDeclaredMethod('writeReplace')
+                    writeReplace.accessible = true
+                    (SerializedLambda) writeReplace.invoke(value)
+                }
+            }
+
+            def dispatcher = C.getDeclaredMethod('$deserializeLambda$', 
SerializedLambda)
+            dispatcher.accessible = true
+
+            C.SerFunc<Integer, String> methodRef =
+                (C.SerFunc<Integer, String>) dispatcher.invoke(null, 
C.serialized(C.createMethodReference()))
+            C.SerFunc<Integer, String> lambda =
+                (C.SerFunc<Integer, String>) dispatcher.invoke(null, 
C.serialized(C.createLambda()))
+
+            assert methodRef.apply(3) == '3'
+            assert lambda.apply(3) == 'L3'
+        '''
+    }
+
+    @Test
+    void 
testDeserializeDispatcherRejectsWrongCapturingClassEvenWhenOtherSerializedFieldsMatch()
 {
+        assertScript shell, '''
+            import java.io.Serializable
+            import java.lang.invoke.SerializedLambda
+
+            @CompileStatic
+            class C {
+                interface SerFunc<I, O> extends Serializable, Function<I, O> {}
+
+                static SerFunc<Integer, String> createMethodReference() {
+                    Integer::toString
+                }
+
+                static SerFunc<Integer, String> createLambda() {
+                    (Integer i) -> 'L' + i
+                }
+
+                @CompileDynamic
+                static SerializedLambda serialized(Serializable value) {
+                    def writeReplace = 
value.class.getDeclaredMethod('writeReplace')
+                    writeReplace.accessible = true
+                    (SerializedLambda) writeReplace.invoke(value)
+                }
+
+                @CompileDynamic
+                static SerializedLambda withCapturingClass(SerializedLambda 
serialized, Class capturingClass) {
+                    new SerializedLambda(
+                        capturingClass,
+                        serialized.functionalInterfaceClass,
+                        serialized.functionalInterfaceMethodName,
+                        serialized.functionalInterfaceMethodSignature,
+                        serialized.implMethodKind,
+                        serialized.implClass,
+                        serialized.implMethodName,
+                        serialized.implMethodSignature,
+                        serialized.instantiatedMethodType,
+                        (0..<serialized.capturedArgCount).collect { 
serialized.getCapturedArg(it) } as Object[]
+                    )
+                }
+            }
+
+            def dispatcher = C.getDeclaredMethod('$deserializeLambda$', 
SerializedLambda)
+            dispatcher.accessible = true
+
+            def assertInvalid = { SerializedLambda serialized ->
+                def err
+                try {
+                    dispatcher.invoke(null, serialized)
+                    assert false: 'dispatcher invocation should fail'
+                } catch (java.lang.reflect.InvocationTargetException e) {
+                    err = e
+                }
+                assert err.cause instanceof IllegalArgumentException
+                assert err.cause.message == 'Invalid serialized functional 
interface'
+            }
+
+            
assertInvalid(C.withCapturingClass(C.serialized(C.createMethodReference()), 
String))
+            assertInvalid(C.withCapturingClass(C.serialized(C.createLambda()), 
Integer))
+        '''
+    }
+
+    @Test
+    void 
testDeserializeDispatcherReportsClearErrorForMismatchedSerializedForm() {
+        assertScript shell, '''
+            import java.lang.invoke.MethodHandleInfo
+            import java.lang.invoke.SerializedLambda
+
+            @CompileStatic
+            class C {
+                interface SerFunc<I, O> extends Serializable, Function<I, O> {}
+
+                static SerFunc<Integer, String> create() {
+                    Integer::toString
+                }
+            }
+
+            def dispatcher = C.getDeclaredMethod('$deserializeLambda$', 
SerializedLambda)
+            dispatcher.accessible = true
+
+            def serialized = new SerializedLambda(
+                C,
+                'java/util/function/Function',
+                'apply',
+                '(Ljava/lang/Object;)Ljava/lang/Object;',
+                MethodHandleInfo.REF_invokeStatic,
+                'java/lang/Integer',
+                'toString',
+                '(I)Ljava/lang/String;',
+                '(Ljava/lang/Integer;)Ljava/lang/String;',
+                [] as Object[]
+            )
+
+            def err
+            try {
+                dispatcher.invoke(null, serialized)
+                assert false: 'dispatcher invocation should fail'
+            } catch (java.lang.reflect.InvocationTargetException e) {
+                err = e
+            }
+            assert err.cause instanceof IllegalArgumentException
+            assert err.cause.message == 'Invalid serialized functional 
interface'
+        '''
+    }
+
     // GROOVY-11467
     @Test
     void testSuperInterfaceMethodReference() {

Reply via email to