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 2023/04/27 18:19:24 UTC
[groovy] branch GROOVY_4_0_X updated: GROOVY-11028: STC: empty list/map literals
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY_4_0_X by this push:
new a2964a0b3d GROOVY-11028: STC: empty list/map literals
a2964a0b3d is described below
commit a2964a0b3d1e0b42fc9ff4ee9de104687f5ea559
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Thu Apr 27 11:45:06 2023 -0500
GROOVY-11028: STC: empty list/map literals
---
.../transform/stc/StaticTypeCheckingSupport.java | 6 +-
.../transform/stc/StaticTypeCheckingVisitor.java | 116 +++++++++++----------
.../stc/ArraysAndCollectionsSTCTest.groovy | 111 ++++++++++++++------
.../groovy/transform/stc/CoercionSTCTest.groovy | 40 ++++---
4 files changed, 166 insertions(+), 107 deletions(-)
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 4bde9a0dc8..ffc7832165 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -1953,8 +1953,10 @@ public abstract class StaticTypeCheckingSupport {
static GenericsType getCombinedGenericsType(final GenericsType gt1, final GenericsType gt2) {
// GROOVY-7992, GROOVY-10765: "? super T" for gt1 or gt2?
- if (isUnboundedWildcard(gt1) != isUnboundedWildcard(gt2))
- return isUnboundedWildcard(gt2) ? gt1 : gt2;
+ if (isUnboundedWildcard(gt1) != isUnboundedWildcard(gt2)) return isUnboundedWildcard(gt2) ? gt1 : gt2;
+ // GROOVY-11028, et al.: empty list / map for gt1 or gt2?
+ if (gt2.isPlaceholder() && gt2.getName().startsWith("#")) return gt1;
+ if (gt1.isPlaceholder() && gt1.getName().startsWith("#")) return gt2;
// GROOVY-10315, GROOVY-10317, GROOVY-10339, ...
ClassNode cn1 = GenericsUtils.makeClassSafe0(CLASS_Type, gt1);
ClassNode cn2 = GenericsUtils.makeClassSafe0(CLASS_Type, gt2);
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 99157c09e5..0063fb67e3 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -5216,36 +5216,6 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
return exp.getNodeMetaData(INFERRED_RETURN_TYPE);
}
- protected ClassNode inferListExpressionType(final ListExpression list) {
- List<Expression> expressions = list.getExpressions();
- int nExpressions = expressions.size();
- if (nExpressions == 0) {
- return list.getType();
- }
- ClassNode listType = list.getType();
- GenericsType[] genericsTypes = listType.getGenericsTypes();
- if ((genericsTypes == null
- || genericsTypes.length == 0
- || (genericsTypes.length == 1 && isObjectType(genericsTypes[0].getType())))) {
- // maybe we can infer the component type
- List<ClassNode> nodes = new ArrayList<>(nExpressions);
- for (Expression expression : expressions) {
- if (isNullConstant(expression)) {
- // a null element is found in the list, skip it because we'll use the other elements from the list
- } else {
- nodes.add(getType(expression));
- }
- }
- if (!nodes.isEmpty()) {
- ClassNode itemType = lowestUpperBound(nodes);
-
- listType = listType.getPlainNodeReference();
- listType.setGenericsTypes(new GenericsType[]{new GenericsType(wrapTypeIfNecessary(itemType))});
- }
- }
- return listType;
- }
-
protected static boolean isNullConstant(final Expression expression) {
return expression instanceof ConstantExpression && ((ConstantExpression) expression).isNullExpression();
}
@@ -5258,38 +5228,70 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
return expression instanceof VariableExpression && ((VariableExpression) expression).isSuperExpression();
}
+ protected ClassNode inferListExpressionType(final ListExpression list) {
+ ClassNode listType = list.getType();
+
+ GenericsType[] genericsTypes = listType.getGenericsTypes();
+ if (!asBoolean(genericsTypes)
+ || (genericsTypes.length == 1 && genericsTypes[0].isPlaceholder())) {
+ // maybe infer the element type
+ List<ClassNode> expressionTypes = list.getExpressions().stream()
+ .filter(e -> !isNullConstant(e)).map(this::getType).collect(Collectors.toList());
+ if (!expressionTypes.isEmpty()) {
+ ClassNode subType = lowestUpperBound(expressionTypes);
+ genericsTypes = new GenericsType[]{new GenericsType(wrapTypeIfNecessary(subType))};
+ } else { // GROOVY-11028
+ GenericsType[] typeVars = listType.redirect().getGenericsTypes();
+ Map<GenericsTypeName, GenericsType> spec = extractGenericsConnectionsFromArguments(
+ typeVars, Parameter.EMPTY_ARRAY, ArgumentListExpression.EMPTY_ARGUMENTS, null);
+ genericsTypes = applyGenericsContext(spec, typeVars);
+ }
+ listType = listType.getPlainNodeReference();
+ listType.setGenericsTypes(genericsTypes);
+ }
+
+ return listType;
+ }
+
protected ClassNode inferMapExpressionType(final MapExpression map) {
- ClassNode mapType = LinkedHashMap_TYPE.getPlainNodeReference();
- List<MapEntryExpression> entryExpressions = map.getMapEntryExpressions();
- int nExpressions = entryExpressions.size();
- if (nExpressions == 0) return mapType;
+ ClassNode mapType = map.getType();
GenericsType[] genericsTypes = mapType.getGenericsTypes();
- if (genericsTypes == null
- || genericsTypes.length < 2
- || (genericsTypes.length == 2 && isObjectType(genericsTypes[0].getType()) && isObjectType(genericsTypes[1].getType()))) {
- ClassNode keyType;
- ClassNode valueType;
- List<ClassNode> keyTypes = new ArrayList<>(nExpressions);
- List<ClassNode> valueTypes = new ArrayList<>(nExpressions);
- for (MapEntryExpression entryExpression : entryExpressions) {
- valueType = getType(entryExpression.getValueExpression());
- if (!(entryExpression.getKeyExpression() instanceof SpreadMapExpression)) {
- keyType = getType(entryExpression.getKeyExpression());
- } else { // GROOVY-7247
- valueType = GenericsUtils.parameterizeType(valueType, MAP_TYPE);
- keyType = getCombinedBoundType(valueType.getGenericsTypes()[0]);
- valueType = getCombinedBoundType(valueType.getGenericsTypes()[1]);
- }
- keyTypes.add(keyType);
- valueTypes.add(valueType);
- }
- keyType = lowestUpperBound(keyTypes);
- valueType = lowestUpperBound(valueTypes);
- if (!isObjectType(keyType) || !isObjectType(valueType)) {
- mapType.setGenericsTypes(new GenericsType[]{new GenericsType(wrapTypeIfNecessary(keyType)), new GenericsType(wrapTypeIfNecessary(valueType))});
+ if (!asBoolean(genericsTypes)
+ || (genericsTypes.length == 2 && genericsTypes[0].isPlaceholder() && genericsTypes[1].isPlaceholder())) {
+ // maybe infer the entry type
+ List<MapEntryExpression> entryExpressions = map.getMapEntryExpressions();
+ int nExpressions = entryExpressions.size();
+ if (nExpressions != 0) {
+ ClassNode keyType;
+ ClassNode valueType;
+ List<ClassNode> keyTypes = new ArrayList<>(nExpressions);
+ List<ClassNode> valueTypes = new ArrayList<>(nExpressions);
+ for (MapEntryExpression entryExpression : entryExpressions) {
+ valueType = getType(entryExpression.getValueExpression());
+ if (!(entryExpression.getKeyExpression() instanceof SpreadMapExpression)) {
+ keyType = getType(entryExpression.getKeyExpression());
+ } else { // GROOVY-7247
+ valueType = GenericsUtils.parameterizeType(valueType, MAP_TYPE);
+ keyType = getCombinedBoundType(valueType.getGenericsTypes()[0]);
+ valueType = getCombinedBoundType(valueType.getGenericsTypes()[1]);
+ }
+ keyTypes.add(keyType);
+ valueTypes.add(valueType); // TODO: skip null value
+ }
+ keyType = lowestUpperBound(keyTypes);
+ valueType = lowestUpperBound(valueTypes);
+ genericsTypes = new GenericsType[]{new GenericsType(wrapTypeIfNecessary(keyType)), new GenericsType(wrapTypeIfNecessary(valueType))};
+ } else { // GROOVY-11028
+ GenericsType[] typeVars = LinkedHashMap_TYPE.getGenericsTypes();
+ Map<GenericsTypeName, GenericsType> spec = extractGenericsConnectionsFromArguments(
+ typeVars, Parameter.EMPTY_ARRAY, ArgumentListExpression.EMPTY_ARGUMENTS, null);
+ genericsTypes = applyGenericsContext(spec, typeVars);
}
+ mapType = LinkedHashMap_TYPE.getPlainNodeReference();
+ mapType.setGenericsTypes(genericsTypes);
}
+
return mapType;
}
diff --git a/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy b/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
index 237547ca6a..9f9c14bc89 100644
--- a/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
@@ -604,20 +604,6 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
'''
}
- void testListPlusEquals() {
- assertScript '''
- List<String> list = ['a','b']
- list += ['c']
- assert list == ['a','b','c']
- '''
-
- assertScript '''
- Collection<String> list = ['a','b']
- list += 'c'
- assert list == ['a','b','c']
- '''
- }
-
void testObjectArrayGet() {
assertScript '''
Object[] arr = [new Object()]
@@ -706,7 +692,7 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
String[] arr = ['1','2','3']
arr.getAt(1)
}
- assert m()=='2'
+ assert m() == '2'
'''
}
@@ -717,12 +703,12 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
@ASTTest(phase=INSTRUCTION_SELECTION, value={
assert node.getNodeMetaData(INFERRED_TYPE) == Integer_TYPE
})
- Integer j = org.codehaus.groovy.runtime.DefaultGroovyMethods.find(list) { int it -> it%2 == 0 }
+ Integer i = list.find { int it -> it % 2 == 0 }
@ASTTest(phase=INSTRUCTION_SELECTION, value={
assert node.getNodeMetaData(INFERRED_TYPE) == Integer_TYPE
})
- Integer i = list.find { int it -> it % 2 == 0 }
+ Integer j = org.codehaus.groovy.runtime.DefaultGroovyMethods.find(list) { int it -> it % 2 == 0 }
'''
}
@@ -860,6 +846,7 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
List<String> strings = ['y','z']
test(['x', *strings])
'''
+
assertScript '''
void test(List<String> list) {
assert list == ['x','y','z']
@@ -874,30 +861,63 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
// GROOVY-6241
void testAsImmutable() {
assertScript '''
- List<Integer> list = [1, 2, 3]
- List<Integer> immutableList = [1, 2, 3].asImmutable()
- Map<String, Integer> map = [foo: 123, bar: 456]
- Map<String, Integer> immutableMap = [foo: 123, bar: 456].asImmutable()
+ List<Integer> list = [1,2,3]
+ List<Integer> immutableList = [1,2,3].asImmutable()
+ assert list !== immutableList && list.equals(immutableList)
+
+ Map<String,Integer> map = [a:1]
+ Map<String,Integer> immutableMap = [a:1].asImmutable()
+ assert map !== immutableMap && map.equals(immutableMap)
'''
}
- // GROOVY-6350
- void testListPlusList() {
+ void testAsUnmodifiable() {
+ assertScript '''
+ List<Integer> list = [1,2,3]
+ List<Integer> immutableList = [1,2,3].asUnmodifiable()
+ assert list !== immutableList && list.equals(immutableList)
+
+ Map<String,Integer> map = [a:1]
+ Map<String,Integer> immutableMap = [a:1].asUnmodifiable()
+ assert map !== immutableMap && map.equals(immutableMap)
+ '''
+ }
+
+ void testListPlusEquals() {
+ assertScript '''
+ List<String> list = ['a','b']
+ list += ['c']
+ assert list == ['a','b','c']
+ '''
+
assertScript '''
- def foo = [] + []
- assert foo==[]
+ Collection<String> list = ['a','b']
+ list += 'c'
+ assert list == ['a','b','c']
'''
}
+ // GROOVY-6350
+ void testListPlusList() {
+ [['[]','Collections.emptyList()'], ['[]','Collections.emptyList()']].eachCombination { lhs, rhs ->
+ assertScript """
+ def list = $lhs + $rhs
+ assert list.isEmpty()
+ """
+ }
+ }
+
// GROOVY-7122
void testIterableLoop() {
assertScript '''
int countIt(Iterable<Integer> list) {
int count = 0
- for (Integer obj : list) {count ++}
+ for (Integer obj : list) {
+ count++
+ }
return count
}
- countIt([1,2,3])==3
+ countIt([1,2,3]) == 3
'''
}
@@ -1027,12 +1047,12 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
shouldFailWithMessages '''
Deque<String> deque = []
''',
- 'Cannot assign value of type java.util.List<E> to variable of type java.util.Deque<java.lang.String>'
+ 'Cannot assign value of type java.util.List','to variable of type java.util.Deque<java.lang.String>'
shouldFailWithMessages '''
Queue<String> queue = []
''',
- 'Cannot assign value of type java.util.List<E> to variable of type java.util.Queue<java.lang.String>'
+ 'Cannot assign value of type java.util.List','to variable of type java.util.Queue<java.lang.String>'
shouldFailWithMessages '''
Deque<String> deque = [""]
@@ -1061,6 +1081,28 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
'''
}
+ // GROOVY-11028
+ void testCollectionTypesInitializedByListLiteral5() {
+ assertScript '''
+ Collection<Integer> collection = [].withDefault { 1 }
+ assert collection.size() == 0
+ assert collection.get(0) == 1
+ assert collection.size() == 1
+ '''
+
+ assertScript '''
+ List<Integer> list = [].withDefault { 2 }
+ assert list.size() == 0
+ assert list.get(1) == 2
+ assert list.size() == 2
+ '''
+
+ shouldFailWithMessages '''
+ Set<Integer> set = [].withDefault { 3 }
+ ''',
+ 'Cannot assign value of type groovy.lang.ListWithDefault<java.lang.Integer> to variable of type java.util.Set<java.lang.Integer>'
+ }
+
void testMapWithTypeArgumentsInitializedByMapLiteral() {
['CharSequence,Integer', 'String,Number', 'CharSequence,Number'].each { spec ->
assertScript """
@@ -1082,6 +1124,13 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
assert c.map['key'] == '42'
'''
+ // GROOVY-11028
+ assertScript '''
+ Map<String,Integer> map = [:].withDefault { 0 }
+ assert map.size() == 0
+ assert map.foo == 0
+ '''
+
shouldFailWithMessages '''
Map<String,Integer> map = [1:2]
''',
@@ -1094,7 +1143,7 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
interface MVM<K, V> extends Map<K, List<V>> { }
MVM map = [:] // no STC error; fails at runtime
''',
- 'Cannot find matching constructor MVM(java.util.LinkedHashMap)'
+ 'Cannot find matching constructor MVM(', 'Map', ')'
}
// GROOVY-8136
@@ -1103,6 +1152,6 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
abstract class MVM<K, V> implements Map<K, List<V>> { }
MVM map = [:] // no STC error; fails at runtime
''',
- 'Cannot find matching constructor MVM(java.util.LinkedHashMap)'
+ 'Cannot find matching constructor MVM(', 'Map', ')'
}
}
diff --git a/src/test/groovy/transform/stc/CoercionSTCTest.groovy b/src/test/groovy/transform/stc/CoercionSTCTest.groovy
index 7e9c0ac8d2..5e304fa348 100644
--- a/src/test/groovy/transform/stc/CoercionSTCTest.groovy
+++ b/src/test/groovy/transform/stc/CoercionSTCTest.groovy
@@ -100,12 +100,16 @@ class CoercionSTCTest extends StaticTypeCheckingTestCase {
Class c = 'java.lang.String'
assert String.class
'''
+
shouldFailWithMessages '''
Class c = []
- ''', 'Cannot find matching constructor java.lang.Class()'
+ ''',
+ 'Cannot find matching constructor java.lang.Class()'
+
shouldFailWithMessages '''
Class c = [:]
- ''', 'Cannot find matching constructor java.lang.Class(java.util.LinkedHashMap)'
+ ''',
+ 'Cannot find matching constructor java.lang.Class(', 'Map', ')'
}
// GROOVY-6803
@@ -126,36 +130,38 @@ class CoercionSTCTest extends StaticTypeCheckingTestCase {
// GROOVY-10277
void testCoerceToFunctionalInterface() {
- assertScript '''
- import java.util.function.*
+ assertScript '''import java.util.function.*
Consumer<Number> c = { n -> }
Supplier<Number> s = { -> 42 }
Predicate<Number> p = { n -> 42 }
'''
- assertScript '''
- import java.util.function.*
+
+ assertScript '''import java.util.function.*
def c = (Consumer<Number>) { n -> }
def s = (Supplier<Number>) { -> 42 }
def p = (Predicate<Number>) { n -> 42 }
'''
- assertScript '''
- import java.util.function.*
+
+ assertScript '''import java.util.function.*
def c = { n -> } as Consumer<Number>
def s = { -> 42 } as Supplier<Number>
def p = { n -> 42 } as Predicate<Number>
'''
- shouldFailWithMessages '''
- import java.util.function.*
+
+ shouldFailWithMessages '''import java.util.function.*
def s = (Supplier<Number>) { -> false }
- ''', 'Cannot return value of type boolean for closure expecting java.lang.Number'
- shouldFailWithMessages '''
- import java.util.function.*
+ ''',
+ 'Cannot return value of type boolean for closure expecting java.lang.Number'
+
+ shouldFailWithMessages '''import java.util.function.*
def s = { -> false } as Supplier<Number>
- ''', 'Cannot return value of type boolean for closure expecting java.lang.Number'
- shouldFailWithMessages '''
- import java.util.function.*
+ ''',
+ 'Cannot return value of type boolean for closure expecting java.lang.Number'
+
+ shouldFailWithMessages '''import java.util.function.*
def s = (() -> ['']) as Supplier<Number>
- ''', 'Cannot return value of type java.util.List<java.lang.String> for lambda expecting java.lang.Number'
+ ''',
+ 'Cannot return value of type java.util.List<java.lang.String> for lambda expecting java.lang.Number'
}
// GROOVY-8045