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 2e084efe49 GROOVY-5893: STC: merge type info for `<T, U extends T, V extends T>` 2e084efe49 is described below commit 2e084efe49c0ef21a866232d6ab8a55182efba62 Author: Eric Milles <eric.mil...@thomsonreuters.com> AuthorDate: Tue Nov 7 14:36:59 2023 -0600 GROOVY-5893: STC: merge type info for `<T, U extends T, V extends T>` --- .../groovy/runtime/ArrayGroovyMethods.java | 83 +++++--- .../groovy/runtime/DefaultGroovyMethods.java | 236 +++++++++++---------- .../transform/stc/StaticTypeCheckingVisitor.java | 8 +- .../stc/ClosureParamTypeInferenceSTCTest.groovy | 76 ++++--- .../groovy/transform/stc/GenericsSTCTest.groovy | 35 +-- .../asm/sc/GenericsStaticCompileTest.groovy | 6 + 6 files changed, 263 insertions(+), 181 deletions(-) diff --git a/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java index 4751572e27..3df60a0e95 100644 --- a/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java +++ b/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java @@ -81,10 +81,10 @@ import java.util.function.LongConsumer; import java.util.function.LongUnaryOperator; /** - * This class defines new groovy methods which appear on primitive arrays inside - * the Groovy environment. Static methods are used with the first parameter being - * the destination class, i.e. <code>public static int[] each(int[] self, Closure closure)</code> - * provides a <code>each({i -> })</code> method for <code>int[]</code>. + * Defines new groovy methods which appear on arrays inside the Groovy environment. + * Static methods are used with the first parameter being the destination class, + * i.e. <code>public static int[] each(int[] self, Closure closure)</code> + * provides an <code>each({i -> })</code> method for <code>int[]</code>. * <p> * NOTE: While this class contains many 'public' static methods, it is * primarily regarded as an internal class (its internal package name @@ -4616,42 +4616,77 @@ public class ArrayGroovyMethods extends DefaultGroovyMethodsSupport { // inject /** - * Iterates through the given array as with inject(Object[],initialValue,closure), but - * using the first element of the array as the initialValue, and then iterating - * the remaining elements of the array. + * Iterates through the given array, passing the first two elements to the + * closure. The result is passed back (injected) to the closure along with + * the third element and so on until all array elements have been consumed. + * + * <pre class="groovyTestCase"> + * def array = new Number[] {1, 2, 3, 4} + * def value = array.inject { acc, val -> acc * val } + * assert value == 24 + * + * array = new Object[] {['a','b'], ['b','c'], ['d','b']} + * value = array.inject { acc, val -> acc.intersect(val) } + * assert value == ['b'] + * + * array = new String[] {'t', 'i', 'm'} + * value = array.inject { acc, val -> acc + val } + * assert value == 'tim' + * </pre> * * @param self an array * @param closure a closure - * @return the result of the last closure call - * @throws NoSuchElementException if the array is empty. + * @return first value for single-element array or the result of the last closure call + * @throws NoSuchElementException if the array is empty * @see #inject(Object[], Object, Closure) * @since 1.8.7 */ public static <E, T, V extends T> T inject(E[] self, @ClosureParams(value=FromString.class,options="E,E") Closure<V> closure) { - return DefaultGroovyMethods.inject((Object) self, closure); + if (self.length == 0) { + throw new NoSuchElementException("Cannot call inject() on an empty array without passing an initial value."); + } + T value = (T) self[0]; + Object[] params = new Object[2]; + for (int i = 1; i < self.length; i += 1) { + params[0] = value; + params[1] = self[i]; + value = closure.call(params); + } + return value; } /** - * Iterates through the given array, passing in the initial value to - * the closure along with the first item. The result is passed back (injected) into - * the closure along with the second item. The new result is injected back into - * the closure along with the third item and so on until all elements of the array - * have been used. - * <p> - * Also known as foldLeft in functional parlance. + * Iterates through the given array, passing in the initial value and the + * first element to the closure. The result is sent back (injected) with the + * second element. The new result is injected back into the closure with the + * third element and so on until all elements have been consumed. + * + * <pre class="groovyTestCase"> + * def array = new Number[] {2, 3, 4} + * def value = array.inject(1) { acc, val -> acc * val } + * assert value == 24 + * + * array = new Object[] {['a','b'], ['b','c'], ['d','b']} + * value = array.inject(['a','b','c']) { acc, val -> acc.intersect(val) } + * assert value == ['b'] * - * @param self an Object[] - * @param initialValue some initial value + * array = new String[] {'t', 'i', 'm'} + * value = array.inject("") { acc, val -> acc + val } + * assert value == 'tim' + * </pre> + * + * @param self an array + * @param initialValue base value * @param closure a closure - * @return the result of the last closure call + * @return base value for empty array or the result of the last closure call * @since 1.5.0 */ public static <E, T, U extends T, V extends T> T inject(E[] self, U initialValue, @ClosureParams(value=FromString.class,options="U,E") Closure<V> closure) { - Object[] params = new Object[2]; T value = initialValue; - for (Object next : self) { + Object[] params = new Object[2]; + for (int i = 0; i < self.length; i += 1) { params[0] = value; - params[1] = next; + params[1] = self[i]; value = closure.call(params); } return value; @@ -5064,7 +5099,7 @@ public class ArrayGroovyMethods extends DefaultGroovyMethodsSupport { */ public static <T> T last(T[] self) { if (self.length == 0) { - throw new NoSuchElementException("Cannot access last() element from an empty Array"); + throw new NoSuchElementException("Cannot access last() element from an empty array"); } return self[self.length - 1]; } diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java index abe244f857..49391aae2c 100644 --- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java +++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java @@ -157,11 +157,10 @@ import java.util.function.Function; import static groovy.lang.groovydoc.Groovydoc.EMPTY_GROOVYDOC; /** - * This class defines new groovy methods which appear on normal JDK - * classes inside the Groovy environment. Static methods are used with the - * first parameter being the destination class, - * i.e. <code>public static String reverse(String self)</code> - * provides a <code>reverse()</code> method for <code>String</code>. + * Defines new groovy methods which appear on classes inside the Groovy environment. + * Static methods are used with the first parameter being the destination class, + * i.e. <code>public static String reverse(String self)</code> provides a + * <code>reverse()</code> method for <code>String</code>. * <p> * NOTE: While this class contains many 'public' static methods, it is * primarily regarded as an internal class (its internal package name @@ -7304,43 +7303,90 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { // inject /** - * Performs the same function as the version of inject that takes an initial value, but - * uses the head of the Collection as the initial value, and iterates over the tail. - * <pre class="groovyTestCase"> - * assert 1 * 2 * 3 * 4 == [ 1, 2, 3, 4 ].inject { acc, val {@code ->} acc * val } - * assert ['b'] == [['a','b'], ['b','c'], ['d','b']].inject { acc, val {@code ->} acc.intersect( val ) } - * LinkedHashSet set = [ 't', 'i', 'm' ] - * assert 'tim' == set.inject { a, b {@code ->} a + b } - * </pre> + * Iterates through the given object, passing the first two elements to the + * closure. The result is passed back (injected) to the closure along with + * the third element and so on until all elements have been consumed. * - * @param self a Collection + * @param self an object * @param closure a closure * @return the result of the last closure call - * @throws NoSuchElementException if the collection is empty. - * @see #inject(Collection, Object, Closure) + * @throws NoSuchElementException if the iterator is empty + * @see #inject(Object, Object, Closure) * @since 1.8.7 */ - @SuppressWarnings("unchecked") - public static <T, V extends T> T inject(Collection<T> self, @ClosureParams(value=FromString.class,options="V,T") Closure<V> closure ) { - if( self.isEmpty() ) { - throw new NoSuchElementException( "Cannot call inject() on an empty collection without passing an initial value." ) ; + public static <T, V extends T> T inject(Object self, @ClosureParams(value=FromString.class,options="?,?") Closure<V> closure) { + Iterator<?> iter = InvokerHelper.asIterator(self); + if (!iter.hasNext()) { + throw new NoSuchElementException("Cannot call inject() on an empty iterable without passing an initial value."); } - Iterator<T> iter = self.iterator(); - T head = iter.next(); - Collection<T> tail = tail(self); - if (!tail.iterator().hasNext()) { - return head; + return (T) inject(iter, iter.next(), closure); + } + + /** + * Iterates through the given object, passing the first two elements to the + * closure. The result is passed back (injected) to the closure along with + * the third element and so on until all elements have been consumed. + * + * <pre class="groovyTestCase"> + * def items = [1, 2, 3, 4] + * def value = items.inject { acc, val -> acc * val } + * assert value == 1 * 2 * 3 * 4 + * + * items = [['a','b'], ['b','c'], ['d','b']] + * value = items.inject { acc, val -> acc.intersect(val) } + * assert value == ['b'] + * + * items = ['j', 'o', 'i', 'n'] as Set + * value = items.inject(String.&plus) + * assert value == 'join' + * </pre> + * + * @param self an iterable + * @param closure a closure + * @return the result of the last closure call + * @throws NoSuchElementException if the iterator is empty + * @see #inject(Iterable, Object, Closure) + * @since 5.0.0 + */ + public static <E, T, V extends T> T inject(Iterable<E> self, @ClosureParams(value=FromString.class,options="E,E") Closure<V> closure) { + Iterator<E> iter = self.iterator(); + if (!iter.hasNext()) { + throw new NoSuchElementException("Cannot call inject() on an empty iterable without passing an initial value."); } - // cast with explicit weaker generics for now to keep jdk6 happy, TODO: find better fix - return (T) inject((Collection) tail, head, closure); + return (T) inject(iter, iter.next(), closure); + } + + // + + /** + * Iterates through the given object, passing in the initial value to + * the closure along with the first item. The result is passed back (injected) into + * the closure along with the second item. The new result is injected back into + * the closure along with the third item and so on until further iteration is + * not possible. + * <p> + * Also known as <tt>foldLeft</tt> or <tt>reduce</tt> in functional parlance. + * + * @param self an object + * @param initialValue some initial value + * @param closure a closure + * @return the result of the last closure call + * @see #inject(Iterator, Object, Closure) + * @since 1.5.0 + */ + public static <T, U extends T, V extends T> T inject(Object self, U initialValue, @ClosureParams(value=FromString.class,options="U,?") Closure<V> closure) { + Iterator iter = InvokerHelper.asIterator(self); + return (T) inject(iter, initialValue, closure); } /** - * Iterates through the given Collection, passing in the initial value to + * Iterates through the given object, passing in the initial value to * the 2-arg closure along with the first item. The result is passed back (injected) into * the closure along with the second item. The new result is injected back into - * the closure along with the third item and so on until the entire collection - * has been used. Also known as <tt>foldLeft</tt> or <tt>reduce</tt> in functional parlance. + * the closure along with the third item and so on until further iteration is + * not possible. + * <p> + * Also known as <tt>foldLeft</tt> or <tt>reduce</tt> in functional parlance. * * Examples: * <pre class="groovyTestCase"> @@ -7369,25 +7415,51 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * max('rat', 'cat') {@code =>} 'rat' * </pre> * - * @param self a Collection + * @param self an iterable * @param initialValue some initial value * @param closure a closure * @return the result of the last closure call - * @since 1.0 + * @since 5.0.0 */ - @SuppressWarnings("unchecked") - public static <E, T, U extends T, V extends T> T inject(Collection<E> self, U initialValue, @ClosureParams(value=FromString.class,options="U,E") Closure<V> closure) { - // cast with explicit weaker generics for now to keep jdk6 happy, TODO: find better fix - return (T) inject((Iterator) self.iterator(), initialValue, closure); + public static <E, T, U extends T, V extends T> T inject(Iterable<E> self, U initialValue, @ClosureParams(value=FromString.class,options="U,E") Closure<V> closure) { + return inject(self.iterator(), initialValue, closure); + } + + /** + * Iterates through the given iterator, passing in the initial value to + * the closure along with the first item. The result is passed back (injected) into + * the closure along with the second item. The new result is injected back into + * the closure along with the third item and so on until further iteration is + * not possible. + * <p> + * Also known as <tt>foldLeft</tt> or <tt>reduce</tt> in functional parlance. + * + * @param self an iterator + * @param initialValue some initial value + * @param closure a closure + * @return the result of the last closure call + * @since 1.5.0 + */ + public static <E, T, U extends T, V extends T> T inject(Iterator<E> self, U initialValue, @ClosureParams(value=FromString.class,options="U,E") Closure<V> closure) { + T value = initialValue; + Object[] params = new Object[2]; + while (self.hasNext()) { + params[0] = value; + params[1] = self.next(); + value = closure.call(params); + } + return value; } /** - * Iterates through the given Map, passing in the initial value to + * Iterates through the given map, passing in the initial value to * the 2-arg Closure along with the first item (or 3-arg Closure along with the first key and value). * The result is passed back (injected) into * the closure along with the second item. The new result is injected back into - * the closure along with the third item and so on until the entire collection - * has been used. Also known as <tt>foldLeft</tt> or <tt>reduce</tt> in functional parlance. + * the closure along with the third item and so on until further iteration is + * not possible. + * <p> + * Also known as <tt>foldLeft</tt> or <tt>reduce</tt> in functional parlance. * * Examples: * <pre class="groovyTestCase"> @@ -7397,13 +7469,13 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { * } == ['a', 'b', 'b', 'c', 'c', 'c'] * </pre> * - * @param self a Map + * @param self a map * @param initialValue some initial value * @param closure a 2 or 3 arg Closure * @return the result of the last closure call * @since 1.8.1 */ - public static <K, V, T, U extends T, W extends T> T inject(Map<K, V> self, U initialValue, @ClosureParams(value=FromString.class,options={"U,Map.Entry<K,V>","U,K,V"}) Closure<W> closure) { + public static <K, V, T, U extends T, W extends T> T inject(Map<K, V> self, U initialValue, @ClosureParams(value=FromString.class,options={"U,Map.Entry<K,V>","U,K,V"}) Closure<W> closure) { T value = initialValue; for (Map.Entry<K, V> entry : self.entrySet()) { if (closure.getMaximumNumberOfParameters() == 3) { @@ -7415,76 +7487,6 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { return value; } - /** - * Iterates through the given Iterator, passing in the initial value to - * the closure along with the first item. The result is passed back (injected) into - * the closure along with the second item. The new result is injected back into - * the closure along with the third item and so on until the Iterator has been - * expired of values. Also known as foldLeft in functional parlance. - * - * @param self an Iterator - * @param initialValue some initial value - * @param closure a closure - * @return the result of the last closure call - * @see #inject(Collection, Object, Closure) - * @since 1.5.0 - */ - public static <E,T, U extends T, V extends T> T inject(Iterator<E> self, U initialValue, @ClosureParams(value=FromString.class,options="U,E") Closure<V> closure) { - T value = initialValue; - Object[] params = new Object[2]; - while (self.hasNext()) { - Object item = self.next(); - params[0] = value; - params[1] = item; - value = closure.call(params); - } - return value; - } - - /** - * Iterates through the given Object, passing in the first value to - * the closure along with the first item. The result is passed back (injected) into - * the closure along with the second item. The new result is injected back into - * the closure along with the third item and so on until further iteration of - * the object is not possible. Also known as foldLeft in functional parlance. - * - * @param self an Object - * @param closure a closure - * @return the result of the last closure call - * @throws NoSuchElementException if the collection is empty. - * @see #inject(Collection, Object, Closure) - * @since 1.8.7 - */ - @SuppressWarnings("unchecked") - public static <T, V extends T> T inject(Object self, Closure<V> closure) { - Iterator iter = InvokerHelper.asIterator(self); - if( !iter.hasNext() ) { - throw new NoSuchElementException( "Cannot call inject() over an empty iterable without passing an initial value." ) ; - } - Object initialValue = iter.next() ; - return (T) inject(iter, initialValue, closure); - } - - /** - * Iterates through the given Object, passing in the initial value to - * the closure along with the first item. The result is passed back (injected) into - * the closure along with the second item. The new result is injected back into - * the closure along with the third item and so on until further iteration of - * the object is not possible. Also known as foldLeft in functional parlance. - * - * @param self an Object - * @param initialValue some initial value - * @param closure a closure - * @return the result of the last closure call - * @see #inject(Collection, Object, Closure) - * @since 1.5.0 - */ - @SuppressWarnings("unchecked") - public static <T, U extends T, V extends T> T inject(Object self, U initialValue, Closure<V> closure) { - Iterator iter = InvokerHelper.asIterator(self); - return (T) inject(iter, initialValue, closure); - } - //-------------------------------------------------------------------------- // inspect @@ -16680,15 +16682,25 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { } @Deprecated(since = "5.0.0") - public static <E,T, V extends T> T inject(E[] self, @ClosureParams(value=FromString.class,options="E,E") Closure<V> closure) { + public static <E, T, V extends T> T inject(E[] self, @ClosureParams(value=FromString.class,options="E,E") Closure<V> closure) { return ArrayGroovyMethods.inject(self, closure); } + @Deprecated(since = "5.0.0") + public static <E, T, V extends T> T inject(Collection<E> self, @ClosureParams(value=FromString.class,options="E,E") Closure<V> closure) { + return inject((Iterable<E>) self, closure); + } + @Deprecated(since = "5.0.0") public static <E, T, U extends T, V extends T> T inject(E[] self, U initialValue, @ClosureParams(value=FromString.class,options="U,E") Closure<V> closure) { return ArrayGroovyMethods.inject(self, initialValue, closure); } + @Deprecated(since = "5.0.0") + public static <E, T, U extends T, V extends T> T inject(Collection<E> self, U initialValue, @ClosureParams(value=FromString.class,options="U,E") Closure<V> closure) { + return inject((Iterable<E>) self, initialValue, closure); + } + @Deprecated(since = "5.0.0") public static <T> Iterator<T> iterator(T[] self) { return ArrayGroovyMethods.iterator(self); diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index fa97205798..7dff684042 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -5465,7 +5465,7 @@ out: if (mn.size() != 1) { } } - // in case of "<T, U extends Type<T>>" we can learn about "T" from a resolved "U" + // in case of "<T, U extends Type<T>>", we can learn about "T" from a resolved "U" extractGenericsConnectionsForBoundTypes(methodGenericTypes, resolvedPlaceholders); } @@ -5506,7 +5506,7 @@ out: if (mn.size() != 1) { } else if (a instanceof MapExpression) { actuals[i] = getLiteralResultType(pt, at, LinkedHashMap_TYPE); } else if (a instanceof ConstructorCallExpression) { - inferDiamondType((ConstructorCallExpression) a, pt); // GROOVY-8974, GROOVY-9983, GROOVY-10086, et al. + inferDiamondType((ConstructorCallExpression) a, pt); // GROOVY-8974, GROOVY-9983, GROOVY-10086, GROOVY-10890, et al. } else if (a instanceof TernaryExpression && at.getGenericsTypes() != null && at.getGenericsTypes().length == 0) { // GROOVY-9983: double diamond scenario -- "m(flag ? new Type<>(...) : new Type<>(...))" typeCheckingContext.pushEnclosingBinaryExpression(assignX(varX(p), a, a)); @@ -5556,6 +5556,7 @@ out: if (mn.size() != 1) { private static void extractGenericsConnectionsForBoundTypes(final GenericsType[] spec, final Map<GenericsTypeName, GenericsType> target) { if (spec.length < 2) return; + Map<GenericsTypeName, GenericsType> outer = new HashMap<>(); for (GenericsType tp : spec) { ClassNode[] bounds = tp.getUpperBounds(); if (bounds == null || bounds.length == 0) continue; @@ -5568,8 +5569,9 @@ out: if (mn.size() != 1) { for (ClassNode bound : bounds) { extractGenericsConnections(inner,value.getType(),bound); } - inner.forEach(target::putIfAbsent); // GROOVY-10890 + inner.forEach((k, v) -> outer.merge(k, v, StaticTypeCheckingSupport::getCombinedGenericsType)); // GROOVY-5893 } + outer.forEach(target::putIfAbsent); } private static ClassNode[] collateMethodReferenceParameterTypes(final MethodPointerExpression source, final MethodNode target) { diff --git a/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy b/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy index 3edaff876b..64a4e9a68e 100644 --- a/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy +++ b/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy @@ -18,6 +18,7 @@ */ package groovy.transform.stc +import groovy.test.NotYetImplemented import org.codehaus.groovy.control.customizers.ImportCustomizer /** @@ -1498,6 +1499,7 @@ class ClosureParamTypeInferenceSTCTest extends StaticTypeCheckingTestCase { assert [a:'1',b:'2',c:'C'].groupBy { it.key.toUpperCase()==it.value?1:0 } == [0:[a:'1',b:'2'], 1:[c:'C']] ''' } + void testDGM_groupEntriesBy() { assertScript ''' def result = [a:1,b:2,c:3,d:4,e:5,f:6].groupEntriesBy { k,v -> v % 2 } @@ -1505,57 +1507,71 @@ class ClosureParamTypeInferenceSTCTest extends StaticTypeCheckingTestCase { result = [a:1,b:2,c:3,d:4,e:5,f:6].groupEntriesBy { e -> e.value % 2 } assert result[0]*.key == ["b", "d", "f"] assert result[1]*.value == [1, 3, 5] - ''' - } - - void testDGM_injectOnCollectionWithInitialValue() { - assertScript ''' - assert ['a','bb','ccc'].inject(0) { acc, str -> acc += str.length(); acc } == 6 ''' } - void testDGM_injectOnArrayWithInitialValue() { + void testDGM_injectOnCollection() { assertScript ''' - String[] array = ['a','bb','ccc'] - assert array.inject(0) { acc, str -> acc += str.length(); acc } == 6 + def items = ['a','bb','ccc'] + def value = items.inject { acc, str -> acc += str.toUpperCase(); acc } + assert value == 'aBBCCC' ''' - } - - void testDGM_injectOnIteratorWithInitialValue() { - assertScript ''' - assert ['a','bb','ccc'].iterator().inject(0) { acc, str -> acc += str.length(); acc } == 6 + assertScript '''import org.codehaus.groovy.runtime.DefaultGroovyMethods as DGM + def items = ['a','bb','ccc'] + def value = DGM.inject(items, { acc, str -> acc += str.toUpperCase(); acc }) + assert value == 'aBBCCC' ''' } - - void testDGM_injectOnCollection() { + @NotYetImplemented + void testDGM_injectOnIterator() { assertScript ''' - assert ['a','bb','ccc'].inject { acc, str -> acc += str.toUpperCase(); acc } == 'aBBCCC' + def items = ['a','bb','ccc'].iterator() + def value = items.inject { acc, str -> acc += str.toUpperCase(); acc } + // ^^^^^^ inject(Object,Closure) without metadata + assert value == 'aBBCCC' ''' } - void testDGM_injectOnArray() { assertScript ''' - String[] array = ['a','bb','ccc'] - assert array.inject { acc, str -> acc += str.toUpperCase(); acc } == 'aBBCCC' + def items = new String[]{'a','bb','ccc'} + def value = items.inject { acc, str -> acc += str.toUpperCase(); acc } + assert value == 'aBBCCC' ''' } - void testDGM_injectOnCollectionWithInitialValueDirect() { + void testDGM_injectOnCollectionWithInitialValue() { + assertScript ''' + def items = ['a','bb','ccc'] + def value = items.inject(0) { acc, str -> acc += str.length(); acc } + assert value == 6 + ''' assertScript '''import org.codehaus.groovy.runtime.DefaultGroovyMethods as DGM - assert DGM.inject(['a','bb','ccc'],0) { acc, str -> acc += str.length(); acc } == 6 + def items = ['a','bb','ccc'] + def value = DGM.inject(items, 0, { acc, str -> acc += str.length(); acc }) + assert value == 6 ''' } - - void testDGM_injectOnCollectionDirect() { - assertScript '''import org.codehaus.groovy.runtime.DefaultGroovyMethods as DGM - assert DGM.inject(['a','bb','ccc']) { acc, str -> acc += str.toUpperCase(); acc } == 'aBBCCC' + void testDGM_injectOnIteratorWithInitialValue() { + assertScript ''' + def items = ['a','bb','ccc'].iterator() + def value = items.inject(0) { acc, str -> acc += str.length(); acc } + assert value == 6 ''' } - - void testDGM_injectOnMap() { + void testDGM_injectOnArrayWithInitialValue() { + assertScript ''' + def items = new String[]{'a','bb','ccc'} + def value = items.inject(0) { acc, str -> acc += str.length(); acc } + assert value == 6 + ''' + } + void testDGM_injectOnMapWithInitialValue() { assertScript ''' - assert [a:1,b:2].inject(0) { acc, entry -> acc += entry.value; acc} == 3 - assert [a:1,b:2].inject(0) { acc, k, v -> acc += v; acc} == 3 + def items = [a:1,b:2] + def value = items.inject(0) { acc, entry -> acc += entry.value; acc} + assert value == 3 + value = items.inject(0) { acc, k, v -> acc += v; acc} + assert value == 3 ''' } diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy index 86024fae2b..e21e84ba3d 100644 --- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy +++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy @@ -4050,9 +4050,21 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { list.each { int i -> sum += i } assert sum == 6 - $type sumWithInject = list.inject(0, { int x, int y -> x + y }) - // ^^^^^^^^^^^^^ T ^^^^ E[] ^ U ^ U ^ E ^^^^^ V - assert sumWithInject == 6 + $type sumViaInject = list.inject(0, { int x, int y -> x + y }) + // ^^^^^^^^^^^^ T ^^^^ E[] ^ U ^ U ^ E ^^^^^ V + assert sumViaInject == 6 + """ + } + } + + // GROOVY-5893, GROOVY-7934 + void testPlusInClosure2() { + for (type in ['def', 'var', 'Object', 'String', 'Serializable']) { + assertScript """ + String[] array = ['1', '2', '3'] + $type joinViaInject = array.inject(0, { x,y -> (x + ',' + y) }) + // integer or string ^ ^ string + assert joinViaInject == '0,1,2,3' """ } } @@ -4616,30 +4628,29 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { } // GROOVY-6504 - void testInjectMethodWithInitialValueChoosesTheCollectionVersion() { - assertScript '''import org.codehaus.groovy.transform.stc.ExtensionMethodNode + void testInjectMethodWithInitialValueChoosesTheIterableVersion() { + assertScript ''' @ASTTest(phase=INSTRUCTION_SELECTION, value={ def method = node.rightExpression.getNodeMetaData(DIRECT_METHOD_CALL_TARGET) assert method.name == 'inject' - assert method instanceof ExtensionMethodNode method = method.extensionMethodNode - assert method.parameters[0].type == make(Collection) + assert method.parameters[0].type == ITERABLE_TYPE }) def result = ['a','bb','ccc'].inject(0) { int acc, String str -> acc += str.length(); acc } - assert result == 6 + assert result == 6 ''' } // GROOVY-6504 - void testInjectMethodWithInitialValueChoosesTheCollectionVersionUsingDGM() { + void testInjectMethodWithInitialValueChoosesTheCollectionVersion() { assertScript '''import org.codehaus.groovy.runtime.DefaultGroovyMethods @ASTTest(phase=INSTRUCTION_SELECTION, value={ def method = node.rightExpression.getNodeMetaData(DIRECT_METHOD_CALL_TARGET) assert method.name == 'inject' - assert method.parameters[0].type == make(Collection) + assert method.parameters[0].type == COLLECTION_TYPE }) - def result = DefaultGroovyMethods.inject(['a','bb','ccc'],0, { int acc, String str -> acc += str.length(); acc }) - assert result == 6 + def result = DefaultGroovyMethods.inject(['a','bb','ccc'], 0, { int acc, String str -> acc += str.length(); acc }) + assert result == 6 ''' } diff --git a/src/test/org/codehaus/groovy/classgen/asm/sc/GenericsStaticCompileTest.groovy b/src/test/org/codehaus/groovy/classgen/asm/sc/GenericsStaticCompileTest.groovy index 4a599b2827..03c8aae050 100644 --- a/src/test/org/codehaus/groovy/classgen/asm/sc/GenericsStaticCompileTest.groovy +++ b/src/test/org/codehaus/groovy/classgen/asm/sc/GenericsStaticCompileTest.groovy @@ -18,10 +18,16 @@ */ package org.codehaus.groovy.classgen.asm.sc +import groovy.test.NotYetImplemented import groovy.transform.stc.GenericsSTCTest /** * Unit tests for static compilation : generics. */ class GenericsStaticCompileTest extends GenericsSTCTest implements StaticCompilationTestSupport { + + @NotYetImplemented + void testPlusInClosure2() { + super.testPlusInClosure2() + } }