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, '''