You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/05/28 18:40:00 UTC
[groovy] branch master updated: GROOVY-9813: SC: match functional interface to variadic method reference
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new 2b6db817e3 GROOVY-9813: SC: match functional interface to variadic method reference
2b6db817e3 is described below
commit 2b6db817e3c12fea5d028d6a0bc984a91b7119b2
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sat May 28 13:25:54 2022 -0500
GROOVY-9813: SC: match functional interface to variadic method reference
---
.../codehaus/groovy/ast/tools/ParameterUtils.java | 8 +
.../groovy/classgen/AsmClassGenerator.java | 5 +-
...StaticTypesMethodReferenceExpressionWriter.java | 180 +++++++++++++++------
.../transform/stc/StaticTypeCheckingSupport.java | 4 +-
.../transform/stc/MethodReferenceTest.groovy | 105 +++++++++---
5 files changed, 224 insertions(+), 78 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/ast/tools/ParameterUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/ParameterUtils.java
index 372c4aa3f3..a7db7d998c 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/ParameterUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/ParameterUtils.java
@@ -27,6 +27,14 @@ import java.util.function.BiPredicate;
public class ParameterUtils {
+ /**
+ * @since 5.0.0
+ */
+ public static boolean isVargs(final Parameter[] parameters) {
+ if (parameters == null || parameters.length == 0) return false;
+ return (parameters[parameters.length - 1].getType().isArray());
+ }
+
public static boolean parametersEqual(final Parameter[] a, final Parameter[] b) {
return parametersEqual(a, b, false);
}
diff --git a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
index e949dc3415..d9947b1ad5 100644
--- a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
+++ b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
@@ -154,6 +154,7 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.maybeFallsThrough;
import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.thisPropX;
+import static org.codehaus.groovy.ast.tools.ParameterUtils.isVargs;
import static org.codehaus.groovy.transform.SealedASTTransformation.sealedNative;
import static org.codehaus.groovy.transform.SealedASTTransformation.sealedSkipAnnotation;
import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.PROPERTY_OWNER;
@@ -2323,10 +2324,6 @@ public class AsmClassGenerator extends ClassGenerator {
return ExpressionUtils.isNullConstant(expression);
}
- private static boolean isVargs(final Parameter[] parameters) {
- return (parameters.length > 0 && parameters[parameters.length - 1].getType().isArray());
- }
-
private void loadThis(final VariableExpression thisOrSuper) {
MethodVisitor mv = controller.getMethodVisitor();
mv.visitVarInsn(ALOAD, 0);
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 4c5b44fff1..8a13ac663d 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
@@ -46,15 +46,15 @@ import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
-import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.block;
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.ctorX;
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.ast.tools.ParameterUtils.isVargs;
import static org.codehaus.groovy.ast.tools.ParameterUtils.parametersCompatible;
+import static org.codehaus.groovy.runtime.DefaultGroovyMethods.last;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.filterMethodsByVisibility;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsForClassNode;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isAssignableTo;
@@ -111,15 +111,23 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
if (extensionMethodNode.isStaticExtension()) {
methodRefMethod = addSyntheticMethodForDGSM(methodRefMethod);
}
-
- typeOrTargetRefType = methodRefMethod.getDeclaringClass();
- Expression classExpression = classX(typeOrTargetRefType);
- classExpression.setSourcePosition(typeOrTargetRef);
- typeOrTargetRef = classExpression;
+ typeOrTargetRef = makeClassTarget(methodRefMethod.getDeclaringClass(), typeOrTargetRef);
+ typeOrTargetRefType = typeOrTargetRef.getType();
+
+ } else if (isVargs(methodRefMethod.getParameters())) {
+ int mParameters = abstractMethod.getParameters().length;
+ int nParameters = methodRefMethod.getParameters().length;
+ if (isTypeReferringInstanceMethod(typeOrTargetRef, methodRefMethod)) nParameters += 1;
+ if (mParameters > nParameters || mParameters == nParameters-1 || (mParameters == nParameters
+ && !isAssignableTo(last(parametersWithExactType).getType(), last(methodRefMethod.getParameters()).getType()))) {
+ methodRefMethod = addSyntheticMethodForVariadicReference(methodRefMethod, mParameters, isClassExpression); // GROOVY-9813
+ if (methodRefMethod.isStatic()) {
+ typeOrTargetRef = makeClassTarget(methodRefMethod.getDeclaringClass(), typeOrTargetRef);
+ typeOrTargetRefType = typeOrTargetRef.getType();
+ }
+ }
}
- methodRefMethod.putNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE, parametersWithExactType);
-
if (!isClassExpression) {
if (isConstructorReference) {
// TODO: move the checking code to the parser
@@ -143,18 +151,23 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
referenceKind = Opcodes.H_INVOKEVIRTUAL;
}
- controller.getMethodVisitor().visitInvokeDynamicInsn(
- abstractMethod.getName(),
- createAbstractMethodDesc(functionalInterfaceType, typeOrTargetRef),
- createBootstrapMethod(classNode.isInterface(), false),
- createBootstrapMethodArguments(
- abstractMethodDesc,
- referenceKind,
- isConstructorReference ? classNode : typeOrTargetRefType,
- methodRefMethod,
- false
- )
- );
+ methodRefMethod.putNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE, parametersWithExactType);
+ try {
+ controller.getMethodVisitor().visitInvokeDynamicInsn(
+ abstractMethod.getName(),
+ createAbstractMethodDesc(functionalInterfaceType, typeOrTargetRef),
+ createBootstrapMethod(classNode.isInterface(), false),
+ createBootstrapMethodArguments(
+ abstractMethodDesc,
+ referenceKind,
+ isConstructorReference ? classNode : typeOrTargetRefType,
+ methodRefMethod,
+ false
+ )
+ );
+ } finally {
+ methodRefMethod.removeNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE);
+ }
if (isClassExpression) {
controller.getOperandStack().push(redirect);
@@ -181,29 +194,64 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
private MethodNode addSyntheticMethodForDGSM(final MethodNode mn) {
Parameter[] parameters = removeFirstParameter(mn.getParameters());
- ArgumentListExpression args = args(parameters);
+ ArgumentListExpression args = new ArgumentListExpression(parameters);
args.getExpressions().add(0, nullX());
- MethodCallExpression returnValue = callX(classX(mn.getDeclaringClass()), mn.getName(), args);
- returnValue.putNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET, mn);
- returnValue.setMethodTarget(mn);
+ MethodCallExpression methodCall = callX(classX(mn.getDeclaringClass()), mn.getName(), args);
+ methodCall.setImplicitThis(false);
+ methodCall.setMethodTarget(mn);
+ methodCall.putNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET, mn);
- MethodNode delegateMethod = addGeneratedMethod(controller.getClassNode(),
- "dgsm$$" + mn.getParameters()[0].getType().getName().replace('.', '$') + "$$" + mn.getName(),
- Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC,
- mn.getReturnType(),
- parameters,
- ClassNode.EMPTY_ARRAY,
- block(returnS(returnValue))
- );
+ String methodName = "dgsm$$" + mn.getParameters()[0].getType().getName().replace('.', '$') + "$$" + mn.getName();
+ MethodNode delegateMethod = addSyntheticMethod(methodName, mn.getReturnType(), methodCall, parameters, mn.getExceptions());
delegateMethod.putNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE, Boolean.TRUE);
+ return delegateMethod;
+ }
+
+ private MethodNode addSyntheticMethodForVariadicReference(final MethodNode mn, final int samParameters, final boolean isStaticTarget) {
+ Parameter[] parameters = new Parameter[samParameters];
+ Expression arguments = null, receiver = null;
+ if (mn.isStatic()) {
+ for (int i = 0, j = mn.getParameters().length-1; i < samParameters; i += 1) {
+ ClassNode t = mn.getParameters()[Math.min(i, j)].getType();
+ if (i >= j) t = t.getComponentType(); // targets the array
+ parameters[i] = new Parameter(t, "p" + i);
+ }
+ arguments = new ArgumentListExpression(parameters);
+ receiver = classX(mn.getDeclaringClass());
+ } else {
+ int p = 0;
+ if (isStaticTarget) parameters[p++] = new Parameter(mn.getDeclaringClass(), "o");
+ for (int i = 0, j = mn.getParameters().length-1; i < samParameters - p; i += 1) {
+ ClassNode t = mn.getParameters()[Math.min(i, j)].getType();
+ if (i >= j) t = t.getComponentType(); // targets the array
+ parameters[p++] = new Parameter(t, "p" + p);
+ }
+ if (isStaticTarget) {
+ arguments = new ArgumentListExpression(removeFirstParameter(parameters));
+ receiver = varX(parameters[0]);
+ } else {
+ arguments = new ArgumentListExpression(parameters);
+ receiver = varX("this", controller.getClassNode());
+ }
+ }
+
+ MethodCallExpression methodCall = callX(receiver, mn.getName(), arguments);
+ methodCall.setImplicitThis(false);
+ methodCall.setMethodTarget(mn);
+ methodCall.putNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET, mn);
+ String methodName = "adapt$" + mn.getDeclaringClass().getNameWithoutPackage() + "$" + mn.getName() + "$" + System.nanoTime();
+
+ MethodNode delegateMethod = addSyntheticMethod(methodName, mn.getReturnType(), methodCall, parameters, mn.getExceptions());
+ if (!isStaticTarget && !mn.isStatic()) delegateMethod.setModifiers(delegateMethod.getModifiers() & ~Opcodes.ACC_STATIC);
+ delegateMethod.putNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE, Boolean.TRUE);
return delegateMethod;
}
- private MethodNode addSyntheticMethodForConstructorReference(final String syntheticMethodName, final ClassNode returnType, final Parameter[] parametersWithExactType) {
- ArgumentListExpression ctorArgs = args(parametersWithExactType);
+ private MethodNode addSyntheticMethodForConstructorReference(final String methodName, final ClassNode returnType, final Parameter[] parametersWithExactType) {
+ ArgumentListExpression ctorArgs = new ArgumentListExpression(parametersWithExactType);
Expression returnValue;
if (returnType.isArray()) {
@@ -214,23 +262,24 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
returnValue = ctorX(returnType, ctorArgs);
}
- MethodNode delegateMethod = addGeneratedMethod(controller.getClassNode(),
- syntheticMethodName,
- Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC,
- returnType,
- parametersWithExactType,
- ClassNode.EMPTY_ARRAY,
- block(returnS(returnValue))
- );
-
+ MethodNode delegateMethod = addSyntheticMethod(methodName, returnType, returnValue, parametersWithExactType, ClassNode.EMPTY_ARRAY);
// TODO: if StaticTypesMarker.DIRECT_METHOD_CALL_TARGET or
// OptimizingStatementWriter.StatementMeta.class metadatas
// can bet set for the ctorX above, then this can be TRUE:
delegateMethod.putNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE, Boolean.FALSE);
-
return delegateMethod;
}
+ private MethodNode addSyntheticMethod(final String methodName, final ClassNode returnType, final Expression returnValue, final Parameter[] parameters, final ClassNode[] exceptions) {
+ return controller.getClassNode().addSyntheticMethod(
+ methodName,
+ Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL,
+ returnType,
+ parameters,
+ exceptions,
+ returnS(returnValue));
+ }
+
private String createAbstractMethodDesc(final ClassNode functionalInterfaceType, final Expression methodRef) {
List<Parameter> methodReferenceSharedVariableList = new ArrayList<>();
@@ -266,7 +315,7 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
private MethodNode findMethodRefMethod(final String methodName, final Parameter[] samParameters, final Expression typeOrTargetRef, final ClassNode typeOrTargetRefType) {
List<MethodNode> methods = findVisibleMethods(methodName, typeOrTargetRefType);
- return chooseMethodRefMethodCandidate(typeOrTargetRef, methods.stream().filter(method -> {
+ methods.removeIf(method -> {
Parameter[] parameters = method.getParameters();
if (isTypeReferringInstanceMethod(typeOrTargetRef, method)) {
// there is an implicit parameter for "String::length"
@@ -279,8 +328,31 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
parameters = plusOne;
}
- return parametersCompatible(samParameters, parameters);
- }).collect(Collectors.toList()));
+
+ // check direct match
+ if (parametersCompatible(samParameters, parameters)) return false;
+
+ // check vararg match
+ if (isVargs(parameters)) {
+ int nParameters = parameters.length;
+ if (samParameters.length == nParameters - 1) { // 0 case
+ parameters = Arrays.copyOf(parameters, nParameters - 1);
+ if (parametersCompatible(samParameters, parameters)) return false;
+ }
+ else if (samParameters.length >= nParameters) { // 1+ case
+ Parameter p = new Parameter(parameters[nParameters - 1].getType().getComponentType(), "");
+ parameters = Arrays.copyOf(parameters, samParameters.length);
+ for (int i = nParameters - 1; i < parameters.length; i += 1){
+ parameters[i] = p;
+ }
+ if (parametersCompatible(samParameters, parameters)) return false;
+ }
+ }
+
+ return true; // no match; remove method
+ });
+
+ return chooseMethodRefMethodCandidate(typeOrTargetRef, methods);
}
private List<MethodNode> findVisibleMethods(final String name, final ClassNode type) {
@@ -310,15 +382,21 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
|| (isExtensionMethod(mn) && !((ExtensionMethodNode) mn).isStaticExtension()));
}
+ private static Expression makeClassTarget(final ClassNode target, final Expression source) {
+ Expression expression = classX(target);
+ expression.setSourcePosition(source);
+ return expression;
+ }
+
private static Parameter[] removeFirstParameter(final Parameter[] parameters) {
return Arrays.copyOfRange(parameters, 1, parameters.length);
}
/**
- * Choose the best method node for method reference.
+ * Chooses the best method node for method reference.
*/
private static MethodNode chooseMethodRefMethodCandidate(final Expression methodRef, final List<MethodNode> candidates) {
- if (1 == candidates.size()) return candidates.get(0);
+ if (candidates.size() == 1) return candidates.get(0);
return candidates.stream()
.map(e -> Tuple.tuple(e, matchingScore(e, methodRef)))
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
index 819da3e79d..649d6f988c 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -491,9 +491,9 @@ public abstract class StaticTypeCheckingSupport {
return (type.isDerivedFrom(CLOSURE_TYPE) && isSAMType(toBeAssignedTo));
}
+ @Deprecated
static boolean isVargs(final Parameter[] parameters) {
- if (parameters == null || parameters.length == 0) return false;
- return (parameters[parameters.length - 1].getType().isArray());
+ return ParameterUtils.isVargs(parameters);
}
public static boolean isCompareToBoolean(final int op) {
diff --git a/src/test/groovy/transform/stc/MethodReferenceTest.groovy b/src/test/groovy/transform/stc/MethodReferenceTest.groovy
index a3f401a1f4..f1188942e7 100644
--- a/src/test/groovy/transform/stc/MethodReferenceTest.groovy
+++ b/src/test/groovy/transform/stc/MethodReferenceTest.groovy
@@ -126,8 +126,41 @@ final class MethodReferenceTest {
'''
}
- @Test // class::instanceMethod -- GROOVY-9853
+ @Test // class::instanceMethod -- GROOVY-9813
void testFunctionCI6() {
+ String head = '''
+ @CompileStatic
+ class C {
+ def <T> List<T> asList(T... a) {
+ return Arrays.asList(a)
+ }
+ static main(args) {
+ '''
+ String tail = '''
+ }
+ }
+ '''
+
+ shouldFail shell, head + '''
+ Supplier<List> zero = C::asList
+ ''' + tail
+
+ assertScript shell, head + '''
+ Function<C, List> one = C::asList
+ def list = one.apply(new C())
+ assert list.isEmpty()
+ ''' + tail
+
+ assertScript shell, head + '''
+ BiFunction<C, Integer, List> two = C::asList
+ def list = two.apply(new C(),1)
+ assert list.size() == 1
+ assert list[0] == 1
+ ''' + tail
+ }
+
+ @Test // class::instanceMethod -- GROOVY-9853
+ void testFunctionCI7() {
assertScript shell, '''
@CompileStatic
void test() {
@@ -493,6 +526,32 @@ final class MethodReferenceTest {
'''
}
+ @Test // class::new
+ void testFunctionCN5() {
+ assertScript shell, '''
+ @CompileStatic
+ void p() {
+ Function<String, Integer> f = Integer::new
+ assert [1, 2, 3] == ["1", "2", "3"].stream().map(f).collect(Collectors.toList())
+ }
+
+ p()
+ '''
+ }
+
+ @Test // arrayClass::new
+ void testIntFunctionCN6() {
+ assertScript shell, '''
+ @CompileStatic
+ void p() {
+ IntFunction<Integer[]> f = Integer[]::new
+ assert new Integer[] { 1, 2, 3 } == [1, 2, 3].stream().toArray(f)
+ }
+
+ p()
+ '''
+ }
+
@Test // class::staticMethod
void testFunctionCS() {
assertScript shell, '''
@@ -545,7 +604,7 @@ final class MethodReferenceTest {
}
@Test // class::staticMethod
- void testFunctionCS_RHS() {
+ void testFunctionCS4() {
assertScript shell, '''
@CompileStatic
void p() {
@@ -553,13 +612,12 @@ final class MethodReferenceTest {
def result = [1, -2, 3].stream().map(f).collect(Collectors.toList())
assert [1, 2, 3] == result
}
-
p()
'''
}
@Test // class::staticMethod
- void testFunctionCS_RHS_NOTYPE() {
+ void testFunctionCS5() {
assertScript shell, '''
@CompileStatic
void p() {
@@ -567,33 +625,42 @@ final class MethodReferenceTest {
def result = [1, -2, 3].stream().map(f).collect(Collectors.toList())
assert [1, 2, 3] == result
}
-
p()
'''
}
- @Test // class::new
- void testFunctionCN_RHS() {
+ @Test // class::staticMethod -- GROOVY-9813
+ void testFunctionCS6() {
assertScript shell, '''
@CompileStatic
void p() {
- Function<String, Integer> f = Integer::new
- assert [1, 2, 3] == ["1", "2", "3"].stream().map(f).collect(Collectors.toList())
+ Supplier<List> zero = Arrays::asList
+ def list = zero.get()
+ assert list.isEmpty()
}
-
p()
'''
- }
- @Test // arrayClass::new
- void testIntFunctionCN_RHS() {
assertScript shell, '''
@CompileStatic
void p() {
- IntFunction<Integer[]> f = Integer[]::new
- assert new Integer[] { 1, 2, 3 } == [1, 2, 3].stream().toArray(f)
+ Function<Integer, List> one = Arrays::asList
+ def list = one.apply(1)
+ assert list.size() == 1
+ assert list[0] == 1
}
+ p()
+ '''
+ assertScript shell, '''
+ @CompileStatic
+ void p() {
+ BiFunction<Integer, Integer, List> two = Arrays::asList
+ def list = two.apply(2,3)
+ assert list.size() == 2
+ assert list[0] == 2
+ assert list[1] == 3
+ }
p()
'''
}
@@ -606,7 +673,6 @@ final class MethodReferenceTest {
def result = ['a', 'ab', 'abc'].stream().map(String::size).collect(Collectors.toList())
assert [1, 2, 3] == result
}
-
p()
'''
}
@@ -619,7 +685,6 @@ final class MethodReferenceTest {
def result = [{}, {}, {}].stream().map(Thread::startDaemon).collect(Collectors.toList())
assert result.every(e -> e instanceof Thread)
}
-
p()
'''
}
@@ -633,7 +698,6 @@ final class MethodReferenceTest {
assert 3 == result.size()
assert ['[a:1]', '[b:2]', '[c:3]'] == result
}
-
p()
'''
}
@@ -649,7 +713,6 @@ final class MethodReferenceTest {
result = [{}, {}, {}].stream().map(Thread::startDaemon).collect(Collectors.toList())
assert result.every(e -> e instanceof Thread)
}
-
p()
'''
}
@@ -713,8 +776,8 @@ final class MethodReferenceTest {
@Test // GROOVY-10635
void testRecordComponentMethodReference() {
assertScript shell, '''
- record Bar(String name) { }
-
+ record Bar(String name) {
+ }
def bars = [new Bar(name: 'A'), new Bar(name: 'B')]
assert bars.stream().map(Bar::name).map(String::toLowerCase).toList() == ['a', 'b']
'''