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