You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2021/04/15 05:37:13 UTC

[groovy] branch master updated: GROOVY-10028: STC: support "Type[] array = streamOfTypeOrSubtype" (closes #1559)

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

sunlan 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 112bcee  GROOVY-10028: STC: support "Type[] array = streamOfTypeOrSubtype" (closes #1559)
112bcee is described below

commit 112bcee492b93c1abdbb83eb322b25b80395e552
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Apr 13 05:25:44 2021 +0800

    GROOVY-10028: STC: support "Type[] array = streamOfTypeOrSubtype" (closes #1559)
    
    and "Collection<Type> collection = streamOfTypeOrSubtype"
    and "List<Type> list = streamOfTypeOrSubtype"
    and "Set<Type> set = streamOfTypeOrSubtype"
---
 .../typehandling/DefaultTypeTransformation.java    | 93 ++++++++++++++++------
 .../transform/stc/StaticTypeCheckingSupport.java   |  9 ++-
 src/spec/doc/core-semantics.adoc                   |  4 +-
 src/spec/test/typing/TypeCheckingTest.groovy       | 14 ++++
 src/test/groovy/ArrayCoerceTest.groovy             | 60 +++++++++-----
 .../DefaultTypeTransformationTest.groovy           | 67 +++++++++++++++-
 6 files changed, 198 insertions(+), 49 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java b/src/main/java/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java
index e841b2a..c96e4f9 100644
--- a/src/main/java/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java
+++ b/src/main/java/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java
@@ -31,6 +31,7 @@ import org.codehaus.groovy.runtime.MethodClosure;
 import org.codehaus.groovy.runtime.NullObject;
 import org.codehaus.groovy.runtime.ResourceGroovyMethods;
 import org.codehaus.groovy.runtime.StringGroovyMethods;
+import org.codehaus.groovy.vmplugin.v8.PluginDefaultGroovyMethods;
 
 import java.io.File;
 import java.io.IOException;
@@ -44,11 +45,17 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Supplier;
+import java.util.stream.BaseStream;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+
+import static org.codehaus.groovy.reflection.ReflectionCache.isArray;
 
 /**
  * Class providing various type conversions, coercions and boxing/unboxing operations.
@@ -214,17 +221,18 @@ public class DefaultTypeTransformation {
         }
     }
 
-    public static Object castToType(Object object, Class type) {
+    public static Object castToType(final Object object, final Class type) {
         if (object == null) return type == boolean.class ? Boolean.FALSE : null;
         if (type == Object.class) return object;
 
         final Class aClass = object.getClass();
-        if (type == aClass) return object;
-        if (type.isAssignableFrom(aClass)) return object;
-
-        if (ReflectionCache.isArray(type)) return asArray(object, type);
+        if (type == aClass || type.isAssignableFrom(aClass)) {
+            return object;
+        }
 
-        if (type.isEnum()) {
+        if (isArray(type)) {
+            return asArray(object, type);
+        } else if (type.isEnum()) {
             return ShortTypeHandling.castToEnum(object, type);
         } else if (Collection.class.isAssignableFrom(type)) {
             return continueCastOnCollection(object, type);
@@ -243,25 +251,33 @@ public class DefaultTypeTransformation {
         return continueCastOnNumber(object, type);
     }
 
-    private static Object continueCastOnCollection(Object object, Class type) {
+    private static Object continueCastOnCollection(final Object object, final Class type) {
         if (object instanceof Collection && type.isAssignableFrom(LinkedHashSet.class)) {
             return new LinkedHashSet((Collection) object);
         }
 
-        if (object.getClass().isArray()) {
-            Collection answer;
+        Supplier<Collection> newCollection = () -> {
             if (type.isAssignableFrom(ArrayList.class) && Modifier.isAbstract(type.getModifiers())) {
-                answer = new ArrayList();
+                return new ArrayList();
             } else if (type.isAssignableFrom(LinkedHashSet.class) && Modifier.isAbstract(type.getModifiers())) {
-                answer = new LinkedHashSet();
+                return new LinkedHashSet();
             } else {
                 try {
-                    answer = (Collection) type.getDeclaredConstructor().newInstance();
+                    return (Collection) type.getDeclaredConstructor().newInstance();
                 } catch (Exception e) {
                     throw new GroovyCastException("Could not instantiate instance of: " + type.getName() + ". Reason: " + e);
                 }
             }
+        };
+
+        if (object instanceof BaseStream) {
+            Collection answer = newCollection.get();
+            answer.addAll(asCollection(object));
+            return answer;
+        }
 
+        if (object.getClass().isArray()) {
+            Collection answer = newCollection.get();
             // we cannot just wrap in a List as we support primitive type arrays
             int length = Array.getLength(object);
             for (int i = 0; i < length; i += 1) {
@@ -403,39 +419,64 @@ public class DefaultTypeTransformation {
         throw gce;
     }
 
-    public static Object asArray(Object object, Class type) {
+    public static Object asArray(final Object object, final Class type) {
         if (type.isAssignableFrom(object.getClass())) {
             return object;
         }
 
-        Collection list = asCollection(object);
-        int size = list.size();
-        Class elementType = type.getComponentType();
-        Object array = Array.newInstance(elementType, size);
+        if (object instanceof IntStream) {
+            if (type.equals(int[].class)) {
+                return ((IntStream) object).toArray();
+            } else if (type.equals(long[].class)) {
+                return ((IntStream) object).asLongStream().toArray();
+            } else if (type.equals(double[].class)) {
+                return ((IntStream) object).asDoubleStream().toArray();
+            } else if (type.equals(Integer[].class)) {
+                return ((IntStream) object).boxed().toArray(Integer[]::new);
+            }
+        } else if (object instanceof LongStream) {
+            if (type.equals(long[].class)) {
+                return ((LongStream) object).toArray();
+            } else if (type.equals(double[].class)) {
+                return ((LongStream) object).asDoubleStream().toArray();
+            } else if (type.equals(Long[].class)) {
+                return ((LongStream) object).boxed().toArray(Long[]::new);
+            }
+        } else if (object instanceof DoubleStream) {
+            if (type.equals(double[].class)) {
+                return ((DoubleStream) object).toArray();
+            } else if (type.equals(Double[].class)) {
+                return ((DoubleStream) object).boxed().toArray(Double[]::new);
+            }
+        }
+
+        Class<?> elementType = type.getComponentType();
+        Collection<?> collection = asCollection(object);
+        Object array = Array.newInstance(elementType, collection.size());
 
-        int idx = 0;
-        for (Iterator iter = list.iterator(); iter.hasNext(); idx++) {
-            Object element = iter.next();
-            Array.set(array, idx, castToType(element, elementType));
+        int i = 0;
+        for (Object element : collection) {
+            Array.set(array, i++, castToType(element, elementType));
         }
 
         return array;
     }
 
-    public static <T> Collection<T> asCollection(T[] value) {
+    public static <T> Collection<T> asCollection(final T[] value) {
         return arrayAsCollection(value);
     }
 
-    public static Collection asCollection(Object value) {
+    public static Collection asCollection(final Object value) {
         if (value == null) {
             return Collections.EMPTY_LIST;
         } else if (value instanceof Collection) {
             return (Collection) value;
         } else if (value instanceof Map) {
-            Map map = (Map) value;
-            return map.entrySet();
+            return ((Map) value).entrySet();
         } else if (value.getClass().isArray()) {
             return arrayAsCollection(value);
+        } else if (value instanceof BaseStream) {
+            return PluginDefaultGroovyMethods.toList((BaseStream) value);
         } else if (value instanceof MethodClosure) {
             MethodClosure method = (MethodClosure) value;
             IteratorClosureAdapter adapter = new IteratorClosureAdapter(method.getDelegate());
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
index d5e54d9..d9f8c3e 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -68,6 +68,7 @@ import java.util.Set;
 import java.util.TreeSet;
 import java.util.UUID;
 import java.util.regex.Matcher;
+import java.util.stream.BaseStream;
 
 import static java.lang.Math.min;
 import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod;
@@ -163,7 +164,8 @@ public abstract class StaticTypeCheckingSupport {
 
     protected static final ClassNode Matcher_TYPE = makeWithoutCaching(Matcher.class);
     protected static final ClassNode ArrayList_TYPE = makeWithoutCaching(ArrayList.class);
-    protected static final ClassNode Collection_TYPE = COLLECTION_TYPE; // TODO: deprecate?
+    protected static final ClassNode BaseStream_TYPE = makeWithoutCaching(BaseStream.class);
+    protected static final ClassNode Collection_TYPE = makeWithoutCaching(Collection.class);
     protected static final ClassNode Deprecated_TYPE = makeWithoutCaching(Deprecated.class);
     protected static final ClassNode LinkedHashMap_TYPE = makeWithoutCaching(LinkedHashMap.class);
     protected static final ClassNode LinkedHashSet_TYPE = makeWithoutCaching(LinkedHashSet.class);
@@ -672,6 +674,11 @@ public abstract class StaticTypeCheckingSupport {
                     || (elementType.getLowerBound() == null && isCovariant(extractType(elementType), left.getComponentType()));
                     //  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GROOVY-8984: "? super T" is only compatible with an Object[] target
             }
+            if (GeneralUtils.isOrImplements(right, BaseStream_TYPE)) {
+                GenericsType elementType = GenericsUtils.parameterizeType(right, BaseStream_TYPE).getGenericsTypes()[0];
+                return OBJECT_TYPE.equals(left.getComponentType()) // Object[] can accept any stream API element type(s)
+                    || (elementType.getLowerBound() == null && isCovariant(extractType(elementType), getWrapper(left.getComponentType())));
+            }
         }
 
         ClassNode leftRedirect = left.redirect();
diff --git a/src/spec/doc/core-semantics.adoc b/src/spec/doc/core-semantics.adoc
index 45e7e42..4b3e0b3 100644
--- a/src/spec/doc/core-semantics.adoc
+++ b/src/spec/doc/core-semantics.adoc
@@ -1179,7 +1179,7 @@ include::../test/typing/TypeCheckingTest.groovy[tags=stc_assign_array_fail,inden
 ----
 ====
 
-* _or_ `T` is an array and `A` is a list and the component type of `A` is assignable to the component type of `T`
+* _or_ `T` is an array and `A` is a collection or stream and the component type of `A` is assignable to the component type of `T`
 
 +
 
@@ -1189,6 +1189,8 @@ include::../test/typing/TypeCheckingTest.groovy[tags=stc_assign_array_fail,inden
 ----
 include::../test/typing/TypeCheckingTest.groovy[tags=stc_assign_array_list,indent=0]
 include::../test/typing/TypeCheckingTest.groovy[tags=stc_assign_array_list_fail,indent=0]
+include::../test/typing/TypeCheckingTest.groovy[tags=stc_assign_array_set,indent=0]
+include::../test/typing/TypeCheckingTest.groovy[tags=stc_assign_array_stream,indent=0]
 ----
 ====
 
diff --git a/src/spec/test/typing/TypeCheckingTest.groovy b/src/spec/test/typing/TypeCheckingTest.groovy
index 99f23e4..fbcb876 100644
--- a/src/spec/test/typing/TypeCheckingTest.groovy
+++ b/src/spec/test/typing/TypeCheckingTest.groovy
@@ -105,6 +105,20 @@ class TypeCheckingTest extends StaticTypeCheckingTestCase {
             // end::stc_assign_array_list[]
         '''
 
+        assertScript '''
+            // tag::stc_assign_array_set[]
+            Set set = [1,2,3]
+            Number[] na = set               // passes
+            // end::stc_assign_array_set[]
+        '''
+
+        assertScript '''
+            // tag::stc_assign_array_stream[]
+            def stream = Arrays.stream(1,2,3)
+            int[] i = stream                // passes
+            // end::stc_assign_array_stream[]
+        '''
+
         shouldFailWithMessages '''
             // tag::stc_assign_array_fail[]
             int[] i = new String[4]     // fails
diff --git a/src/test/groovy/ArrayCoerceTest.groovy b/src/test/groovy/ArrayCoerceTest.groovy
index 6f84b27..18846dc 100644
--- a/src/test/groovy/ArrayCoerceTest.groovy
+++ b/src/test/groovy/ArrayCoerceTest.groovy
@@ -20,7 +20,7 @@ package groovy
 
 import groovy.test.GroovyTestCase
 
-class ArrayCoerceTest extends GroovyTestCase {
+final class ArrayCoerceTest extends GroovyTestCase {
 
     Object[] field
     Long[] numberField
@@ -34,52 +34,36 @@ class ArrayCoerceTest extends GroovyTestCase {
 
     void testStaticallyTypedPrimitiveFieldArrays() {
         primitiveField = [1, 2, 3]
-
         assert primitiveField instanceof int[]
         assert primitiveField.length == 3
     }
 
-
-    void testFoo2() {
-        def x = [1, 2, 3] as Object[]
-        assert x instanceof Object[]
-        def c = x.getClass()
-        def et = c.componentType
-        assert et == Object.class
-    }
-
     void testStaticallyTypedObjectArrays() {
         Object[] b = [1, 2, 3]
-
         assert b instanceof Object[]
         assert b.length == 3
         def c = b.getClass()
         def et = c.componentType
         assert et == Object.class
-
     }
 
     void testStaticallyTypedArrays() {
         Integer[] b = [1, 2, 3]
-
         assert b instanceof Integer[]
         assert b.length == 3
         def c = b.getClass()
         def et = c.componentType
         assert et == Integer.class
-
     }
 
     void testStaticallyTypedObjectFieldArrays() {
         field = [1, 2, 3]
-
         assert field instanceof Object[]
         assert field.length == 3
     }
 
     void testStaticallyTypedFieldArrays() {
         numberField = [1, 2, 3]
-
         assert numberField instanceof Long[]
         assert numberField.length == 3
     }
@@ -120,10 +104,9 @@ class ArrayCoerceTest extends GroovyTestCase {
         assert x instanceof double[]
     }
 
-
-
     void testAsObjectArray() {
         def x = [1, 2, 3] as Object[]
+        assert x instanceof Object[]
         def c = x.getClass()
         def et = c.componentType
         assert et == Object.class
@@ -154,7 +137,6 @@ class ArrayCoerceTest extends GroovyTestCase {
         assert x instanceof int[]
     }
 
-
     void testMakeArrayTypes() {
         def x = null
 
@@ -191,4 +173,42 @@ class ArrayCoerceTest extends GroovyTestCase {
         assert x instanceof Double[]
     }
 
+    // GROOVY-10028
+    void testMakeArrayFromOtherTypes() {
+        int[] a = Arrays.stream(0, 1, 2)
+        assert a instanceof int[]
+        assert a.length == 3
+
+        long[] b = Arrays.stream(0, 1, 2)
+        assert b instanceof long[]
+        assert b.length == 3
+
+        long[] c = Arrays.stream(0L, 1L, 2L)
+        assert c instanceof long[]
+        assert c.length == 3
+
+        double[] d = Arrays.stream(0, 1, 2)
+        assert d instanceof double[]
+        assert d.length == 3
+
+        d = Arrays.stream(0L, 1L, 2L)
+        assert d instanceof double[]
+        assert d.length == 3
+
+        d = Arrays.stream(0D, 1D, 2D)
+        assert d instanceof double[]
+        assert d.length == 3
+
+        primitiveField = Arrays.stream(0, 1, 2)
+        assert primitiveField instanceof int[]
+        assert primitiveField.length == 3
+
+        numberField = Arrays.stream(0, 1, 2)
+        assert numberField instanceof Long[]
+        assert numberField.length == 3
+
+        field = Arrays.stream(0, 1, 2)
+        assert field instanceof Object[]
+        assert field.length == 3
+    }
 }
diff --git a/src/test/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformationTest.groovy b/src/test/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformationTest.groovy
index b5264ff..5054fb5 100644
--- a/src/test/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformationTest.groovy
+++ b/src/test/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformationTest.groovy
@@ -25,7 +25,7 @@ import static groovy.test.GroovyAssert.shouldFail
 final class DefaultTypeTransformationTest {
 
     @Test
-    void testCastToType() {
+    void testCastToType1() {
         def input = null, result
 
         result = DefaultTypeTransformation.castToType(input, int)
@@ -54,6 +54,71 @@ final class DefaultTypeTransformationTest {
     }
 
     @Test
+    void testCastToType2() {
+        def input = new int[] {0,1}, result
+
+        result = DefaultTypeTransformation.castToType(input, Number[])
+        assert result instanceof Number[]
+        assert result[0] == 0
+        assert result[1] == 1
+
+        result = DefaultTypeTransformation.castToType(input, List)
+        assert result instanceof List
+        assert result[0] == 0
+        assert result[1] == 1
+
+        result = DefaultTypeTransformation.castToType(input, Set)
+        assert result instanceof Set
+        assert result[0] == 0
+        assert result[1] == 1
+    }
+
+    @Test
+    void testCastToType3() {
+        def input = Arrays.asList(0,1), result
+
+        result = DefaultTypeTransformation.castToType(input, Number[])
+        assert result instanceof Number[]
+        assert result[0] == 0
+        assert result[1] == 1
+
+        result = DefaultTypeTransformation.castToType(input, List)
+        assert result === input
+        assert result[0] == 0
+        assert result[1] == 1
+
+        result = DefaultTypeTransformation.castToType(input, Set)
+        assert result instanceof Set
+        assert result[0] == 0
+        assert result[1] == 1
+    }
+
+    @Test // GROOVY-10028
+    void testCastToType4() {
+        def result
+
+        result = DefaultTypeTransformation.castToType(Arrays.stream(0,1), Number[])
+        assert result instanceof Number[]
+        assert result[0] == 0
+        assert result[1] == 1
+
+        result = DefaultTypeTransformation.castToType(Arrays.stream(0,1), int[])
+        assert result instanceof int[]
+        assert result[0] == 0
+        assert result[1] == 1
+
+        result = DefaultTypeTransformation.castToType(Arrays.stream(0,1), List)
+        assert result instanceof List
+        assert result[0] == 0
+        assert result[1] == 1
+
+        result = DefaultTypeTransformation.castToType(Arrays.stream(0,1), Set)
+        assert result instanceof Set
+        assert result[0] == 0
+        assert result[1] == 1
+    }
+
+    @Test
     void testCompareTo() {
         // objects
         Object object1 = new Object()