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() {
