You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2026/05/04 17:35:43 UTC

(groovy) branch GROOVY-11993 created (now 6a69ca4f58)

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

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


      at 6a69ca4f58 GROOVY-11993: Support serializable method reference

This branch includes the following new commits:

     new 6a69ca4f58 GROOVY-11993: Support serializable method reference

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(groovy) 01/01: GROOVY-11993: Support serializable method reference

Posted by su...@apache.org.
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 6a69ca4f58cc7729c2d32a5b53040798016c71d3
Author: Daniel Sun <su...@apache.org>
AuthorDate: Tue May 5 02:33:46 2026 +0900

    GROOVY-11993: Support serializable method reference
---
 .../asm/sc/AbstractFunctionalInterfaceWriter.java  | 139 ++++++++++++++++++---
 .../classgen/asm/sc/StaticTypesLambdaWriter.java   |  49 ++------
 ...StaticTypesMethodReferenceExpressionWriter.java |  85 ++++++++++++-
 .../transform/stc/MethodReferenceTest.groovy       | 131 +++++++++++++++++++
 4 files changed, 352 insertions(+), 52 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..a729a21f9c 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,18 +22,37 @@ 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.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.classgen.asm.WriterController;
 import org.codehaus.groovy.syntax.RuntimeParserException;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
 import java.util.List;
+import java.util.Objects;
 
 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.ClassHelper.OBJECT_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.SERIALIZEDLAMBDA_TYPE;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.hasUnresolvedGenerics;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.andX;
+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.eqX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.nullX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
 import static org.codehaus.groovy.classgen.asm.BytecodeHelper.getClassInternalName;
 import static org.codehaus.groovy.classgen.asm.BytecodeHelper.getMethodDescriptor;
 
@@ -59,6 +78,23 @@ public interface AbstractFunctionalInterfaceWriter {
     }
 
     default Object[] createBootstrapMethodArguments(final String abstractMethodDesc, final int insn, final ClassNode methodOwner, final MethodNode methodNode, final Parameter[] parameters, final boolean serializable) {
+        Object[] arguments = !serializable ? new Object[3] : new Object[]{null, null, null, 5, 0};
+
+        arguments[0] = Type.getMethodType(abstractMethodDesc);
+
+        arguments[1] = new Handle(
+                insn, // H_INVOKESTATIC or H_INVOKEVIRTUAL or H_INVOKEINTERFACE (GROOVY-9853)
+                getClassInternalName(methodOwner.getName()),
+                methodNode.getName(),
+                getMethodDescriptor(methodNode),
+                methodOwner.isInterface());
+
+        arguments[2] = createInstantiatedMethodType(abstractMethodDesc, methodNode, parameters);
+
+        return arguments;
+    }
+
+    default Type createInstantiatedMethodType(final String abstractMethodDesc, final MethodNode methodNode, final Parameter[] parameters) {
         ClassNode returnType = methodNode.getReturnType();
         switch (Type.getReturnType(abstractMethodDesc).getSort()) {
           case Type.BOOLEAN:
@@ -89,20 +125,7 @@ public interface AbstractFunctionalInterfaceWriter {
             returnType = ClassHelper.VOID_TYPE; // GROOVY-10933
         }
 
-        Object[] arguments = !serializable ? new Object[3] : new Object[]{null, null, null, 5, 0};
-
-        arguments[0] = Type.getMethodType(abstractMethodDesc);
-
-        arguments[1] = new Handle(
-                insn, // H_INVOKESTATIC or H_INVOKEVIRTUAL or H_INVOKEINTERFACE (GROOVY-9853)
-                getClassInternalName(methodOwner.getName()),
-                methodNode.getName(),
-                getMethodDescriptor(methodNode),
-                methodOwner.isInterface());
-
-        arguments[2] = Type.getMethodType(getMethodDescriptor(returnType, parameters));
-
-        return arguments;
+        return Type.getMethodType(getMethodDescriptor(returnType, parameters));
     }
 
     default ClassNode convertParameterType(final ClassNode parameterType, final ClassNode inferredType) {
@@ -156,4 +179,92 @@ public interface AbstractFunctionalInterfaceWriter {
         parameterList.add(0, parameter);
         return parameter;
     }
+
+    default SerializedLambdaKey createSerializedLambdaKey(final String abstractMethodDesc, final int implMethodKind, final ClassNode implClass, final MethodNode implMethod, final Parameter[] parameters, final ClassNode functionalType, final MethodNode abstractMethod, final int capturedArgCount) {
+        return new SerializedLambdaKey(
+                implMethodKind,
+                getClassInternalName(implClass),
+                implMethod.getName(),
+                getMethodDescriptor(implMethod),
+                getClassInternalName(functionalType.redirect()),
+                abstractMethod.getName(),
+                abstractMethodDesc,
+                createInstantiatedMethodType(abstractMethodDesc, implMethod, parameters).getDescriptor(),
+                capturedArgCount
+        );
+    }
+
+    default void addDeserializeLambdaDispatcherEntry(final WriterController controller, final Parameter[] parameters, final SerializedLambdaKey key, final MethodNode helperMethod) {
+        BlockStatement dispatcher = getOrAddDeserializeLambdaDispatcher(controller, parameters);
+        MethodCallExpression helperCall = callX(classX(controller.getClassNode()), helperMethod.getName(), args(varX(parameters[0])));
+        helperCall.setImplicitThis(false);
+        helperCall.setMethodTarget(helperMethod);
+
+        List<Statement> statements = dispatcher.getStatements();
+        statements.add(statements.size() - 1, ifS(boolX(matchesSerializedLambda(varX(parameters[0]), key)), returnS(helperCall)));
+    }
+
+    default BlockStatement getOrAddDeserializeLambdaDispatcher(final WriterController controller, final Parameter[] parameters) {
+        ClassNode enclosingClass = controller.getClassNode();
+        BlockStatement dispatcher = enclosingClass.getNodeMetaData(DeserializeLambdaDispatcher.class);
+        if (dispatcher != null) {
+            return dispatcher;
+        }
+
+        dispatcher = new BlockStatement();
+        dispatcher.addStatement(returnS(callX(classX(ClassHelper.make(Objects.class)), "requireNonNull", args(nullX(), constX("Invalid lambda deserialization")))));
+
+        enclosingClass.addSyntheticMethod(
+                "$deserializeLambda$",
+                Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
+                OBJECT_TYPE,
+                parameters,
+                ClassNode.EMPTY_ARRAY,
+                dispatcher);
+        enclosingClass.putNodeMetaData(DeserializeLambdaDispatcher.class, dispatcher);
+        return dispatcher;
+    }
+
+    default Expression matchesSerializedLambda(final Expression serializedLambda, final SerializedLambdaKey key) {
+        return andX(
+                eqX(callX(serializedLambda, "getImplMethodKind"), constX(key.implMethodKind(), true)),
+                andX(
+                        eqX(callX(serializedLambda, "getImplClass"), constX(key.implClass())),
+                        andX(
+                                eqX(callX(serializedLambda, "getImplMethodName"), constX(key.implMethodName())),
+                                andX(
+                                        eqX(callX(serializedLambda, "getImplMethodSignature"), constX(key.implMethodSignature())),
+                                        andX(
+                                                eqX(callX(serializedLambda, "getFunctionalInterfaceClass"), constX(key.functionalInterfaceClass())),
+                                                andX(
+                                                        eqX(callX(serializedLambda, "getFunctionalInterfaceMethodName"), constX(key.functionalInterfaceMethodName())),
+                                                        andX(
+                                                                eqX(callX(serializedLambda, "getFunctionalInterfaceMethodSignature"), constX(key.functionalInterfaceMethodSignature())),
+                                                                andX(
+                                                                        eqX(callX(serializedLambda, "getInstantiatedMethodType"), constX(key.instantiatedMethodType())),
+                                                                        eqX(callX(serializedLambda, "getCapturedArgCount"), constX(key.capturedArgCount(), true))
+                                                                )
+                                                        )
+                                                )
+                                        )
+                                )
+                        )
+                )
+        );
+    }
+
+    default Parameter[] createDeserializeLambdaMethodParams() {
+        return new Parameter[]{new Parameter(SERIALIZEDLAMBDA_TYPE, "serializedLambda")};
+    }
+
+    final class DeserializeLambdaDispatcher {
+        private DeserializeLambdaDispatcher() {
+        }
+    }
+
+    record SerializedLambdaKey(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/StaticTypesLambdaWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
index b61dc6c930..0acad4ba00 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,7 +25,6 @@ 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;
@@ -50,13 +49,10 @@ 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 +103,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,8 +122,17 @@ public class StaticTypesLambdaWriter extends LambdaWriter implements AbstractFun
             return;
         }
 
-        addDeserializeLambdaMethodForLambdaExpression(expression, generatedLambda);
-        addDeserializeLambdaMethod();
+        MethodNode helperMethod = addDeserializeLambdaMethodForLambdaExpression(expression, generatedLambda);
+        addDeserializeLambdaDispatcherEntry(controller, createDeserializeLambdaMethodParams(), createSerializedLambdaKey(
+                createMethodDescriptor(ClassHelper.findSAM(expression.getNodeMetaData(PARAMETER_TYPE))),
+                generatedLambda.getMethodHandleKind(),
+                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) {
@@ -327,36 +328,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()) {
@@ -394,6 +370,7 @@ public class StaticTypesLambdaWriter extends LambdaWriter implements AbstractFun
             // The deserialize helper preloads the captured receiver before it reuses the original lambda expression.
             deserializeLambdaMethod.putNodeMetaData(MetaDataKey.PRELOADED_LAMBDA_RECEIVER, generatedLambda.lambdaClass);
         }
+        return deserializeLambdaMethod;
     }
 
     private static String createDeserializeLambdaMethodName(final ClassNode lambdaClass) {
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..03f2155ce5 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;
@@ -41,6 +43,7 @@ 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;
@@ -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.
@@ -192,17 +203,25 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
         String methodName = abstractMethod.getName();
         String methodDesc = 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,
+                    referenceKind, !isClassExpression, typeOrTargetRefType, methodDesc);
+        }
 
-        Handle bootstrapMethod = createBootstrapMethod(classNode.isInterface(), false);
+        Handle bootstrapMethod = createBootstrapMethod(classNode.isInterface(), serializable);
         Object[] bootstrapArgs = createBootstrapMethodArguments(
                 createMethodDescriptor(abstractMethod),
                 referenceKind,
                 methodRefMethod.getDeclaringClass(),
                 methodRefMethod,
                 parametersWithExactType,
-                false
+                serializable
         );
         controller.getMethodVisitor().visitInvokeDynamicInsn(methodName, methodDesc, bootstrapMethod, bootstrapArgs);
+        if (serializable) {
+            controller.getMethodVisitor().visitTypeInsn(CHECKCAST, "java/io/Serializable");
+        }
 
         if (isClassExpression) {
             controller.getOperandStack().push(functionalType);
@@ -454,8 +473,70 @@ 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 referenceKind, final boolean capturing, final ClassNode capturedTargetType, final String methodDesc) {
+        String helperName = methodReferenceExpression.getNodeMetaData(MetaDataKey.DESERIALIZE_LAMBDA_METHOD_NAME);
+        if (helperName == null) {
+            helperName = createDeserializeLambdaMethodName();
+            methodReferenceExpression.putNodeMetaData(MetaDataKey.DESERIALIZE_LAMBDA_METHOD_NAME, helperName);
+        }
+
+        Parameter[] parameters = createDeserializeLambdaMethodParams();
+        if (controller.getClassNode().hasMethod(helperName, parameters)) {
+            return;
+        }
+
+        String abstractMethodDesc = createMethodDescriptor(abstractMethod);
+        MethodNode helperMethod = addDeserializeLambdaMethodForMethodReference(helperName, abstractMethod, methodRefMethod,
+                parametersWithExactType, referenceKind, capturing, capturedTargetType, methodDesc, abstractMethodDesc);
+        addDeserializeLambdaDispatcherEntry(controller, parameters,
+                createSerializedLambdaKey(abstractMethodDesc, referenceKind, 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 referenceKind, final boolean capturing,
+                                                                    final ClassNode capturedTargetType, final String methodDesc,
+                                                                    final String abstractMethodDesc) {
+        Handle bootstrapMethod = createBootstrapMethod(controller.getClassNode().isInterface(), true);
+        Object[] bootstrapArgs = createBootstrapMethodArguments(abstractMethodDesc, referenceKind,
+                methodRefMethod.getDeclaringClass(), methodRefMethod, parametersWithExactType, true);
+
+        return controller.getClassNode().addSyntheticMethod(
+                methodName,
+                ACC_PRIVATE | ACC_STATIC,
+                ClassHelper.OBJECT_TYPE,
+                createDeserializeLambdaMethodParams(),
+                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));
+                        }
+                        mv.visitInvokeDynamicInsn(abstractMethod.getName(), methodDesc, bootstrapMethod, bootstrapArgs);
+                        mv.visitTypeInsn(CHECKCAST, "java/io/Serializable");
+                        mv.visitInsn(ARETURN);
+                    }
+                }));
+    }
+
+    private String createDeserializeLambdaMethodName() {
+        return "$deserializeLambda_methodref$" + controller.getNextHelperMethodIndex() + "$";
+    }
+
     //--------------------------------------------------------------------------
 
+    private enum MetaDataKey {
+        DESERIALIZE_LAMBDA_METHOD_NAME
+    }
+
     private static boolean isBridgeMethod(final MethodNode mn) {
         int staticSynthetic = Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC;
         return ((mn.getModifiers() & staticSynthetic) == staticSynthetic) && mn.getName().startsWith("access$");
diff --git a/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy b/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy
index 3c6b5e1b90..45e8f46709 100644
--- a/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy
@@ -1662,6 +1662,137 @@ 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
+
+            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'
+        '''
+    }
+
     // GROOVY-11467
     @Test
     void testSuperInterfaceMethodReference() {