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 2020/12/10 20:25:26 UTC
[groovy] 01/02: GROOVY-9803: translate generics of method reference
using SAM parameters
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 e78f3e0e0bf0d5be35eb4ccd38dca7955fd616fd
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Mon Nov 9 11:44:01 2020 -0600
GROOVY-9803: translate generics of method reference using SAM parameters
Conflicts:
src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
---
.../transform/stc/StaticTypeCheckingVisitor.java | 128 +++++++++++++++------
.../groovy/transform/stc/GenericsSTCTest.groovy | 36 ++++++
2 files changed, 127 insertions(+), 37 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 5749494..afe5910 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -131,6 +131,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
import static org.apache.groovy.util.BeanUtils.capitalize;
import static org.apache.groovy.util.BeanUtils.decapitalize;
@@ -266,6 +267,7 @@ import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isPowe
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isShiftOperation;
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.isUsingUncheckedGenerics;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isVargs;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isWildcardLeftHandSide;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.lastArgMatchesVarg;
@@ -2408,6 +2410,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
.reduce(WideningCategories::lowestUpperBound)
.filter(returnType -> !returnType.equals(OBJECT_TYPE))
.ifPresent(returnType -> storeType(expression, wrapClosureType(returnType)));
+ expression.putNodeMetaData(MethodNode.class, candidates);
} else if (!(expression instanceof MethodReferenceExpression)) {
ClassNode type = wrapTypeIfNecessary(getType(expression.getExpression()));
if (isClassClassNodeWrappingConcreteType(type)) type = type.getGenericsTypes()[0].getType();
@@ -5166,10 +5169,13 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
argumentType = argumentType.getComponentType();
}
if (isUsingGenericsOrIsArrayUsingGenerics(paramType)) {
- if (implementsInterfaceOrIsSubclassOf(argumentType, CLOSURE_TYPE) && isSAMType(paramType)) {
- // implicit closure coercion in action!
- Map<GenericsTypeName, GenericsType> pholders = applyGenericsContextToParameterClass(resolvedPlaceholders, paramType);
- argumentType = convertClosureTypeToSAMType(expressions.get(i), argumentType, paramType, pholders);
+ if (argumentType.isDerivedFrom(CLOSURE_TYPE)) {
+ MethodNode sam = findSAM(paramType);
+ if (sam != null) { // implicit closure coercion in action!
+ argumentType = !paramType.isUsingGenerics() ? paramType
+ : convertClosureTypeToSAMType(expressions.get(i), argumentType, sam, paramType,
+ applyGenericsContextToParameterClass(resolvedPlaceholders, paramType));
+ }
}
if (isVargs && lastArg && argumentType.isArray()) {
argumentType = argumentType.getComponentType();
@@ -5292,42 +5298,90 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
}
}
+ private static MethodNode chooseMethod(final MethodPointerExpression source, final Supplier<ClassNode[]> samSignature) {
+ List<MethodNode> options = source.getNodeMetaData(MethodNode.class);
+ if (options == null || options.isEmpty()) {
+ return null;
+ }
+
+ ClassNode[] paramTypes = samSignature.get();
+ return options.stream().filter((MethodNode option) -> {
+ ClassNode[] types = collateMethodReferenceParameterTypes(source, option);
+ if (types.length == paramTypes.length) {
+ for (int i = 0, n = types.length; i < n; i += 1) {
+ if (!types[i].isGenericsPlaceHolder() && !isAssignableTo(types[i], paramTypes[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }).findFirst().orElse(null); // TODO: order matches by param distance
+ }
+
+ private static ClassNode[] collateMethodReferenceParameterTypes(final MethodPointerExpression source, final MethodNode target) {
+ Parameter[] params;
+
+ if (target instanceof ExtensionMethodNode && !((ExtensionMethodNode) target).isStaticExtension()) {
+ params = ((ExtensionMethodNode) target).getExtensionMethodNode().getParameters();
+ } else if (!target.isStatic() && source.getExpression() instanceof ClassExpression) {
+ ClassNode thisType = ((ClassExpression) source.getExpression()).getType();
+ // there is an implicit parameter for "String::length"
+ int n = target.getParameters().length;
+ params = new Parameter[n + 1];
+ params[0] = new Parameter(thisType, "");
+ System.arraycopy(target.getParameters(), 0, params, 1, n);
+ } else {
+ params = target.getParameters();
+ }
+
+ return extractTypesFromParameters(params);
+ }
+
/**
- * This method will convert a closure type to the appropriate SAM type, which will be used
- * to infer return type generics.
+ * Converts a closure type to the appropriate SAM type, which is used to
+ * infer return type generics.
*
- * @param closureType the inferred type of a closure (Closure<ClosureReturnType>)
+ * @param closureType the inferred type of a closure {@code Closure<Type>}
* @param samType the type into which the closure is coerced into
- * @return same SAM type, but completed with information from the closure node
+ * @return SAM type augmented using information from the argument expression
*/
- private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final ClassNode samType, final Map<GenericsTypeName, GenericsType> placeholders) {
- if (!samType.isUsingGenerics()) return samType;
-
- // use the generics information from the Closure to further specify the type
- MethodNode sam = findSAM(samType);
- if (closureType.isUsingGenerics() && sam != null) {
- //correct SAM type for generics
- //sam = applyGenericsContext(placeholders, sam);
-
- // the return type of the SAM method exactly corresponds to the inferred return type
- ClassNode samReturnType = sam.getReturnType();
- ClassNode closureReturnType = expression.getNodeMetaData(INFERRED_TYPE);
- if (closureReturnType != null && closureReturnType.isUsingGenerics()) {
- ClassNode unwrapped = closureReturnType.getGenericsTypes()[0].getType();
- extractGenericsConnections(placeholders, unwrapped, samReturnType);
- } else if (samReturnType.isGenericsPlaceHolder()) {
- placeholders.put(new GenericsTypeName(samReturnType.getGenericsTypes()[0].getName()), closureType.getGenericsTypes()[0]);
- }
-
- // now repeat the same for each parameter given in the ClosureExpression
- if (expression instanceof ClosureExpression && sam.getParameters().length > 0) {
- List<ClassNode[]> genericsToConnect = new LinkedList<>();
- Parameter[] closureParams = ((ClosureExpression) expression).getParameters();
- ClassNode[] closureParamTypes = extractTypesFromParameters(closureParams);
- if (expression.getNodeMetaData(CLOSURE_ARGUMENTS) != null) {
- closureParamTypes = expression.getNodeMetaData(CLOSURE_ARGUMENTS);
+ private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final MethodNode sam, final ClassNode samType, final Map<GenericsTypeName, GenericsType> placeholders) {
+ // use the generics information from Closure to further specify the type
+ if (closureType.isUsingGenerics()) {
+ ClassNode closureReturnType = closureType.getGenericsTypes()[0].getType();
+
+ Parameter[] parameters = sam.getParameters();
+ if (parameters.length > 0 && expression instanceof MethodPointerExpression
+ && isUsingUncheckedGenerics(closureReturnType)) { // needs resolve
+ MethodPointerExpression mp = (MethodPointerExpression) expression;
+ MethodNode mn = chooseMethod(mp, () ->
+ applyGenericsContext(placeholders, extractTypesFromParameters(parameters))
+ );
+ if (mn != null) {
+ ClassNode[] pTypes = collateMethodReferenceParameterTypes(mp, mn);
+ Map<GenericsTypeName, GenericsType> connections = new HashMap<>();
+ for (int i = 0, n = parameters.length; i < n; i += 1) {
+ // SAM parameters should align one-for-one with the referenced method's parameters
+ extractGenericsConnections(connections, parameters[i].getOriginType(), pTypes[i]);
+ }
+ // convert the method reference's generics into the SAM's generics domain
+ closureReturnType = applyGenericsContext(connections, closureReturnType);
+ // apply known generics connections to the placeholders of the return type
+ closureReturnType = applyGenericsContext(placeholders, closureReturnType);
}
- Parameter[] parameters = sam.getParameters();
+ }
+
+ // the SAM's return type exactly corresponds to the inferred closure return type
+ extractGenericsConnections(placeholders, closureReturnType, sam.getReturnType());
+
+ // repeat the same for each parameter given in the ClosureExpression
+ if (parameters.length > 0 && expression instanceof ClosureExpression) {
+ List<ClassNode[]> genericsToConnect = new ArrayList<>();
+ Parameter[] closureParams = ((ClosureExpression) expression).getParameters();
+ ClassNode[] closureParamTypes = expression.getNodeMetaData(CLOSURE_ARGUMENTS);
+ if (closureParamTypes == null) closureParamTypes = extractTypesFromParameters(closureParams);
+
for (int i = 0, n = parameters.length; i < n; i += 1) {
Parameter parameter = parameters[i];
if (parameter.getOriginType().isUsingGenerics() && closureParamTypes.length > i) {
@@ -5363,8 +5417,8 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
}
}
}
- ClassNode result = applyGenericsContext(placeholders, samType.redirect());
- return result;
+
+ return applyGenericsContext(placeholders, samType.redirect());
}
private ClassNode resolveGenericsWithContext(final Map<GenericsTypeName, GenericsType> resolvedPlaceholders, final ClassNode currentType) {
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index fe70435..68f6b78 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -644,6 +644,42 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
}
}
+ // GROOVY-9803
+ void testShouldUseMethodGenericType8() {
+ assertScript '''
+ def opt = Optional.of(42)
+ .map(Collections::singleton)
+ .map(x -> x.first().intValue()) // Cannot find matching method java.lang.Object#intValue()
+ assert opt.get() == 42
+ '''
+ // same as above but with separate type parameter name for each location
+ assertScript '''
+ abstract class A<I,O> {
+ abstract O apply(I input)
+ }
+ class C<T> {
+ static <U> C<U> of(U item) {
+ new C<U>()
+ }
+ def <V> C<V> map(A<? super T, ? super V> func) {
+ new C<V>()
+ }
+ }
+ class D {
+ static <W> Set<W> wrap(W o) {
+ }
+ }
+
+ void test() {
+ def c = C.of(42)
+ def d = c.map(D.&wrap)
+ def e = d.map(x -> x.first().intValue())
+ }
+
+ test()
+ '''
+ }
+
// GROOVY-5516
void testAddAllWithCollectionShouldBeAllowed() {
assertScript '''import org.codehaus.groovy.transform.stc.ExtensionMethodNode