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()