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

emilles pushed a commit to branch GROOVY-8499
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit adc07b5daa35f64ae14b0e730b227a69aff31e96
Author: Eric Milles <[email protected]>
AuthorDate: Tue Jun 13 11:33:34 2023 -0500

    GROOVY-8499, GROOVY-11092: STC: `combinations` metadata
---
 src/main/java/groovy/util/GroovyCollections.java   | 111 +++++++++++----------
 .../groovy/runtime/DefaultGroovyMethods.java       |  33 +++---
 .../stc/ClosureParamTypeInferenceSTCTest.groovy    |  13 ++-
 .../groovy/transform/stc/CoercionSTCTest.groovy    |  14 ++-
 src/test/groovy/util/GroovyCollectionsTest.groovy  |  42 ++++----
 5 files changed, 118 insertions(+), 95 deletions(-)

diff --git a/src/main/java/groovy/util/GroovyCollections.java 
b/src/main/java/groovy/util/GroovyCollections.java
index fc8bb8e92f..4eba93638f 100644
--- a/src/main/java/groovy/util/GroovyCollections.java
+++ b/src/main/java/groovy/util/GroovyCollections.java
@@ -34,50 +34,21 @@ import java.util.List;
 import java.util.Set;
 import java.util.TreeSet;
 
-/**
- * A Collections utility class
- */
 public class GroovyCollections {
+
     /**
      * Finds all combinations of items from the given collections.
      *
      * @param collections the given collections
-     * @return a List of the combinations found
+     * @return A list of the combinations found.
      * @see #combinations(Iterable)
      */
-    public static List combinations(Object[] collections) {
+    public static List<List> combinations(Object[]    collections) {
         return combinations(Arrays.asList(collections));
     }
 
     /**
-     * Finds all non-empty subsequences of a list.
-     * E.g. <code>subsequences([1, 2, 3])</code> would be:
-     * [[1, 2, 3], [1, 3], [2, 3], [1, 2], [1], [2], [3]]
-     *
-     * @param items the List of items
-     * @return the subsequences from items
-     */
-    public static <T> Set<List<T>> subsequences(List<T> items) {
-        // items.inject([]){ ss, h -> ss.collect { it + [h] }  + ss + [[h]] }
-        Set<List<T>> ans = new HashSet<>();
-        for (T h : items) {
-            Set<List<T>> next = new HashSet<>();
-            for (List<T> it : ans) {
-                List<T> sublist = new ArrayList<>(it);
-                sublist.add(h);
-                next.add(sublist);
-            }
-            next.addAll(ans);
-            List<T> hlist = new ArrayList<>();
-            hlist.add(h);
-            next.add(hlist);
-            ans = next;
-        }
-        return ans;
-    }
-
-    /**
-     * Finds all combinations of items from the given Iterable aggregate of 
collections.
+     * Finds all combinations of items from the given collections.
      * So, <code>combinations([[true, false], [true, false]])</code>
      * is <code>[[true, true], [false, true], [true, false], [false, 
false]]</code>
      * and <code>combinations([['a', 'b'],[1, 2, 3]])</code>
@@ -87,38 +58,38 @@ public class GroovyCollections {
      * If an empty collection is found within the given collections, the 
result will be an empty list.
      *
      * @param collections the Iterable of given collections
-     * @return a List of the combinations found
+     * @return A list of the combinations found.
      * @since 2.2.0
      */
-    public static List combinations(Iterable collections) {
-        List collectedCombos = new ArrayList();
-        for (Object collection : collections) {
-            Iterable items = 
DefaultTypeTransformation.asCollection(collection);
-            if (collectedCombos.isEmpty()) {
-                for (Object item : items) {
-                    List l = new ArrayList();
-                    l.add(item);
-                    collectedCombos.add(l);
+    public static List<List> combinations(Iterable<?> collections) {
+        List<List<Object>> combos = new ArrayList<>();
+        for (var collection : collections) {
+            if (combos.isEmpty()) {
+                for (var object : 
DefaultTypeTransformation.asCollection(collection)) {
+                    List<Object> list = new ArrayList<>();
+                    list.add(object);
+                    combos.add(list);
                 }
             } else {
-                List savedCombos = new ArrayList(collectedCombos);
-                List newCombos = new ArrayList();
-                for (Object value : items) {
-                    for (Object savedCombo : savedCombos) {
-                        List oldList = new ArrayList((List) savedCombo);
-                        oldList.add(value);
-                        newCombos.add(oldList);
+                List<List<Object>> next = new ArrayList<>(); // each list plus 
each item
+                for (var object : 
DefaultTypeTransformation.asCollection(collection)) {
+                    for (var combo : combos) {
+                        List<Object> list = new ArrayList<>(combo);
+                        list.add(object);
+                        next.add(list);
                     }
                 }
-                collectedCombos = newCombos;
+                combos = next;
             }
-
-            if (collectedCombos.isEmpty())
+            if (combos.isEmpty())
                 break;
         }
-        return collectedCombos;
+        return (List) combos;
     }
 
+    /**
+     * @since 2.5.0
+     */
     public static <T> List<List<T>> inits(Iterable<T> collections) {
         List<T> copy = DefaultGroovyMethods.toList(collections);
         List<List<T>> result = new ArrayList<>();
@@ -129,6 +100,9 @@ public class GroovyCollections {
         return result;
     }
 
+    /**
+     * @since 2.5.0
+     */
     public static <T> List<List<T>> tails(Iterable<T> collections) {
         List<T> copy = DefaultGroovyMethods.toList(collections);
         List<List<T>> result = new ArrayList<>();
@@ -139,6 +113,34 @@ public class GroovyCollections {
         return result;
     }
 
+    /**
+     * Finds all non-empty subsequences of a list.
+     * E.g. <code>subsequences([1, 2, 3])</code> would be:
+     * [[1, 2, 3], [1, 3], [2, 3], [1, 2], [1], [2], [3]]
+     *
+     * @param items the List of items
+     * @return the subsequences from items
+     * @since 1.8.0
+     */
+    public static <T> Set<List<T>> subsequences(List<T> items) {
+        // items.inject([]){ ss, h -> ss.collect { it + [h] }  + ss + [[h]] }
+        Set<List<T>> ans = new HashSet<>();
+        for (T h : items) {
+            Set<List<T>> next = new HashSet<>();
+            for (List<T> it : ans) {
+                List<T> sublist = new ArrayList<>(it);
+                sublist.add(h);
+                next.add(sublist);
+            }
+            next.addAll(ans);
+            List<T> hlist = new ArrayList<>();
+            hlist.add(h);
+            next.add(hlist);
+            ans = next;
+        }
+        return ans;
+    }
+
     /**
      * Transposes an array of lists.
      *
@@ -355,5 +357,4 @@ public class GroovyCollections {
                 : new ClosureComparator<>(condition);
         return union(iterables, comparator);
     }
-
 }
diff --git 
a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java 
b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index 851a393ff9..8605cff657 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -4982,36 +4982,41 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
     }
 
     /**
-     * Adds GroovyCollections#combinations(Iterable) as a method on Iterables.
+     * Finds all combinations of items from the given aggregate of collections.
      * <p>
      * Example usage:
      * <pre class="groovyTestCase">
-     * assert [['a', 'b'],[1, 2, 3]].combinations() == [['a', 1], ['b', 1], 
['a', 2], ['b', 2], ['a', 3], ['b', 3]]
+     * result = [['a', 'b'], [1, 2, 3]].combinations()
+     * assert result == [['a', 1], ['b', 1], ['a', 2], ['b', 2], ['a', 3], 
['b', 3]]
      * </pre>
      *
-     * @param self an Iterable of collections
-     * @return a List of the combinations found
-     * @see groovy.util.GroovyCollections#combinations(java.lang.Iterable)
+     * @param self an iterable of collections
+     * @return A list of the combinations (lists).
+     * @see GroovyCollections#combinations(Iterable)
      * @since 2.2.0
      */
-    public static List combinations(Iterable self) {
+    public static List<List> combinations(Iterable self) {
         return GroovyCollections.combinations(self);
     }
 
     /**
-     * Adds GroovyCollections#combinations(Iterable, Closure) as a method on 
collections.
+     * Finds all combinations of items from the given aggregate of collections,
+     * then returns the results of the supplied transform.
      * <p>
      * Example usage:
-     * <pre class="groovyTestCase">assert [[2, 3],[4, 5, 6]].combinations {x,y 
{@code ->} x*y } == [8, 12, 10, 15, 12, 18]</pre>
+     * <pre class="groovyTestCase">
+     * result = [[2, 3], [4, 5, 6]].combinations { x,y {@code ->} x*y }
+     * assert result == [8, 12, 10, 15, 12, 18]
+     * </pre>
      *
-     * @param self a Collection of lists
-     * @param function a closure to be called on each combination
-     * @return a List of the results of applying the closure to each 
combination found
-     * @see groovy.util.GroovyCollections#combinations(Iterable)
+     * @param self an iterable of collections
+     * @param function a closure to be called on each combination (list)
+     * @return A list of the results of applying the closure to each 
combination.
+     * @see GroovyCollections#combinations(Iterable)
+     * @see #collect(Iterable,Closure)
      * @since 2.2.0
      */
-    @SuppressWarnings("unchecked")
-    public static List combinations(Iterable self, Closure<?> function) {
+    public static <T> List<T> combinations(Iterable self, 
@ClosureParams(value=FromString.class, options="List") Closure<T> function) {
         return collect(GroovyCollections.combinations(self), function);
     }
 
diff --git 
a/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy 
b/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy
index f8909ba0c7..d120383055 100644
--- a/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy
@@ -388,10 +388,15 @@ class ClosureParamTypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
 
     // GROOVY-8499
     void testParamCountCheck6() {
-        shouldFailWithMessages '''
-            ['ab'.chars,'12'.chars].combinations().collect((l,n) -> "$l$n")
-        ''',
-        'Incorrect number of parameters. Expected 1 but found 2'
+        assertScript '''
+            def result = ['ab'.chars,'12'.chars].combinations { l,n -> "$l$n" }
+            assert result == ['a1','b1','a2','b2']
+        '''
+        // cannot know in advance how many list elements
+        def err = shouldFail '''
+            ['ab'.chars,'12'.chars].combinations((l,n,x) -> "$l$n")
+        '''
+        assert err =~ /No signature of method.* is applicable for argument 
types: \(ArrayList\)/
     }
 
     // GROOVY-8816
diff --git a/src/test/groovy/transform/stc/CoercionSTCTest.groovy 
b/src/test/groovy/transform/stc/CoercionSTCTest.groovy
index 77fa5eeaef..aa2b1a0358 100644
--- a/src/test/groovy/transform/stc/CoercionSTCTest.groovy
+++ b/src/test/groovy/transform/stc/CoercionSTCTest.groovy
@@ -561,10 +561,14 @@ class CoercionSTCTest extends StaticTypeCheckingTestCase {
 
     // GROOVY-11092, GROOVY-8499
     void testCoerceToFunctionalInterface21() {
-        // TODO: if extension combinations indicates List<List<?>> this can 
work
-        shouldFailWithMessages '''
-            ['ab'.chars,'12'.chars].combinations().stream().map((l,n) -> 
"$l$n")
-        ''',
-        'Wrong number of parameters for method target: apply(java.lang.Object)'
+        assertScript '''
+            def result = 
['ab'.chars,'12'.chars].combinations().stream().map((l,n) -> "$l$n").toList()
+            assert result == ['a1','b1','a2','b2']
+        '''
+        // cannot know in advance how many list elements
+        def err = shouldFail '''
+            ['ab'.chars,'12'.chars].combinations().stream().map((l,n,x) -> 
"").toList()
+        '''
+        assert err =~ /No signature of method.* is applicable for argument 
types: \(ArrayList\)/
     }
 }
diff --git a/src/test/groovy/util/GroovyCollectionsTest.groovy 
b/src/test/groovy/util/GroovyCollectionsTest.groovy
index 2597182bdb..4602e096ea 100644
--- a/src/test/groovy/util/GroovyCollectionsTest.groovy
+++ b/src/test/groovy/util/GroovyCollectionsTest.groovy
@@ -18,7 +18,7 @@
  */
 package groovy.util
 
-import groovy.test.GroovyTestCase
+import org.junit.Test
 
 import static groovy.util.GroovyCollections.combinations
 import static groovy.util.GroovyCollections.max
@@ -26,34 +26,39 @@ import static groovy.util.GroovyCollections.min
 import static groovy.util.GroovyCollections.sum
 import static groovy.util.GroovyCollections.transpose
 
-final class GroovyCollectionsTest extends GroovyTestCase {
+final class GroovyCollectionsTest {
 
-    void testCombinations() {
+    @Test
+    void testCombinations0() {
+        // an empty iterable at any stage should result in an empty result
+        def input = [['a', 'b'], [1, 2, 3]]
+        assert combinations([[]] + input).isEmpty()
+        assert combinations(input + [[]]).isEmpty()
+        assert combinations(input + [[]] + input).isEmpty()
+    }
+
+    @Test
+    void testCombinations1() {
         // use Sets because we don't care about order
         Set expected = [
-                        ['a', 1], ['a', 2], ['a', 3],
-                        ['b', 1], ['b', 2], ['b', 3]
-                ]
-        Collection input = [['a', 'b'], [1, 2, 3]]
+            ['a', 1], ['a', 2], ['a', 3], ['b', 1], ['b', 2], ['b', 3]
+        ]
+        def input = [['a', 'b'], [1, 2, 3]]
 
-        // normal varargs versions should match Object[]
-        assert GroovyCollections.combinations(['a', 'b'], [1, 2, 3]) as Set == 
expected
-        assert combinations(['a', 'b'], [1, 2, 3]) as Set == expected
+        // varargs versions should match Object[]
+        assert GroovyCollections.combinations(input[0], input[1]) as Set == 
expected
+        assert combinations(input[0], input[1]) as Set == expected
 
         // spread versions should match Object[]
         assert GroovyCollections.combinations(*input) as Set == expected
         assert combinations(*input) as Set == expected
 
-        // collection versions should match Collection
+        // list versions should match Iterable
         assert GroovyCollections.combinations(input) as Set == expected
         assert combinations(input) as Set == expected
-
-        // an empty iterable should result in no combination
-        assert combinations([[]] + input).isEmpty()
-        assert combinations(input + [[]]).isEmpty()
-        assert combinations(input + [[]] + input).isEmpty()
     }
 
+    @Test
     void testTranspose() {
         // normal varargs versions should match Object[]
         assert GroovyCollections.transpose(['a', 'b'], [1, 2, 3]) == [['a', 
1], ['b', 2]]
@@ -70,6 +75,7 @@ final class GroovyCollectionsTest extends GroovyTestCase {
         assert transpose([]) == []
     }
 
+    @Test
     void testMin() {
         // normal varargs versions should match Object[]
         assert GroovyCollections.min('a', 'b') == 'a'
@@ -84,6 +90,7 @@ final class GroovyCollectionsTest extends GroovyTestCase {
         assert min([1, 2, 3]) == 1
     }
 
+    @Test
     void testMax() {
         // normal varargs versions should match Object[]
         assert GroovyCollections.max('a', 'b') == 'b'
@@ -98,6 +105,7 @@ final class GroovyCollectionsTest extends GroovyTestCase {
         assert max([1, 2, 3]) == 3
     }
 
+    @Test
     void testSum() {
         // normal varargs versions should match Object[]
         assert GroovyCollections.sum('a', 'b') == 'ab'
@@ -112,7 +120,7 @@ final class GroovyCollectionsTest extends GroovyTestCase {
         assert sum([1, 2, 3]) == 6
     }
 
-    // GROOVY-7267
+    @Test // GROOVY-7267
     void testHashCodeCollisionInMinus() {
         assert ([[1:2],[2:3]]-[["b":"a"]]) == [[1:2],[2:3]]
     }

Reply via email to