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/06/04 15:06:37 UTC
[groovy] branch GROOVY_3_0_X updated: GROOVY-10049, GROOVY-10648: don't mix type param contexts when resolving
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
The following commit(s) were added to refs/heads/GROOVY_3_0_X by this push:
new 55ba573d7e GROOVY-10049, GROOVY-10648: don't mix type param contexts when resolving
55ba573d7e is described below
commit 55ba573d7eb3a6b152390f18badd24255a341650
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Sat Jun 4 09:14:13 2022 -0500
GROOVY-10049, GROOVY-10648: don't mix type param contexts when resolving
[1].stream().reduce(0, (r,e) -> r + e)
^ Stream<Integer> ^ ^ both Integer -- not int or unknown
def <T extends Number> List<T> f(Class<T> t) {
[t.newInstance(1)].stream().filter(n -> n.intValue() > 0).toList()
^ Stream<T ext Num> ^ T or Number -- not Object
3_0_X backport -- see also GROOVY-8247, GROOVY-8917, GROOVY-9790
---
.../transform/stc/StaticTypeCheckingVisitor.java | 238 ++++++++-------------
.../groovy/transform/stc/GenericsSTCTest.groovy | 22 +-
2 files changed, 113 insertions(+), 147 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 ba83a7144e..23f95c673c 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -254,9 +254,9 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDG
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsForClassNode;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findSetters;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findTargetVariable;
+import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.fullyResolve;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.fullyResolveType;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getCombinedBoundType;
-import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getCorrectedClassNode;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getGenericsWithoutArray;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getOperationName;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf;
@@ -2881,149 +2881,99 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
}
/**
- * This method is responsible for performing type inference on closure argument types whenever code like this is
- * found: <code>foo.collect { it.toUpperCase() }</code>.
- * In this case, the type checker tries to find if the <code>collect</code> method has its {@link Closure} argument
- * annotated with {@link groovy.transform.stc.ClosureParams}. If yes, then additional type inference can be performed
- * and the type of <code>it</code> may be inferred.
+ * Performs type inference on closure argument types whenever code like this
+ * is found: <code>foo.collect { it.toUpperCase() }</code>.
+ * <p>
+ * In this case the type checker tries to find if the {@code collect} method
+ * has its {@link Closure} argument annotated with {@link ClosureParams}. If
+ * so, then additional type inference can be performed and the type of
+ * {@code it} may be inferred.
*
* @param receiver
* @param arguments
- * @param expression a closure expression for which the argument types should be inferred
- * @param param the parameter where to look for a {@link groovy.transform.stc.ClosureParams} annotation.
- * @param selectedMethod the method accepting a closure
+ * @param expression closure or lambda expression for which the argument types should be inferred
+ * @param target parameter which may provide {@link ClosureParams} annotation or SAM type
+ * @param method method that declares {@code target}
*/
- protected void inferClosureParameterTypes(final ClassNode receiver, final Expression arguments, final ClosureExpression expression, final Parameter param, final MethodNode selectedMethod) {
- List<AnnotationNode> annotations = param.getAnnotations(CLOSUREPARAMS_CLASSNODE);
+ protected void inferClosureParameterTypes(final ClassNode receiver, final Expression arguments, final ClosureExpression expression, final Parameter target, final MethodNode method) {
+ List<AnnotationNode> annotations = target.getAnnotations(CLOSUREPARAMS_CLASSNODE);
if (annotations != null && !annotations.isEmpty()) {
for (AnnotationNode annotation : annotations) {
Expression hintClass = annotation.getMember("value");
- Expression options = annotation.getMember("options");
- Expression resolverClass = annotation.getMember("conflictResolutionStrategy");
if (hintClass instanceof ClassExpression) {
- doInferClosureParameterTypes(receiver, arguments, expression, selectedMethod, hintClass, resolverClass, options);
- }
- }
- } else if (isSAMType(param.getOriginType())) {
- // SAM coercion
- inferSAMType(param, receiver, selectedMethod, InvocationWriter.makeArgumentList(arguments), expression);
- }
- }
-
- /**
- * In a method call with SAM coercion the inference is to be understood as a
- * two phase process. We have the normal method call to the target method
- * with the closure argument and we have the SAM method that will be called
- * inside the normal target method. To infer correctly we have to "simulate"
- * this process. We know the call to the closure will be done through the SAM
- * type, so the SAM type generics deliver information about the Closure. At
- * the same time the SAM class is used in the target method parameter,
- * providing a connection from the SAM type and the target method's class.
- */
- private void inferSAMType(final Parameter param, final ClassNode receiver, final MethodNode methodWithSAMParameter, final ArgumentListExpression originalMethodCallArguments, final ClosureExpression openBlock) {
- // first we try to get as much information about the declaration class through the receiver
- Map<GenericsTypeName, GenericsType> targetMethodConnections = new HashMap<>();
- for (ClassNode face : receiver.getAllInterfaces()) {
- extractGenericsConnections(targetMethodConnections, getCorrectedClassNode(receiver, face, true), face.redirect());
- }
- if (!receiver.isInterface()) {
- extractGenericsConnections(targetMethodConnections, receiver, receiver.redirect());
- }
-
- // then we use the method with the SAM-type parameter to get more information about the declaration
- Parameter[] parametersOfMethodContainingSAM = methodWithSAMParameter.getParameters();
- for (int i = 0, n = parametersOfMethodContainingSAM.length; i < n; i += 1) {
- ClassNode parameterType = parametersOfMethodContainingSAM[i].getType();
- // potentially skip empty varargs
- if (i == (n - 1) && i == originalMethodCallArguments.getExpressions().size() && parameterType.isArray()) {
- continue;
- }
- Expression callArg = originalMethodCallArguments.getExpression(i);
- // we look at the closure later in detail, so skip it here
- if (callArg == openBlock) {
- continue;
- }
- extractGenericsConnections(targetMethodConnections, getType(callArg), parameterType);
- }
-
- // To make a connection to the SAM class we use that new information
- // to replace the generics in the SAM type parameter of the target
- // method and than that to make the connections to the SAM type generics
- ClassNode paramTypeWithReceiverInformation = applyGenericsContext(targetMethodConnections, param.getOriginType());
- Map<GenericsTypeName, GenericsType> samTypeConnections = new HashMap<>();
- ClassNode samTypeRedirect = paramTypeWithReceiverInformation.redirect();
- extractGenericsConnections(samTypeConnections, paramTypeWithReceiverInformation, samTypeRedirect);
-
- // should the open block provide final information we apply that
- // to the corresponding parameters of the SAM type method
- MethodNode abstractMethod = findSAM(samTypeRedirect);
- ClassNode[] abstractMethodParamTypes = extractTypesFromParameters(abstractMethod.getParameters());
- ClassNode[] blockParamTypes = openBlock.getNodeMetaData(CLOSURE_ARGUMENTS);
- if (blockParamTypes == null) {
- Parameter[] p = openBlock.getParameters();
- if (p == null) {
- // zero parameter closure e.g. { -> println 'no args' }
- blockParamTypes = ClassNode.EMPTY_ARRAY;
- } else if (p.length == 0 && abstractMethodParamTypes.length != 0) {
- // implicit it
- blockParamTypes = abstractMethodParamTypes;
- } else {
- blockParamTypes = new ClassNode[p.length];
- for (int i = 0, n = p.length; i < n; i += 1) {
- if (p[i] != null && !p[i].isDynamicTyped()) {
- blockParamTypes[i] = p[i].getType();
- } else {
- blockParamTypes[i] = typeOrNull(abstractMethodParamTypes, i);
+ Expression options = annotation.getMember("options");
+ Expression resolverClass = annotation.getMember("conflictResolutionStrategy");
+ doInferClosureParameterTypes(receiver, arguments, expression, method, hintClass, resolverClass, options);
+ }
+ }
+ } else if (isSAMType(target.getOriginType())) { // SAM-type coercion
+ Map<GenericsTypeName, GenericsType> context = method.isStatic() ? new HashMap<>() : extractPlaceHolders(null, receiver, getDeclaringClass(method, arguments));
+ GenericsType[] typeParameters = method instanceof ConstructorNode ? method.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, method.getGenericsTypes());
+
+ if (typeParameters != null) {
+ boolean typeParametersResolved = false;
+ // first check for explicit type arguments
+ Expression emc = typeCheckingContext.getEnclosingMethodCall();
+ if (emc instanceof MethodCallExpression) {
+ MethodCallExpression mce = (MethodCallExpression) emc;
+ if (mce.getArguments() == arguments) {
+ GenericsType[] typeArguments = mce.getGenericsTypes();
+ if (typeArguments != null) {
+ int n = typeParameters.length;
+ if (n == typeArguments.length) {
+ typeParametersResolved = true;
+ for (int i = 0; i < n; i += 1) {
+ context.put(new GenericsTypeName(typeParameters[i].getName()), typeArguments[i]);
+ }
+ }
+ }
}
}
- }
- }
- for (int i = 0, n = blockParamTypes.length; i < n; i += 1) {
- extractGenericsConnections(samTypeConnections, blockParamTypes[i], typeOrNull(abstractMethodParamTypes, i));
- }
+ if (!typeParametersResolved) {
+ // check for implicit type arguments
+ int i = -1; Parameter[] p = method.getParameters();
+ for (Expression argument : (ArgumentListExpression) arguments) { i += 1;
+ if (argument instanceof ClosureExpression || isNullConstant(argument)) continue;
- // finally apply the generics information to the parameters and
- // store the type of parameter and block type as meta information
- for (int i = 0, n = blockParamTypes.length; i < n; i += 1) {
- blockParamTypes[i] = applyGenericsContext(samTypeConnections, typeOrNull(abstractMethodParamTypes, i));
- }
-
- tryToInferUnresolvedBlockParameterType(paramTypeWithReceiverInformation, abstractMethod, blockParamTypes);
+ ClassNode pType = p[Math.min(i, p.length - 1)].getType();
+ Map<GenericsTypeName, GenericsType> gc = new HashMap<>();
+ extractGenericsConnections(gc, wrapTypeIfNecessary(getType(argument)), pType);
- openBlock.putNodeMetaData(CLOSURE_ARGUMENTS, blockParamTypes);
- }
+ gc.forEach((key, gt) -> {
+ for (GenericsType tp : typeParameters) {
+ if (tp.getName().equals(key.getName())) {
+ context.putIfAbsent(key, gt); // TODO: merge
+ break;
+ }
+ }
+ });
+ }
- private void tryToInferUnresolvedBlockParameterType(final ClassNode paramTypeWithReceiverInformation, final MethodNode methodForSAM, final ClassNode[] blockParameterTypes) {
- List<Integer> indexList = new LinkedList<>();
- for (int i = 0, n = blockParameterTypes.length; i < n; i += 1) {
- ClassNode blockParameterType = blockParameterTypes[i];
- if (blockParameterType != null && blockParameterType.isGenericsPlaceHolder()) {
- indexList.add(i);
+ for (GenericsType tp : typeParameters) {
+ context.computeIfAbsent(new GenericsTypeName(tp.getName()), x -> fullyResolve(tp, context));
+ }
+ }
}
- }
- if (!indexList.isEmpty()) {
- // If the parameter type failed to resolve, try to find the parameter type through the class hierarchy
- Map<GenericsType, GenericsType> genericsTypeMap = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(methodForSAM.getDeclaringClass(), paramTypeWithReceiverInformation);
+ ClassNode[] samParamTypes = GenericsUtils.parameterizeSAM(applyGenericsContext(context, target.getType())).getV1();
- for (Integer index : indexList) {
- for (Map.Entry<GenericsType, GenericsType> entry : genericsTypeMap.entrySet()) {
- if (entry.getKey().getName().equals(blockParameterTypes[index].getUnresolvedName())) {
- ClassNode type = entry.getValue().getType();
- if (type != null && !type.isGenericsPlaceHolder()) {
- blockParameterTypes[index] = type;
- }
- break;
- }
+ ClassNode[] paramTypes = expression.getNodeMetaData(CLOSURE_ARGUMENTS);
+ if (paramTypes == null) {
+ int n; Parameter[] p = expression.getParameters();
+ if (p == null) {
+ // zero parameters
+ paramTypes = ClassNode.EMPTY_ARRAY;
+ } else if ((n = p.length) == 0) {
+ // implicit parameter(s)
+ paramTypes = samParamTypes;
+ } else { // TODO: error for length mismatch
+ paramTypes = Arrays.copyOf(samParamTypes, n);
}
+ expression.putNodeMetaData(CLOSURE_ARGUMENTS, paramTypes);
}
}
}
- private static ClassNode typeOrNull(final ClassNode[] parameterTypesForSAM, final int i) {
- return i < parameterTypesForSAM.length ? parameterTypesForSAM[i] : null;
- }
-
private List<ClassNode[]> getSignaturesFromHint(final ClosureExpression expression, final MethodNode selectedMethod, final Expression hintClass, final Expression options) {
// initialize hints
List<ClassNode[]> closureSignatures;
@@ -3089,25 +3039,24 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
}
}
if (candidates.size() > 1) {
- Iterator<ClassNode[]> candIt = candidates.iterator();
- while (candIt.hasNext()) {
+ for (Iterator<ClassNode[]> candIt = candidates.iterator(); candIt.hasNext(); ) {
ClassNode[] inferred = candIt.next();
for (int i = 0, n = closureParams.length; i < n; i += 1) {
Parameter closureParam = closureParams[i];
- ClassNode originType = closureParam.getOriginType();
+ ClassNode declaredType = closureParam.getOriginType();
ClassNode inferredType;
- if (i < inferred.length - 1 || inferred.length == closureParams.length) {
+ if (i < inferred.length - 1 || inferred.length == n) {
inferredType = inferred[i];
- } else { // vargs?
- ClassNode lastArgInferred = inferred[inferred.length - 1];
- if (lastArgInferred.isArray()) {
- inferredType = lastArgInferred.getComponentType();
+ } else {
+ ClassNode lastInferred = inferred[inferred.length - 1];
+ if (lastInferred.isArray()) {
+ inferredType = lastInferred.getComponentType();
} else {
candIt.remove();
continue;
}
}
- if (!typeCheckMethodArgumentWithGenerics(originType, inferredType, i == (n - 1))) {
+ if (!typeCheckMethodArgumentWithGenerics(declaredType, inferredType, i == (n - 1))) {
candIt.remove();
}
}
@@ -3126,24 +3075,21 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
} else {
for (int i = 0, n = closureParams.length; i < n; i += 1) {
Parameter closureParam = closureParams[i];
- ClassNode originType = closureParam.getOriginType();
+ ClassNode declaredType = closureParam.getOriginType();
ClassNode inferredType = OBJECT_TYPE;
- if (i < inferred.length - 1 || inferred.length == closureParams.length) {
+ if (i < inferred.length - 1 || inferred.length == n) {
inferredType = inferred[i];
- } else { // vargs?
- ClassNode lastArgInferred = inferred[inferred.length - 1];
- if (lastArgInferred.isArray()) {
- inferredType = lastArgInferred.getComponentType();
+ } else {
+ ClassNode lastInferred = inferred[inferred.length - 1];
+ if (lastInferred.isArray()) {
+ inferredType = lastInferred.getComponentType();
} else {
- addError("Incorrect number of parameters. Expected " + inferred.length + " but found " + closureParams.length, expression);
+ addError("Incorrect number of parameters. Expected " + inferred.length + " but found " + n, expression);
}
}
- boolean lastArg = i == (n - 1);
-
- if (!typeCheckMethodArgumentWithGenerics(originType, inferredType, lastArg)) {
- addError("Expected parameter of type " + inferredType.toString(false) + " but got " + originType.toString(false), closureParam.getType());
+ if (!typeCheckMethodArgumentWithGenerics(declaredType, inferredType, i == n-1)) {
+ addError("Expected parameter of type " + prettyPrintType(inferredType) + " but got " + prettyPrintType(declaredType), closureParam.getType());
}
-
typeCheckingContext.controlStructureVariables.put(closureParam, inferredType);
}
}
@@ -5291,8 +5237,8 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
ArgumentListExpression args = new ArgumentListExpression();
VariableExpression vexp = varX("$self", receiver);
args.addExpression(vexp);
- if (arguments instanceof ArgumentListExpression) {
- for (Expression argument : (ArgumentListExpression) arguments) {
+ if (arguments instanceof TupleExpression) {
+ for (Expression argument : (TupleExpression) arguments) {
args.addExpression(argument);
}
} else {
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index 6b8b2d2e48..f021659414 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -289,7 +289,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
'''
}
- @NotYetImplemented // GROOVY-10049, GROOVY-10053
+ // GROOVY-10049
void testReturnTypeInferenceWithMethodGenerics9() {
assertScript '''
def <X> Set<X> f(Class<X> x) {
@@ -302,7 +302,10 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
def result = g(Integer)
assert result == [ 42 ]
'''
+ }
+ @NotYetImplemented // GROOVY-10053
+ void testReturnTypeInferenceWithMethodGenericsA() {
['t::cast', 'n -> t.cast(n)', 'n -> (N) n', '{ n -> (N) n }'].each { cast ->
assertScript """
Set<Number> f() {
@@ -319,7 +322,10 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
assert result == [42] as Set
"""
}
+ }
+ @NotYetImplemented
+ void testReturnTypeInferenceWithMethodGenericsB() {
assertScript '''
def <T> String test(Iterable<T> iterable) {
Iterator<T> it = iterable.iterator()
@@ -1975,6 +1981,20 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
'''
}
+ // GROOVY-10648
+ void testShouldUseMethodGenericType16() {
+ assertScript '''
+ def <T extends Number> Set<T> test(Iterable<T> iterable) {
+ final Set<T> result = new HashSet<>()
+ iterable.forEach { result.add(it) }
+ return result
+ }
+ def set = test([1,2.3])
+ assert set.size() == 2
+ assert 1 in set
+ '''
+ }
+
// GROOVY-5516
void testAddAllWithCollectionShouldBeAllowed() {
assertScript '''import org.codehaus.groovy.transform.stc.ExtensionMethodNode