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 <[email protected]>
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() {

Reply via email to