groovy git commit: GROOVY-8252: AIOOBE in combination with ncurry and rcurry (closes #612)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

groovy git commit: GROOVY-8252: AIOOBE in combination with ncurry and rcurry (closes #612)

paulk
Repository: groovy
Updated Branches:
  refs/heads/GROOVY_2_4_X 38b0757e7 -> 37a8ef920


GROOVY-8252: AIOOBE in combination with ncurry and rcurry (closes #612)


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/37a8ef92
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/37a8ef92
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/37a8ef92

Branch: refs/heads/GROOVY_2_4_X
Commit: 37a8ef9204686658a628a60d58efddd0edfc61ee
Parents: 38b0757
Author: paulk <[hidden email]>
Authored: Mon Oct 2 11:29:43 2017 +1000
Committer: paulk <[hidden email]>
Committed: Mon Oct 9 16:12:03 2017 +1000

----------------------------------------------------------------------
 src/main/groovy/lang/Closure.java               |  8 ++++++
 .../codehaus/groovy/runtime/CurriedClosure.java | 26 +++++++++++++++---
 src/test/groovy/ClosureCurryTest.groovy         | 28 +++++++++++++++++++-
 3 files changed, 58 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/37a8ef92/src/main/groovy/lang/Closure.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/lang/Closure.java b/src/main/groovy/lang/Closure.java
index 503a01b..56602a5 100644
--- a/src/main/groovy/lang/Closure.java
+++ b/src/main/groovy/lang/Closure.java
@@ -555,6 +555,10 @@ public abstract class Closure<V> extends GroovyObjectSupport implements Cloneabl
      * assert halver(8) == 4
      * </pre>
      *
+     * The position of the curried parameters will be calculated lazily, for example,
+     * if two overloaded doCall methods are available, the supplied arguments plus the
+     * curried arguments will be concatenated and the result used for method selection.
+     *
      * @param arguments the arguments to bind
      * @return the new closure with its arguments bound
      * @see #curry(Object...)
@@ -599,6 +603,10 @@ public abstract class Closure<V> extends GroovyObjectSupport implements Cloneabl
      * // [BEE, Cat, ant, dog]  Not found but would belong in position 3
      * </pre>
      *
+     * The position of the curried parameters will be calculated eagerly
+     * and implies all arguments prior to the specified n index are supplied.
+     * Default parameter values prior to the n index will not be available.
+     *
      * @param n the index from which to bind parameters (may be -ve in which case it will be normalized)
      * @param arguments the arguments to bind
      * @return the new closure with its arguments bound

http://git-wip-us.apache.org/repos/asf/groovy/blob/37a8ef92/src/main/org/codehaus/groovy/runtime/CurriedClosure.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/runtime/CurriedClosure.java b/src/main/org/codehaus/groovy/runtime/CurriedClosure.java
index c66825e..22a83cb 100644
--- a/src/main/org/codehaus/groovy/runtime/CurriedClosure.java
+++ b/src/main/org/codehaus/groovy/runtime/CurriedClosure.java
@@ -39,15 +39,25 @@ import groovy.lang.Closure;
  * assert new CurriedClosure(unitAdder, "six", "ty")("minutes") == "sixty minutes"
  * </pre>
  *
- * @author Jochen Theodorou
- * @author Paul King
+ * Notes:
+ * <ul>
+ *     <li>Caters for Groovy's lazy (rcurry) and eager (ncurry) calculation of argument position</li>
+ * </ul>
  */
 public final class CurriedClosure<V> extends Closure<V> {
 
-    private Object[] curriedParams;
+    private final Object[] curriedParams;
+    private final int minParamsExpected;
     private int index;
     private Class varargType = null;
 
+    /**
+     * 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) {
         super(uncurriedClosure.clone());
         curriedParams = arguments;
@@ -65,6 +75,9 @@ public final class CurriedClosure<V> extends Closure<V> {
             if (index < 0) {
                 // normalise
                 this.index += origMaxLen;
+                minParamsExpected = 0;
+            } else {
+                minParamsExpected = index + arguments.length;
             }
             if (maximumNumberOfParameters < 0) {
                 throw new IllegalArgumentException("Can't curry " + arguments.length + " arguments for a closure with " + origMaxLen + " parameters.");
@@ -77,6 +90,8 @@ public final class CurriedClosure<V> extends Closure<V> {
                 throw new IllegalArgumentException("To curry " + arguments.length + " argument(s) expect index range 0.." +
                         maximumNumberOfParameters + " but found " + index);
             }
+        } else {
+            minParamsExpected = 0;
         }
     }
 
@@ -98,8 +113,13 @@ public final class CurriedClosure<V> extends Closure<V> {
                 System.arraycopy(arguments, normalizedIndex, newCurriedParams, curriedParams.length + normalizedIndex, arguments.length - normalizedIndex);
             return newCurriedParams;
         }
+        if (curriedParams.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);
+        }
         final Object newCurriedParams[] = new Object[curriedParams.length + arguments.length];
         int newIndex = Math.min(index, curriedParams.length + arguments.length - 1);
+        // rcurried arguments are done lazily to allow normal method selection between overloaded alternatives
+        newIndex = Math.min(newIndex, arguments.length);
         System.arraycopy(arguments, 0, newCurriedParams, 0, newIndex);
         System.arraycopy(curriedParams, 0, newCurriedParams, newIndex, curriedParams.length);
         if (arguments.length - newIndex > 0)

http://git-wip-us.apache.org/repos/asf/groovy/blob/37a8ef92/src/test/groovy/ClosureCurryTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/groovy/ClosureCurryTest.groovy b/src/test/groovy/ClosureCurryTest.groovy
index fbd5d1d..5bdfb42 100644
--- a/src/test/groovy/ClosureCurryTest.groovy
+++ b/src/test/groovy/ClosureCurryTest.groovy
@@ -21,7 +21,7 @@ package groovy
 import org.codehaus.groovy.runtime.DefaultGroovyMethods
 
 /**
- * @author Hallvard Tr�tteberg
+ * Tests for curried closures
  */
 class ClosureCurryTest extends GroovyTestCase {
 
@@ -252,6 +252,7 @@ class ClosureCurryTest extends GroovyTestCase {
     private a(b,c){ "2Obj: $b $c" }
     private a(b){ "1Obj: $b" }
 
+
     void testCurryWithMethodClosure() {
         def c = (this.&a).curry(0)
         assert c(1,2) == '3Int: 0 1 2'
@@ -265,4 +266,29 @@ class ClosureCurryTest extends GroovyTestCase {
         assert b(1) == '2Obj: 1 0'
         assert b() == '1Obj: 0'
     }
+
+    void testInsufficientSuppliedArgsWhenUsingNCurry() {
+        def cl = { a, b, c = 'x' -> a + b + c }
+        assert cl.ncurry(1, 'b')('a') == 'abx'
+        // ncurry is done eagerly and implies a minimum number of params
+        shouldFail(IllegalArgumentException) {
+            cl.ncurry(1, 'b')()
+        }
+        // rcurry is done lazily
+        assert cl.rcurry('b', 'c')('a') == 'abc'
+        assert cl.rcurry('b', 'c')() == 'bcx'
+    }
+
+    void testCurryInComboWithDefaultArgs() {
+        def cl = { a, b, c='c', d='d' -> a + b + c + d }
+        assert 'abcd' == cl.ncurry(0, 'a')('b')
+        assert 'xycd' == cl.ncurry(1, 'y')('x')
+        assert 'abdd' == cl.ncurry(0, 'a').rcurry('d')('b')
+        assert 'abcx' == cl.ncurry(3, 'x')('a', 'b', 'c')
+        shouldFail(IllegalArgumentException) {
+            // ncurry is done eagerly and implies a minimum number of params
+            // default arguments are ignored prior to the ncurried argument
+            cl.ncurry(4, 'd')('a', 'b')
+        }
+    }
 }