GROOVY-9320: Support serializable lambda expression
diff --git a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
index b5f7fac..bbca52a 100644
--- a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
+++ b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
@@ -57,8 +57,10 @@
 import org.codehaus.groovy.vmplugin.VMPluginFactory;
 import org.objectweb.asm.Opcodes;
 
+import java.io.Serializable;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
+import java.lang.invoke.SerializedLambda;
 import java.lang.ref.SoftReference;
 import java.lang.reflect.Modifier;
 import java.math.BigDecimal;
@@ -100,6 +102,7 @@
             DYNAMIC_TYPE = makeCached(Object.class),
             OBJECT_TYPE = DYNAMIC_TYPE,
             CLOSURE_TYPE = makeCached(Closure.class),
+            SERIALIZEDLAMBDA_TYPE = makeCached(SerializedLambda .class),
             GSTRING_TYPE = makeCached(GString.class),
             RANGE_TYPE = makeCached(Range.class),
             PATTERN_TYPE = makeCached(Pattern.class),
@@ -134,6 +137,7 @@
             Annotation_TYPE = makeCached(Annotation.class),
             ELEMENT_TYPE_TYPE = makeCached(ElementType.class),
             AUTOCLOSEABLE_TYPE = makeCached(AutoCloseable.class),
+            SERIALIZABLE_TYPE = makeCached(Serializable.class),
 
             // uncached constants
             MAP_TYPE = makeWithoutCaching(Map.class),
diff --git a/src/main/java/org/codehaus/groovy/ast/expr/LambdaExpression.java b/src/main/java/org/codehaus/groovy/ast/expr/LambdaExpression.java
index 080e450..64cb989 100644
--- a/src/main/java/org/codehaus/groovy/ast/expr/LambdaExpression.java
+++ b/src/main/java/org/codehaus/groovy/ast/expr/LambdaExpression.java
@@ -36,6 +36,7 @@
  * </pre>
  */
 public class LambdaExpression extends ClosureExpression {
+    private boolean serializable;
     public LambdaExpression(Parameter[] parameters, Statement code) {
         super(parameters, code);
     }
@@ -53,4 +54,12 @@
             return "() -> { ... }";
         }
     }
+
+    public boolean isSerializable() {
+        return serializable;
+    }
+
+    public void setSerializable(boolean serializable) {
+        this.serializable = serializable;
+    }
 }
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 fe03ad7..e425cc0 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
@@ -29,6 +29,7 @@
 import org.objectweb.asm.Type;
 
 import java.util.Arrays;
+import java.util.LinkedList;
 import java.util.List;
 
 import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
@@ -60,7 +61,17 @@
         );
     }
 
-    default Handle createBootstrapMethod(boolean isInterface) {
+    default Handle createBootstrapMethod(boolean isInterface, boolean serializable) {
+        if (serializable) {
+            return new Handle(
+                    Opcodes.H_INVOKESTATIC,
+                    "java/lang/invoke/LambdaMetafactory",
+                    "altMetafactory",
+                    "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;",
+                    isInterface
+            );
+        }
+
         return new Handle(
                 Opcodes.H_INVOKESTATIC,
                 "java/lang/invoke/LambdaMetafactory",
@@ -70,20 +81,28 @@
         );
     }
 
-    default Object[] createBootstrapMethodArguments(String abstractMethodDesc, int insn, ClassNode methodOwnerClassNode, MethodNode methodNode) {
+    default Object[] createBootstrapMethodArguments(String abstractMethodDesc, int insn, ClassNode methodOwnerClassNode, MethodNode methodNode, boolean serializable) {
         Parameter[] parameters = methodNode.getNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE);
+        List<Object> argumentList = new LinkedList<>();
 
-        return new Object[]{
-                Type.getType(abstractMethodDesc),
+        argumentList.add(Type.getType(abstractMethodDesc));
+        argumentList.add(
                 new Handle(
                         insn,
                         BytecodeHelper.getClassInternalName(methodOwnerClassNode.getName()),
                         methodNode.getName(),
                         BytecodeHelper.getMethodDescriptor(methodNode),
                         methodOwnerClassNode.isInterface()
-                ),
-                Type.getType(BytecodeHelper.getMethodDescriptor(methodNode.getReturnType(), parameters))
-        };
+                )
+        );
+        argumentList.add(Type.getType(BytecodeHelper.getMethodDescriptor(methodNode.getReturnType(), parameters)));
+
+        if (serializable) {
+            argumentList.add(5);
+            argumentList.add(0);
+        }
+
+        return argumentList.toArray();
     }
 
     default ClassNode convertParameterType(ClassNode parameterType, ClassNode inferredType) {
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 521bc2d..5005e33 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
@@ -27,12 +27,18 @@
 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.ClassExpression;
 import org.codehaus.groovy.ast.expr.ClosureExpression;
 import org.codehaus.groovy.ast.expr.ConstantExpression;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.LambdaExpression;
 import org.codehaus.groovy.ast.expr.MethodCallExpression;
 import org.codehaus.groovy.ast.expr.VariableExpression;
+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;
 import org.codehaus.groovy.classgen.asm.BytecodeHelper;
 import org.codehaus.groovy.classgen.asm.CompileStack;
 import org.codehaus.groovy.classgen.asm.LambdaWriter;
@@ -43,7 +49,6 @@
 import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys;
 import org.codehaus.groovy.transform.stc.StaticTypesMarker;
 import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
 
 import java.util.Arrays;
 import java.util.HashMap;
@@ -52,13 +57,24 @@
 import java.util.Map;
 import java.util.stream.Collectors;
 
+import static org.codehaus.groovy.ast.ClassHelper.SERIALIZABLE_TYPE;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
+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.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.ACC_SYNTHETIC;
 import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.CHECKCAST;
 import static org.objectweb.asm.Opcodes.DUP;
+import static org.objectweb.asm.Opcodes.H_INVOKEVIRTUAL;
+import static org.objectweb.asm.Opcodes.ICONST_0;
+import static org.objectweb.asm.Opcodes.ICONST_1;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 import static org.objectweb.asm.Opcodes.NEW;
 
 /**
@@ -99,18 +115,29 @@
             return;
         }
 
+        boolean implementsSerializable = functionalInterfaceType.implementsInterface(SERIALIZABLE_TYPE);
+        expression.setSerializable(expression.isSerializable() || implementsSerializable);
+
         MethodNode abstractMethodNode = ClassHelper.findSAM(redirect);
         String abstractMethodDesc = createMethodDescriptor(abstractMethodNode);
 
         ClassNode classNode = controller.getClassNode();
+
         boolean isInterface = classNode.isInterface();
         ClassNode lambdaWrapperClassNode = getOrAddLambdaClass(expression, ACC_PUBLIC | ACC_FINAL | (isInterface ? ACC_STATIC : 0) | ACC_SYNTHETIC, abstractMethodNode);
         MethodNode syntheticLambdaMethodNode = lambdaWrapperClassNode.getMethods(DO_CALL).get(0);
 
-        newGroovyLambdaWrapperAndLoad(lambdaWrapperClassNode, syntheticLambdaMethodNode);
+        boolean canDeserialize = classNode.hasMethod(createDeserializeLambdaMethodName(lambdaWrapperClassNode), createDeserializeLambdaMethodParams());
 
-        loadEnclosingClassInstance();
+        if (!canDeserialize) {
+            if (expression.isSerializable()) {
+                addDeserializeLambdaMethodForEachLambdaExpression(expression, lambdaWrapperClassNode);
+                addDeserializeLambdaMethod();
+            }
 
+            newGroovyLambdaWrapperAndLoad(lambdaWrapperClassNode, expression);
+            loadEnclosingClassInstance();
+        }
 
         MethodVisitor mv = controller.getMethodVisitor();
         OperandStack operandStack = controller.getOperandStack();
@@ -118,12 +145,21 @@
         mv.visitInvokeDynamicInsn(
                 abstractMethodNode.getName(),
                 createAbstractMethodDesc(functionalInterfaceType, lambdaWrapperClassNode),
-                createBootstrapMethod(isInterface),
-                createBootstrapMethodArguments(abstractMethodDesc, Opcodes.H_INVOKEVIRTUAL, lambdaWrapperClassNode, syntheticLambdaMethodNode)
+                createBootstrapMethod(isInterface, expression.isSerializable()),
+                createBootstrapMethodArguments(abstractMethodDesc, H_INVOKEVIRTUAL, lambdaWrapperClassNode, syntheticLambdaMethodNode, expression.isSerializable())
         );
+
+        if (expression.isSerializable()) {
+            mv.visitTypeInsn(CHECKCAST, "java/io/Serializable");
+        }
+
         operandStack.replace(redirect, 2);
     }
 
+    private Parameter[] createDeserializeLambdaMethodParams() {
+        return new Parameter[]{new Parameter(ClassHelper.SERIALIZEDLAMBDA_TYPE, SERIALIZED_LAMBDA_PARAM_NAME)};
+    }
+
     private void loadEnclosingClassInstance() {
         MethodVisitor mv = controller.getMethodVisitor();
         OperandStack operandStack = controller.getOperandStack();
@@ -137,7 +173,7 @@
         }
     }
 
-    private void newGroovyLambdaWrapperAndLoad(ClassNode lambdaWrapperClassNode, MethodNode syntheticLambdaMethodNode) {
+    private void newGroovyLambdaWrapperAndLoad(ClassNode lambdaWrapperClassNode, LambdaExpression expression) {
         MethodVisitor mv = controller.getMethodVisitor();
         String lambdaWrapperClassInternalName = BytecodeHelper.getClassInternalName(lambdaWrapperClassNode);
         mv.visitTypeInsn(NEW, lambdaWrapperClassInternalName);
@@ -146,7 +182,7 @@
         loadEnclosingClassInstance();
         controller.getOperandStack().dup();
 
-        loadSharedVariables(syntheticLambdaMethodNode);
+        loadSharedVariables(expression);
 
         List<ConstructorNode> constructorNodeList =
                 lambdaWrapperClassNode.getDeclaredConstructors().stream()
@@ -164,8 +200,8 @@
         operandStack.replace(ClassHelper.CLOSURE_TYPE, lambdaWrapperClassConstructorParameters.length);
     }
 
-    private Parameter[] loadSharedVariables(MethodNode syntheticLambdaMethodNode) {
-        Parameter[] lambdaSharedVariableParameters = syntheticLambdaMethodNode.getNodeMetaData(LAMBDA_SHARED_VARIABLES);
+    private Parameter[] loadSharedVariables(LambdaExpression expression) {
+        Parameter[] lambdaSharedVariableParameters = expression.getNodeMetaData(LAMBDA_SHARED_VARIABLES);
         for (Parameter parameter : lambdaSharedVariableParameters) {
             String parameterName = parameter.getName();
             loadReference(parameterName, controller);
@@ -211,6 +247,8 @@
         answer.setUsingGenerics(outerClass.isUsingGenerics());
         answer.setSourcePosition(expression);
 
+        addSerialVersionUIDField(answer);
+
         if (staticMethodOrInStaticClass) {
             answer.setStaticClass(true);
         }
@@ -219,7 +257,7 @@
         }
 
         MethodNode syntheticLambdaMethodNode = addSyntheticLambdaMethodNode(expression, answer, abstractMethodNode);
-        Parameter[] localVariableParameters = syntheticLambdaMethodNode.getNodeMetaData(LAMBDA_SHARED_VARIABLES);
+        Parameter[] localVariableParameters = expression.getNodeMetaData(LAMBDA_SHARED_VARIABLES);
 
         addFieldsAndGettersForLocalVariables(answer, localVariableParameters);
         ConstructorNode constructorNode = addConstructor(expression, localVariableParameters, answer, createBlockStatementForConstructor(expression, outerClass, classNode));
@@ -231,6 +269,10 @@
         return answer;
     }
 
+    private void addSerialVersionUIDField(InnerClassNode answer) {
+        answer.addFieldFirst("serialVersionUID", ACC_PRIVATE | ACC_STATIC | ACC_FINAL, ClassHelper.long_TYPE, new ConstantExpression(-1L, true));
+    }
+
     private String genLambdaClassName() {
         ClassNode classNode = controller.getClassNode();
         ClassNode outerClass = controller.getOutermostClass();
@@ -252,14 +294,14 @@
         MethodNode methodNode =
                 answer.addMethod(
                         DO_CALL,
-                        Opcodes.ACC_PUBLIC,
+                        ACC_PUBLIC,
                         abstractMethodNode.getReturnType() /*ClassHelper.OBJECT_TYPE*/ /*returnType*/,
                         methodParameterList.toArray(Parameter.EMPTY_ARRAY),
                         ClassNode.EMPTY_ARRAY,
                         expression.getCode()
                 );
         methodNode.putNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE, parametersWithExactType);
-        methodNode.putNodeMetaData(LAMBDA_SHARED_VARIABLES, localVariableParameters);
+        expression.putNodeMetaData(LAMBDA_SHARED_VARIABLES, localVariableParameters);
         methodNode.setSourcePosition(expression);
 
         return methodNode;
@@ -292,6 +334,73 @@
         return parameters;
     }
 
+    private static final String SERIALIZED_LAMBDA_PARAM_NAME = "serializedLambda";
+    private static final String DESERIALIZE_LAMBDA_METHOD_NAME = "$deserializeLambda$";
+    private void addDeserializeLambdaMethod() {
+        ClassNode classNode = controller.getClassNode();
+        Parameter[] parameters = createDeserializeLambdaMethodParams();
+        if (classNode.hasMethod(DESERIALIZE_LAMBDA_METHOD_NAME, parameters)) {
+            return;
+        }
+        Statement code = block(
+                declS(localVarX("enclosingClass", ClassHelper.DYNAMIC_TYPE), new ClassExpression(classNode)),
+                ((BlockStatement) new AstStringCompiler().compile(
+                        "return enclosingClass" +
+                                ".getDeclaredMethod(\"\\$deserializeLambda_${serializedLambda.getImplClass().replace('/', '$')}\\$\", serializedLambda.getClass())" +
+                                ".invoke(null, serializedLambda)"
+                ).get(0)).getStatements().get(0)
+        );
+
+        classNode.addSyntheticMethod(
+                DESERIALIZE_LAMBDA_METHOD_NAME,
+                ACC_PRIVATE | ACC_STATIC,
+                ClassHelper.OBJECT_TYPE,
+                parameters,
+                ClassNode.EMPTY_ARRAY,
+                code);
+    }
+
+    private void addDeserializeLambdaMethodForEachLambdaExpression(LambdaExpression lambdaExpression, ClassNode lambdaWrapperClassNode) {
+        ClassNode classNode = controller.getClassNode();
+        Statement code = block(
+                new BytecodeSequence(new BytecodeInstruction() {
+                    @Override
+                    public void visit(MethodVisitor mv) {
+                        callGetCapturedArg(mv, ICONST_0, lambdaWrapperClassNode);
+                        callGetCapturedArg(mv, ICONST_1, classNode);
+                    }
+
+                    private void callGetCapturedArg(MethodVisitor mv, int capturedArgIndex, ClassNode resultType) {
+                        OperandStack operandStack = controller.getOperandStack();
+
+                        mv.visitVarInsn(ALOAD, 0);
+                        mv.visitInsn(capturedArgIndex);
+                        mv.visitMethodInsn(
+                                INVOKEVIRTUAL,
+                                "java/lang/invoke/SerializedLambda",
+                                "getCapturedArg",
+                                "(I)Ljava/lang/Object;",
+                                false);
+                        mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(resultType));
+                        operandStack.push(resultType);
+                    }
+                }),
+                returnS(lambdaExpression)
+        );
+
+        classNode.addSyntheticMethod(
+                createDeserializeLambdaMethodName(lambdaWrapperClassNode),
+                ACC_PUBLIC | ACC_STATIC,
+                ClassHelper.OBJECT_TYPE,
+                createDeserializeLambdaMethodParams(),
+                ClassNode.EMPTY_ARRAY,
+                code);
+    }
+
+    private String createDeserializeLambdaMethodName(ClassNode lambdaWrapperClassNode) {
+        return "$deserializeLambda_" + lambdaWrapperClassNode.getName().replace('.', '$') + "$";
+    }
+
     @Override
     protected ClassNode createClosureClass(final ClosureExpression expression, final int mods) {
         return staticTypesClosureWriter.createClosureClass(expression, mods);
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 a8f8c29..c12a556 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
@@ -153,12 +153,12 @@
         mv.visitInvokeDynamicInsn(
                 abstractMethodNode.getName(),
                 createAbstractMethodDesc(functionalInterfaceType, typeOrTargetRef),
-                createBootstrapMethod(isInterface),
+                createBootstrapMethod(isInterface, false),
                 createBootstrapMethodArguments(
                         abstractMethodDesc,
                         methodRefMethod.isStatic() || isConstructorReference ? Opcodes.H_INVOKESTATIC : Opcodes.H_INVOKEVIRTUAL,
                         isConstructorReference ? controller.getClassNode() : typeOrTargetRefType,
-                        methodRefMethod)
+                        methodRefMethod, false)
         );
 
         if (isClassExpr) {
diff --git a/src/test/groovy/transform/stc/LambdaTest.groovy b/src/test/groovy/transform/stc/LambdaTest.groovy
index f762710..b01bb77 100644
--- a/src/test/groovy/transform/stc/LambdaTest.groovy
+++ b/src/test/groovy/transform/stc/LambdaTest.groovy
@@ -954,4 +954,338 @@
             p()
         '''
     }
+
+    void testSerialize() {
+        assertScript '''
+        import java.util.function.Function
+        
+        interface SerializableFunction<T, R> extends Function<T, R>, Serializable {}
+        
+        @groovy.transform.CompileStatic
+        class Test1 implements Serializable {
+            private static final long serialVersionUID = -1L;
+            def p() {
+                    def out = new ByteArrayOutputStream()
+                    out.withObjectOutputStream {
+                        SerializableFunction<Integer, String> f = ((Integer e) -> 'a' + e)
+                        it.writeObject(f)
+                    }
+                    
+                    return out.toByteArray()
+            }
+        }
+
+        assert new Test1().p().length > 0
+        '''
+    }
+
+    void testSerializeFailed() {
+        shouldFail(NotSerializableException, '''
+        import java.util.function.Function
+        
+        @groovy.transform.CompileStatic
+        class Test1 implements Serializable {
+            private static final long serialVersionUID = -1L;
+            def p() {
+                    def out = new ByteArrayOutputStream()
+                    out.withObjectOutputStream {
+                        Function<Integer, String> f = ((Integer e) -> 'a' + e)
+                        it.writeObject(f)
+                    }
+                    
+                    return out.toByteArray()
+            }
+        }
+
+        new Test1().p()
+        ''')
+    }
+
+    void testDeserialize() {
+        assertScript '''
+        package tests.lambda
+        import java.util.function.Function
+        
+        @groovy.transform.CompileStatic
+        class Test1 implements Serializable {
+            private static final long serialVersionUID = -1L;
+            byte[] p() {
+                    def out = new ByteArrayOutputStream()
+                    out.withObjectOutputStream {
+                        SerializableFunction<Integer, String> f = ((Integer e) -> 'a' + e)
+                        it.writeObject(f)
+                    }
+                    
+                    return out.toByteArray()
+            }
+            
+            static void main(String[] args) {
+                new ByteArrayInputStream(new Test1().p()).withObjectInputStream(Test1.class.classLoader) {
+                    SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+                    assert 'a1' == f.apply(1)
+                }
+            }
+            
+            interface SerializableFunction<T, R> extends Function<T, R>, Serializable {}
+        }
+        '''
+    }
+
+
+    void testDeserialize2() {
+        assertScript '''
+        import java.util.function.Function
+        
+        @groovy.transform.CompileStatic
+        class Test1 implements Serializable {
+            private static final long serialVersionUID = -1L;
+            static byte[] p() {
+                    def out = new ByteArrayOutputStream()
+                    out.withObjectOutputStream {
+                        SerializableFunction<Integer, String> f = ((Integer e) -> 'a' + e)
+                        it.writeObject(f)
+                    }
+                    
+                    return out.toByteArray()
+            }
+            
+            static void main(String[] args) {
+                new ByteArrayInputStream(Test1.p()).withObjectInputStream(Test1.class.classLoader) {
+                    SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+                    assert 'a1' == f.apply(1)
+                }
+            }
+            
+            interface SerializableFunction<T, R> extends Function<T, R>, Serializable {}
+        }
+        '''
+    }
+
+    void testDeserializeNestedLambda() {
+        assertScript '''
+        import java.util.function.Function
+        
+        interface SerializableFunction<T, R> extends Function<T, R>, Serializable {}
+        
+        @groovy.transform.CompileStatic
+        class Test1 implements Serializable {
+            private static final long serialVersionUID = -1L;
+            def p() {
+                    def out1 = new ByteArrayOutputStream()
+                    SerializableFunction<Integer, String> f1 = (Integer e) -> 'a' + e
+                    out1.withObjectOutputStream {
+                        it.writeObject(f1)
+                    }
+                    def result1 = out1.toByteArray()
+                    
+                    def out2 = new ByteArrayOutputStream()
+                    SerializableFunction<Integer, String> f2 = (Integer e) -> 'b' + e
+                    out2.withObjectOutputStream {
+                        it.writeObject(f2)
+                    }
+                    def result2 = out2.toByteArray()
+                    
+                    // nested lambda expression
+                    def out3 = new ByteArrayOutputStream()
+                    SerializableFunction<Integer, String> f3 = (Integer e) -> {
+                        SerializableFunction<Integer, String> nf = ((Integer ne) -> 'n' + ne)
+                        'c' + nf(e)
+                    }
+                    out3.withObjectOutputStream {
+                        it.writeObject(f3)
+                    }
+                    def result3 = out3.toByteArray()
+                    
+                    return [result1, result2, result3]
+            }
+        }
+        
+        def (byte[] serializedLambdaBytes1, byte[] serializedLambdaBytes2, byte[] serializedLambdaBytes3) = new Test1().p()
+        new ByteArrayInputStream(serializedLambdaBytes1).withObjectInputStream(Test1.class.classLoader) {
+            SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+            assert 'a1' == f.apply(1)
+        }
+        
+        new ByteArrayInputStream(serializedLambdaBytes2).withObjectInputStream(Test1.class.classLoader) {
+            SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+            assert 'b1' == f.apply(1)
+        }
+        
+        new ByteArrayInputStream(serializedLambdaBytes3).withObjectInputStream(Test1.class.classLoader) {
+            SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+            assert 'cn1' == f.apply(1)
+        }
+        '''
+    }
+
+    void testDeserializeNestedLambda2() {
+        assertScript '''
+        import java.util.function.Function
+        
+        interface SerializableFunction<T, R> extends Function<T, R>, Serializable {}
+        
+        @groovy.transform.CompileStatic
+        class Test1 implements Serializable {
+            private static final long serialVersionUID = -1L;
+            def p() {
+                    def out1 = new ByteArrayOutputStream()
+                    out1.withObjectOutputStream {
+                        SerializableFunction<Integer, String> f = ((Integer e) -> 'a' + e)
+                        it.writeObject(f)
+                    }
+                    def result1 = out1.toByteArray()
+                    
+                    def out2 = new ByteArrayOutputStream()
+                    out2.withObjectOutputStream {
+                        SerializableFunction<Integer, String> f = ((Integer e) -> 'b' + e)
+                        it.writeObject(f)
+                    }
+                    def result2 = out2.toByteArray()
+                    
+                    // nested lambda expression
+                    def out3 = new ByteArrayOutputStream()
+                    out3.withObjectOutputStream {
+                        SerializableFunction<Integer, String> f = ((Integer e) -> {
+                            SerializableFunction<Integer, String> nf = ((Integer ne) -> 'n' + ne)
+                            'c' + nf(e)
+                        })
+                        it.writeObject(f)
+                    }
+                    def result3 = out3.toByteArray()
+                    
+                    return [result1, result2, result3]
+            }
+        }
+        
+        def (byte[] serializedLambdaBytes1, byte[] serializedLambdaBytes2, byte[] serializedLambdaBytes3) = new Test1().p()
+        new ByteArrayInputStream(serializedLambdaBytes1).withObjectInputStream(Test1.class.classLoader) {
+            SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+            assert 'a1' == f.apply(1)
+        }
+        
+        new ByteArrayInputStream(serializedLambdaBytes2).withObjectInputStream(Test1.class.classLoader) {
+            SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+            assert 'b1' == f.apply(1)
+        }
+        
+        new ByteArrayInputStream(serializedLambdaBytes3).withObjectInputStream(Test1.class.classLoader) {
+            SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+            assert 'cn1' == f.apply(1)
+        }
+        '''
+    }
+
+    void testDeserializeNestedLambda3() {
+        assertScript '''
+        import java.util.function.Function
+        
+        interface SerializableFunction<T, R> extends Function<T, R>, Serializable {}
+        
+        @groovy.transform.CompileStatic
+        class Test1 implements Serializable {
+            private static final long serialVersionUID = -1L;
+            static p() {
+                    def out1 = new ByteArrayOutputStream()
+                    out1.withObjectOutputStream {
+                        SerializableFunction<Integer, String> f = ((Integer e) -> 'a' + e)
+                        it.writeObject(f)
+                    }
+                    def result1 = out1.toByteArray()
+                    
+                    def out2 = new ByteArrayOutputStream()
+                    out2.withObjectOutputStream {
+                        SerializableFunction<Integer, String> f = ((Integer e) -> 'b' + e)
+                        it.writeObject(f)
+                    }
+                    def result2 = out2.toByteArray()
+                    
+                    // nested lambda expression
+                    def out3 = new ByteArrayOutputStream()
+                    out3.withObjectOutputStream {
+                        SerializableFunction<Integer, String> f = ((Integer e) -> {
+                            SerializableFunction<Integer, String> nf = ((Integer ne) -> 'n' + ne)
+                            'c' + nf(e)
+                        })
+                        it.writeObject(f)
+                    }
+                    def result3 = out3.toByteArray()
+                    
+                    return [result1, result2, result3]
+            }
+        }
+        
+        def (byte[] serializedLambdaBytes1, byte[] serializedLambdaBytes2, byte[] serializedLambdaBytes3) = Test1.p()
+        new ByteArrayInputStream(serializedLambdaBytes1).withObjectInputStream(Test1.class.classLoader) {
+            SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+            assert 'a1' == f.apply(1)
+        }
+        
+        new ByteArrayInputStream(serializedLambdaBytes2).withObjectInputStream(Test1.class.classLoader) {
+            SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+            assert 'b1' == f.apply(1)
+        }
+        
+        new ByteArrayInputStream(serializedLambdaBytes3).withObjectInputStream(Test1.class.classLoader) {
+            SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+            assert 'cn1' == f.apply(1)
+        }
+        '''
+    }
+
+    void testDeserializeNestedLambda4() {
+        assertScript '''
+        import java.util.function.Function
+        
+        interface SerializableFunction<T, R> extends Function<T, R>, Serializable {}
+        
+        @groovy.transform.CompileStatic
+        class Test1 implements Serializable {
+            private static final long serialVersionUID = -1L;
+            static p() {
+                    def out1 = new ByteArrayOutputStream()
+                    SerializableFunction<Integer, String> f1 = (Integer e) -> 'a' + e
+                    out1.withObjectOutputStream {
+                        it.writeObject(f1)
+                    }
+                    def result1 = out1.toByteArray()
+                    
+                    def out2 = new ByteArrayOutputStream()
+                    SerializableFunction<Integer, String> f2 = (Integer e) -> 'b' + e
+                    out2.withObjectOutputStream {
+                        it.writeObject(f2)
+                    }
+                    def result2 = out2.toByteArray()
+                    
+                    // nested lambda expression
+                    def out3 = new ByteArrayOutputStream()
+                    SerializableFunction<Integer, String> f3 = (Integer e) -> {
+                        SerializableFunction<Integer, String> nf = ((Integer ne) -> 'n' + ne)
+                        'c' + nf(e)
+                    }
+                    out3.withObjectOutputStream {
+                        it.writeObject(f3)
+                    }
+                    def result3 = out3.toByteArray()
+                    
+                    return [result1, result2, result3]
+            }
+        }
+        
+        def (byte[] serializedLambdaBytes1, byte[] serializedLambdaBytes2, byte[] serializedLambdaBytes3) = Test1.p()
+        new ByteArrayInputStream(serializedLambdaBytes1).withObjectInputStream(Test1.class.classLoader) {
+            SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+            assert 'a1' == f.apply(1)
+        }
+        
+        new ByteArrayInputStream(serializedLambdaBytes2).withObjectInputStream(Test1.class.classLoader) {
+            SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+            assert 'b1' == f.apply(1)
+        }
+        
+        new ByteArrayInputStream(serializedLambdaBytes3).withObjectInputStream(Test1.class.classLoader) {
+            SerializableFunction<Integer, String> f = (SerializableFunction<Integer, String>) it.readObject()
+            assert 'cn1' == f.apply(1)
+        }
+        '''
+    }
 }