You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2022/06/11 04:04:33 UTC

[groovy] 04/04: GROOVY-10653: SC: instance target is implied parameter for static method

This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 92ca7fbd79b074e79ca1a3802ea909da06110ecc
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Thu Jun 9 12:49:35 2022 -0500

    GROOVY-10653: SC: instance target is implied parameter for static method
---
 ...StaticTypesMethodReferenceExpressionWriter.java | 78 +++++++++++-----------
 src/test/groovy/transform/stc/LambdaTest.groovy    | 25 +++++++
 .../transform/stc/MethodReferenceTest.groovy       | 24 +++++--
 3 files changed, 82 insertions(+), 45 deletions(-)

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 c16229448d..1e4b3fdaa3 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
@@ -39,9 +39,9 @@ import org.codehaus.groovy.syntax.RuntimeParserException;
 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.Opcodes;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -86,6 +86,7 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
         ClassNode classNode = controller.getClassNode();
         Expression typeOrTargetRef = methodReferenceExpression.getExpression();
         boolean isClassExpression = (typeOrTargetRef instanceof ClassExpression);
+        boolean targetIsArgument = false; // implied argument for expr::staticMethod?
         ClassNode typeOrTargetRefType = isClassExpression ? typeOrTargetRef.getType()
                 : controller.getTypeChooser().resolveType(typeOrTargetRef, classNode);
 
@@ -107,35 +108,44 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
 
         if (isExtensionMethod(methodRefMethod)) {
             ExtensionMethodNode extensionMethodNode = (ExtensionMethodNode) methodRefMethod;
-            methodRefMethod = extensionMethodNode.getExtensionMethodNode();
-            if (extensionMethodNode.isStaticExtension()) {
+            methodRefMethod  = extensionMethodNode.getExtensionMethodNode();
+            boolean isStatic = extensionMethodNode.isStaticExtension();
+            if (isStatic) { // create adapter method to pass extra argument
                 methodRefMethod = addSyntheticMethodForDGSM(methodRefMethod);
             }
-            typeOrTargetRef = makeClassTarget(methodRefMethod.getDeclaringClass(), typeOrTargetRef);
-            typeOrTargetRefType = typeOrTargetRef.getType();
-
+            if (isStatic || isClassExpression) {
+                // replace expression with declaring type
+                typeOrTargetRefType = methodRefMethod.getDeclaringClass();
+                typeOrTargetRef = makeClassTarget(typeOrTargetRefType, typeOrTargetRef);
+            } else { // GROOVY-10653
+                targetIsArgument = true; // ex: "string"::size
+            }
         } 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();
+                // GROOVY-9813: reference to variadic method which needs adapter method to match runtime arguments to its parameters
+                if (!isClassExpression && !methodRefMethod.isStatic() && !methodRefMethod.getDeclaringClass().equals(classNode)) {
+                    targetIsArgument = true; // GROOVY-10653: create static adapter in source class with target as first parameter
+                    mParameters += 1;
+                }
+                methodRefMethod = addSyntheticMethodForVariadicReference(methodRefMethod, mParameters, isClassExpression || targetIsArgument);
+                if (methodRefMethod.isStatic() && !targetIsArgument) {
+                    // replace expression with declaring type
+                    typeOrTargetRefType = methodRefMethod.getDeclaringClass();
+                    typeOrTargetRef = makeClassTarget(typeOrTargetRefType, typeOrTargetRef);
                 }
             }
         }
 
         if (!isClassExpression) {
-            if (isConstructorReference) {
-                // TODO: move the checking code to the parser
-                addFatalError("Constructor reference must be className::new", methodReferenceExpression);
-            } else if (methodRefMethod.isStatic()) {
-                ClassExpression classExpression = classX(typeOrTargetRefType);
-                classExpression.setSourcePosition(typeOrTargetRef);
-                typeOrTargetRef = classExpression;
+            if (isConstructorReference) { // TODO: move this check to the parser
+                addFatalError("Constructor reference must be TypeName::new", methodReferenceExpression);
+            } else if (methodRefMethod.isStatic() && !targetIsArgument) {
+                // "string"::valueOf refers to static method, so instance is superflous
+                typeOrTargetRef = makeClassTarget(typeOrTargetRefType, typeOrTargetRef);
                 isClassExpression = true;
             } else {
                 typeOrTargetRef.visit(controller.getAcg());
@@ -151,20 +161,21 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
             referenceKind = Opcodes.H_INVOKEVIRTUAL;
         }
 
+        String methodName = abstractMethod.getName();
+        String methodDesc = BytecodeHelper.getMethodDescriptor(functionalInterfaceType.redirect(),
+                isClassExpression ? Parameter.EMPTY_ARRAY : new Parameter[]{new Parameter(typeOrTargetRefType, "__METHODREF_EXPR_INSTANCE")});
+
         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
-                    )
+            Handle bootstrapMethod = createBootstrapMethod(classNode.isInterface(), false);
+            Object[] bootstrapArgs = createBootstrapMethodArguments(
+                    abstractMethodDesc,
+                    referenceKind,
+                    methodRefMethod.getDeclaringClass(),
+                    methodRefMethod,
+                    false
             );
+            controller.getMethodVisitor().visitInvokeDynamicInsn(methodName, methodDesc, bootstrapMethod, bootstrapArgs);
         } finally {
             methodRefMethod.removeNodeMetaData(ORIGINAL_PARAMETERS_WITH_EXACT_TYPE);
         }
@@ -281,17 +292,6 @@ public class StaticTypesMethodReferenceExpressionWriter extends MethodReferenceE
             returnS(returnValue));
     }
 
-    private String createAbstractMethodDesc(final ClassNode functionalInterfaceType, final Expression methodRef) {
-        List<Parameter> methodReferenceSharedVariableList = new ArrayList<>();
-
-        if (!(methodRef instanceof ClassExpression)) {
-            prependParameter(methodReferenceSharedVariableList, "__METHODREF_EXPR_INSTANCE",
-                controller.getTypeChooser().resolveType(methodRef, controller.getClassNode()));
-        }
-
-        return BytecodeHelper.getMethodDescriptor(functionalInterfaceType.redirect(), methodReferenceSharedVariableList.toArray(Parameter.EMPTY_ARRAY));
-    }
-
     private Parameter[] createParametersWithExactType(final MethodNode abstractMethod, final ClassNode[] inferredParamTypes) {
         // MUST clone the parameters to avoid impacting the original parameter type of SAM
         Parameter[] parameters = GeneralUtils.cloneParams(abstractMethod.getParameters());
diff --git a/src/test/groovy/transform/stc/LambdaTest.groovy b/src/test/groovy/transform/stc/LambdaTest.groovy
index 20451116df..a0165db6a7 100644
--- a/src/test/groovy/transform/stc/LambdaTest.groovy
+++ b/src/test/groovy/transform/stc/LambdaTest.groovy
@@ -130,6 +130,31 @@ final class LambdaTest {
         '''
     }
 
+    @Test
+    void testBiFunctionAndVariadicMethod() {
+        assertScript '''
+            import groovy.transform.CompileStatic
+            import java.util.function.BiFunction
+
+            class C {
+                List m(... args) {
+                    [this,*args]
+                }
+            }
+
+            @CompileStatic
+            void test(C c) {
+                BiFunction<Integer, Integer, List> f = (i, j) -> c.m(i, j)
+                def list = f.apply(1,2)
+                assert list.size() == 3
+                assert list[0] == c
+                assert list[1] == 1
+                assert list[2] == 2
+            }
+            test(new C())
+        '''
+    }
+
     @Test
     void testPredicate() {
         assertScript '''
diff --git a/src/test/groovy/transform/stc/MethodReferenceTest.groovy b/src/test/groovy/transform/stc/MethodReferenceTest.groovy
index e3b5f894cb..fc9f07d58c 100644
--- a/src/test/groovy/transform/stc/MethodReferenceTest.groovy
+++ b/src/test/groovy/transform/stc/MethodReferenceTest.groovy
@@ -294,13 +294,12 @@ final class MethodReferenceTest {
         '''
     }
 
-    @Test // instance::instanceMethod
-    @groovy.test.NotYetImplemented
+    @Test // instance::instanceMethod -- GROOVY-10653
     void testFunctionII2() {
-        assertScript shell, """
+        assertScript shell, '''
             class C {
-                def m(... args) {
-                    [this, *args]
+                List m(... args) {
+                    [this,*args]
                 }
             }
             @CompileStatic
@@ -313,7 +312,20 @@ final class MethodReferenceTest {
                 assert list[2] == 2
             }
             test(new C())
-        """
+        '''
+    }
+
+    @Test // instance::instanceMethod (DGM) -- GROOVY-10653
+    void testFunctionII3() {
+        assertScript shell, '''
+            @CompileStatic
+            int test(CharSequence chars) {
+                IntSupplier sizing = chars::size // from StringGroovyMethods
+                return sizing.getAsInt()
+            }
+            int size = test("foo")
+            assert size == 3
+        '''
     }
 
     @Test // instance::instanceMethod -- GROOVY-10057