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 2023/07/14 17:11:47 UTC
[groovy] branch master updated: GROOVY-5051, GROOVY-10568: add test case
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 e16a44ca98 GROOVY-5051, GROOVY-10568: add test case
e16a44ca98 is described below
commit e16a44ca98cdcecfcbac5aeffe55b4800fa14139
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Fri Jul 14 11:44:32 2023 -0500
GROOVY-5051, GROOVY-10568: add test case
---
src/main/java/groovy/lang/MetaClassImpl.java | 2 +-
.../codehaus/groovy/runtime/CurriedClosure.java | 128 +++++++++++----------
.../org/codehaus/groovy/runtime/InvokerHelper.java | 5 +-
.../org/codehaus/groovy/runtime/MethodClosure.java | 30 +++--
.../groovy/runtime/ScriptBytecodeAdapter.java | 8 +-
src/test/groovy/ClosureCurryTest.groovy | 48 ++++++--
6 files changed, 133 insertions(+), 88 deletions(-)
diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java
index f06923693f..b6a5257bff 100644
--- a/src/main/java/groovy/lang/MetaClassImpl.java
+++ b/src/main/java/groovy/lang/MetaClassImpl.java
@@ -2799,7 +2799,7 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
checkInitalised();
//----------------------------------------------------------------------
- // Unwrap wrapped values fo now - the new MOP will handle them properly
+ // Unwrap wrapped values for now - the new MOP will handle them properly
//----------------------------------------------------------------------
if (newValue instanceof Wrapper) newValue = ((Wrapper) newValue).unwrap();
diff --git a/src/main/java/org/codehaus/groovy/runtime/CurriedClosure.java b/src/main/java/org/codehaus/groovy/runtime/CurriedClosure.java
index f770dc65fb..eea7321790 100644
--- a/src/main/java/org/codehaus/groovy/runtime/CurriedClosure.java
+++ b/src/main/java/org/codehaus/groovy/runtime/CurriedClosure.java
@@ -20,10 +20,13 @@ package org.codehaus.groovy.runtime;
import groovy.lang.Closure;
+import static org.codehaus.groovy.runtime.ArrayGroovyMethods.last;
+
/**
- * A wrapper for Closure to support currying.
- * Normally used only internally through the <code>curry()</code>, <code>rcurry()</code> or
+ * A wrapper for Closure to support currying. Normally used only internally
+ * through the <code>curry()</code>, <code>rcurry()</code> or
* <code>ncurry()</code> methods on <code>Closure</code>.
+ * <p>
* Typical usages:
* <pre class="groovyTestCase">
* // normal usage
@@ -47,118 +50,127 @@ import groovy.lang.Closure;
public final class CurriedClosure<V> extends Closure<V> {
private static final long serialVersionUID = 2077643745780234126L;
- private final Object[] curriedParams;
+ private final Object[] curriedArguments;
private final int minParamsExpected;
private int index;
- private Class varargType = null;
+ /** the last parameter type, if it's an array */
+ private Class<?> varargType;
/**
- * Creates the curried closure.
- *
* @param index the position where the parameters should be injected (-ve for lazy)
* @param uncurriedClosure the closure to be called after the curried parameters are injected
* @param arguments the supplied parameters
*/
- public CurriedClosure(int index, Closure<V> uncurriedClosure, Object... arguments) {
+ public CurriedClosure(final int index, final Closure<V> uncurriedClosure, final Object... arguments) {
super(uncurriedClosure.clone());
- curriedParams = arguments;
+
this.index = index;
- final int origMaxLen = uncurriedClosure.getMaximumNumberOfParameters();
- maximumNumberOfParameters = origMaxLen - arguments.length;
- Class[] classes = uncurriedClosure.getParameterTypes();
- Class lastType = classes.length == 0 ? null : classes[classes.length-1];
- if (lastType != null && lastType.isArray()) {
- varargType = lastType;
+ this.curriedArguments = arguments;
+ int maxLen = uncurriedClosure.getMaximumNumberOfParameters();
+ this.maximumNumberOfParameters = (maxLen - arguments.length);
+
+ Class<?>[] parameterTypes = uncurriedClosure.getParameterTypes();
+ if (parameterTypes.length > 0 && last(parameterTypes).isArray()){
+ this.varargType = last(parameterTypes);
}
- if (!isVararg()) {
+ if (isVararg()) {
+ this.minParamsExpected = 0;
+ } else {
// perform some early param checking for non-vararg case
if (index < 0) {
// normalise
- this.index += origMaxLen;
- minParamsExpected = 0;
+ this.index += maxLen;
+ this.minParamsExpected = 0;
} else {
- minParamsExpected = index + arguments.length;
+ this.minParamsExpected = index + arguments.length;
}
- if (maximumNumberOfParameters < 0) {
- throw new IllegalArgumentException("Can't curry " + arguments.length + " arguments for a closure with " + origMaxLen + " parameters.");
+
+ if (this.maximumNumberOfParameters < 0) {
+ throw new IllegalArgumentException("Can't curry " + arguments.length + " arguments for a closure with " + maxLen + " parameters.");
}
if (index < 0) {
- if (index < -origMaxLen || index > -arguments.length)
- throw new IllegalArgumentException("To curry " + arguments.length + " argument(s) expect index range " +
- (-origMaxLen) + ".." + (-arguments.length) + " but found " + index);
- } else if (index > maximumNumberOfParameters) {
- throw new IllegalArgumentException("To curry " + arguments.length + " argument(s) expect index range 0.." +
- maximumNumberOfParameters + " but found " + index);
+ int lower = -maxLen;
+ int upper = -arguments.length;
+ if (index < lower || index > upper)
+ throw new IllegalArgumentException("To curry " + arguments.length + " argument(s) expect index range " + lower + ".." + upper + " but found " + index);
+ } else if (index > this.maximumNumberOfParameters) {
+ throw new IllegalArgumentException("To curry " + arguments.length + " argument(s) expect index range 0.." + this.maximumNumberOfParameters + " but found " + index);
}
- } else {
- minParamsExpected = 0;
}
}
- public CurriedClosure(Closure<V> uncurriedClosure, Object... arguments) {
+ public CurriedClosure(final Closure<V> uncurriedClosure, final Object... arguments) {
this(0, uncurriedClosure, arguments);
}
- public Object[] getUncurriedArguments(Object... arguments) {
+ //--------------------------------------------------------------------------
+
+ public Object[] getUncurriedArguments(final Object... arguments) {
if (isVararg()) {
- int normalizedIndex = index < 0 ? index + arguments.length + curriedParams.length : index;
+ int normalizedIndex = index < 0 ? index + arguments.length + curriedArguments.length : index;
if (normalizedIndex < 0 || normalizedIndex > arguments.length) {
throw new IllegalArgumentException("When currying expected index range between " +
- (-arguments.length - curriedParams.length) + ".." + (arguments.length + curriedParams.length) + " but found " + index);
+ (-arguments.length - curriedArguments.length) + ".." + (arguments.length + curriedArguments.length) + " but found " + index);
}
- return createNewCurriedParams(normalizedIndex, arguments);
+ return getArguments(normalizedIndex, arguments);
}
- if (curriedParams.length + arguments.length < minParamsExpected) {
+ if (curriedArguments.length + arguments.length < minParamsExpected) {
throw new IllegalArgumentException("When currying expected at least " + index + " argument(s) to be supplied before known curried arguments but found " + arguments.length);
}
- int newIndex = Math.min(index, curriedParams.length + arguments.length - 1);
+ int newIndex = Math.min(index, curriedArguments.length + arguments.length - 1);
// rcurried arguments are done lazily to allow normal method selection between overloaded alternatives
newIndex = Math.min(newIndex, arguments.length);
- return createNewCurriedParams(newIndex, arguments);
+ return getArguments(newIndex, arguments);
}
- private Object[] createNewCurriedParams(int normalizedIndex, Object[] arguments) {
- Object[] newCurriedParams = new Object[curriedParams.length + arguments.length];
- System.arraycopy(arguments, 0, newCurriedParams, 0, normalizedIndex);
- System.arraycopy(curriedParams, 0, newCurriedParams, normalizedIndex, curriedParams.length);
- if (arguments.length - normalizedIndex > 0)
- System.arraycopy(arguments, normalizedIndex, newCurriedParams, curriedParams.length + normalizedIndex, arguments.length - normalizedIndex);
- return newCurriedParams;
+ private Object[] getArguments(final int index, final Object[] arguments) {
+ Object[] newArguments = new Object[curriedArguments.length + arguments.length];
+ System.arraycopy(arguments, 0, newArguments, 0, index);
+ System.arraycopy(curriedArguments, 0, newArguments, index, curriedArguments.length);
+ if (arguments.length - index > 0)
+ System.arraycopy(arguments, index, newArguments, curriedArguments.length + index, arguments.length - index);
+ return newArguments;
}
@Override
- public void setDelegate(Object delegate) {
- ((Closure) getOwner()).setDelegate(delegate);
+ public void setDelegate(final Object delegate) {
+ getOwner().setDelegate(delegate);
}
@Override
public Object getDelegate() {
- return ((Closure) getOwner()).getDelegate();
+ return getOwner().getDelegate();
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Closure<V> getOwner() {
+ return (Closure<V>) super.getOwner();
}
@Override
- public void setResolveStrategy(int resolveStrategy) {
- ((Closure) getOwner()).setResolveStrategy(resolveStrategy);
+ public void setResolveStrategy(final int resolveStrategy) {
+ getOwner().setResolveStrategy(resolveStrategy);
}
@Override
public int getResolveStrategy() {
- return ((Closure) getOwner()).getResolveStrategy();
+ return getOwner().getResolveStrategy();
}
@Override
- @SuppressWarnings("unchecked")
public Object clone() {
- Closure<V> uncurriedClosure = (Closure<V>) ((Closure) getOwner()).clone();
- return new CurriedClosure<V>(index, uncurriedClosure, curriedParams);
+ @SuppressWarnings("unchecked")
+ Closure<V> uncurriedClosure = (Closure<V>) getOwner().clone();
+ return new CurriedClosure<V>(index, uncurriedClosure, curriedArguments);
}
@Override
public Class[] getParameterTypes() {
- Class[] oldParams = ((Closure) getOwner()).getParameterTypes();
+ Class[] oldParams = getOwner().getParameterTypes();
int extraParams = 0;
- int gobbledParams = curriedParams.length;
+ int gobbledParams = curriedArguments.length;
if (isVararg()) {
int numNonVarargs = oldParams.length - 1;
if (index < 0) {
@@ -167,7 +179,7 @@ public final class CurriedClosure<V> extends Closure<V> {
// so work out minimal type params and vararg on end will allow for other possibilities
if (absIndex > numNonVarargs) gobbledParams = numNonVarargs;
int newNumNonVarargs = numNonVarargs - gobbledParams;
- if (absIndex - curriedParams.length > newNumNonVarargs) extraParams = absIndex - curriedParams.length - newNumNonVarargs;
+ if (absIndex - curriedArguments.length > newNumNonVarargs) extraParams = absIndex - curriedArguments.length - newNumNonVarargs;
int keptParams = Math.max(numNonVarargs - absIndex, 0);
Class[] newParams = new Class[keptParams + newNumNonVarargs + extraParams + 1];
System.arraycopy(oldParams, 0, newParams, 0, keptParams);
@@ -177,11 +189,11 @@ public final class CurriedClosure<V> extends Closure<V> {
return newParams;
}
int leadingKept = Math.min(index, numNonVarargs);
- int trailingKept = Math.max(numNonVarargs - leadingKept - curriedParams.length, 0);
+ int trailingKept = Math.max(numNonVarargs - leadingKept - curriedArguments.length, 0);
if (index > leadingKept) extraParams = index - leadingKept;
Class[] newParams = new Class[leadingKept + trailingKept + extraParams + 1];
System.arraycopy(oldParams, 0, newParams, 0, leadingKept);
- if (trailingKept > 0) System.arraycopy(oldParams, leadingKept + curriedParams.length, newParams, leadingKept, trailingKept);
+ if (trailingKept > 0) System.arraycopy(oldParams, leadingKept + curriedArguments.length, newParams, leadingKept, trailingKept);
for (int i = 0; i < extraParams; i++) newParams[leadingKept + trailingKept + i] = varargType.getComponentType();
newParams[newParams.length - 1] = varargType;
return newParams;
@@ -189,7 +201,7 @@ public final class CurriedClosure<V> extends Closure<V> {
Class[] newParams = new Class[oldParams.length - gobbledParams + extraParams];
System.arraycopy(oldParams, 0, newParams, 0, index);
if (newParams.length - index > 0)
- System.arraycopy(oldParams, curriedParams.length + index, newParams, index, newParams.length - index);
+ System.arraycopy(oldParams, curriedArguments.length + index, newParams, index, newParams.length - index);
return newParams;
}
diff --git a/src/main/java/org/codehaus/groovy/runtime/InvokerHelper.java b/src/main/java/org/codehaus/groovy/runtime/InvokerHelper.java
index 7e4f547e53..87d3f990a5 100644
--- a/src/main/java/org/codehaus/groovy/runtime/InvokerHelper.java
+++ b/src/main/java/org/codehaus/groovy/runtime/InvokerHelper.java
@@ -226,9 +226,10 @@ public class InvokerHelper {
}
/**
- * Returns the method pointer for the given object name
+ * Returns a method closure for the given object and name.
*/
- public static Closure getMethodPointer(Object object, String methodName) {
+ @SuppressWarnings("rawtypes")
+ public static Closure getMethodPointer(final Object object, final String methodName) {
if (object == null) {
throw new NullPointerException("Cannot access method pointer for '" + methodName + "' on null object");
}
diff --git a/src/main/java/org/codehaus/groovy/runtime/MethodClosure.java b/src/main/java/org/codehaus/groovy/runtime/MethodClosure.java
index eaad0bdeb0..897c22f852 100644
--- a/src/main/java/org/codehaus/groovy/runtime/MethodClosure.java
+++ b/src/main/java/org/codehaus/groovy/runtime/MethodClosure.java
@@ -20,7 +20,6 @@ package org.codehaus.groovy.runtime;
import groovy.lang.Closure;
import groovy.lang.MetaMethod;
-import org.codehaus.groovy.reflection.CachedConstructor;
import org.codehaus.groovy.reflection.ReflectionCache;
import java.util.Arrays;
@@ -54,20 +53,21 @@ public class MethodClosure extends Closure {
this.maximumNumberOfParameters = 0;
this.parameterTypes = MetaClassHelper.EMPTY_TYPE_ARRAY;
- Class<?> clazz = owner.getClass() == Class.class ? (Class<?>) owner : owner.getClass();
+ Class<?> theClass = owner.getClass();
+ if (theClass == Class.class) theClass = (Class<?>) owner;
- if (NEW.equals(method)) {
- if (clazz.isArray()) {
- Class<?>[] sizeTypes = new Class[ArrayTypeUtils.dimension(clazz)];
+ if (method.equals(NEW)) {
+ if (theClass.isArray()) {
+ Class<?>[] sizeTypes = new Class[ArrayTypeUtils.dimension(theClass)];
Arrays.fill(sizeTypes, int.class);
setParameterTypesAndNumber(sizeTypes);
} else {
- for (CachedConstructor c : ReflectionCache.getCachedClass(clazz).getConstructors()) {
+ for (var c : ReflectionCache.getCachedClass(theClass).getConstructors()) {
setParameterTypesAndNumber(c.getNativeParameterTypes());
}
}
} else {
- for (MetaMethod m : InvokerHelper.getMetaClass(clazz).respondsTo(owner, method)) {
+ for (var m : InvokerHelper.getMetaClass(theClass).respondsTo(owner, method)) {
setParameterTypesAndNumber(makeParameterTypes(owner, m));
if (!m.isStatic()) {
this.anyInstanceMethodExists = true;
@@ -76,19 +76,17 @@ public class MethodClosure extends Closure {
}
}
- private void setParameterTypesAndNumber(final Class[] newParameterTypes) {
- if (!(newParameterTypes.length > this.maximumNumberOfParameters)) {
- return;
+ private void setParameterTypesAndNumber(final Class[] parameterTypes) {
+ if (parameterTypes.length > this.maximumNumberOfParameters) {
+ this.maximumNumberOfParameters = parameterTypes.length;
+ this.parameterTypes = parameterTypes;
}
- this.maximumNumberOfParameters = newParameterTypes.length;
- this.parameterTypes = newParameterTypes;
}
/*
- * Create a new array of parameter type.
- *
- * If the owner is a class instance(e.g. String) and the method is instance method,
- * we expand the original array of parameter type by inserting the owner at the first place of the expanded array
+ * Creates an array of parameter types. If the owner is a class instance (ex:
+ * String) and the method is instance method, we expand the original array of
+ * parameter type by inserting the owner at the first position of the array.
*/
private Class[] makeParameterTypes(final Object owner, final MetaMethod m) {
Class[] newParameterTypes;
diff --git a/src/main/java/org/codehaus/groovy/runtime/ScriptBytecodeAdapter.java b/src/main/java/org/codehaus/groovy/runtime/ScriptBytecodeAdapter.java
index 692904a8ab..1e14400e80 100644
--- a/src/main/java/org/codehaus/groovy/runtime/ScriptBytecodeAdapter.java
+++ b/src/main/java/org/codehaus/groovy/runtime/ScriptBytecodeAdapter.java
@@ -578,13 +578,13 @@ public class ScriptBytecodeAdapter {
// --------------------------------------------------------
/**
- * Returns the method pointer for the given object name
+ * Returns a method closure for the given object and name.
*
- * @param object the object containing the method
- * @param methodName the name of the method of interest
+ * @param object the object or class providing the method
+ * @param methodName the method(s) of interest
* @return the resulting Closure
*/
- public static Closure getMethodPointer(Object object, String methodName) {
+ public static Closure getMethodPointer(final Object object, final String methodName) {
return InvokerHelper.getMethodPointer(object, methodName);
}
diff --git a/src/test/groovy/ClosureCurryTest.groovy b/src/test/groovy/ClosureCurryTest.groovy
index 799b91fa18..20b5abbd76 100644
--- a/src/test/groovy/ClosureCurryTest.groovy
+++ b/src/test/groovy/ClosureCurryTest.groovy
@@ -18,14 +18,14 @@
*/
package groovy
-import groovy.test.GroovyTestCase
-import org.codehaus.groovy.runtime.DefaultGroovyMethods
+import org.junit.Test
-/**
- * Tests for curried closures
- */
-class ClosureCurryTest extends GroovyTestCase {
+import static groovy.test.GroovyAssert.assertScript
+import static groovy.test.GroovyAssert.shouldFail
+
+final class ClosureCurryTest {
+ @Test
void testCurry() {
def clos1 = {s1, s2 -> s1 + s2}
def clos2 = clos1.curry("hi")
@@ -103,6 +103,7 @@ class ClosureCurryTest extends GroovyTestCase {
assert value == "afg"
}
+ @Test
void testParameterTypes() {
def cl1 = {String s1, int i -> return s1 + i }
assert "foo5" == cl1("foo", 5)
@@ -114,6 +115,7 @@ class ClosureCurryTest extends GroovyTestCase {
assert [int] == cl2.getParameterTypes().toList()
}
+ @Test
void testVarargParameterTypes() {
def a = { Object one, Object[] others -> }
def b = a.ncurry(2, 'x', 'y')
@@ -127,6 +129,7 @@ class ClosureCurryTest extends GroovyTestCase {
assert h.parameterTypes.name == ['java.lang.String', '[Ljava.lang.Object;']
}
+ @Test
void testVarargCurry() {
def c = { arg, Object[] extras -> arg + ', ' + extras.join(', ') }
def d = c.curry( 1 ) //curry first param only
@@ -137,6 +140,7 @@ class ClosureCurryTest extends GroovyTestCase {
assert f( 13, 15 ) == '1, 3, 5, 7, 9, 11, 13, 15'
}
+ @Test
void testDelegate() {
def res = null
def c = {a -> res = z}
@@ -147,6 +151,7 @@ class ClosureCurryTest extends GroovyTestCase {
assert res == cc.delegate.z
}
+ @Test
void testExpandoWithCurry() {
def sz = 'java.util.Date'.size()
def c = {arg -> arg + delegate.getClass().name.size() }
@@ -157,18 +162,21 @@ class ClosureCurryTest extends GroovyTestCase {
assert d.bar() == 'baz' + sz
}
+ @Test
void testCurryMultiply() {
def multiply = { a, b -> a * b }
def doubler = multiply.curry(2)
assert doubler(4) == 8
}
+ @Test
void testRCurryDivide() {
def divide = { a, b -> a / b }
def halver = divide.rcurry(2)
assert halver(8) == 4
}
+ @Test
void testNCurryBinarySearch() {
def caseInsensitive = { a, b -> a.toLowerCase() <=> b.toLowerCase() } as Comparator
def caseSensitive = { a, b -> a <=> b } as Comparator
@@ -185,6 +193,7 @@ class ClosureCurryTest extends GroovyTestCase {
} == [-3, 2, -3, -4]
}
+ @Test
void testNestedNcurryRcurry() {
def operation = { int x, Closure f, int y -> f(x, y) }
def divider = operation.ncurry(1) { a, b -> a / b }
@@ -192,12 +201,14 @@ class ClosureCurryTest extends GroovyTestCase {
assert 50 == halver(100)
}
+ @Test
void testEquivalentsNormal() {
def a = {one, two, three, four, five -> "$one,$two,$three,$four,$five"}
def expected = '1,2,3,4,5'
checkEquivalents(a, expected)
}
+ @Test
void testEquivalentsVararg() {
def a = {one, two, Object[] others -> "one=$one, two=$two, others=${others.join(',')}"}
def expected = 'one=1, two=2, others=3,4,5'
@@ -229,6 +240,7 @@ class ClosureCurryTest extends GroovyTestCase {
}
}
+ @Test
void testNullVariants() {
assert { x, y -> x ?: y }.curry(null)(null) == null
assert { x, y -> x ?: y }.curry(null)(2) == 2
@@ -253,7 +265,7 @@ class ClosureCurryTest extends GroovyTestCase {
private a(b,c){ "2Obj: $b $c" }
private a(b){ "1Obj: $b" }
-
+ @Test
void testCurryWithMethodClosure() {
def c = (this.&a).curry(0)
assert c(1,2) == '3Int: 0 1 2'
@@ -268,6 +280,27 @@ class ClosureCurryTest extends GroovyTestCase {
assert b() == '1Obj: 0'
}
+ @Test // GROOVY-5051, GROOVY-10568
+ void testCurryWithMethodClosure2() {
+ assertScript '''
+ class C {
+ private void m(p) {
+ println p
+ }
+ void test() {
+ def proc = C.&m.curry(this, 'x') // was "this.&m.curry('x')"
+ proc()
+ }
+ }
+ new C().test()
+
+ class D extends C {
+ }
+ new D().test() // IllegalArgumentException: Can't curry 1 arguments for a closure with 0 parameters
+ '''
+ }
+
+ @Test
void testInsufficientSuppliedArgsWhenUsingNCurry() {
def cl = { a, b, c = 'x' -> a + b + c }
assert cl.ncurry(1, 'b')('a') == 'abx'
@@ -280,6 +313,7 @@ class ClosureCurryTest extends GroovyTestCase {
assert cl.rcurry('b', 'c')() == 'bcx'
}
+ @Test
void testCurryInComboWithDefaultArgs() {
def cl = { a, b, c='c', d='d' -> a + b + c + d }
assert 'abcd' == cl.ncurry(0, 'a')('b')