Skip to content

Commit

Permalink
GROOVY-9853: SC: method reference to interface abstract / default method
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed May 21, 2022
1 parent a976ecd commit c40dba6
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 46 deletions.
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Object> 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) {
Expand Down
Expand Up @@ -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
)
Expand Down
90 changes: 67 additions & 23 deletions src/test/groovy/transform/stc/MethodReferenceTest.groovy
Expand Up @@ -18,19 +18,20 @@
*/
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
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() {
Expand Down Expand Up @@ -61,27 +62,23 @@ final class MethodReferenceTest {
@Test // class::instanceMethod -- GROOVY-10047
void testFunctionCI3() {
assertScript shell, '''
import static java.util.stream.Collectors.toMap
@CompileStatic
void p() {
List<String> list = ['a','bc','def']
Function<String,String> 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]
}
p()
'''

assertScript shell, '''
import static java.util.stream.Collectors.toMap
@CompileStatic
void p() {
List<String> list = ['a','bc','def']
// TODO: inference for T in toMap(Function<? super T,...>, Function<? super T,...>)
def map = list.stream().collect(toMap(Function.<String>identity(), String::length))
def map = list.stream().collect(Collectors.toMap(Function.<String>identity(), String::length))
assert map == [a: 1, bc: 2, 'def': 3]
}
Expand All @@ -99,7 +96,6 @@ final class MethodReferenceTest {
p()
'''

assert err =~ /Invalid receiver type: java.lang.Integer is not compatible with java.lang.String/
}

Expand Down Expand Up @@ -129,6 +125,61 @@ final class MethodReferenceTest {
'''
}

@Test // class::instanceMethod -- GROOVY-9853
void testFunctionCI6() {
assertScript shell, '''
@CompileStatic
void test() {
ToIntFunction<CharSequence> f = CharSequence::size
int size = f.applyAsInt("")
assert size == 0
}
test()
'''

assertScript shell, '''
@CompileStatic
void test() {
ToIntFunction<CharSequence> f = CharSequence::length
int length = f.applyAsInt("")
assert length == 0
}
test()
'''

assertScript shell, '''
@CompileStatic
void test() {
Function<CharSequence,Integer> f = CharSequence::length
Integer length = f.apply("")
assert length == 0
}
test()
'''

assertScript shell, '''
import java.util.stream.IntStream
@CompileStatic
void test() {
Function<CharSequence,IntStream> f = CharSequence::chars // default method
IntStream chars = f.apply("")
assert chars.count() == 0
}
test()
'''

assertScript shell, '''
@CompileStatic
void test() {
ToIntBiFunction<CharSequence,CharSequence> f = CharSequence::compare // static method
int result = f.applyAsInt("","")
assert result == 0
}
test()
'''
}

@Test // class::instanceMethod -- GROOVY-9974
void testPredicateCI() {
assertScript shell, '''
Expand All @@ -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()
'''
}

Expand Down Expand Up @@ -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<String> 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']]
}
Expand Down Expand Up @@ -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]')
}

Expand All @@ -623,7 +670,6 @@ final class MethodReferenceTest {
Function<String,String> reference = String::toLowerCaseX
}
'''

assert err.message.contains('Failed to find the expected method[toLowerCaseX(java.lang.String)] in the type[java.lang.String]')
}

Expand All @@ -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/
}

Expand All @@ -659,7 +704,6 @@ final class MethodReferenceTest {
}
}
'''

assert err =~ /The argument is a method reference, but the parameter type is not a functional interface/
}
}

0 comments on commit c40dba6

Please sign in to comment.