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() } '''
