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/02/09 20:10:34 UTC

[groovy] 01/03: GROOVY-8133: STC: support spread-dot for any iterable type, incl. Stream

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

commit 5748ecb8fcf9ba158e9872e3b1982e621fcc1d45
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Feb 8 19:01:53 2022 -0600

    GROOVY-8133: STC: support spread-dot for any iterable type, incl. Stream
---
 .../transform/stc/StaticTypeCheckingVisitor.java   | 50 +++++++++++++---------
 .../groovy/transform/stc/MethodCallsSTCTest.groovy | 29 ++++++++++++-
 .../stc/PrecompiledExtensionNotExtendingDSL.groovy | 31 +++++++-------
 .../stc/TypeCheckingExtensionsTest.groovy          |  8 ++--
 4 files changed, 77 insertions(+), 41 deletions(-)

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 e42bf45..b121c4b 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -218,7 +218,6 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.elvisX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.getGetterName;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements;
-import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.thisPropX;
 import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
@@ -2481,9 +2480,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
     }
 
     private static ClassNode wrapClosureType(final ClassNode returnType) {
-        ClassNode inferredType = CLOSURE_TYPE.getPlainNodeReference();
-        inferredType.setGenericsTypes(new GenericsType[]{new GenericsType(wrapTypeIfNecessary(returnType))});
-        return inferredType;
+        return makeClassSafe0(CLOSURE_TYPE, wrapTypeIfNecessary(returnType).asGenericsType());
     }
 
     protected DelegationMetadata getDelegationMetadata(final ClosureExpression expression) {
@@ -3307,16 +3304,16 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
         if (objectExpression instanceof ConstructorCallExpression) { // GROOVY-10228
             inferDiamondType((ConstructorCallExpression) objectExpression, receiver.getPlainNodeReference());
         }
-        if (call.isSpreadSafe()) { // make sure receiver is array or collection then check element type
-            if (!receiver.isArray() && !implementsInterfaceOrIsSubclassOf(receiver, Collection_TYPE)) {
-                addStaticTypeError("Spread operator can only be used on collection types", objectExpression);
+        if (call.isSpreadSafe()) {
+            ClassNode componentType = inferComponentType(receiver, null);
+            if (componentType == null) {
+                addStaticTypeError("Spread-dot operator can only be used on iterable types", objectExpression);
             } else {
-                ClassNode componentType = inferComponentType(receiver, int_TYPE);
-                MethodCallExpression subcall = callX(castX(componentType, EmptyExpression.INSTANCE), name, call.getArguments());
+                MethodCallExpression subcall = callX(varX("item", componentType), name, call.getArguments());
                 subcall.setLineNumber(call.getLineNumber()); subcall.setColumnNumber(call.getColumnNumber());
                 subcall.setImplicitThis(call.isImplicitThis());
                 visitMethodCallExpression(subcall);
-                // the inferred type here should be a list of what the subcall returns
+                // inferred type should be a list of what sub-call returns
                 storeType(call, extension.buildListType(getType(subcall)));
                 storeTargetMethod(call, subcall.getNodeMetaData(DIRECT_METHOD_CALL_TARGET));
             }
@@ -4546,23 +4543,36 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
         return Number_TYPE;
     }
 
-    protected ClassNode inferComponentType(final ClassNode containerType, final ClassNode indexType) {
-        ClassNode componentType = containerType.getComponentType();
+    protected ClassNode inferComponentType(final ClassNode receiverType, final ClassNode subscriptType) {
+        ClassNode componentType = receiverType.getComponentType();
         if (componentType == null) {
-            // GROOVY-5521
-            // try to identify a getAt method
+            MethodCallExpression mce;
+            if (subscriptType != null) { // GROOVY-5521: check for a suitable "getAt(T)" method
+                mce = callX(varX("#", receiverType), "getAt", varX("selector", subscriptType));
+            } else { // GROOVY-8133: check for an "iterator()" method
+                mce = callX(varX("#", receiverType), "iterator");
+            }
+            mce.setImplicitThis(false); // GROOVY-8943
+
             typeCheckingContext.pushErrorCollector();
-            MethodCallExpression vcall = callX(localVarX("_hash_", containerType), "getAt", varX("_index_", indexType));
-            vcall.setImplicitThis(false); // GROOVY-8943
             try {
-                visitMethodCallExpression(vcall);
+                visitMethodCallExpression(mce);
             } finally {
                 typeCheckingContext.popErrorCollector();
             }
-            return getType(vcall);
-        } else {
-            return componentType;
+
+            if (subscriptType != null) {
+                componentType = getType(mce);
+            } else {
+                ClassNode iteratorType = getType(mce);
+                if (isOrImplements(iteratorType, Iterator_TYPE) && (iteratorType.getGenericsTypes() != null
+                        // ignore the iterator(Object) extension method, since it makes *everything* appear iterable
+                        || !mce.<MethodNode>getNodeMetaData(DIRECT_METHOD_CALL_TARGET).getDeclaringClass().equals(OBJECT_TYPE))) {
+                    componentType = Optional.ofNullable(iteratorType.getGenericsTypes()).map(gt -> getCombinedBoundType(gt[0])).orElse(OBJECT_TYPE);
+                }
+            }
         }
+        return componentType;
     }
 
     protected MethodNode findMethodOrFail(final Expression expr, final ClassNode receiver, final String name, final ClassNode... args) {
diff --git a/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy b/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy
index 481c496..dc0fb19 100644
--- a/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/MethodCallsSTCTest.groovy
@@ -19,6 +19,7 @@
 package groovy.transform.stc
 
 import org.codehaus.groovy.control.MultipleCompilationErrorsException
+import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
 import org.codehaus.groovy.control.customizers.ImportCustomizer
 
 /**
@@ -1159,6 +1160,32 @@ class MethodCallsSTCTest extends StaticTypeCheckingTestCase {
         'Cannot call closure that accepts [java.lang.String, java.lang.String, java.lang.String] with [java.lang.Object]'
     }
 
+    // GROOVY-8133
+    void testSpreadDotOperator() {
+        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']
+        '''
+    }
+
     void testBoxingShouldCostMore() {
         assertScript '''
             int foo(int x) { 1 }
@@ -1411,7 +1438,7 @@ class MethodCallsSTCTest extends StaticTypeCheckingTestCase {
     void testStaticContextScoping() {
         assertScript '''
             class A {
-                static List foo = 'a,b,c'.split(/,/).toList()*.trim()
+                static List foo = 'a,b,c'.split(/,/)*.trim()
             }
             assert A.foo == ['a','b','c']
         '''
diff --git a/src/test/groovy/transform/stc/PrecompiledExtensionNotExtendingDSL.groovy b/src/test/groovy/transform/stc/PrecompiledExtensionNotExtendingDSL.groovy
index 020304b..6598a4b 100644
--- a/src/test/groovy/transform/stc/PrecompiledExtensionNotExtendingDSL.groovy
+++ b/src/test/groovy/transform/stc/PrecompiledExtensionNotExtendingDSL.groovy
@@ -18,28 +18,27 @@
  */
 package groovy.transform.stc
 
+import groovy.transform.AutoFinal
+import groovy.transform.InheritConstructors
+import org.codehaus.groovy.ast.ClassHelper
+import org.codehaus.groovy.ast.ClassNode
 import org.codehaus.groovy.ast.MethodNode
 import org.codehaus.groovy.ast.expr.Expression
 import org.codehaus.groovy.transform.stc.AbstractTypeCheckingExtension
-import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor
 
-class PrecompiledExtensionNotExtendingDSL extends AbstractTypeCheckingExtension {
-
-
-    PrecompiledExtensionNotExtendingDSL(
-            final StaticTypeCheckingVisitor typeCheckingVisitor) {
-        super(typeCheckingVisitor)
-    }
-
-    @Override
-    void setup() {
-        addStaticTypeError('Error thrown from extension in setup', context.enclosingClassNode)
-    }
+import static org.codehaus.groovy.ast.tools.GenericsUtils.makeClassSafeWithGenerics
 
+@AutoFinal @InheritConstructors
+class PrecompiledExtensionNotExtendingDSL extends AbstractTypeCheckingExtension {
     @Override
-    void onMethodSelection(final Expression expression, final MethodNode target) {
-        if (target.name=='println') {
+    void onMethodSelection(Expression expression, MethodNode target) {
+        switch (target.name) {
+          case 'println':
             addStaticTypeError('Error thrown from extension in onMethodSelection', expression.arguments[0])
+            break
+          case 'iterator':
+            ClassNode iteratorType = makeClassSafeWithGenerics(ClassHelper.Iterator_TYPE, ClassHelper.STRING_TYPE.asGenericsType())
+            storeType(expression, iteratorType) // indicate "string.iterator()" returns Iterator<String>
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/test/groovy/transform/stc/TypeCheckingExtensionsTest.groovy b/src/test/groovy/transform/stc/TypeCheckingExtensionsTest.groovy
index 3558c23..af5ddce 100644
--- a/src/test/groovy/transform/stc/TypeCheckingExtensionsTest.groovy
+++ b/src/test/groovy/transform/stc/TypeCheckingExtensionsTest.groovy
@@ -483,8 +483,8 @@ class TypeCheckingExtensionsTest extends StaticTypeCheckingTestCase {
         extension = 'groovy.transform.stc.PrecompiledExtension'
         shouldFailWithMessages '''
             println 'Everything is ok'
-        ''', 'Error thrown from extension'
-
+        ''',
+        'Error thrown from extension'
     }
 
     void testPrecompiledExtensionNotExtendingTypeCheckingDSL() {
@@ -495,7 +495,7 @@ class TypeCheckingExtensionsTest extends StaticTypeCheckingTestCase {
         extension = 'groovy.transform.stc.PrecompiledExtensionNotExtendingDSL'
         shouldFailWithMessages '''
             println 'Everything is ok'
-        ''', 'Error thrown from extension in setup', 'Error thrown from extension in onMethodSelection'
-
+        ''',
+        'Error thrown from extension in onMethodSelection'
     }
 }