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 2021/04/12 21:36:36 UTC

[groovy] branch GROOVY-10028 created (now e6c1292)

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

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


      at e6c1292  GROOVY-10028: STC: support "Type[] array = streamOfTypeOrSubtype"

This branch includes the following new commits:

     new e6c1292  GROOVY-10028: STC: support "Type[] array = streamOfTypeOrSubtype"

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[groovy] 01/01: GROOVY-10028: STC: support "Type[] array = streamOfTypeOrSubtype"

Posted by em...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit e6c1292b297f01ec0b7d77515ec98843c87a3c54
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Apr 12 16:25:44 2021 -0500

    GROOVY-10028: STC: support "Type[] array = streamOfTypeOrSubtype"
    
    and "Collection<Type> collection = streamOfTypeOrSubtype"
    and "List<Type> list = streamOfTypeOrSubtype"
    and "Set<Type> set = streamOfTypeOrSubtype"
---
 .../typehandling/DefaultTypeTransformation.java    | 93 ++++++++++++++++------
 .../transform/stc/StaticTypeCheckingSupport.java   |  7 ++
 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, 197 insertions(+), 48 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 d0e73ea..acb83be 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;
@@ -162,6 +163,7 @@ 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 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);
@@ -671,6 +673,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()