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 2022/04/26 20:09:04 UTC

[groovy] branch master updated: GROOVY-10599: STC: support spread list elements: `['a',*letters(),'z']`

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

emilles 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 5c468cd352 GROOVY-10599: STC: support spread list elements: `['a',*letters(),'z']`
5c468cd352 is described below

commit 5c468cd352f37fb5c599a3f51534ffcc55b339ed
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Apr 26 15:00:28 2022 -0500

    GROOVY-10599: STC: support spread list elements: `['a',*letters(),'z']`
    
    https://docs.groovy-lang.org/latest/html/documentation/#_spread_list_elements
---
 .../transform/sc/StaticCompilationVisitor.java     |   5 -
 .../transform/stc/StaticTypeCheckingVisitor.java   | 128 ++++++++++-----------
 .../stc/ArraysAndCollectionsSTCTest.groovy         |  89 ++++++++++----
 .../groovy/transform/stc/MethodCallsSTCTest.groovy |  60 +++-------
 .../groovy/transform/AutoFinalTransformTest.groovy |   2 +-
 5 files changed, 149 insertions(+), 135 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java b/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
index 44d7015432..3c222adc29 100644
--- a/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
@@ -40,7 +40,6 @@ import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.MethodCallExpression;
 import org.codehaus.groovy.ast.expr.PropertyExpression;
-import org.codehaus.groovy.ast.expr.SpreadExpression;
 import org.codehaus.groovy.ast.stmt.EmptyStatement;
 import org.codehaus.groovy.ast.stmt.ExpressionStatement;
 import org.codehaus.groovy.ast.stmt.ForStatement;
@@ -575,8 +574,4 @@ public class StaticCompilationVisitor extends StaticTypeCheckingVisitor {
             expression.getObjectExpression().putNodeMetaData(RECEIVER_OF_DYNAMIC_PROPERTY, dynamic);
         }
     }
-
-    @Override
-    public void visitSpreadExpression(final SpreadExpression expression) {
-    }
 }
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 5369c505df..37338abd0b 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -4979,24 +4979,22 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
         }
     }
 
-    protected ClassNode getType(final ASTNode exp) {
-        ClassNode cn = exp.getNodeMetaData(INFERRED_TYPE);
-        if (cn != null) {
-            return cn;
+    protected ClassNode getType(final ASTNode node) {
+        ClassNode type = node.getNodeMetaData(INFERRED_TYPE);
+        if (type != null) {
+            return type;
         }
-        if (exp instanceof ClassExpression) {
-            ClassNode node = CLASS_Type.getPlainNodeReference();
-            node.setGenericsTypes(new GenericsType[]{
-                    new GenericsType(((ClassExpression) exp).getType())
-            });
-            return node;
+        if (node instanceof ClassExpression) {
+            type = ((ClassExpression) node).getType();
+            return makeClassSafe0(CLASS_Type, new GenericsType(type));
         }
-        if (exp instanceof VariableExpression) {
-            VariableExpression vexp = (VariableExpression) exp;
-            ClassNode selfTrait = isTraitSelf(vexp);
-            if (selfTrait != null) return makeSelf(selfTrait);
+        if (node instanceof VariableExpression) {
+            VariableExpression vexp = (VariableExpression) node;
+            type = isTraitSelf(vexp);
+            if (type != null) return makeSelf(type);
             if (vexp.isThisExpression()) return makeThis();
             if (vexp.isSuperExpression()) return makeSuper();
+
             Variable variable = vexp.getAccessedVariable();
             if (variable instanceof FieldNode) {
                 FieldNode fieldNode = (FieldNode) variable;
@@ -5012,7 +5010,6 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             }
             if (variable instanceof Parameter) {
                 Parameter parameter = (Parameter) variable;
-                ClassNode type = null;
                 // check if param part of control structure - but not if inside instanceof
                 List<ClassNode> temporaryTypesForExpression = getTemporaryTypesForExpression(vexp);
                 if (temporaryTypesForExpression == null || temporaryTypesForExpression.isEmpty()) {
@@ -5031,74 +5028,73 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             }
             return vexp.getOriginType();
         }
-
-        if (exp instanceof ListExpression) {
-            return inferListExpressionType((ListExpression) exp);
-        }
-        if (exp instanceof MapExpression) {
-            return inferMapExpressionType((MapExpression) exp);
-        }
-        if (exp instanceof ConstructorCallExpression) {
-            return ((ConstructorCallExpression) exp).getType();
+        if (node instanceof Parameter || node instanceof FieldNode || node instanceof PropertyNode) {
+            return ((Variable) node).getOriginType();
         }
-        if (exp instanceof MethodNode) {
-            if ((exp == GET_DELEGATE || exp == GET_OWNER || exp == GET_THISOBJECT) && typeCheckingContext.getEnclosingClosure() != null) {
+
+        if (node instanceof MethodNode) {
+            if ((node == GET_DELEGATE || node == GET_OWNER || node == GET_THISOBJECT)
+                    && typeCheckingContext.getEnclosingClosure() != null) {
                 return typeCheckingContext.getEnclosingClassNode();
             }
-            ClassNode ret = getInferredReturnType(exp);
-            return ret != null ? ret : ((MethodNode) exp).getReturnType();
+            type = ((MethodNode) node).getReturnType();
+            return Optional.ofNullable(getInferredReturnType(node)).orElse(type);
         }
-        if (exp instanceof FieldNode || exp instanceof PropertyNode) {
-            return ((Variable) exp).getOriginType();
-        }
-        if (exp instanceof RangeExpression) {
-            ClassNode plain = RANGE_TYPE.getPlainNodeReference();
-            RangeExpression re = (RangeExpression) exp;
-            ClassNode fromType = getType(re.getFrom());
-            ClassNode toType = getType(re.getTo());
-            if (fromType.equals(toType)) {
-                plain.setGenericsTypes(new GenericsType[]{
-                        new GenericsType(wrapTypeIfNecessary(fromType))
-                });
-            } else {
-                plain.setGenericsTypes(new GenericsType[]{
-                        new GenericsType(wrapTypeIfNecessary(lowestUpperBound(fromType, toType)))
-                });
+        if (node instanceof MethodCall) {
+            if (node instanceof ConstructorCallExpression) {
+                return ((ConstructorCallExpression) node).getType();
+            }
+            MethodNode target = node.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
+            if (target != null) {
+                return getType(target);
             }
-            return plain;
-        }
-        if (exp instanceof UnaryPlusExpression) {
-            return getType(((UnaryPlusExpression) exp).getExpression());
-        }
-        if (exp instanceof UnaryMinusExpression) {
-            return getType(((UnaryMinusExpression) exp).getExpression());
-        }
-        if (exp instanceof BitwiseNegationExpression) {
-            return getType(((BitwiseNegationExpression) exp).getExpression());
-        }
-        if (exp instanceof Parameter) {
-            return ((Parameter) exp).getOriginType();
         }
-        if (exp instanceof ClosureExpression) {
-            ClassNode type = CLOSURE_TYPE.getPlainNodeReference();
-            ClassNode returnType = getInferredReturnType(exp);
+        if (node instanceof ClosureExpression) {
+            type = CLOSURE_TYPE.getPlainNodeReference();
+            ClassNode returnType = getInferredReturnType(node);
             if (returnType != null) {
                 type.setGenericsTypes(new GenericsType[]{
                     new GenericsType(wrapTypeIfNecessary(returnType))
                 });
             }
-            Parameter[] parameters = ((ClosureExpression) exp).getParameters();
+            Parameter[] parameters = ((ClosureExpression) node).getParameters();
             int nParameters = parameters == null ? 0
                : parameters.length == 0 ? -1 : parameters.length;
             type.putNodeMetaData(CLOSURE_ARGUMENTS, nParameters);
             return type;
-        } else if (exp instanceof MethodCall) {
-            MethodNode target = exp.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
-            if (target != null) {
-                return getType(target);
+        }
+
+        if (node instanceof ListExpression) {
+            return inferListExpressionType((ListExpression) node);
+        }
+        if (node instanceof MapExpression) {
+            return inferMapExpressionType((MapExpression) node);
+        }
+        if (node instanceof RangeExpression) {
+            RangeExpression re = (RangeExpression) node;
+            ClassNode fromType = getType(re.getFrom());
+            ClassNode toType = getType(re.getTo());
+            if (fromType.equals(toType)) {
+                type = wrapTypeIfNecessary(fromType);
+            } else {
+                type = wrapTypeIfNecessary(lowestUpperBound(fromType, toType));
             }
+            return makeClassSafe0(RANGE_TYPE, new GenericsType(type));
+        }
+        if (node instanceof SpreadExpression) {
+            type = getType(((SpreadExpression) node).getExpression());
+            return inferComponentType(type, null); // for list literal
+        }
+        if (node instanceof UnaryPlusExpression) {
+            return getType(((UnaryPlusExpression) node).getExpression());
+        }
+        if (node instanceof UnaryMinusExpression) {
+            return getType(((UnaryMinusExpression) node).getExpression());
+        }
+        if (node instanceof BitwiseNegationExpression) {
+            return getType(((BitwiseNegationExpression) node).getExpression());
         }
-        return ((Expression) exp).getType();
+        return ((Expression) node).getType();
     }
 
     private ClassNode getTypeFromClosureArguments(final Parameter parameter, final TypeCheckingContext.EnclosingClosure enclosingClosure) {
diff --git a/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy b/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
index 0636c293f1..4c567495e1 100644
--- a/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ArraysAndCollectionsSTCTest.groovy
@@ -18,6 +18,8 @@
  */
 package groovy.transform.stc
 
+import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
+
 /**
  * Unit tests for static type checking : arrays and collections.
  */
@@ -222,6 +224,31 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
             List classes = list*.toUpperCase()
             assert classes == ['A','B','C']
         '''
+
+        assertScript '''
+            def list = 'a,b,c'.split(',')*.toUpperCase()
+            assert list == ['A', 'B', 'C']
+        '''
+
+        // GROOVY-8133
+        assertScript '''
+            def list = ['a','b','c'].stream()*.toUpperCase()
+            assert list == ['A', 'B', 'C']
+        '''
+
+        shouldFailWithMessages '''
+            def list = 'abc'*.toUpperCase()
+            assert list == ['A', 'B', 'C']
+        ''',
+        'Spread-dot operator can only be used on iterable types'
+
+        config.compilationCustomizers
+              .find { it instanceof ASTTransformationCustomizer }
+              .annotationParameters = [extensions: PrecompiledExtensionNotExtendingDSL.name]
+        assertScript '''
+            def list = 'abc'*.toUpperCase()
+            assert list == ['A', 'B', 'C']
+        '''
     }
 
     void testInferredMapDotProperty() {
@@ -299,7 +326,7 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
 
     void testForInLoopWithRange() {
         assertScript '''
-            for (int i in 1..10) { i*2 }
+            for (int i in 1..10) { i * 2 }
         '''
     }
 
@@ -755,7 +782,7 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
 
     // GROOVY-6311
     void testSetSpread() {
-        assertScript """
+        assertScript '''
             class Inner {Set<String> strings}
             class Outer {Set<Inner> inners}
             Outer outer = new Outer(inners: [ new Inner(strings: ['abc', 'def'] as Set), new Inner(strings: ['ghi'] as Set) ] as Set)
@@ -763,25 +790,59 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
             assert res[1].contains('ghi')
             assert res[0].contains('abc')
             assert res[0].contains('def')
-        """
+        '''
+    }
+
+    // GROOVY-8033
+    void testSetSpreadPropertyInStaticContext() {
+        assertScript '''
+            class Foo {
+                String name
+            }
+            static List<String> meth() {
+                Set<Foo> foos = [new Foo(name: 'pls'), new Foo(name: 'bar')].toSet()
+                foos*.name
+            }
+            assert meth().toSet() == ['pls', 'bar'].toSet()
+        '''
+    }
+
+    // GROOVY-10599
+    void testListExpressionWithSpreadExpression() {
+        assertScript '''
+            void test(List<String> list) {
+                assert list == ['x','y','z']
+            }
+            List<String> strings = ['y','z']
+            test(['x', *strings])
+        '''
+        assertScript '''
+            void test(List<String> list) {
+                assert list == ['x','y','z']
+            }
+            List<String> getStrings() {
+                return ['y','z']
+            }
+            test(['x', *strings])
+        '''
     }
 
     // GROOVY-6241
     void testAsImmutable() {
-        assertScript """
+        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()
-        """
+        '''
     }
 
     // GROOVY-6350
     void testListPlusList() {
-        assertScript """
+        assertScript '''
             def foo = [] + []
             assert foo==[]
-        """
+        '''
     }
 
     // GROOVY-7122
@@ -796,20 +857,6 @@ class ArraysAndCollectionsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
-    // GROOVY-8033
-    void testSetSpreadPropertyInStaticContext() {
-        assertScript '''
-            class Foo {
-                String name
-            }
-            static List<String> meth() {
-                Set<Foo> foos = [new Foo(name: 'pls'), new Foo(name: 'bar')].toSet()
-                foos*.name
-            }
-            assert meth().toSet() == ['pls', 'bar'].toSet()
-        '''
-    }
-
     void testAbstractTypeInitializedByListLiteral() {
         shouldFailWithMessages '''
             abstract class A {
diff --git a/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy b/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy
index 3b11fc9e83..792b8e3b7d 100644
--- a/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy
@@ -19,7 +19,6 @@
 package groovy.transform.stc
 
 import org.codehaus.groovy.control.MultipleCompilationErrorsException
-import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
 
 import static org.codehaus.groovy.control.customizers.builder.CompilerCustomizationBuilder.withConfig
 
@@ -1149,75 +1148,52 @@ class MethodCallsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
-    void testSpreadArgsForbiddenInMethodCall() {
+    void testSpreadArgsForbiddenInNonStaticMethodCall() {
         shouldFailWithMessages '''
-            void foo(String a, String b, int c, double d1, double d2) {}
-            void bar(String[] args, int c, double[] nums) {
-                foo(*args, c, *nums)
+            def foo(String a, String b, int c, double d, double e) {
+            }
+            def bar(String[] strings, int i, double[] numbers) {
+                foo(*strings, i, *numbers)
             }
         ''',
         'The spread operator cannot be used as argument of method or closure calls with static type checking because the number of arguments cannot be determined at compile time',
         'The spread operator cannot be used as argument of method or closure calls with static type checking because the number of arguments cannot be determined at compile time',
-        'Cannot find matching method'
+        'Cannot find matching method '
     }
 
     void testSpreadArgsForbiddenInStaticMethodCall() {
         shouldFailWithMessages '''
-            static void foo(String a, String b, int c, double d1, double d2) {}
-            static void bar(String[] args, int c, double[] nums) {
-                foo(*args, c, *nums)
+            static foo(String a, String b, int c, double d, double e) {
+            }
+            static bar(String[] strings, int i, double[] numbers) {
+                foo(*strings, i, *numbers)
             }
         ''',
         'The spread operator cannot be used as argument of method or closure calls with static type checking because the number of arguments cannot be determined at compile time',
         'The spread operator cannot be used as argument of method or closure calls with static type checking because the number of arguments cannot be determined at compile time',
-        'Cannot find matching method'
+        'Cannot find matching method '
     }
 
     void testSpreadArgsForbiddenInConstructorCall() {
         shouldFailWithMessages '''
-            class SpreadInCtor {
-                SpreadInCtor(String a, String b) { }
+            class C {
+                C(String a, String b) {
+                }
             }
-            new SpreadInCtor(*['A', 'B'])
+            new C(*['A','B'])
         ''',
         'The spread operator cannot be used as argument of method or closure calls with static type checking because the number of arguments cannot be determined at compile time',
-        'Cannot find matching method SpreadInCtor#<init>(java.util.List<E>)'
+        'Cannot find matching method '
     }
 
     void testSpreadArgsForbiddenInClosureCall() {
         shouldFailWithMessages '''
             def closure = { String a, String b, String c -> println "$a $b $c" }
-            def strings = ['A', 'B', 'C']
+            def strings = ['A','B','C']
             closure(*strings)
         ''',
         'The spread operator cannot be used as argument of method or closure calls with static type checking because the number of arguments cannot be determined at compile time',
-        'Cannot call closure that accepts [java.lang.String, java.lang.String, java.lang.String] with [java.lang.Object]'
-    }
-
-    // GROOVY-8133
-    void testSpreadDot() {
-        assertScript '''
-            def list = ['a','b','c'].stream()*.toUpperCase()
-            assert list == ['A', 'B', 'C']
-        '''
-        assertScript '''
-            def list = 'a,b,c'.split(',')*.toUpperCase()
-            assert list == ['A', 'B', 'C']
-        '''
-
-        shouldFailWithMessages '''
-            def list = 'abc'*.toUpperCase()
-            assert list == ['A', 'B', 'C']
-        ''',
-        'Spread-dot operator can only be used on iterable types'
-
-        config.compilationCustomizers
-              .find { it instanceof ASTTransformationCustomizer }
-              .annotationParameters = [extensions: PrecompiledExtensionNotExtendingDSL.name]
-        assertScript '''
-            def list = 'abc'*.toUpperCase()
-            assert list == ['A', 'B', 'C']
-        '''
+        'Cannot call closure that accepts [java.lang.String, java.lang.String, java.lang.String] with '
     }
 
     // GROOVY-10476
diff --git a/src/test/org/codehaus/groovy/transform/AutoFinalTransformTest.groovy b/src/test/org/codehaus/groovy/transform/AutoFinalTransformTest.groovy
index 99422ffa13..c39b9e0681 100644
--- a/src/test/org/codehaus/groovy/transform/AutoFinalTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/AutoFinalTransformTest.groovy
@@ -18,7 +18,7 @@
  */
 package org.codehaus.groovy.transform
 
-import org.codehaus.groovy.control.customizers.*
+import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
 import org.junit.Test
 
 import static groovy.test.GroovyAssert.assertScript