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/09/06 15:07:37 UTC
[groovy] branch GROOVY_2_5_X updated: GROOVY-9762, GROOVY-9803: generics of method pointer from SAM parameters
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch GROOVY_2_5_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY_2_5_X by this push:
new 73de66587b GROOVY-9762, GROOVY-9803: generics of method pointer from SAM parameters
73de66587b is described below
commit 73de66587bdeeb8e0cede25a3fba24de5a7a2498
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Sep 6 09:47:13 2022 -0500
GROOVY-9762, GROOVY-9803: generics of method pointer from SAM parameters
2_5_X backport
---
.../transform/stc/StaticTypeCheckingSupport.java | 21 ++++---
.../transform/stc/StaticTypeCheckingVisitor.java | 71 +++++++++++++++++++++-
.../groovy/transform/stc/GenericsSTCTest.groovy | 59 ++++++++++++++++++
3 files changed, 141 insertions(+), 10 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 83f1800153..41e95f748a 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -1876,16 +1876,21 @@ public abstract class StaticTypeCheckingSupport {
return type.getGenericsTypes();
}
- static Map<GenericsTypeName, GenericsType> applyGenericsContextToParameterClass(
- Map<GenericsTypeName, GenericsType> spec, ClassNode parameterUsage
- ) {
+ static Map<GenericsTypeName, GenericsType> applyGenericsContextToParameterClass(final Map<GenericsTypeName, GenericsType> spec, final ClassNode parameterUsage) {
GenericsType[] gts = parameterUsage.getGenericsTypes();
- if (gts == null) return Collections.EMPTY_MAP;
+ if (gts == null) return Collections.emptyMap();
+
+ ClassNode newType = parameterUsage.redirect().getPlainNodeReference();
+ newType.setGenericsTypes(applyGenericsContext(spec, gts));
- GenericsType[] newGTs = applyGenericsContext(spec, gts);
- ClassNode newTarget = parameterUsage.redirect().getPlainNodeReference();
- newTarget.setGenericsTypes(newGTs);
- return GenericsUtils.extractPlaceholders(newTarget);
+ Map<GenericsTypeName, GenericsType> newSpec = GenericsUtils.extractPlaceholders(newType);
+ for (Map.Entry<GenericsTypeName, GenericsType> entry : newSpec.entrySet()) {
+ // GROOVY-9762, GROOVY-9803: reduce "? super T" to "T"
+ ClassNode lowerBound = entry.getValue().getLowerBound();
+ if (lowerBound != null)
+ entry.setValue(new GenericsType(lowerBound));
+ }
+ return newSpec;
}
static GenericsType[] applyGenericsContext(final Map<GenericsTypeName, GenericsType> spec, final GenericsType[] gts) {
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 b11de5b7eb..ee65cc9e51 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -2581,6 +2581,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
if (!returnType.equals(OBJECT_TYPE)) {
storeType(expression, wrapClosureType(returnType));
}
+ expression.putNodeMetaData(MethodNode.class, candidates);
} else { // GROOVY-9463
ClassNode type = wrapTypeIfNecessary(getType(expression.getExpression()));
if (isClassClassNodeWrappingConcreteType(type)) type = type.getGenericsTypes()[0].getType();
@@ -5410,6 +5411,45 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
}
}
+ private static MethodNode chooseMethod(final MethodPointerExpression source, final Function<Void,ClassNode[]> samSignature) {
+ List<MethodNode> options = source.getNodeMetaData(MethodNode.class);
+ if (options != null && !options.isEmpty()) {
+ ClassNode[] paramTypes = samSignature.apply(null);
+ for (MethodNode option : options) {
+ ClassNode[] types = collateMethodReferenceParameterTypes(source, option);
+ int nTypes = types.length;
+ if (nTypes == paramTypes.length) {
+ for (int i = 0; i < nTypes; i += 1) {
+ if (!types[i].isGenericsPlaceHolder() && !isAssignableTo(types[i], paramTypes[i])) {
+ continue;
+ }
+ }
+ return option;
+ }
+ }
+ }
+ return null;
+ }
+
+ 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.
@@ -5418,15 +5458,42 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
* @param samType the type into which the closure is coerced into
* @return SAM type augmented using information from the argument expression
*/
- private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final MethodNode sam, final ClassNode samType, final Map<GenericsTypeName, GenericsType> placeholders) {
+ private static ClassNode convertClosureTypeToSAMType(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 (isClosureWithType(closureType)) {
ClassNode closureReturnType = closureType.getGenericsTypes()[0].getType();
+ final Parameter[] parameters = sam.getParameters();
+ if (parameters.length > 0 && expression instanceof MethodPointerExpression) {
+ MethodPointerExpression mp = (MethodPointerExpression) expression;
+ MethodNode mn = chooseMethod(mp, new Function<Void,ClassNode[]>(){
+ @Override public ClassNode[] apply(Void x) {
+ return 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[] pa = new Parameter[pTypes.length];
+ for (int i = 0; i < pTypes.length; i += 1) {
+ pa[i] = new Parameter(pTypes[i], "");
+ }
+ expression = new ClosureExpression(pa,null);
+ }
+ }
+
// the SAM's return type exactly corresponds to the inferred closure return type
extractGenericsConnections(placeholders, closureReturnType, sam.getReturnType());
- Parameter[] parameters = sam.getParameters();
// repeat the same for each parameter given in the ClosureExpression
if (parameters.length > 0 && expression instanceof ClosureExpression) {
ClassNode[] paramTypes = applyGenericsContext(placeholders, extractTypesFromParameters(parameters));
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index a03dc9801c..da631e99fa 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -1654,6 +1654,65 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
'''
}
+ // GROOVY-9762
+ void testShouldUseMethodGenericType7() {
+ if (!GroovyAssert.isAtLeastJdk('1.8')) return
+
+ for (toList in ['{ list(it) }', 'this.&list']) {
+ assertScript """
+ def <T> List<T> list(T item) {
+ return [item]
+ }
+ def test() {
+ Optional<Integer> opt = Optional.ofNullable(1)
+ List<Integer> ret = opt.map($toList).get()
+ return ret
+ }
+ assert test() == [1]
+ """
+ }
+ }
+
+ // GROOVY-9803
+ void testShouldUseMethodGenericType8() {
+ if (GroovyAssert.isAtLeastJdk('1.8')) {
+ assertScript '''
+ def opt = Optional.of(42)
+ .map(Collections.&singleton)
+ .map{it.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
+ for (toSet in ['D.&wrap', 'Collections.&singleton', '{x -> [x].toSet()}', '{Collections.singleton(it)}']) {
+ 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($toSet)
+ def e = d.map{it.first().intValue()}
+ }
+
+ test()
+ """
+ }
+ }
+
// GROOVY-9945
void testShouldUseMethodGenericType9() {
assertScript '''