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 '''