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 e40ca37fd1bc49a38ba478df06a89839ebf4de77
Author: Daniel Sun <[email protected]>
AuthorDate: Tue May 5 02:33:46 2026 +0900

    GROOVY-11993: Support serializable method reference
---
 .../asm/sc/AbstractFunctionalInterfaceWriter.java  | 309 ++++++++++++----
 .../classgen/asm/sc/StaticTypesLambdaWriter.java   |  76 ++--
 ...StaticTypesMethodReferenceExpressionWriter.java | 119 +++++-
 .../transform/stc/MethodReferenceTest.groovy       | 411 +++++++++++++++++++++
 4 files changed, 782 insertions(+), 133 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..9a0ab4b248 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,185 @@ 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.List;
+import java.util.Arrays;
 
+import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.SERIALIZEDLAMBDA_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper;
 import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
 import static org.codehaus.groovy.ast.ClassHelper.isDynamicTyped;
 import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.eqX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.params;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
 import static 
org.codehaus.groovy.ast.tools.GenericsUtils.hasUnresolvedGenerics;
 import static 
org.codehaus.groovy.classgen.asm.BytecodeHelper.getClassInternalName;
 import static 
org.codehaus.groovy.classgen.asm.BytecodeHelper.getMethodDescriptor;
+import static 
org.codehaus.groovy.transform.stc.StaticTypesMarker.DIRECT_METHOD_CALL_TARGET;
+import static org.objectweb.asm.Opcodes.CHECKCAST;
 
 /**
- * Represents functional interface writer which contains some common methods 
to complete generating bytecode
+ * Shared bytecode and deserialization support for statically-compiled 
functional interface implementations,
+ * including both lambdas and method references.
+ *
  * @since 3.0.0
  */
 public interface AbstractFunctionalInterfaceWriter {
 
+    default void writeFunctionalInterfaceIndy(final MethodVisitor 
methodVisitor,
+                                              final String samMethodName, 
final String invokedTypeDescriptor,
+                                              final String 
samMethodDescriptor, final int implMethodKind,
+                                              final ClassNode implClassNode, 
final MethodNode implMethodNode,
+                                              final Parameter[] 
implMethodParameters, final boolean serializable) {
+        methodVisitor.visitInvokeDynamicInsn(
+                samMethodName,
+                invokedTypeDescriptor,
+                createBootstrapMethod(serializable),
+                createBootstrapMethodArguments(samMethodDescriptor, 
implMethodKind, implClassNode, implMethodNode, implMethodParameters, 
serializable)
+        );
+        if (serializable) {
+            methodVisitor.visitTypeInsn(CHECKCAST, 
BytecodeHelper.getClassInternalName(ClassHelper.SERIALIZABLE_TYPE));
+        }
+    }
+
     default String createMethodDescriptor(final MethodNode method) {
         return getMethodDescriptor(method.getReturnType(), 
method.getParameters());
     }
 
-    default Handle createBootstrapMethod(final boolean isInterface, final 
boolean serializable) {
+    default ClassNode convertParameterType(final ClassNode targetType, final 
ClassNode parameterType, final ClassNode inferredType) {
+        if 
(!getWrapper(inferredType).isDerivedFrom(getWrapper(parameterType))) {
+            throw new RuntimeParserException("The inferred type[" + 
inferredType.redirect() + "] is not compatible with the parameter type[" + 
parameterType.redirect() + "]", parameterType);
+        }
+
+        ClassNode type;
+        if (isPrimitiveType(parameterType)) {
+            if (!isPrimitiveType(inferredType)) {
+                // The non-primitive type and primitive type are not allowed 
to mix since Java 9+
+                // java.lang.invoke.LambdaConversionException: Type mismatch 
for instantiated parameter 0: class java.lang.Integer is not a subtype of int
+                type = getUnwrapper(inferredType).getPlainNodeReference(false);
+            } else {
+                type = inferredType.getPlainNodeReference(false);
+            }
+        } else if (isPrimitiveType(inferredType)) {
+            // GROOVY-9790: bootstrap method initialization exception raised 
when lambda parameter type is wrong
+            // (1) java.lang.invoke.LambdaConversionException: Type mismatch 
for instantiated parameter 0: class java.lang.Integer is not a subtype of int
+            // (2) java.lang.BootstrapMethodError: bootstrap method 
initialization exception
+            if (!(isDynamicTyped(parameterType) && 
isPrimitiveType(targetType)) // (1)
+                && (parameterType.equals(getUnwrapper(parameterType)) || 
inferredType.equals(getWrapper(inferredType)))) { // (2)
+                // The non-primitive type and primitive type are not allowed 
to mix since Java 9+
+                // java.lang.invoke.LambdaConversionException: Type mismatch 
for instantiated parameter 0: int is not a subtype of class java.lang.Object
+                type = getWrapper(inferredType).getPlainNodeReference();
+            } else {
+                type = inferredType.getPlainNodeReference(false);
+            }
+        } else {
+            type = inferredType;
+            // GROOVY-11304: no placeholders
+            if (hasUnresolvedGenerics(type)) type = type.redirect();
+            // GROOVY-11479: mutable for node metadata or type annotations
+            if (type.toString(false).equals(parameterType.toString(false))) {
+                type = parameterType;
+            } else {
+                // TODO: deep copy if type args set
+                type = type.getPlainNodeReference();
+            }
+        }
+        return type;
+    }
+
+    default SerializedFunctionalInterface 
createSerializedFunctionalInterface(final String samMethodDescriptor, final 
ClassNode capturingClass,
+                                                                              
final int implMethodKind, final ClassNode implClassNode,
+                                                                              
final MethodNode implMethodNode, final Parameter[] implMethodParameters,
+                                                                              
final ClassNode functionalType, final MethodNode abstractMethod,
+                                                                              
final int capturedArgumentCount) {
+        return new SerializedFunctionalInterface(
+            getClassInternalName(capturingClass),
+            implMethodKind,
+            getClassInternalName(implClassNode),
+            implMethodNode.getName(),
+            getMethodDescriptor(implMethodNode),
+            getClassInternalName(functionalType.redirect()),
+            abstractMethod.getName(),
+            samMethodDescriptor,
+            createInstantiatedMethodType(samMethodDescriptor, implMethodNode, 
implMethodParameters).getDescriptor(),
+            capturedArgumentCount
+        );
+    }
+
+    default void addDeserializeDispatcherEntry(final WriterController 
controller, final Parameter[] deserializeMethodParameters,
+                                               final 
SerializedFunctionalInterface serializedLambdaFingerprint,
+                                               final MethodNode helperMethod) {
+        BlockStatement dispatcherGuards = 
getOrAddDeserializeDispatcherGuards(controller, deserializeMethodParameters);
+        MethodCallExpression helperCall = 
callX(classX(controller.getClassNode()), helperMethod.getName(), 
args(varX(deserializeMethodParameters[0])));
+        helperCall.setImplicitThis(false);
+        helperCall.setMethodTarget(helperMethod);
+
+        dispatcherGuards.addStatement(
+            // Keep this guard strict: deserialization must route to exactly 
one synthetic helper
+            // whose serialized-lambda fingerprint fully matches the incoming 
SerializedLambda.
+            
ifS(boolX(matchesSerializedFunctionalInterface(varX(deserializeMethodParameters[0]),
 serializedLambdaFingerprint)),
+                returnS(helperCall)
+            )
+        );
+    }
+
+    private Handle createBootstrapMethod(final boolean serializable) {
         return new Handle(
-                Opcodes.H_INVOKESTATIC,
-                "java/lang/invoke/LambdaMetafactory",
-                serializable ? "altMetafactory" : "metafactory",
-                serializable ? 
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"
-                             : 
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
-                false // GROOVY-8299, GROOVY-8989, GROOVY-11265
+            Opcodes.H_INVOKESTATIC,
+            "java/lang/invoke/LambdaMetafactory",
+            serializable ? "altMetafactory" : "metafactory",
+            serializable ? 
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"
+                : 
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
+            false // GROOVY-8299, GROOVY-8989, GROOVY-11265
         );
     }
 
-    default Object[] createBootstrapMethodArguments(final String 
abstractMethodDesc, final int insn, final ClassNode methodOwner, final 
MethodNode methodNode, final Parameter[] parameters, final boolean 
serializable) {
-        ClassNode returnType = methodNode.getReturnType();
-        switch (Type.getReturnType(abstractMethodDesc).getSort()) {
+    private Object[] createBootstrapMethodArguments(final String 
samMethodDescriptor, final int implMethodKind,
+                                                    final ClassNode 
implClassNode, final MethodNode implMethodNode,
+                                                    final Parameter[] 
implMethodParameters, final boolean serializable) {
+        Object[] arguments = !serializable ? new Object[3] : new 
Object[]{null, null, null, 5, 0};
+
+        arguments[0] = Type.getMethodType(samMethodDescriptor);
+
+        arguments[1] = new Handle(
+            implMethodKind, // H_INVOKESTATIC or H_INVOKEVIRTUAL or 
H_INVOKEINTERFACE (GROOVY-9853)
+            getClassInternalName(implClassNode.getName()),
+            implMethodNode.getName(),
+            getMethodDescriptor(implMethodNode),
+            implClassNode.isInterface());
+
+        arguments[2] = createInstantiatedMethodType(samMethodDescriptor, 
implMethodNode, implMethodParameters);
+
+        return arguments;
+    }
+
+    private Type createInstantiatedMethodType(final String 
samMethodDescriptor, final MethodNode implMethodNode, final Parameter[] 
implMethodParameters) {
+        ClassNode returnType = implMethodNode.getReturnType();
+        switch (Type.getReturnType(samMethodDescriptor).getSort()) {
           case Type.BOOLEAN:
             if (returnType.isGenericsPlaceHolder()) returnType = 
ClassHelper.Boolean_TYPE; // GROOVY-10975
             break;
@@ -89,71 +229,106 @@ public interface AbstractFunctionalInterfaceWriter {
             returnType = ClassHelper.VOID_TYPE; // GROOVY-10933
         }
 
-        Object[] arguments = !serializable ? new Object[3] : new 
Object[]{null, null, null, 5, 0};
+        return Type.getMethodType(getMethodDescriptor(returnType, 
implMethodParameters));
+    }
 
-        arguments[0] = Type.getMethodType(abstractMethodDesc);
+    private BlockStatement getOrAddDeserializeDispatcherGuards(final 
WriterController controller, final Parameter[] deserializeMethodParameters) {
+        ClassNode enclosingClass = controller.getClassNode();
+        BlockStatement dispatcherGuards = 
enclosingClass.getNodeMetaData(MetaDataKey.DESERIALIZE_DISPATCHER);
+        if (dispatcherGuards != null) {
+            return dispatcherGuards;
+        }
 
-        arguments[1] = new Handle(
-                insn, // H_INVOKESTATIC or H_INVOKEVIRTUAL or 
H_INVOKEINTERFACE (GROOVY-9853)
-                getClassInternalName(methodOwner.getName()),
-                methodNode.getName(),
-                getMethodDescriptor(methodNode),
-                methodOwner.isInterface());
+        dispatcherGuards = new BlockStatement();
+        BlockStatement dispatcher = new BlockStatement();
+        dispatcher.addStatement(dispatcherGuards);
+        dispatcher.addStatement(createInvalidDeserializationStatement());
 
-        arguments[2] = Type.getMethodType(getMethodDescriptor(returnType, 
parameters));
+        enclosingClass.addSyntheticMethod(
+            "$deserializeLambda$",
+                Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
+                OBJECT_TYPE,
+                deserializeMethodParameters,
+                ClassNode.EMPTY_ARRAY,
+                dispatcher);
+        enclosingClass.putNodeMetaData(MetaDataKey.DESERIALIZE_DISPATCHER, 
dispatcherGuards);
+        return dispatcherGuards;
+    }
 
-        return arguments;
+    private Statement createInvalidDeserializationStatement() {
+        final ClassNode cn = ClassHelper.make(IllegalArgumentException.class);
+        ConstructorCallExpression exception = ctorX(cn, constX("Invalid 
serialized functional interface"));
+        exception.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, 
cn.getDeclaredConstructor(
+                params(param(ClassHelper.STRING_TYPE, "message"))
+        ));
+        return throwS(exception);
     }
 
-    default ClassNode convertParameterType(final ClassNode parameterType, 
final ClassNode inferredType) {
-        return convertParameterType(parameterType, parameterType, 
inferredType);
+    /**
+     * Builds the identity check used by {@code $deserializeLambda$} to select 
the
+     * correct synthetic helper for a serialized lambda/method reference.
+     * <p>
+     * The generated expression is a conjunction over all stable
+     * {@link java.lang.invoke.SerializedLambda} identity fields we emit via
+     * {@link #createSerializedFunctionalInterface(String, ClassNode, int, 
ClassNode, MethodNode, Parameter[], ClassNode, MethodNode, int)}:
+     * capturing class, implementation kind/class/name/signature, functional 
interface class/SAM method/signature,
+     * instantiated method type, and captured argument count.
+     * <p>
+     * Do not weaken this predicate (for example, by checking only method 
name/class or
+     * by returning a constant). Doing so can misroute deserialization to the 
wrong helper,
+     * while overly strict constant-false behavior breaks valid 
deserialization.
+     *
+     * @param serializedForm the deserialized {@code SerializedLambda} 
expression
+     * @param serializedLambdaFingerprint compile-time fingerprint of one 
serialized lambda target
+     * @return expression that evaluates to {@code true} only for this exact 
target
+     */
+    private Expression matchesSerializedFunctionalInterface(final Expression 
serializedForm, final SerializedFunctionalInterface 
serializedLambdaFingerprint) {
+        return allMatch(
+            matchesSerializedFormInt(serializedForm, "getCapturedArgCount", 
serializedLambdaFingerprint.capturedArgCount()),
+            matchesSerializedFormString(serializedForm, "getCapturingClass", 
serializedLambdaFingerprint.capturingClass()),
+            matchesSerializedFormInt(serializedForm, "getImplMethodKind", 
serializedLambdaFingerprint.implMethodKind()),
+            matchesSerializedFormString(serializedForm, "getImplClass", 
serializedLambdaFingerprint.implClass()),
+            matchesSerializedFormString(serializedForm, "getImplMethodName", 
serializedLambdaFingerprint.implMethodName()),
+            matchesSerializedFormString(serializedForm, 
"getImplMethodSignature", serializedLambdaFingerprint.implMethodSignature()),
+            matchesSerializedFormString(serializedForm, 
"getFunctionalInterfaceClass", 
serializedLambdaFingerprint.functionalInterfaceClass()),
+            matchesSerializedFormString(serializedForm, 
"getFunctionalInterfaceMethodName", 
serializedLambdaFingerprint.functionalInterfaceMethodName()),
+            matchesSerializedFormString(serializedForm, 
"getFunctionalInterfaceMethodSignature", 
serializedLambdaFingerprint.functionalInterfaceMethodSignature()),
+            matchesSerializedFormString(serializedForm, 
"getInstantiatedMethodType", 
serializedLambdaFingerprint.instantiatedMethodType())
+        );
     }
 
-    default ClassNode convertParameterType(final ClassNode targetType, final 
ClassNode parameterType, final ClassNode inferredType) {
-        if 
(!getWrapper(inferredType).isDerivedFrom(getWrapper(parameterType))) {
-            throw new RuntimeParserException("The inferred type[" + 
inferredType.redirect() + "] is not compatible with the parameter type[" + 
parameterType.redirect() + "]", parameterType);
-        }
+    /**
+     * Combines predicates with logical
+     *
+     * @param expressions match predicates to combine
+     * @return conjunction of all predicates
+     * @throws IllegalArgumentException if no predicates are supplied
+     */
+    private Expression allMatch(final Expression... expressions) {
+        return Arrays.stream(expressions)
+                .reduce(GeneralUtils::andX)
+                .orElseThrow(() -> new IllegalArgumentException("expressions 
must not be empty"));
+    }
 
-        ClassNode type;
-        if (isPrimitiveType(parameterType)) {
-            if (!isPrimitiveType(inferredType)) {
-                // The non-primitive type and primitive type are not allowed 
to mix since Java 9+
-                // java.lang.invoke.LambdaConversionException: Type mismatch 
for instantiated parameter 0: class java.lang.Integer is not a subtype of int
-                type = getUnwrapper(inferredType).getPlainNodeReference(false);
-            } else {
-                type = inferredType.getPlainNodeReference(false);
-            }
-        } else if (isPrimitiveType(inferredType)) {
-            // GROOVY-9790: bootstrap method initialization exception raised 
when lambda parameter type is wrong
-            // (1) java.lang.invoke.LambdaConversionException: Type mismatch 
for instantiated parameter 0: class java.lang.Integer is not a subtype of int
-            // (2) java.lang.BootstrapMethodError: bootstrap method 
initialization exception
-            if (!(isDynamicTyped(parameterType) && 
isPrimitiveType(targetType)) // (1)
-                    && (parameterType.equals(getUnwrapper(parameterType)) || 
inferredType.equals(getWrapper(inferredType)))) { // (2)
-                // The non-primitive type and primitive type are not allowed 
to mix since Java 9+
-                // java.lang.invoke.LambdaConversionException: Type mismatch 
for instantiated parameter 0: int is not a subtype of class java.lang.Object
-                type = getWrapper(inferredType).getPlainNodeReference();
-            } else {
-                type = inferredType.getPlainNodeReference(false);
-            }
-        } else {
-            type = inferredType;
-            // GROOVY-11304: no placeholders
-            if (hasUnresolvedGenerics(type)) type = type.redirect();
-            // GROOVY-11479: mutable for node metadata or type annotations
-            if (type.toString(false).equals(parameterType.toString(false))) {
-                type = parameterType;
-            } else {
-                // TODO: deep copy if type args set
-                type = type.getPlainNodeReference();
-            }
-        }
-        return type;
+    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") };
+    }
+
+    enum MetaDataKey {
+        DESERIALIZE_DISPATCHER
     }
 
-    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(String capturingClass, int 
implMethodKind, String implClass, String implMethodName,
+                                         String implMethodSignature, String 
functionalInterfaceClass,
+                                         String functionalInterfaceMethodName, 
String functionalInterfaceMethodSignature,
+                                         String instantiatedMethodType, int 
capturedArgCount) {
     }
 }
diff --git 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
index b61dc6c930..19dc5b166c 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,32 @@ 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))),
+                controller.getClassNode(),
+                generatedLambda.getImplMethodKind(),
+                generatedLambda.lambdaClass,
+                generatedLambda.lambdaMethod,
+                generatedLambda.lambdaMethod.getParameters(),
+                expression.getNodeMetaData(PARAMETER_TYPE),
+                
ClassHelper.findSAM(expression.getNodeMetaData(PARAMETER_TYPE)),
+                generatedLambda.isCapturing() ? 1 : 0
+        ), helperMethod);
     }
 
     private void writeLambdaFactoryInvocation(final ClassNode functionalType, 
final MethodNode abstractMethod, final GeneratedLambda generatedLambda, final 
boolean serializable) {
-        MethodVisitor mv = controller.getMethodVisitor();
-        mv.visitInvokeDynamicInsn(
+        writeFunctionalInterfaceIndy(
+            controller.getMethodVisitor(),
             abstractMethod.getName(),
             createLambdaFactoryMethodDescriptor(functionalType, 
generatedLambda),
-            createBootstrapMethod(controller.getClassNode().isInterface(), 
serializable),
-            
createBootstrapMethodArguments(createMethodDescriptor(abstractMethod),
-                generatedLambda.getMethodHandleKind(),
-                generatedLambda.lambdaClass, generatedLambda.lambdaMethod, 
generatedLambda.lambdaMethod.getParameters(), serializable)
+            createMethodDescriptor(abstractMethod),
+            generatedLambda.getImplMethodKind(),
+            generatedLambda.lambdaClass,
+            generatedLambda.lambdaMethod,
+            generatedLambda.lambdaMethod.getParameters(),
+            serializable
         );
-        if (serializable) {
-            mv.visitTypeInsn(CHECKCAST, "java/io/Serializable");
-        }
 
         if (generatedLambda.nonCapturing()) {
             controller.getOperandStack().push(functionalType);
@@ -156,7 +154,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 +325,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 +360,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) {
@@ -422,7 +396,7 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
             return !nonCapturing;
         }
 
-        private int getMethodHandleKind() {
+        private int getImplMethodKind() {
             return nonCapturing ? H_INVOKESTATIC : H_INVOKEVIRTUAL;
         }
     }
diff --git 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
index c526caa7b4..503513729e 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.
@@ -180,29 +190,35 @@ public class StaticTypesMethodReferenceExpressionWriter 
extends MethodReferenceE
             }
         }
 
-        int referenceKind;
+        int implMethodKind;
         if (isConstructorReference || methodRefMethod.isStatic()) {
-            referenceKind = Opcodes.H_INVOKESTATIC;
+            implMethodKind = Opcodes.H_INVOKESTATIC;
         } else if (methodRefMethod.getDeclaringClass().isInterface()) {
-            referenceKind = Opcodes.H_INVOKEINTERFACE; // GROOVY-9853
+            implMethodKind = Opcodes.H_INVOKEINTERFACE; // GROOVY-9853
         } else {
-            referenceKind = Opcodes.H_INVOKEVIRTUAL;
+            implMethodKind = Opcodes.H_INVOKEVIRTUAL;
         }
 
-        String methodName = abstractMethod.getName();
-        String methodDesc = 
BytecodeHelper.getMethodDescriptor(functionalType.redirect(),
+        String samMethodName = abstractMethod.getName();
+        String invokedTypeDescriptor = 
BytecodeHelper.getMethodDescriptor(functionalType.redirect(),
                 isClassExpression ? Parameter.EMPTY_ARRAY : new 
Parameter[]{new Parameter(typeOrTargetRefType, "__METHODREF_EXPR_INSTANCE")});
+        boolean serializable = 
functionalType.implementsInterface(ClassHelper.SERIALIZABLE_TYPE);
+        if (serializable) {
+            ensureDeserializeLambdaSupport(methodReferenceExpression, 
functionalType, abstractMethod, methodRefMethod, parametersWithExactType,
+                    implMethodKind, !isClassExpression, typeOrTargetRefType, 
invokedTypeDescriptor);
+        }
 
-        Handle bootstrapMethod = 
createBootstrapMethod(classNode.isInterface(), false);
-        Object[] bootstrapArgs = createBootstrapMethodArguments(
+        writeFunctionalInterfaceIndy(
+            controller.getMethodVisitor(),
+                samMethodName,
+                invokedTypeDescriptor,
                 createMethodDescriptor(abstractMethod),
-                referenceKind,
+                implMethodKind,
                 methodRefMethod.getDeclaringClass(),
                 methodRefMethod,
                 parametersWithExactType,
-                false
+                serializable
         );
-        controller.getMethodVisitor().visitInvokeDynamicInsn(methodName, 
methodDesc, bootstrapMethod, bootstrapArgs);
 
         if (isClassExpression) {
             controller.getOperandStack().push(functionalType);
@@ -255,7 +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,81 @@ public class StaticTypesMethodReferenceExpressionWriter 
extends MethodReferenceE
         throw new 
MultipleCompilationErrorsException(controller.getSourceUnit().getErrorCollector());
     }
 
+    private void ensureDeserializeLambdaSupport(final 
MethodReferenceExpression methodReferenceExpression, final ClassNode 
functionalType,
+                                                 final MethodNode 
abstractMethod, final MethodNode methodRefMethod, final Parameter[] 
parametersWithExactType,
+                                                 final int implMethodKind, 
final boolean capturing,
+                                                 final ClassNode 
capturedTargetType, final String invokedTypeDescriptor) {
+        String helperName = 
methodReferenceExpression.getNodeMetaData(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 samMethodDescriptor = createMethodDescriptor(abstractMethod);
+        MethodNode helperMethod = 
addDeserializeLambdaMethodForMethodReference(helperName, abstractMethod, 
methodRefMethod,
+            parametersWithExactType, implMethodKind, capturing, 
capturedTargetType, invokedTypeDescriptor, samMethodDescriptor);
+        addDeserializeDispatcherEntry(controller, parameters,
+            createSerializedFunctionalInterface(samMethodDescriptor, 
controller.getClassNode(), implMethodKind,
+                methodRefMethod.getDeclaringClass(), methodRefMethod,
+                parametersWithExactType, functionalType, abstractMethod, 
capturing ? 1 : 0),
+            helperMethod);
+    }
+
+    private MethodNode addDeserializeLambdaMethodForMethodReference(final 
String methodName, final MethodNode abstractMethod,
+                                                                    final 
MethodNode methodRefMethod, final Parameter[] parametersWithExactType,
+                                                                    final int 
implMethodKind, final boolean capturing,
+                                                                    final 
ClassNode capturedTargetType, final String invokedTypeDescriptor,
+                                                                    final 
String samMethodDescriptor) {
+        return controller.getClassNode().addSyntheticMethod(
+                methodName,
+                ACC_PRIVATE | ACC_STATIC,
+                ClassHelper.OBJECT_TYPE,
+                createDeserializeMethodParameters(),
+                ClassNode.EMPTY_ARRAY,
+                new BytecodeSequence(new BytecodeInstruction() {
+                    @Override
+                    public void visit(final MethodVisitor mv) {
+                        if (capturing) {
+                            mv.visitVarInsn(ALOAD, 0);
+                            mv.visitInsn(ICONST_0);
+                            mv.visitMethodInsn(INVOKEVIRTUAL, 
"java/lang/invoke/SerializedLambda", "getCapturedArg", "(I)Ljava/lang/Object;", 
false);
+                            mv.visitTypeInsn(CHECKCAST, 
getClassInternalName(capturedTargetType));
+                        }
+                        writeFunctionalInterfaceIndy(
+                            mv,
+                            abstractMethod.getName(),
+                            invokedTypeDescriptor,
+                            samMethodDescriptor,
+                            implMethodKind,
+                            methodRefMethod.getDeclaringClass(),
+                            methodRefMethod,
+                            parametersWithExactType,
+                            true
+                        );
+                        mv.visitInsn(ARETURN);
+                    }
+                }));
+    }
+
+    private String createSyntheticMethodName(final String prefix, final 
ClassNode owner, final String name) {
+        return prefix + "$" + owner.getNameWithoutPackage() + "$" + name + "$" 
+ controller.getNextHelperMethodIndex();
+    }
+
+    private String createDeserializeLambdaMethodName() {
+        return "$deserializeLambda_methodref$" + 
controller.getNextHelperMethodIndex() + "$";
+    }
+
     
//--------------------------------------------------------------------------
 
+    private 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..76a78dcdba 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,281 @@ 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 
testDeserializeDispatcherReturnsMatchingMethodReferenceAndLambdaBeforeFallback()
 {
+        assertScript shell, '''
+            import java.io.Serializable
+            import java.lang.invoke.SerializedLambda
+
+            @CompileStatic
+            class C {
+                interface SerFunc<I, O> extends Serializable, Function<I, O> {}
+
+                static SerFunc<Integer, String> createMethodReference() {
+                    Integer::toString
+                }
+
+                static SerFunc<Integer, String> createLambda() {
+                    (Integer i) -> 'L' + i
+                }
+
+                @CompileDynamic
+                static SerializedLambda serialized(Serializable value) {
+                    def writeReplace = 
value.class.getDeclaredMethod('writeReplace')
+                    writeReplace.accessible = true
+                    (SerializedLambda) writeReplace.invoke(value)
+                }
+            }
+
+            def dispatcher = C.getDeclaredMethod('$deserializeLambda$', 
SerializedLambda)
+            dispatcher.accessible = true
+
+            C.SerFunc<Integer, String> methodRef =
+                (C.SerFunc<Integer, String>) dispatcher.invoke(null, 
C.serialized(C.createMethodReference()))
+            C.SerFunc<Integer, String> lambda =
+                (C.SerFunc<Integer, String>) dispatcher.invoke(null, 
C.serialized(C.createLambda()))
+
+            assert methodRef.apply(3) == '3'
+            assert lambda.apply(3) == 'L3'
+        '''
+    }
+
+    @Test
+    void 
testDeserializeDispatcherRejectsWrongCapturingClassEvenWhenOtherSerializedFieldsMatch()
 {
+        assertScript shell, '''
+            import java.io.Serializable
+            import java.lang.invoke.SerializedLambda
+
+            @CompileStatic
+            class C {
+                interface SerFunc<I, O> extends Serializable, Function<I, O> {}
+
+                static SerFunc<Integer, String> createMethodReference() {
+                    Integer::toString
+                }
+
+                static SerFunc<Integer, String> createLambda() {
+                    (Integer i) -> 'L' + i
+                }
+
+                @CompileDynamic
+                static SerializedLambda serialized(Serializable value) {
+                    def writeReplace = 
value.class.getDeclaredMethod('writeReplace')
+                    writeReplace.accessible = true
+                    (SerializedLambda) writeReplace.invoke(value)
+                }
+
+                @CompileDynamic
+                static SerializedLambda withCapturingClass(SerializedLambda 
serialized, Class capturingClass) {
+                    new SerializedLambda(
+                        capturingClass,
+                        serialized.functionalInterfaceClass,
+                        serialized.functionalInterfaceMethodName,
+                        serialized.functionalInterfaceMethodSignature,
+                        serialized.implMethodKind,
+                        serialized.implClass,
+                        serialized.implMethodName,
+                        serialized.implMethodSignature,
+                        serialized.instantiatedMethodType,
+                        (0..<serialized.capturedArgCount).collect { 
serialized.getCapturedArg(it) } as Object[]
+                    )
+                }
+            }
+
+            def dispatcher = C.getDeclaredMethod('$deserializeLambda$', 
SerializedLambda)
+            dispatcher.accessible = true
+
+            def assertInvalid = { SerializedLambda serialized ->
+                def err
+                try {
+                    dispatcher.invoke(null, serialized)
+                    assert false: 'dispatcher invocation should fail'
+                } catch (java.lang.reflect.InvocationTargetException e) {
+                    err = e
+                }
+                assert err.cause instanceof IllegalArgumentException
+                assert err.cause.message == 'Invalid serialized functional 
interface'
+            }
+
+            
assertInvalid(C.withCapturingClass(C.serialized(C.createMethodReference()), 
String))
+            assertInvalid(C.withCapturingClass(C.serialized(C.createLambda()), 
Integer))
+        '''
+    }
+
+    @Test
+    void 
testDeserializeDispatcherReportsClearErrorForMismatchedSerializedForm() {
+        assertScript shell, '''
+            import java.lang.invoke.MethodHandleInfo
+            import java.lang.invoke.SerializedLambda
+
+            @CompileStatic
+            class C {
+                interface SerFunc<I, O> extends Serializable, Function<I, O> {}
+
+                static SerFunc<Integer, String> create() {
+                    Integer::toString
+                }
+            }
+
+            def dispatcher = C.getDeclaredMethod('$deserializeLambda$', 
SerializedLambda)
+            dispatcher.accessible = true
+
+            def serialized = new SerializedLambda(
+                C,
+                'java/util/function/Function',
+                'apply',
+                '(Ljava/lang/Object;)Ljava/lang/Object;',
+                MethodHandleInfo.REF_invokeStatic,
+                'java/lang/Integer',
+                'toString',
+                '(I)Ljava/lang/String;',
+                '(Ljava/lang/Integer;)Ljava/lang/String;',
+                [] as Object[]
+            )
+
+            def err
+            try {
+                dispatcher.invoke(null, serialized)
+                assert false: 'dispatcher invocation should fail'
+            } catch (java.lang.reflect.InvocationTargetException e) {
+                err = e
+            }
+            assert err.cause instanceof IllegalArgumentException
+            assert err.cause.message == 'Invalid serialized functional 
interface'
+        '''
+    }
+
     // GROOVY-11467
     @Test
     void testSuperInterfaceMethodReference() {

Reply via email to