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/11/09 22:52:36 UTC
[groovy] branch GROOVY-9803 created (now 0f91eb8)
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a change to branch GROOVY-9803
in repository https://gitbox.apache.org/repos/asf/groovy.git.
at 0f91eb8 GROOVY-9803: translate generics of method reference using SAM parameters
This branch includes the following new commits:
new 0f91eb8 GROOVY-9803: translate generics of method reference using SAM parameters
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
[groovy] 01/01: GROOVY-9803: translate generics of method reference
using SAM parameters
Posted by em...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch GROOVY-9803
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 0f91eb861bf5245ca1aae0f5db101e9ae26664c8
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
---
.../transform/stc/StaticTypeCheckingSupport.java | 2 +-
.../transform/stc/StaticTypeCheckingVisitor.java | 70 +++++++++++++++++++++-
.../groovy/transform/stc/GenericsSTCTest.groovy | 36 +++++++++++
3 files changed, 104 insertions(+), 4 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 da08043..3a58248 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -1862,7 +1862,7 @@ public abstract class StaticTypeCheckingSupport {
return false;
}
- private static ClassNode[] applyGenericsContext(final Map<GenericsTypeName, GenericsType> spec, final ClassNode[] bounds) {
+ static ClassNode[] applyGenericsContext(final Map<GenericsTypeName, GenericsType> spec, final ClassNode[] bounds) {
if (bounds == null) return null;
ClassNode[] newBounds = new ClassNode[bounds.length];
for (int i = 0, n = bounds.length; i < n; i += 1) {
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 3419204..115988c 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;
@@ -264,6 +265,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;
@@ -2401,6 +2403,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();
@@ -5272,6 +5275,46 @@ 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);
+ }
+
/**
* Converts a closure type to the appropriate SAM type, which is used to
* infer return type generics.
@@ -5283,11 +5326,32 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
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 samReturnType = sam.getReturnType();
- // the return type of the SAM exactly corresponds to the inferred return type of the closure
- extractGenericsConnections(placeholders, closureType.getGenericsTypes()[0].getType(), samReturnType);
+ 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);
+ }
+ }
+
+ // 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<>();
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index 56f2303..345252b 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -742,6 +742,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