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 be19a80a3b GROOVY-11394: STC: check closure call for zero, default(s),
and variadic
be19a80a3b is described below
commit be19a80a3b9b1221883167771391ce3d32715847
Author: Eric Milles <[email protected]>
AuthorDate: Sat Jun 1 11:31:34 2024 -0500
GROOVY-11394: STC: check closure call for zero, default(s), and variadic
---
.../transform/stc/StaticTypeCheckingSupport.java | 37 +-------------
.../transform/stc/StaticTypeCheckingVisitor.java | 59 ++++++++++++++++++++--
.../groovy/transform/stc/ClosuresSTCTest.groovy | 17 +++++--
3 files changed, 69 insertions(+), 44 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 fcc00109dc..516f31a6e7 100644
---
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -386,41 +386,6 @@ public abstract class StaticTypeCheckingSupport {
return dist;
}
- /**
- * Checks that arguments and parameter types match, expecting that the
number of parameters is strictly greater
- * than the number of arguments, allowing possible inclusion of default
parameters.
- *
- * @return -1 if arguments do not match, 0 if arguments are of the exact
type and >0 when one or more argument is
- * not of the exact type but still match
- */
- static int allParametersAndArgumentsMatchWithDefaultParams(final
Parameter[] parameters, final ClassNode[] argumentTypes) {
- int dist = 0;
- ClassNode ptype = null;
- for (int i = 0, j = 0, n = parameters.length; i < n; i += 1) {
- Parameter param = parameters[i];
- ClassNode paramType = param.getType();
- ClassNode arg = (j >= argumentTypes.length ? null :
argumentTypes[j]);
- if (arg == null || !isAssignableTo(arg, paramType)) {
- if (!param.hasInitialExpression() && (ptype == null ||
!ptype.equals(paramType))) {
- return -1; // no default value
- }
- // a default value exists, we can skip this param
- ptype = null;
- } else {
- j += 1;
- if (!paramType.equals(arg)) {
- dist += getDistance(arg, paramType);
- }
- if (param.hasInitialExpression()) {
- ptype = arg;
- } else {
- ptype = null;
- }
- }
- }
- return dist;
- }
-
/**
* Checks that excess arguments match the vararg signature parameter.
*
@@ -446,7 +411,7 @@ public abstract class StaticTypeCheckingSupport {
* @return -1 if no match, 0 if the last argument is exactly the vararg
type and 1 if of an assignable type
*/
@SuppressWarnings("removal")
- static int lastArgMatchesVarg(final Parameter[] parameters, final
ClassNode... argumentTypes) {
+ private static int lastArgMatchesVarg(final Parameter[] parameters, final
ClassNode... argumentTypes) {
if (!isVargs(parameters)) return -1;
int lastParamIndex = parameters.length - 1;
if (lastParamIndex == argumentTypes.length) return 0;
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 2e637cf1d5..4d8effca1e 100644
---
a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++
b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -245,6 +245,7 @@ import static
org.codehaus.groovy.ast.tools.WideningCategories.isNumberCategory;
import static
org.codehaus.groovy.ast.tools.WideningCategories.lowestUpperBound;
import static org.codehaus.groovy.runtime.ArrayGroovyMethods.asBoolean;
import static org.codehaus.groovy.runtime.ArrayGroovyMethods.init;
+import static org.codehaus.groovy.runtime.ArrayGroovyMethods.last;
import static org.codehaus.groovy.syntax.Types.ASSIGN;
import static org.codehaus.groovy.syntax.Types.COMPARE_EQUAL;
import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_EQUAL;
@@ -274,7 +275,6 @@ import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.Linked
import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.Matcher_TYPE;
import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.NUMBER_OPS;
import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.UNKNOWN_PARAMETER_TYPE;
-import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.allParametersAndArgumentsMatchWithDefaultParams;
import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsConnections;
import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.applyGenericsContext;
import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.boundUnboundedWildcards;
@@ -311,7 +311,6 @@ import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isShif
import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isTraitSelf;
import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isUsingGenericsOrIsArrayUsingGenerics;
import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isWildcardLeftHandSide;
-import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.lastArgMatchesVarg;
import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.missesGenericsTypes;
import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.prettyPrintType;
import static
org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.prettyPrintTypeName;
@@ -951,7 +950,9 @@ public class StaticTypeCheckingVisitor extends
ClassCodeVisitorSupport {
// propagate closure parameter type information
if (leftExpression instanceof VariableExpression) {
if (rightExpression instanceof ClosureExpression) {
- leftExpression.putNodeMetaData(CLOSURE_ARGUMENTS,
((ClosureExpression) rightExpression).getParameters());
+ ClosureExpression closure = (ClosureExpression)
rightExpression;
+ if (!hasImplicitParameter(closure)) // GROOVY-11394:
arrow means zero parameters
+ leftExpression.putNodeMetaData(CLOSURE_ARGUMENTS,
getParametersSafe(closure));
} else if (rightExpression instanceof VariableExpression
&& ((VariableExpression)
rightExpression).getAccessedVariable() instanceof Expression
&& ((Expression) ((VariableExpression)
rightExpression).getAccessedVariable()).getNodeMetaData(CLOSURE_ARGUMENTS) !=
null) {
@@ -4088,9 +4089,57 @@ out: if (mn.size() != 1) {
}
protected void typeCheckClosureCall(final Expression arguments, final
ClassNode[] argumentTypes, final Parameter[] parameters) {
- if (allParametersAndArgumentsMatchWithDefaultParams(parameters,
argumentTypes) < 0 && lastArgMatchesVarg(parameters, argumentTypes) < 0) {
- addStaticTypeError("Cannot call closure that accepts " +
formatArgumentList(extractTypesFromParameters(parameters)) + " with " +
formatArgumentList(argumentTypes), arguments);
+ int nArguments = argumentTypes.length;
+ List<ClassNode[]> signatures = new LinkedList<>();
+ signatures.add(extractTypesFromParameters(parameters));
+
+ var n =
Arrays.stream(parameters).filter(Parameter::hasInitialExpression).count();
+ for (int i = 1; i <= n; i += 1) { // drop parameters with value from
right to left
+ ClassNode[] signature = new ClassNode[parameters.length - i];
+ int j = 1, index = 0;
+ for (Parameter parameter : parameters) {
+ if (j > n - i && parameter.hasInitialExpression()) {
+ // skip parameter with default argument
+ } else {
+ signature[index++] = parameter.getType();
+ }
+ if (parameter.hasInitialExpression()) j += 1;
+ }
+ signatures.add(signature);
+ }
+
+ for (var it = signatures.listIterator(); it.hasNext(); ) { var
signature = it.next();
+ if (asBoolean(signature) && last(signature).isArray()) {
+ int vaIndex = signature.length - 1;
+ if (vaIndex == nArguments) { // empty array
+ it.add(Arrays.copyOf(signature, nArguments));
+ } else if (vaIndex < nArguments) { // spread arg(s)?
+ var subType = last(signature).getComponentType();
+ signature = Arrays.copyOf(signature, nArguments);
+ Arrays.fill(signature, vaIndex, nArguments, subType);
+ it.add(signature);
+ }
+ }
}
+
+ ClassNode[] firstMatch = null;
+
+trying: for (ClassNode[] signature : signatures) {
+ if (nArguments == signature.length) {
+ if (firstMatch == null)
+ firstMatch = signature;
+ for (int i = 0; i < nArguments; i += 1) {
+ if (!isAssignableTo(argumentTypes[i], signature[i])) {
+ continue trying;
+ }
+ }
+ return; // arguments match
+ }
+ }
+
+ String actual = formatArgumentList(argumentTypes);
+ String expect = formatArgumentList(firstMatch != null ? firstMatch :
signatures.get(0));
+ addStaticTypeError("Cannot call closure that accepts " + expect + "
with " + actual, arguments);
}
@Override
diff --git a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
index 80968d78c0..b6354e9642 100644
--- a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
@@ -18,8 +18,6 @@
*/
package groovy.transform.stc
-import groovy.test.NotYetImplemented
-
/**
* Unit tests for static type checking : closures.
*/
@@ -39,7 +37,7 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
'''
}
- @NotYetImplemented
+ // GROOVY-11394
void testCallClosure3() {
shouldFailWithMessages '''
def c = { -> }
@@ -150,6 +148,19 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
'''
}
+ // GROOVY-11394
+ void testCallClosure16() {
+ assertScript '''
+ def c = { Object[] arr, opt = null -> Arrays.toString(arr) + opt }
+ def x = c()
+ assert x == '[]null'
+ def y = c(1)
+ assert y == '[1]null'
+ def z = c(null)
+ assert z == 'nullnull'
+ '''
+ }
+
void testClosureReturnTypeInference1() {
assertScript '''
def c = { int a, int b -> return a + b }