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 20:24:29 UTC

(groovy) branch GROOVY-11993 updated (f19f9d8763 -> f66004a2c7)

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


 discard f19f9d8763 GROOVY-11993: Support serializable method reference
     new f66004a2c7 GROOVY-11993: Support serializable method reference

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (f19f9d8763)
            \
             N -- N -- N   refs/heads/GROOVY-11993 (f66004a2c7)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

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.


Summary of changes:
 .../asm/sc/AbstractFunctionalInterfaceWriter.java  | 53 +++++++++++-----------
 1 file changed, 27 insertions(+), 26 deletions(-)


(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 f66004a2c78999cc30d99c021892d13c8dbe5160
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  | 272 +++++++++++++-----
 .../classgen/asm/sc/StaticTypesLambdaWriter.java   |  73 ++---
 ...StaticTypesMethodReferenceExpressionWriter.java | 103 ++++++-
 .../transform/stc/MethodReferenceTest.groovy       | 310 +++++++++++++++++++++
 4 files changed, 635 insertions(+), 123 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..bca80bd8ba 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,179 @@ 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.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
+import java.util.Arrays;
 import java.util.List;
 
+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.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 implementationKind,
+                                              final ClassNode implementationOwner, final MethodNode implementationMethod,
+                                              final Parameter[] implementationParameters, final boolean serializable) {
+        methodVisitor.visitInvokeDynamicInsn(
+                samMethodName,
+                invokedTypeDescriptor,
+                createBootstrapMethod(serializable),
+                createBootstrapMethodArguments(samMethodDescriptor, implementationKind, implementationOwner, implementationMethod, implementationParameters, 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 SerializedFunctionalInterface createSerializedFunctionalInterface(final String samMethodDescriptor, final int implementationKind,
+                                                                              final ClassNode implementationOwner, final MethodNode implementationMethod,
+                                                                              final Parameter[] implementationParameters, final ClassNode functionalType,
+                                                                              final MethodNode abstractMethod, final int capturedArgumentCount) {
+        return new SerializedFunctionalInterface(
+            implementationKind,
+            getClassInternalName(implementationOwner),
+            implementationMethod.getName(),
+            getMethodDescriptor(implementationMethod),
+            getClassInternalName(functionalType.redirect()),
+            abstractMethod.getName(),
+            samMethodDescriptor,
+            createInstantiatedMethodType(samMethodDescriptor, implementationMethod, implementationParameters).getDescriptor(),
+            capturedArgumentCount
+        );
+    }
+
+    default void addDeserializeDispatcherEntry(final WriterController controller, final Parameter[] deserializeMethodParameters,
+                                               final SerializedFunctionalInterface serializedFunctionalInterface,
+                                               final MethodNode helperMethod) {
+        BlockStatement dispatcher = getOrAddDeserializeDispatcher(controller, deserializeMethodParameters);
+        MethodCallExpression helperCall = callX(classX(controller.getClassNode()), helperMethod.getName(), args(varX(deserializeMethodParameters[0])));
+        helperCall.setImplicitThis(false);
+        helperCall.setMethodTarget(helperMethod);
+
+        List<Statement> statements = dispatcher.getStatements();
+        statements.add(statements.size() - 1, ifS(boolX(matchesSerializedFunctionalInterface(varX(deserializeMethodParameters[0]), serializedFunctionalInterface)), returnS(helperCall)));
+    }
+
+    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 implementationKind,
+                                                    final ClassNode implementationOwner, final MethodNode implementationMethod,
+                                                    final Parameter[] implementationParameters, 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(
+            implementationKind, // H_INVOKESTATIC or H_INVOKEVIRTUAL or H_INVOKEINTERFACE (GROOVY-9853)
+            getClassInternalName(implementationOwner.getName()),
+            implementationMethod.getName(),
+            getMethodDescriptor(implementationMethod),
+            implementationOwner.isInterface());
+
+        arguments[2] = createInstantiatedMethodType(samMethodDescriptor, implementationMethod, implementationParameters);
+
+        return arguments;
+    }
+
+    private Type createInstantiatedMethodType(final String samMethodDescriptor, final MethodNode implementationMethod, final Parameter[] implementationParameters) {
+        ClassNode returnType = implementationMethod.getReturnType();
+        switch (Type.getReturnType(samMethodDescriptor).getSort()) {
           case Type.BOOLEAN:
             if (returnType.isGenericsPlaceHolder()) returnType = ClassHelper.Boolean_TYPE; // GROOVY-10975
             break;
@@ -89,71 +223,79 @@ 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, implementationParameters));
+    }
 
-        arguments[0] = Type.getMethodType(abstractMethodDesc);
+    private BlockStatement getOrAddDeserializeDispatcher(final WriterController controller, final Parameter[] deserializeMethodParameters) {
+        ClassNode enclosingClass = controller.getClassNode();
+        BlockStatement dispatcher = enclosingClass.getNodeMetaData(DeserializeDispatcherMarker.class);
+        if (dispatcher != null) {
+            return dispatcher;
+        }
 
-        arguments[1] = new Handle(
-                insn, // H_INVOKESTATIC or H_INVOKEVIRTUAL or H_INVOKEINTERFACE (GROOVY-9853)
-                getClassInternalName(methodOwner.getName()),
-                methodNode.getName(),
-                getMethodDescriptor(methodNode),
-                methodOwner.isInterface());
+        dispatcher = new BlockStatement();
+        dispatcher.addStatement(createInvalidDeserializationStatement());
 
-        arguments[2] = Type.getMethodType(getMethodDescriptor(returnType, parameters));
+        enclosingClass.addSyntheticMethod(
+                "$deserializeLambda$",
+                Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
+                OBJECT_TYPE,
+                deserializeMethodParameters,
+                ClassNode.EMPTY_ARRAY,
+                dispatcher);
+        enclosingClass.putNodeMetaData(DeserializeDispatcherMarker.class, dispatcher);
+        return dispatcher;
+    }
 
-        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);
+    private Expression matchesSerializedFunctionalInterface(final Expression serializedForm, final SerializedFunctionalInterface serializedFunctionalInterface) {
+        return allMatch(
+            matchesSerializedFormInt(serializedForm, "getImplMethodKind", serializedFunctionalInterface.implMethodKind()),
+            matchesSerializedFormString(serializedForm, "getImplClass", serializedFunctionalInterface.implClass()),
+            matchesSerializedFormString(serializedForm, "getImplMethodName", serializedFunctionalInterface.implMethodName()),
+            matchesSerializedFormString(serializedForm, "getImplMethodSignature", serializedFunctionalInterface.implMethodSignature()),
+            matchesSerializedFormString(serializedForm, "getFunctionalInterfaceClass", serializedFunctionalInterface.functionalInterfaceClass()),
+            matchesSerializedFormString(serializedForm, "getFunctionalInterfaceMethodName", serializedFunctionalInterface.functionalInterfaceMethodName()),
+            matchesSerializedFormString(serializedForm, "getFunctionalInterfaceMethodSignature", serializedFunctionalInterface.functionalInterfaceMethodSignature()),
+            matchesSerializedFormString(serializedForm, "getInstantiatedMethodType", serializedFunctionalInterface.instantiatedMethodType()),
+            matchesSerializedFormInt(serializedForm, "getCapturedArgCount", serializedFunctionalInterface.capturedArgCount())
+        );
     }
 
-    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);
-        }
+    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(callX(serializedForm, accessorName), constX(expectedValue, true));
+    }
+
+    private Expression matchesSerializedFormString(final Expression serializedForm, final String accessorName, final String expectedValue) {
+        return eqX(callX(serializedForm, accessorName), constX(expectedValue));
+    }
+
+    default Parameter[] createDeserializeMethodParameters() {
+        return new Parameter[]{new Parameter(SERIALIZEDLAMBDA_TYPE, "serializedLambda")};
+    }
+
+    final class DeserializeDispatcherMarker {
+        private DeserializeDispatcherMarker() {
         }
-        return type;
     }
 
-    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 SerializedFunctionalInterface(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..68b7d9111a 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;
@@ -50,18 +47,14 @@ 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;
 import static org.objectweb.asm.Opcodes.ACC_FINAL;
-import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
 import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
 import static org.objectweb.asm.Opcodes.ACC_STATIC;
 import static org.objectweb.asm.Opcodes.ALOAD;
@@ -107,10 +100,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 +119,31 @@ public class StaticTypesLambdaWriter extends LambdaWriter implements AbstractFun
             return;
         }
 
-        addDeserializeLambdaMethodForLambdaExpression(expression, generatedLambda);
-        addDeserializeLambdaMethod();
+        MethodNode helperMethod = addDeserializeLambdaMethodForLambdaExpression(expression, generatedLambda);
+        addDeserializeDispatcherEntry(controller, createDeserializeMethodParameters(), createSerializedFunctionalInterface(
+                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) {
-        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.getMethodHandleKind(),
+            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 +153,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) {
@@ -327,36 +324,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()) {
@@ -387,13 +359,14 @@ public class StaticTypesLambdaWriter extends LambdaWriter implements AbstractFun
             createDeserializeLambdaMethodName(generatedLambda.lambdaClass),
             ACC_PUBLIC | 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);
         }
+        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..75cb7139fe 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;
@@ -60,6 +62,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 +71,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 +202,23 @@ 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);
-        Object[] bootstrapArgs = createBootstrapMethodArguments(
+        writeFunctionalInterfaceIndy(
+            controller.getMethodVisitor(),
+                methodName,
+                methodDesc,
                 createMethodDescriptor(abstractMethod),
                 referenceKind,
                 methodRefMethod.getDeclaringClass(),
                 methodRefMethod,
                 parametersWithExactType,
-                false
+                serializable
         );
-        controller.getMethodVisitor().visitInvokeDynamicInsn(methodName, methodDesc, bootstrapMethod, bootstrapArgs);
 
         if (isClassExpression) {
             controller.getOperandStack().push(functionalType);
@@ -255,7 +271,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 +313,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 +362,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,8 +470,79 @@ 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 = createDeserializeMethodParameters();
+        if (controller.getClassNode().hasMethod(helperName, parameters)) {
+            return;
+        }
+
+        String abstractMethodDesc = createMethodDescriptor(abstractMethod);
+        MethodNode helperMethod = addDeserializeLambdaMethodForMethodReference(helperName, abstractMethod, methodRefMethod,
+                parametersWithExactType, referenceKind, capturing, capturedTargetType, methodDesc, abstractMethodDesc);
+        addDeserializeDispatcherEntry(controller, parameters,
+                createSerializedFunctionalInterface(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) {
+        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(),
+                            methodDesc,
+                            abstractMethodDesc,
+                            referenceKind,
+                            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 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..7633f0d03b 100644
--- a/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy
@@ -1158,6 +1158,142 @@ 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
+
+            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 +1798,180 @@ 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'
+        '''
+    }
+
+    @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() {