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/12/04 15:54:24 UTC

[groovy] branch master updated: GROOVY-10859: `Type::name` resolves instance method of `Type` or `Class`

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 7ea11bbf27 GROOVY-10859: `Type::name` resolves instance method of `Type` or `Class`
7ea11bbf27 is described below

commit 7ea11bbf2724d8dcdd5f6d4894e0fdae1cb2349e
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sun Dec 4 09:38:03 2022 -0600

    GROOVY-10859: `Type::name` resolves instance method of `Type` or `Class`
---
 src/main/java/groovy/lang/MetaClassImpl.java       | 99 ++++++++++------------
 src/test/groovy/ClosureMethodCallTest.groovy       | 62 ++++++++------
 .../transform/stc/MethodReferenceTest.groovy       | 25 ++++--
 3 files changed, 97 insertions(+), 89 deletions(-)

diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java
index 1b9f504ffc..59062fab53 100644
--- a/src/main/java/groovy/lang/MetaClassImpl.java
+++ b/src/main/java/groovy/lang/MetaClassImpl.java
@@ -1010,76 +1010,63 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
         return invokeMethod(theClass, object, methodName, originalArguments, false, false);
     }
 
-    private Object invokeMethodClosure(Object object, Object[] arguments) {
-        MethodClosure mc = (MethodClosure) object;
-
-        Object owner = mc.getOwner();
-        String methodName = mc.getMethod();
+    private Object invokeMethodClosure(final MethodClosure object, final Object[] arguments) {
+        Object owner = object.getOwner();
+        String method = object.getMethod();
         boolean ownerIsClass = (owner instanceof Class);
         Class ownerClass = ownerIsClass ? (Class) owner : owner.getClass();
         final MetaClass ownerMetaClass = registry.getMetaClass(ownerClass);
 
         try {
-            return ownerMetaClass.invokeMethod(ownerClass, owner, methodName, arguments, false, false);
-
-        } catch (MissingMethodExceptionNoStack | InvokerInvocationException | IllegalArgumentException e) {
-            if (ownerIsClass) {
-                if (MethodClosure.NEW.equals(methodName)) { // CONSTRUCTOR REFERENCE
-                    if (!ownerClass.isArray()) {
-                        return ownerMetaClass.invokeConstructor(arguments);
-
-                    } else {
-                        if (arguments.length == 0) {
-                            throw new GroovyRuntimeException("The arguments(specifying size) are required to create array[" + ownerClass.getCanonicalName() + "]");
-                        }
+            return ownerMetaClass.invokeMethod(ownerClass, owner, method, arguments, false, false);
 
-                        int arrayDimension = ArrayTypeUtils.dimension(ownerClass);
+        } catch (MissingMethodException | InvokerInvocationException | IllegalArgumentException e) { // TODO: What if method throws IllegalArgumentException?
+            if (!ownerIsClass) {
+                throw e;
+            }
+            if (MethodClosure.NEW.equals(method)) {
+              // CONSTRUCTOR REFERENCE
 
-                        if (arguments.length > arrayDimension) {
-                            throw new GroovyRuntimeException("The length[" + arguments.length + "] of arguments should not be greater than the dimensions[" + arrayDimension + "] of array[" + ownerClass.getCanonicalName() + "]");
-                        }
+                if (!ownerClass.isArray())
+                    return ownerMetaClass.invokeConstructor(arguments);
 
-                        int[] sizeArray = new int[arguments.length];
+                int nArguments = arguments.length;
+                if (nArguments == 0) {
+                    throw new GroovyRuntimeException("The arguments(specifying size) are required to create array[" + ownerClass.getCanonicalName() + "]");
+                }
+                int arrayDimension = ArrayTypeUtils.dimension(ownerClass);
+                if (arrayDimension < nArguments) {
+                    throw new GroovyRuntimeException("The length[" + nArguments + "] of arguments should not be greater than the dimensions[" + arrayDimension + "] of array[" + ownerClass.getCanonicalName() + "]");
+                }
+                Class elementType = arrayDimension == nArguments
+                        ? ArrayTypeUtils.elementType(ownerClass)
+                        : ArrayTypeUtils.elementType(ownerClass, arrayDimension - nArguments);
+                return Array.newInstance(elementType, Arrays.stream(arguments).mapToInt(argument ->
+                    argument instanceof Integer ? (Integer) argument : Integer.parseInt(String.valueOf(argument))
+                ).toArray());
+            } else {
+              // METHOD REFERENCE
 
-                        for (int i = 0, n = sizeArray.length; i < n; i += 1) {
-                            Object argument = arguments[i];
-                            if (argument instanceof Integer) {
-                                sizeArray[i] = (Integer) argument;
-                            } else {
-                                sizeArray[i] = Integer.parseInt(String.valueOf(argument));
-                            }
-                        }
+                // if the owner is a class and the method closure can be related to some instance method(s)
+                // try to invoke method with adjusted arguments -- first argument is instance of owner type
+                if (arguments.length > 0 && ownerClass.isAssignableFrom(arguments[0].getClass())
+                        && (Boolean) object.getProperty(MethodClosure.ANY_INSTANCE_METHOD_EXISTS)) {
+                    try {
+                        Object newReceiver = arguments[0];
+                        Object[] newArguments = Arrays.copyOfRange(arguments, 1, arguments.length);
+                        return ownerMetaClass.invokeMethod(ownerClass, newReceiver, method, newArguments, false, false);
+                    } catch (MissingMethodException ignore) {}
+                }
 
-                        Class arrayType = arguments.length == arrayDimension
-                                ? ArrayTypeUtils.elementType(ownerClass) // Just for better performance, though we can use reduceDimension only
-                                : ArrayTypeUtils.elementType(ownerClass, (arrayDimension - arguments.length));
-                        return Array.newInstance(arrayType, sizeArray);
-                    }
-                } else if (ownerClass != Class.class) { // not "new"; maybe it's a reference to a Class method
+                if (ownerClass != Class.class) { // maybe it's a reference to a Class method
                     try {
                         MetaClass cmc = registry.getMetaClass(Class.class);
-                        return cmc.invokeMethod(Class.class, owner, methodName, arguments, false, false);
-
-                    } catch (MissingMethodExceptionNoStack nope) {
-                    }
+                        return cmc.invokeMethod(Class.class, owner, method, arguments, false, false);
+                    } catch (MissingMethodException ignore) {}
                 }
-            }
 
-            // METHOD REFERENCE
-            // if the owner is a class and the method closure can be related to some instance method(s),
-            // try to invoke method with adjusted arguments -- first argument is instance of owner type;
-            // otherwise re-throw the exception
-            if (!(ownerIsClass && (Boolean) mc.getProperty(MethodClosure.ANY_INSTANCE_METHOD_EXISTS))) {
-                throw e;
+                return invokeMissingMethod(object, method, arguments);
             }
-
-            if (arguments.length < 1 || !ownerClass.isAssignableFrom(arguments[0].getClass())) {
-                return invokeMissingMethod(object, methodName, arguments);
-            }
-
-            Object newReceiver = arguments[0];
-            Object[] newArguments = Arrays.copyOfRange(arguments, 1, arguments.length);
-            return ownerMetaClass.invokeMethod(ownerClass, newReceiver, methodName, newArguments, false, false);
         }
     }
 
@@ -1221,7 +1208,7 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
             if (CALL_METHOD.equals(methodName) || DO_CALL_METHOD.equals(methodName)) {
                 final Class objectClass = object.getClass();
                 if (objectClass == MethodClosure.class) {
-                    return this.invokeMethodClosure(object, arguments);
+                    return this.invokeMethodClosure((MethodClosure) object, arguments);
                 } else if (objectClass == CurriedClosure.class) {
                     final CurriedClosure cc = (CurriedClosure) object;
                     // change the arguments for an uncurried call
diff --git a/src/test/groovy/ClosureMethodCallTest.groovy b/src/test/groovy/ClosureMethodCallTest.groovy
index 4a689742bd..a68f476d2c 100644
--- a/src/test/groovy/ClosureMethodCallTest.groovy
+++ b/src/test/groovy/ClosureMethodCallTest.groovy
@@ -18,66 +18,75 @@
  */
 package groovy
 
-import groovy.test.GroovyTestCase
+import org.junit.Test
 
 import java.util.concurrent.Executor
 import java.util.concurrent.Executors
 
-class ClosureMethodCallTest extends GroovyTestCase {
+import static groovy.test.GroovyAssert.assertScript
+import static groovy.test.GroovyAssert.shouldFail
 
+final class ClosureMethodCallTest {
+
+    @Test
     void testCallingClosureWithMultipleArguments() {
         def foo
         def closure = { a, b -> foo = "hello ${a} and ${b}".toString() }
 
-        closure("james", "bob")
+        closure('james', 'bob')
 
-        assert foo == "hello james and bob"
+        assert foo == 'hello james and bob'
 
-        closure.call("sam", "james")
+        closure.call('sam', 'james')
 
-        assert foo == "hello sam and james"
+        assert foo == 'hello sam and james'
     }
 
+    @Test // GROOVY-2266
     void testClosureCallMethodWithObjectArray() {
-        // GROOVY-2266
         def args = [1] as Object[]
         def closure = { x -> x[0] }
         assert closure.call(args) == 1
     }
 
+    @Test
     void testClosureWithStringArrayCastet() {
         def doSomething = { list -> list }
 
-        String[] x = ["hello", "world"]
-        String[] y = ["hello", "world"]
+        String[] x = ['hello', 'world']
+        String[] y = ['hello', 'world']
 
         assert doSomething(x as String[]) == x
         assert doSomething(y) == y
     }
 
+    @Test
     void testClosureAsLocalVar() {
         def local = { Map params -> params.x * params.y }
         assert local(x: 2, y: 3) == 6
     }
 
+    @Test
     void testClosureDirectly() {
         assert { Map params -> params.x * params.y }(x: 2, y: 3) == 6
     }
 
     def attribute
 
+    @Test
     void testClosureAsAttribute() {
         attribute = { Map params -> params.x * params.y }
         assert attribute(x: 2, y: 3) == 6
     }
 
+    @Test
     void testSystemOutPrintlnAsAClosure() {
         def closure = System.out.&println
-        closure("Hello world")
+        closure('Hello world')
     }
 
-    //GROOVY-6819
-    void test() {
+    @Test // GROOVY-6819
+    void testFixForIncompatibleClassChangeError() {
         assertScript '''
             class Foo {
                 static justcallme(Closure block) {
@@ -94,7 +103,7 @@ class ClosureMethodCallTest extends GroovyTestCase {
         '''
     }
 
-    //GROOVY-9140
+    @Test // GROOVY-9140
     void testCorrectErrorForClassInstanceMethodReference() {
         assertScript '''
             class Y {
@@ -104,15 +113,16 @@ class ClosureMethodCallTest extends GroovyTestCase {
             ref = Y.&m
             assert ref(new Y()) == 1
         '''
+
         shouldFail MissingMethodException, '''
             class Y {
                 def m() {1}
             }
 
             ref = Y.&m
-            assert ref(new Y()) == 1
             assert ref() == 1
         '''
+
         shouldFail MissingMethodException, '''
             class Y {
                 def m() {1}
@@ -123,19 +133,19 @@ class ClosureMethodCallTest extends GroovyTestCase {
         '''
     }
 
-    //GROOVY-9397
+    @Test // GROOVY-9397
     void testRespondsToIsThreadSafe() {
-      final Executor executor = Executors.newCachedThreadPool()
-      try {
-        final Closure action = { -> }
-        // ensure that executing the closure and calling respondsTo
-        // concurrently doesn't throw an exception.
-        for (int i = 0; i < 500; ++i) {
-          executor.execute(action)
-          executor.execute(() -> action.respondsTo('test'))
+        final Executor executor = Executors.newCachedThreadPool()
+        try {
+            final Closure action = { -> }
+            // ensure that executing the closure and calling respondsTo
+            // concurrently doesn't throw an exception.
+            for (int i = 0; i < 500; ++i) {
+                executor.execute(action)
+                executor.execute(() -> action.respondsTo('test'))
+            }
+        } finally {
+            executor.shutdownNow();
         }
-      } finally {
-        executor.shutdownNow();
-      }
     }
 }
diff --git a/src/test/groovy/transform/stc/MethodReferenceTest.groovy b/src/test/groovy/transform/stc/MethodReferenceTest.groovy
index 8d477a1ead..79dcbf2bcd 100644
--- a/src/test/groovy/transform/stc/MethodReferenceTest.groovy
+++ b/src/test/groovy/transform/stc/MethodReferenceTest.groovy
@@ -28,9 +28,9 @@ final class MethodReferenceTest {
 
     private final GroovyShell shell = GroovyShell.withConfig {
         imports {
-            normal 'groovy.transform.CompileStatic'
-            normal 'java.util.stream.Collectors'
+            star 'groovy.transform'
             star 'java.util.function'
+            normal 'java.util.stream.Collectors'
         }
     }
 
@@ -94,8 +94,6 @@ final class MethodReferenceTest {
             void test() {
                 [1, 2, 3].stream().map(String::toString).collect(Collectors.toList())
             }
-
-            test()
         '''
         assert err =~ /Invalid receiver type: java.lang.Integer is not compatible with java.lang.String/
     }
@@ -103,13 +101,11 @@ final class MethodReferenceTest {
     @Test // class::instanceMethod -- GROOVY-9814
     void testFunctionCI5() {
         assertScript shell, '''
-            @CompileStatic
             class One { String id }
 
-            @CompileStatic
             class Two extends One { }
 
-            @CompileStatic @groovy.transform.Immutable(knownImmutableClasses=[Function])
+            @CompileStatic @Immutable(knownImmutableClasses=[Function])
             class FunctionHolder<T> {
                 Function<T, ?> extractor
 
@@ -959,6 +955,21 @@ final class MethodReferenceTest {
         assert err.message.contains("Failed to find class method 'toString()' for the type: java.lang.Object")
     }
 
+    @Test // GROOVY-10859
+    void testDynamicMethodSelection() {
+        for (tag in ['@TypeChecked', '@CompileStatic', '@CompileDynamic']) {
+            assertScript shell, """
+                $tag
+                void test() {
+                    def result = [[]].stream().flatMap(List::stream).toList()
+                    assert result.isEmpty()
+                }
+
+                test()
+            """
+        }
+    }
+
     @Test // GROOVY-10742, GROOVY-10858
     void testIncompatibleReturnType() {
         def err = shouldFail shell, '''