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

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

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

    GROOVY-11993: Support serializable method reference
---
 .../asm/sc/AbstractFunctionalInterfaceWriter.java  | 139 ++++++++++++++++++---
 .../classgen/asm/sc/StaticTypesLambdaWriter.java   |  49 ++------
 ...StaticTypesMethodReferenceExpressionWriter.java |  85 ++++++++++++-
 .../transform/stc/MethodReferenceTest.groovy       | 131 +++++++++++++++++++
 4 files changed, 352 insertions(+), 52 deletions(-)

diff --git 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java
 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java
index ceb280c43b..a729a21f9c 100644
--- 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java
+++ 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java
@@ -22,18 +22,37 @@ import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.classgen.asm.WriterController;
 import org.codehaus.groovy.syntax.RuntimeParserException;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
 import java.util.List;
+import java.util.Objects;
 
 import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper;
 import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
 import static org.codehaus.groovy.ast.ClassHelper.isDynamicTyped;
 import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
+import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.SERIALIZEDLAMBDA_TYPE;
 import static 
org.codehaus.groovy.ast.tools.GenericsUtils.hasUnresolvedGenerics;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.andX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.boolX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.eqX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ifS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.nullX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
 import static 
org.codehaus.groovy.classgen.asm.BytecodeHelper.getClassInternalName;
 import static 
org.codehaus.groovy.classgen.asm.BytecodeHelper.getMethodDescriptor;
 
@@ -59,6 +78,23 @@ public interface AbstractFunctionalInterfaceWriter {
     }
 
     default Object[] createBootstrapMethodArguments(final String 
abstractMethodDesc, final int insn, final ClassNode methodOwner, final 
MethodNode methodNode, final Parameter[] parameters, final boolean 
serializable) {
+        Object[] arguments = !serializable ? new Object[3] : new 
Object[]{null, null, null, 5, 0};
+
+        arguments[0] = Type.getMethodType(abstractMethodDesc);
+
+        arguments[1] = new Handle(
+                insn, // H_INVOKESTATIC or H_INVOKEVIRTUAL or 
H_INVOKEINTERFACE (GROOVY-9853)
+                getClassInternalName(methodOwner.getName()),
+                methodNode.getName(),
+                getMethodDescriptor(methodNode),
+                methodOwner.isInterface());
+
+        arguments[2] = createInstantiatedMethodType(abstractMethodDesc, 
methodNode, parameters);
+
+        return arguments;
+    }
+
+    default Type createInstantiatedMethodType(final String abstractMethodDesc, 
final MethodNode methodNode, final Parameter[] parameters) {
         ClassNode returnType = methodNode.getReturnType();
         switch (Type.getReturnType(abstractMethodDesc).getSort()) {
           case Type.BOOLEAN:
@@ -89,20 +125,7 @@ public interface AbstractFunctionalInterfaceWriter {
             returnType = ClassHelper.VOID_TYPE; // GROOVY-10933
         }
 
-        Object[] arguments = !serializable ? new Object[3] : new 
Object[]{null, null, null, 5, 0};
-
-        arguments[0] = Type.getMethodType(abstractMethodDesc);
-
-        arguments[1] = new Handle(
-                insn, // H_INVOKESTATIC or H_INVOKEVIRTUAL or 
H_INVOKEINTERFACE (GROOVY-9853)
-                getClassInternalName(methodOwner.getName()),
-                methodNode.getName(),
-                getMethodDescriptor(methodNode),
-                methodOwner.isInterface());
-
-        arguments[2] = Type.getMethodType(getMethodDescriptor(returnType, 
parameters));
-
-        return arguments;
+        return Type.getMethodType(getMethodDescriptor(returnType, parameters));
     }
 
     default ClassNode convertParameterType(final ClassNode parameterType, 
final ClassNode inferredType) {
@@ -156,4 +179,92 @@ public interface AbstractFunctionalInterfaceWriter {
         parameterList.add(0, parameter);
         return parameter;
     }
+
+    default SerializedLambdaKey createSerializedLambdaKey(final String 
abstractMethodDesc, final int implMethodKind, final ClassNode implClass, final 
MethodNode implMethod, final Parameter[] parameters, final ClassNode 
functionalType, final MethodNode abstractMethod, final int capturedArgCount) {
+        return new SerializedLambdaKey(
+                implMethodKind,
+                getClassInternalName(implClass),
+                implMethod.getName(),
+                getMethodDescriptor(implMethod),
+                getClassInternalName(functionalType.redirect()),
+                abstractMethod.getName(),
+                abstractMethodDesc,
+                createInstantiatedMethodType(abstractMethodDesc, implMethod, 
parameters).getDescriptor(),
+                capturedArgCount
+        );
+    }
+
+    default void addDeserializeLambdaDispatcherEntry(final WriterController 
controller, final Parameter[] parameters, final SerializedLambdaKey key, final 
MethodNode helperMethod) {
+        BlockStatement dispatcher = 
getOrAddDeserializeLambdaDispatcher(controller, parameters);
+        MethodCallExpression helperCall = 
callX(classX(controller.getClassNode()), helperMethod.getName(), 
args(varX(parameters[0])));
+        helperCall.setImplicitThis(false);
+        helperCall.setMethodTarget(helperMethod);
+
+        List<Statement> statements = dispatcher.getStatements();
+        statements.add(statements.size() - 1, 
ifS(boolX(matchesSerializedLambda(varX(parameters[0]), key)), 
returnS(helperCall)));
+    }
+
+    default BlockStatement getOrAddDeserializeLambdaDispatcher(final 
WriterController controller, final Parameter[] parameters) {
+        ClassNode enclosingClass = controller.getClassNode();
+        BlockStatement dispatcher = 
enclosingClass.getNodeMetaData(DeserializeLambdaDispatcher.class);
+        if (dispatcher != null) {
+            return dispatcher;
+        }
+
+        dispatcher = new BlockStatement();
+        
dispatcher.addStatement(returnS(callX(classX(ClassHelper.make(Objects.class)), 
"requireNonNull", args(nullX(), constX("Invalid lambda deserialization")))));
+
+        enclosingClass.addSyntheticMethod(
+                "$deserializeLambda$",
+                Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
+                OBJECT_TYPE,
+                parameters,
+                ClassNode.EMPTY_ARRAY,
+                dispatcher);
+        enclosingClass.putNodeMetaData(DeserializeLambdaDispatcher.class, 
dispatcher);
+        return dispatcher;
+    }
+
+    default Expression matchesSerializedLambda(final Expression 
serializedLambda, final SerializedLambdaKey key) {
+        return andX(
+                eqX(callX(serializedLambda, "getImplMethodKind"), 
constX(key.implMethodKind(), true)),
+                andX(
+                        eqX(callX(serializedLambda, "getImplClass"), 
constX(key.implClass())),
+                        andX(
+                                eqX(callX(serializedLambda, 
"getImplMethodName"), constX(key.implMethodName())),
+                                andX(
+                                        eqX(callX(serializedLambda, 
"getImplMethodSignature"), constX(key.implMethodSignature())),
+                                        andX(
+                                                eqX(callX(serializedLambda, 
"getFunctionalInterfaceClass"), constX(key.functionalInterfaceClass())),
+                                                andX(
+                                                        
eqX(callX(serializedLambda, "getFunctionalInterfaceMethodName"), 
constX(key.functionalInterfaceMethodName())),
+                                                        andX(
+                                                                
eqX(callX(serializedLambda, "getFunctionalInterfaceMethodSignature"), 
constX(key.functionalInterfaceMethodSignature())),
+                                                                andX(
+                                                                        
eqX(callX(serializedLambda, "getInstantiatedMethodType"), 
constX(key.instantiatedMethodType())),
+                                                                        
eqX(callX(serializedLambda, "getCapturedArgCount"), 
constX(key.capturedArgCount(), true))
+                                                                )
+                                                        )
+                                                )
+                                        )
+                                )
+                        )
+                )
+        );
+    }
+
+    default Parameter[] createDeserializeLambdaMethodParams() {
+        return new Parameter[]{new Parameter(SERIALIZEDLAMBDA_TYPE, 
"serializedLambda")};
+    }
+
+    final class DeserializeLambdaDispatcher {
+        private DeserializeLambdaDispatcher() {
+        }
+    }
+
+    record SerializedLambdaKey(int implMethodKind, String implClass, String 
implMethodName, String implMethodSignature,
+                               String functionalInterfaceClass, String 
functionalInterfaceMethodName,
+                               String functionalInterfaceMethodSignature, 
String instantiatedMethodType,
+                               int capturedArgCount) {
+    }
 }
diff --git 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
index b61dc6c930..0acad4ba00 100644
--- 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
+++ 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
@@ -25,7 +25,6 @@ import org.codehaus.groovy.ast.ConstructorNode;
 import org.codehaus.groovy.ast.InnerClassNode;
 import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.ast.Parameter;
-import org.codehaus.groovy.ast.builder.AstStringCompiler;
 import org.codehaus.groovy.ast.expr.ClosureExpression;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.LambdaExpression;
@@ -50,13 +49,10 @@ import static 
org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.GENERATED_LAMBDA_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.SERIALIZABLE_TYPE;
-import static org.codehaus.groovy.ast.ClassHelper.SERIALIZEDLAMBDA_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE;
 import static org.codehaus.groovy.ast.tools.ClosureUtils.getParametersSafe;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
 import static 
org.codehaus.groovy.transform.stc.StaticTypesMarker.CLOSURE_ARGUMENTS;
 import static 
org.codehaus.groovy.transform.stc.StaticTypesMarker.PARAMETER_TYPE;
@@ -107,10 +103,6 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
         writeLambdaFactoryInvocation(functionalType.redirect(), 
abstractMethod, generatedLambda, serializable);
     }
 
-    private static Parameter[] createDeserializeLambdaMethodParams() {
-        return new Parameter[]{new Parameter(SERIALIZEDLAMBDA_TYPE, 
"serializedLambda")};
-    }
-
     private static MethodNode resolveFunctionalInterfaceMethod(final ClassNode 
functionalType) {
         if (functionalType == null || !functionalType.isInterface()) {
             return null;
@@ -130,8 +122,17 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
             return;
         }
 
-        addDeserializeLambdaMethodForLambdaExpression(expression, 
generatedLambda);
-        addDeserializeLambdaMethod();
+        MethodNode helperMethod = 
addDeserializeLambdaMethodForLambdaExpression(expression, generatedLambda);
+        addDeserializeLambdaDispatcherEntry(controller, 
createDeserializeLambdaMethodParams(), createSerializedLambdaKey(
+                
createMethodDescriptor(ClassHelper.findSAM(expression.getNodeMetaData(PARAMETER_TYPE))),
+                generatedLambda.getMethodHandleKind(),
+                generatedLambda.lambdaClass,
+                generatedLambda.lambdaMethod,
+                generatedLambda.lambdaMethod.getParameters(),
+                expression.getNodeMetaData(PARAMETER_TYPE),
+                
ClassHelper.findSAM(expression.getNodeMetaData(PARAMETER_TYPE)),
+                generatedLambda.isCapturing() ? 1 : 0
+        ), helperMethod);
     }
 
     private void writeLambdaFactoryInvocation(final ClassNode functionalType, 
final MethodNode abstractMethod, final GeneratedLambda generatedLambda, final 
boolean serializable) {
@@ -327,36 +328,11 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
         return lambdaParameters;
     }
 
-    private void addDeserializeLambdaMethod() {
-        ClassNode enclosingClass = controller.getClassNode();
-        Parameter[] parameters = createDeserializeLambdaMethodParams();
-        if (enclosingClass.hasMethod("$deserializeLambda$", parameters)) {
-            return;
-        }
-
-        Statement code = block(
-            declS(localVarX("enclosingClass", OBJECT_TYPE), 
classX(enclosingClass)),
-            ((BlockStatement) new AstStringCompiler().compile(
-                "return enclosingClass" +
-                    
".getDeclaredMethod(\"\\$deserializeLambda_${serializedLambda.getImplClass().replace('/',
 '$')}\\$\", serializedLambda.getClass())" +
-                    ".invoke(null, serializedLambda)"
-            ).get(0)).getStatements().get(0)
-        );
-
-        enclosingClass.addSyntheticMethod(
-            "$deserializeLambda$",
-            ACC_PRIVATE | ACC_STATIC,
-            OBJECT_TYPE,
-            parameters,
-            ClassNode.EMPTY_ARRAY,
-            code);
-    }
-
     private static boolean requiresLambdaInstance(final MethodNode 
lambdaMethod) {
         return 0 == (lambdaMethod.getModifiers() & ACC_STATIC);
     }
 
-    private void addDeserializeLambdaMethodForLambdaExpression(final 
LambdaExpression expression, final GeneratedLambda generatedLambda) {
+    private MethodNode addDeserializeLambdaMethodForLambdaExpression(final 
LambdaExpression expression, final GeneratedLambda generatedLambda) {
         ClassNode enclosingClass = controller.getClassNode();
         Statement code;
         if (generatedLambda.nonCapturing()) {
@@ -394,6 +370,7 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
             // The deserialize helper preloads the captured receiver before it 
reuses the original lambda expression.
             
deserializeLambdaMethod.putNodeMetaData(MetaDataKey.PRELOADED_LAMBDA_RECEIVER, 
generatedLambda.lambdaClass);
         }
+        return deserializeLambdaMethod;
     }
 
     private static String createDeserializeLambdaMethodName(final ClassNode 
lambdaClass) {
diff --git 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
index c526caa7b4..03f2155ce5 100644
--- 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
+++ 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java
@@ -31,6 +31,8 @@ import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.MethodCallExpression;
 import org.codehaus.groovy.ast.expr.MethodReferenceExpression;
 import org.codehaus.groovy.ast.tools.GeneralUtils;
+import org.codehaus.groovy.classgen.BytecodeInstruction;
+import org.codehaus.groovy.classgen.BytecodeSequence;
 import org.codehaus.groovy.classgen.AsmClassGenerator;
 import org.codehaus.groovy.classgen.asm.BytecodeHelper;
 import org.codehaus.groovy.classgen.asm.MethodReferenceExpressionWriter;
@@ -41,6 +43,7 @@ import 
org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys;
 import org.codehaus.groovy.transform.stc.ExtensionMethodNode;
 import org.codehaus.groovy.transform.stc.StaticTypesMarker;
 import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
 import java.util.Arrays;
@@ -60,6 +63,7 @@ import static 
org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.extractPlaceholders;
 import static org.codehaus.groovy.ast.tools.GenericsUtils.makeClassSafe0;
+import static 
org.codehaus.groovy.classgen.asm.BytecodeHelper.getClassInternalName;
 import static org.codehaus.groovy.ast.tools.ParameterUtils.isVargs;
 import static 
org.codehaus.groovy.ast.tools.ParameterUtils.parametersCompatible;
 import static org.codehaus.groovy.runtime.ArrayGroovyMethods.last;
@@ -68,6 +72,13 @@ import static 
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.filter
 import static 
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsForClassNode;
 import static 
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isAssignableTo;
 import static 
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.resolveClassNodeGenerics;
+import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.CHECKCAST;
+import static org.objectweb.asm.Opcodes.ICONST_0;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 
 /**
  * Generates bytecode for method reference expressions in statically-compiled 
code.
@@ -192,17 +203,25 @@ public class StaticTypesMethodReferenceExpressionWriter 
extends MethodReferenceE
         String methodName = abstractMethod.getName();
         String methodDesc = 
BytecodeHelper.getMethodDescriptor(functionalType.redirect(),
                 isClassExpression ? Parameter.EMPTY_ARRAY : new 
Parameter[]{new Parameter(typeOrTargetRefType, "__METHODREF_EXPR_INSTANCE")});
+        boolean serializable = 
functionalType.implementsInterface(ClassHelper.SERIALIZABLE_TYPE);
+        if (serializable) {
+            ensureDeserializeLambdaSupport(methodReferenceExpression, 
functionalType, abstractMethod, methodRefMethod, parametersWithExactType,
+                    referenceKind, !isClassExpression, typeOrTargetRefType, 
methodDesc);
+        }
 
-        Handle bootstrapMethod = 
createBootstrapMethod(classNode.isInterface(), false);
+        Handle bootstrapMethod = 
createBootstrapMethod(classNode.isInterface(), serializable);
         Object[] bootstrapArgs = createBootstrapMethodArguments(
                 createMethodDescriptor(abstractMethod),
                 referenceKind,
                 methodRefMethod.getDeclaringClass(),
                 methodRefMethod,
                 parametersWithExactType,
-                false
+                serializable
         );
         controller.getMethodVisitor().visitInvokeDynamicInsn(methodName, 
methodDesc, bootstrapMethod, bootstrapArgs);
+        if (serializable) {
+            controller.getMethodVisitor().visitTypeInsn(CHECKCAST, 
"java/io/Serializable");
+        }
 
         if (isClassExpression) {
             controller.getOperandStack().push(functionalType);
@@ -454,8 +473,70 @@ public class StaticTypesMethodReferenceExpressionWriter 
extends MethodReferenceE
         throw new 
MultipleCompilationErrorsException(controller.getSourceUnit().getErrorCollector());
     }
 
+    private void ensureDeserializeLambdaSupport(final 
MethodReferenceExpression methodReferenceExpression, final ClassNode 
functionalType,
+                                                final MethodNode 
abstractMethod, final MethodNode methodRefMethod, final Parameter[] 
parametersWithExactType,
+                                                final int referenceKind, final 
boolean capturing, final ClassNode capturedTargetType, final String methodDesc) 
{
+        String helperName = 
methodReferenceExpression.getNodeMetaData(MetaDataKey.DESERIALIZE_LAMBDA_METHOD_NAME);
+        if (helperName == null) {
+            helperName = createDeserializeLambdaMethodName();
+            
methodReferenceExpression.putNodeMetaData(MetaDataKey.DESERIALIZE_LAMBDA_METHOD_NAME,
 helperName);
+        }
+
+        Parameter[] parameters = createDeserializeLambdaMethodParams();
+        if (controller.getClassNode().hasMethod(helperName, parameters)) {
+            return;
+        }
+
+        String abstractMethodDesc = createMethodDescriptor(abstractMethod);
+        MethodNode helperMethod = 
addDeserializeLambdaMethodForMethodReference(helperName, abstractMethod, 
methodRefMethod,
+                parametersWithExactType, referenceKind, capturing, 
capturedTargetType, methodDesc, abstractMethodDesc);
+        addDeserializeLambdaDispatcherEntry(controller, parameters,
+                createSerializedLambdaKey(abstractMethodDesc, referenceKind, 
methodRefMethod.getDeclaringClass(), methodRefMethod,
+                        parametersWithExactType, functionalType, 
abstractMethod, capturing ? 1 : 0),
+                helperMethod);
+    }
+
+    private MethodNode addDeserializeLambdaMethodForMethodReference(final 
String methodName, final MethodNode abstractMethod,
+                                                                    final 
MethodNode methodRefMethod, final Parameter[] parametersWithExactType,
+                                                                    final int 
referenceKind, final boolean capturing,
+                                                                    final 
ClassNode capturedTargetType, final String methodDesc,
+                                                                    final 
String abstractMethodDesc) {
+        Handle bootstrapMethod = 
createBootstrapMethod(controller.getClassNode().isInterface(), true);
+        Object[] bootstrapArgs = 
createBootstrapMethodArguments(abstractMethodDesc, referenceKind,
+                methodRefMethod.getDeclaringClass(), methodRefMethod, 
parametersWithExactType, true);
+
+        return controller.getClassNode().addSyntheticMethod(
+                methodName,
+                ACC_PRIVATE | ACC_STATIC,
+                ClassHelper.OBJECT_TYPE,
+                createDeserializeLambdaMethodParams(),
+                ClassNode.EMPTY_ARRAY,
+                new BytecodeSequence(new BytecodeInstruction() {
+                    @Override
+                    public void visit(final MethodVisitor mv) {
+                        if (capturing) {
+                            mv.visitVarInsn(ALOAD, 0);
+                            mv.visitInsn(ICONST_0);
+                            mv.visitMethodInsn(INVOKEVIRTUAL, 
"java/lang/invoke/SerializedLambda", "getCapturedArg", "(I)Ljava/lang/Object;", 
false);
+                            mv.visitTypeInsn(CHECKCAST, 
getClassInternalName(capturedTargetType));
+                        }
+                        mv.visitInvokeDynamicInsn(abstractMethod.getName(), 
methodDesc, bootstrapMethod, bootstrapArgs);
+                        mv.visitTypeInsn(CHECKCAST, "java/io/Serializable");
+                        mv.visitInsn(ARETURN);
+                    }
+                }));
+    }
+
+    private String createDeserializeLambdaMethodName() {
+        return "$deserializeLambda_methodref$" + 
controller.getNextHelperMethodIndex() + "$";
+    }
+
     
//--------------------------------------------------------------------------
 
+    private enum MetaDataKey {
+        DESERIALIZE_LAMBDA_METHOD_NAME
+    }
+
     private static boolean isBridgeMethod(final MethodNode mn) {
         int staticSynthetic = Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC;
         return ((mn.getModifiers() & staticSynthetic) == staticSynthetic) && 
mn.getName().startsWith("access$");
diff --git a/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy 
b/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy
index 3c6b5e1b90..45e8f46709 100644
--- a/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/MethodReferenceTest.groovy
@@ -1662,6 +1662,137 @@ final class MethodReferenceTest {
         '''
     }
 
+    @Test
+    void testSerializableNonCapturingMethodReference() {
+        assertScript shell, '''
+            import java.io.ByteArrayInputStream
+            import java.io.ByteArrayOutputStream
+            import java.io.Serializable
+
+            @CompileStatic
+            class C {
+                interface SerFunc<I, O> extends Serializable, Function<I, O> {}
+
+                static SerFunc<Integer, String> create() {
+                    Integer::toString
+                }
+
+                static byte[] serialize(Serializable value) {
+                    def out = new ByteArrayOutputStream()
+                    out.withObjectOutputStream { it.writeObject(value) }
+                    out.toByteArray()
+                }
+
+                static <T> T deserialize(byte[] bytes) {
+                    new 
ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) {
+                        (T) it.readObject()
+                    }
+                }
+            }
+
+            assert C.declaredMethods.count { it.name == '$deserializeLambda$' 
} == 1
+
+            C.SerFunc<Integer, String> fn = 
C.deserialize(C.serialize(C.create()))
+            assert fn instanceof Serializable
+            assert fn.apply(7) == '7'
+        '''
+    }
+
+    @Test
+    void testSerializableCapturingMethodReference() {
+        assertScript shell, '''
+            import java.io.ByteArrayInputStream
+            import java.io.ByteArrayOutputStream
+            import java.io.Serializable
+
+            @CompileStatic
+            class C {
+                interface SerSupplier<T> extends Serializable, Supplier<T> {}
+
+                private final String text
+
+                C(String text) {
+                    this.text = text
+                }
+
+                SerSupplier<String> create() {
+                    text::trim
+                }
+
+                static byte[] serialize(Serializable value) {
+                    def out = new ByteArrayOutputStream()
+                    out.withObjectOutputStream { it.writeObject(value) }
+                    out.toByteArray()
+                }
+
+                static <T> T deserialize(byte[] bytes) {
+                    new 
ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) {
+                        (T) it.readObject()
+                    }
+                }
+            }
+
+            C.SerSupplier<String> supplier = C.deserialize(C.serialize(new C(' 
 answer  ').create()))
+            assert supplier instanceof Serializable
+            assert supplier.get() == 'answer'
+        '''
+    }
+
+    @Test
+    void 
testSerializableMethodReferencesShareDeserializeDispatcherWithLambdas() {
+        assertScript shell, '''
+            import java.io.ByteArrayInputStream
+            import java.io.ByteArrayOutputStream
+            import java.io.Serializable
+
+            @CompileStatic
+            class C {
+                interface SerFunc<I, O> extends Serializable, Function<I, O> {}
+                interface SerSupplier<T> extends Serializable, Supplier<T> {}
+
+                private final String text
+
+                C(String text) {
+                    this.text = text
+                }
+
+                static SerFunc<Integer, String> createMethodReference() {
+                    Integer::toString
+                }
+
+                static SerFunc<Integer, String> createLambda() {
+                    (Integer i) -> 'L' + i
+                }
+
+                SerSupplier<String> createBoundMethodReference() {
+                    text::trim
+                }
+
+                static byte[] serialize(Serializable value) {
+                    def out = new ByteArrayOutputStream()
+                    out.withObjectOutputStream { it.writeObject(value) }
+                    out.toByteArray()
+                }
+
+                static <T> T deserialize(byte[] bytes) {
+                    new 
ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) {
+                        (T) it.readObject()
+                    }
+                }
+            }
+
+            assert C.declaredMethods.count { it.name == '$deserializeLambda$' 
} == 1
+
+            C.SerFunc<Integer, String> methodRef = 
C.deserialize(C.serialize(C.createMethodReference()))
+            C.SerFunc<Integer, String> lambda = 
C.deserialize(C.serialize(C.createLambda()))
+            C.SerSupplier<String> bound = C.deserialize(C.serialize(new C('  x 
 ').createBoundMethodReference()))
+
+            assert methodRef.apply(3) == '3'
+            assert lambda.apply(4) == 'L4'
+            assert bound.get() == 'x'
+        '''
+    }
+
     // GROOVY-11467
     @Test
     void testSuperInterfaceMethodReference() {

Reply via email to