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

emilles pushed a commit to branch GROOVY_3_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 071f9b382627f874984c12bfa7c8e9b3f61aec62
Author: Eric Milles <[email protected]>
AuthorDate: Sat Jun 10 12:26:13 2023 -0500

    GROOVY-11090: STC: tuple spread across closure parameter(s)
    
    supports `withIndex()`
    
    3_0_X backport
---
 .../transform/stc/StaticTypeCheckingVisitor.java   |  36 +++-
 src/test/groovy/bugs/Groovy8816.groovy             |  39 -----
 .../stc/ClosureParamTypeInferenceSTCTest.groovy    | 186 ++++++++++++++++++++-
 3 files changed, 213 insertions(+), 48 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 46c7a031c4..8c1270db71 100644
--- 
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ 
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -128,6 +128,7 @@ import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -335,12 +336,13 @@ public class StaticTypeCheckingVisitor extends 
ClassCodeVisitorSupport {
     protected static final ClassNode ITERABLE_TYPE = 
ClassHelper.make(Iterable.class);
     private   static final ClassNode SET_TYPE = ClassHelper.make(Set.class);
 
+    private static List<ClassNode> TUPLE_TYPES = 
Arrays.stream(ClassHelper.TUPLE_CLASSES).map(ClassHelper::makeWithoutCaching).collect(Collectors.toList());
+
     public static final Statement GENERATED_EMPTY_STATEMENT = 
EmptyStatement.INSTANCE;
 
-    // Cache closure call methods
-    public static final MethodNode CLOSURE_CALL_NO_ARG = 
CLOSURE_TYPE.getDeclaredMethod("call", Parameter.EMPTY_ARRAY);
+    public static final MethodNode CLOSURE_CALL_NO_ARG  = 
CLOSURE_TYPE.getDeclaredMethod("call", Parameter.EMPTY_ARRAY);
     public static final MethodNode CLOSURE_CALL_ONE_ARG = 
CLOSURE_TYPE.getDeclaredMethod("call", new Parameter[]{new 
Parameter(OBJECT_TYPE, "arg")});
-    public static final MethodNode CLOSURE_CALL_VARGS = 
CLOSURE_TYPE.getDeclaredMethod("call", new Parameter[]{new 
Parameter(OBJECT_TYPE.makeArray(), "args")});
+    public static final MethodNode CLOSURE_CALL_VARGS   = 
CLOSURE_TYPE.getDeclaredMethod("call", new Parameter[]{new 
Parameter(OBJECT_TYPE.makeArray(), "args")});
 
     protected final ReturnAdder.ReturnStatementListener returnListener = new 
ReturnAdder.ReturnStatementListener() {
         @Override
@@ -3025,7 +3027,7 @@ public class StaticTypeCheckingVisitor extends 
ClassCodeVisitorSupport {
                 Expression value = annotation.getMember("value");
                 Expression options = annotation.getMember("options");
                 Expression conflictResolver = 
annotation.getMember("conflictResolutionStrategy");
-                doInferClosureParameterTypes(receiver, arguments, expression, 
method, value, conflictResolver, options);
+                processClosureParams(receiver, arguments, expression, method, 
value, conflictResolver, options);
             }
         } else if (isSAMType(target.getOriginType())) { // SAM-type coercion
             Map<GenericsTypeName, GenericsType> context = 
extractPlaceHoldersVisibleToDeclaration(receiver, method, arguments);
@@ -3158,12 +3160,12 @@ public class StaticTypeCheckingVisitor extends 
ClassCodeVisitorSupport {
             addStaticTypeError("Expected type " + prettyPrintType(target) + " 
for " + (lambda ? "lambda" : "closure") + " parameter: " + source.getName(), 
source);
     }
 
-    private void doInferClosureParameterTypes(final ClassNode receiver, final 
Expression arguments, final ClosureExpression expression, final MethodNode 
selectedMethod, final Expression hintClass, final Expression resolverClass, 
final Expression options) {
+    private void processClosureParams(final ClassNode receiver, final 
Expression arguments, final ClosureExpression expression, final MethodNode 
selectedMethod, final Expression hintClass, final Expression resolverClass, 
final Expression options) {
         Parameter[] closureParams = hasImplicitParameter(expression) ? new 
Parameter[]{new Parameter(DYNAMIC_TYPE,"it")} : getParametersSafe(expression);
 
-        List<ClassNode[]> closureSignatures = 
getSignaturesFromHint(selectedMethod, hintClass, options, expression);
+        List<ClassNode[]> closureSignatures = new 
LinkedList<>(getSignaturesFromHint(selectedMethod, hintClass, options, 
expression));
         List<ClassNode[]> candidates = new LinkedList<>();
-        for (ClassNode[]  signature : closureSignatures) {
+        for (ListIterator<ClassNode[]> it = closureSignatures.listIterator(); 
it.hasNext(); ) { ClassNode[] signature = it.next();
             resolveGenericsFromTypeHint(receiver, arguments, selectedMethod, 
signature);
             if (closureParams.length == signature.length) {
                 candidates.add(signature);
@@ -3171,6 +3173,20 @@ public class StaticTypeCheckingVisitor extends 
ClassCodeVisitorSupport {
             if ((closureParams.length > 1 || closureParams.length == 1 && 
!closureParams[0].getOriginType().equals(OBJECT_TYPE))
                     && signature.length == 1 && isOrImplements(signature[0], 
LIST_TYPE)) { // see ClosureMetaClass#invokeMethod
                 // list element(s) spread across the closure parameter(s)
+                int itemCount = TUPLE_TYPES.indexOf(signature[0]);
+                if (itemCount >= 0) { // GROOVY-11090: Tuple[0-16]
+                    if (itemCount != closureParams.length) {
+                        // for param count error messages
+                        it.add(new ClassNode[itemCount]);
+                        continue;
+                    }
+                    GenericsType[] spec = signature[0].getGenericsTypes();
+                    if (spec != null) { // edge case: Tuple0 falls through
+                        signature = 
Arrays.stream(spec).map(GenericsType::getType).toArray(ClassNode[]::new);
+                        candidates.add(signature);
+                        continue;
+                    }
+                }
                 ClassNode itemType = inferLoopElementType(signature[0]);
                 signature = new ClassNode[closureParams.length];
                 Arrays.fill(signature, itemType);
@@ -3178,6 +3194,12 @@ public class StaticTypeCheckingVisitor extends 
ClassCodeVisitorSupport {
             }
         }
 
+        if (candidates.isEmpty() && !closureSignatures.isEmpty()) {
+            String spec = closureSignatures.stream().mapToInt(sig -> 
sig.length).distinct()
+                                           
.sorted().mapToObj(Integer::toString).collect(Collectors.joining(" or "));
+            addError("Incorrect number of parameters. Expected " + spec + " 
but found " + closureParams.length, expression);
+        }
+
         if (candidates.size() > 1) {
             closureSignatures = new ArrayList<>(candidates);
             for (Iterator<ClassNode[]> candIt = candidates.iterator(); 
candIt.hasNext(); ) {
diff --git a/src/test/groovy/bugs/Groovy8816.groovy 
b/src/test/groovy/bugs/Groovy8816.groovy
deleted file mode 100644
index 0bd56e4e04..0000000000
--- a/src/test/groovy/bugs/Groovy8816.groovy
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package groovy.bugs
-
-import org.junit.Test
-
-import static groovy.test.GroovyAssert.shouldFail
-
-final class Groovy8816 {
-
-    @Test
-    void testCallNoArgClosureWithArg() {
-        def err = shouldFail MissingMethodException, '''
-            @groovy.transform.CompileStatic
-            void test() {
-                [0].each { -> }
-            }
-            test()
-        '''
-
-        assert err =~ /No signature of method: .*\.doCall\(\) is applicable 
for argument types: \(Integer\) values: \[0\]/
-    }
-}
diff --git 
a/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy 
b/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy
index d03c255e00..9c64d32efe 100644
--- a/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ClosureParamTypeInferenceSTCTest.groovy
@@ -149,6 +149,55 @@ class ClosureParamTypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
         '''
     }
 
+    // GROOVY-11090, GROOVY-11092
+    void testFromStringWithGenericType3() {
+        String foo = '''
+            void foo(@ClosureParams(value=FromString, 
options="Tuple2<String,Number>") Closure c) {
+                c.call( new Tuple2("",42) )
+            }
+        '''
+/*
+        assertScript foo + '''
+            foo { string, number ->
+                number.doubleValue()
+                string.toUpperCase()
+            }
+        '''
+*/
+        shouldFailWithMessages foo + '''
+            foo { one, two, xxx -> }
+        ''',
+        'Incorrect number of parameters. Expected 1 or 2 but found 3'
+    }
+
+    // GROOVY-11090
+    void testFromStringWithGenericType4() {
+        assertScript '''
+            void foo(@ClosureParams(value=FromString, 
options="List<Tuple2<String,Number>>") Closure c) {
+                c.call(Collections.singletonList(Tuple.tuple("",(Number)42)))
+            }
+            foo {
+                it.each { string, number ->
+                    number.doubleValue()
+                    string.toUpperCase()
+                }
+            }
+        '''
+    }
+
+    void testFromStringWithGenericType5() {
+        assertScript '''
+            void foo(@ClosureParams(value=FromString, 
options="Optional<Tuple2<String,? extends Number>>") Closure c) {
+                c(Optional.of(Tuple.tuple("",42)))
+            }
+            foo { opt ->
+                opt.ifPresent {
+                    it.v2.doubleValue()
+                }
+            }
+        '''
+    }
+
     void testFromStringWithTypeParameter1() {
         assertScript '''
             def <T> void foo(T t, @ClosureParams(value=FromString, 
options="T") Closure c) { c.call(t) }
@@ -310,6 +359,109 @@ class ClosureParamTypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
         '''
     }
 
+    // GROOVY-6939
+    @NotYetImplemented
+    void testParamCountCheck1() {
+        shouldFailWithMessages '''
+            def m(o) {
+                o.each { x, y -> }
+            }
+        ''',
+        'Incorrect number of parameters. Expected 1 but found 2'
+    }
+
+    // GROOVY-6939
+    @NotYetImplemented
+    void testParamCountCheck2() {
+        shouldFailWithMessages '''
+            def m(o) {
+                o.eachWithIndex { x, y, z -> }
+            }
+        ''',
+        'Incorrect number of parameters. Expected 2 but found 3'
+    }
+
+    // GROOVY-6939
+    @NotYetImplemented
+    void testParamCountCheck3() {
+        shouldFailWithMessages '''
+            def m(o) {
+                o.eachWithIndex { print it }
+            }
+        ''',
+        'Incorrect number of parameters. Expected 2 but found 1'
+    }
+
+    // GROOVY-6939
+    void testParamCountCheck4() {
+        shouldFailWithMessages '''
+            def m(... array) {
+                array.each { x, y -> }
+            }
+        ''',
+        'Incorrect number of parameters. Expected 1 but found 2'
+    }
+
+    // GROOVY-6939
+    void testParamCountCheck5() {
+        shouldFailWithMessages '''
+            def m() {
+                [:].each { -> }
+            }
+        ''',
+        'Incorrect number of parameters. Expected 1 or 2 but found 0'
+    }
+
+    // GROOVY-8499
+    void testParamCountCheck6() {
+        assertScript '''
+            def result = ['ab'.chars,'12'.chars].combinations { l,n -> "$l$n" }
+            assert result == ['a1','b1','a2','b2']
+        '''
+        // cannot know in advance how many list elements
+        def err = shouldFail '''
+            ['ab'.chars,'12'.chars].combinations((l,n,x) -> "$l$n")
+        '''
+        assert err =~ /No signature of method.* is applicable for argument 
types: \(ArrayList\)/
+    }
+
+    // GROOVY-8816
+    void testParamCountCheck7() {
+        shouldFailWithMessages '''
+            def m() {
+                [].each { -> }
+            }
+        ''',
+        'Incorrect number of parameters. Expected 1 but found 0'
+    }
+
+    // GROOVY-9854
+    @NotYetImplemented
+    void testParamCountCheck8() {
+        shouldFailWithMessages '''
+            switch (42) { case { -> }: break; }
+        ''',
+        'Incorrect number of parameters. Expected 1 but found 0'
+    }
+
+    // GROOVY-9854
+    @NotYetImplemented
+    void testParamCountCheck9() {
+        shouldFailWithMessages '''
+            switch (42) { case { i, j -> }: break; }
+        ''',
+        'Incorrect number of parameters. Expected 1 but found 2'
+    }
+
+    // GROOVY-11089
+    void testParamCountCheck10() {
+        shouldFailWithMessages '''
+            def array = new String[]{'a','b'}
+            array.with { a,b -> }
+        ''',
+        'Incorrect number of parameters. Expected 1 but found 2'
+    }
+
     // GROOVY-7141
     void testInferenceWithSAMTypeCoercion1() {
         String sam = '''
@@ -654,7 +806,24 @@ class ClosureParamTypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
             assert [1234, 3.14].collect { it.intValue() } == [1234,3]
         '''
     }
-    void testDGM_collectMap() {
+    void testDGM_collectOnList() { // GROOVY-11090
+        assertScript '''
+            def list_of_tuple2 = ['a','b'].withIndex()
+            def list_of_string = list_of_tuple2.collect { it.v1 + it.v2 }
+
+            assert list_of_string == ['a0','b1']
+        '''
+
+        for (spec in ['s,i','String s,int i']) {
+            assertScript """
+                def list_of_tuple2 = ['a','b'].withIndex()
+                def list_of_string = list_of_tuple2.collect { $spec -> s + i }
+
+                assert list_of_string == ['a0','b1']
+            """
+        }
+    }
+    void testDGM_collectOnMap() {
         assertScript '''
             assert [a: 'foo',b:'bar'].collect { k,v -> k+v } == ['afoo','bbar']
             assert [a: 'foo',b:'bar'].collect { e -> e.key+e.value } == 
['afoo','bbar']
@@ -1599,7 +1768,20 @@ class ClosureParamTypeInferenceSTCTest extends 
StaticTypeCheckingTestCase {
         '''
     }
 
-    void testDGM_with() {
+    @NotYetImplemented
+    void testDGM_with0() { // GROOVY-11090: edge case
+        assertScript '''
+            Tuple0.INSTANCE.with { -> }
+        '''
+        assertScript '''
+            Tuple0.INSTANCE.with {
+                assert it instanceof List
+                assert it instanceof Tuple
+                assert it === Tuple0.INSTANCE
+            }
+        '''
+    }
+    void testDGM_with1() {
         assertScript '''
             "string".with { it.toUpperCase() }
         '''

Reply via email to