From c40dba6c6b196e8d7882917b7d3ec0cab963e31f Mon Sep 17 00:00:00 2001 From: Eric Milles Date: Sat, 21 May 2022 14:07:09 -0500 Subject: [PATCH] GROOVY-9853: SC: method reference to interface abstract / default method --- .../sc/AbstractFunctionalInterfaceWriter.java | 36 ++++---- ...cTypesMethodReferenceExpressionWriter.java | 13 ++- .../transform/stc/MethodReferenceTest.groovy | 90 ++++++++++++++----- 3 files changed, 93 insertions(+), 46 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 0223e397ec4..d264d19542f 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 @@ -30,7 +30,6 @@ import org.objectweb.asm.Type; import java.util.Arrays; -import java.util.LinkedList; import java.util.List; import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper; @@ -83,28 +82,23 @@ default Handle createBootstrapMethod(boolean isInterface, boolean serializable) ); } - default Object[] createBootstrapMethodArguments(String abstractMethodDesc, int insn, ClassNode methodOwnerClassNode, MethodNode methodNode, boolean serializable) { - Parameter[] parameters = methodNode.getNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE); - List argumentList = new LinkedList<>(); - - argumentList.add(Type.getType(abstractMethodDesc)); - argumentList.add( - new Handle( - insn, - BytecodeHelper.getClassInternalName(methodOwnerClassNode.getName()), - methodNode.getName(), - BytecodeHelper.getMethodDescriptor(methodNode), - methodOwnerClassNode.isInterface() - ) - ); - argumentList.add(Type.getType(BytecodeHelper.getMethodDescriptor(methodNode.getReturnType(), parameters))); + default Object[] createBootstrapMethodArguments(final String abstractMethodDesc, final int insn, final ClassNode methodOwner, final MethodNode methodNode, final boolean serializable) { + Object[] arguments = !serializable ? new Object[3] : new Object[]{null, null, null, 5, 0}; - if (serializable) { - argumentList.add(5); - argumentList.add(0); - } + arguments[0] = Type.getType(abstractMethodDesc); + + arguments[1] = new Handle( + insn, // H_INVOKESTATIC or H_INVOKEVIRTUAL or H_INVOKEINTERFACE (GROOVY-9853) + BytecodeHelper.getClassInternalName(methodOwner.getName()), + methodNode.getName(), + BytecodeHelper.getMethodDescriptor(methodNode), + methodOwner.isInterface()); + + arguments[2] = Type.getType( + BytecodeHelper.getMethodDescriptor(methodNode.getReturnType(), + (Parameter[]) methodNode.getNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE))); - return argumentList.toArray(); + return arguments; } default ClassNode convertParameterType(ClassNode targetType, ClassNode parameterType, ClassNode inferredType) { 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 222e9ee6fe9..aeba6a9ebcc 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 @@ -134,14 +134,23 @@ public void writeMethodReferenceExpression(final MethodReferenceExpression metho } } + int referenceKind; + if (isConstructorReference || methodRefMethod.isStatic()) { + referenceKind = Opcodes.H_INVOKESTATIC; + } else if (methodRefMethod.getDeclaringClass().isInterface()) { + referenceKind = Opcodes.H_INVOKEINTERFACE; // GROOVY-9853 + } else { + referenceKind = Opcodes.H_INVOKEVIRTUAL; + } + controller.getMethodVisitor().visitInvokeDynamicInsn( abstractMethod.getName(), createAbstractMethodDesc(functionalInterfaceType, typeOrTargetRef), createBootstrapMethod(classNode.isInterface(), false), createBootstrapMethodArguments( abstractMethodDesc, - methodRefMethod.isStatic() || isConstructorReference ? Opcodes.H_INVOKESTATIC : Opcodes.H_INVOKEVIRTUAL, - isConstructorReference ? controller.getClassNode() : typeOrTargetRefType, + referenceKind, + isConstructorReference ? classNode : typeOrTargetRefType, methodRefMethod, false ) diff --git a/src/test/groovy/transform/stc/MethodReferenceTest.groovy b/src/test/groovy/transform/stc/MethodReferenceTest.groovy index da8a61f2bf9..b73012ba40c 100644 --- a/src/test/groovy/transform/stc/MethodReferenceTest.groovy +++ b/src/test/groovy/transform/stc/MethodReferenceTest.groovy @@ -18,8 +18,6 @@ */ package groovy.transform.stc -import org.codehaus.groovy.control.CompilerConfiguration -import org.codehaus.groovy.control.customizers.ImportCustomizer import org.junit.Test import static groovy.test.GroovyAssert.assertScript @@ -27,10 +25,13 @@ import static groovy.test.GroovyAssert.shouldFail final class MethodReferenceTest { - private final GroovyShell shell = new GroovyShell(new CompilerConfiguration().tap { - addCompilationCustomizers(new ImportCustomizer().addStarImports('java.util.function') - .addImports('java.util.stream.Collectors', 'groovy.transform.CompileStatic')) - }) + private final GroovyShell shell = GroovyShell.withConfig { + imports { + normal 'groovy.transform.CompileStatic' + normal 'java.util.stream.Collectors' + star 'java.util.function' + } + } @Test // class::instanceMethod void testFunctionCI() { @@ -61,13 +62,11 @@ final class MethodReferenceTest { @Test // class::instanceMethod -- GROOVY-10047 void testFunctionCI3() { assertScript shell, ''' - import static java.util.stream.Collectors.toMap - @CompileStatic void p() { List list = ['a','bc','def'] Function self = str -> str // help for toMap - def map = list.stream().collect(toMap(self, String::length)) + def map = list.stream().collect(Collectors.toMap(self, String::length)) assert map == [a: 1, bc: 2, 'def': 3] } @@ -75,13 +74,11 @@ final class MethodReferenceTest { ''' assertScript shell, ''' - import static java.util.stream.Collectors.toMap - @CompileStatic void p() { List list = ['a','bc','def'] // TODO: inference for T in toMap(Function, Function) - def map = list.stream().collect(toMap(Function.identity(), String::length)) + def map = list.stream().collect(Collectors.toMap(Function.identity(), String::length)) assert map == [a: 1, bc: 2, 'def': 3] } @@ -99,7 +96,6 @@ final class MethodReferenceTest { p() ''' - assert err =~ /Invalid receiver type: java.lang.Integer is not compatible with java.lang.String/ } @@ -129,6 +125,61 @@ final class MethodReferenceTest { ''' } + @Test // class::instanceMethod -- GROOVY-9853 + void testFunctionCI6() { + assertScript shell, ''' + @CompileStatic + void test() { + ToIntFunction f = CharSequence::size + int size = f.applyAsInt("") + assert size == 0 + } + test() + ''' + + assertScript shell, ''' + @CompileStatic + void test() { + ToIntFunction f = CharSequence::length + int length = f.applyAsInt("") + assert length == 0 + } + test() + ''' + + assertScript shell, ''' + @CompileStatic + void test() { + Function f = CharSequence::length + Integer length = f.apply("") + assert length == 0 + } + test() + ''' + + assertScript shell, ''' + import java.util.stream.IntStream + + @CompileStatic + void test() { + Function f = CharSequence::chars // default method + IntStream chars = f.apply("") + assert chars.count() == 0 + } + test() + ''' + + assertScript shell, ''' + @CompileStatic + void test() { + ToIntBiFunction f = CharSequence::compare // static method + int result = f.applyAsInt("","") + assert result == 0 + } + test() + ''' + } + @Test // class::instanceMethod -- GROOVY-9974 void testPredicateCI() { assertScript shell, ''' @@ -145,12 +196,11 @@ final class MethodReferenceTest { void testBinaryOperatorCI() { assertScript shell, ''' @CompileStatic - void p() { + void test() { def result = [1.0G, 2.0G, 3.0G].stream().reduce(0.0G, BigDecimal::add) assert 6.0G == result } - - p() + test() ''' } @@ -456,12 +506,10 @@ final class MethodReferenceTest { @Test // class::staticMethod void testFunctionCS2() { assertScript shell, ''' - import static java.util.stream.Collectors.toMap - @CompileStatic void p() { List list = ['x','y','z'] - def map = list.stream().collect(toMap(Function.identity(), Collections::singletonList)) + def map = list.stream().collect(Collectors.toMap(Function.identity(), Collections::singletonList)) assert map == [x: ['x'], y: ['y'], z: ['z']] } @@ -611,7 +659,6 @@ final class MethodReferenceTest { [1.0G, 2.0G, 3.0G].stream().reduce(0.0G, BigDecimal::addx) } ''' - assert err.message.contains('Failed to find the expected method[addx(java.math.BigDecimal,java.math.BigDecimal)] in the type[java.math.BigDecimal]') } @@ -623,7 +670,6 @@ final class MethodReferenceTest { Function reference = String::toLowerCaseX } ''' - assert err.message.contains('Failed to find the expected method[toLowerCaseX(java.lang.String)] in the type[java.lang.String]') } @@ -641,7 +687,6 @@ final class MethodReferenceTest { baz(this::foo) // not yet supported! } ''' - assert err =~ /The argument is a method reference, but the parameter type is not a functional interface/ } @@ -659,7 +704,6 @@ final class MethodReferenceTest { } } ''' - assert err =~ /The argument is a method reference, but the parameter type is not a functional interface/ } }