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/02/03 18:43:40 UTC

[groovy] branch GROOVY_2_5_X updated: GROOVY-9974: extract generics connections: Closure vs SAM return type

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 048925d  GROOVY-9974: extract generics connections: Closure<T> vs SAM return type
048925d is described below

commit 048925dcba0331aa4282878ba0b18bacf6f0c13b
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Thu Feb 3 12:34:18 2022 -0600

    GROOVY-9974: extract generics connections: Closure<T> vs SAM return type
    
    2_5_X backport
---
 .../transform/stc/StaticTypeCheckingSupport.java   | 64 +++++++++++++---------
 .../groovy/transform/stc/ClosuresSTCTest.groovy    | 32 ++++++++++-
 2 files changed, 69 insertions(+), 27 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 dcdbd15..00045c2 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -100,6 +100,7 @@ import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.byte_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.char_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.double_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.findSAM;
 import static org.codehaus.groovy.ast.ClassHelper.float_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper;
 import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
@@ -112,7 +113,6 @@ import static org.codehaus.groovy.ast.ClassHelper.make;
 import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
 import static org.codehaus.groovy.ast.ClassHelper.short_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.void_WRAPPER_TYPE;
-import static org.codehaus.groovy.ast.tools.GenericsUtils.getSuperClass;
 import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean;
 import static org.codehaus.groovy.syntax.Types.ASSIGN;
 import static org.codehaus.groovy.syntax.Types.BITWISE_AND;
@@ -1743,37 +1743,51 @@ public abstract class StaticTypeCheckingSupport {
     }
 
     /**
-     * use supplied type to make a connection from usage to declaration
-     * The method operates in two modes.
-     * * For type !instanceof target a structural compare will be done
-     * (for example Dummy&lt;T&gt; and List&lt;R&gt; to get T=R)
-     * * If type equals target, a structural match is done as well
-     * (for example Collection&lt;U&gt; and Collection&lt;E&gt; to get U=E)
-     * * otherwise we climb the hierarchy to find a case of type equals target
-     * to then execute the structural match, while applying possibly existing
-     * generics contexts on the way (for example for IntRange and Collection&lt;E&gt;
-     * to get E=Integer, since IntRange is an AbstractList&lt;Integer&gt;)
+     * Uses supplied type to make a connection from usage to declaration.
+     * <p>
+     * The method operates in two modes:
+     * <ul>
+     * <li>For type !instanceof target a structural compare will be done
+     *     (for example Type&lt;T&gt; and List&lt;E&gt; to get E -> T)
+     * <li>If type equals target, a structural match is done as well
+     *     (for example Collection&lt;T&gt; and Collection&lt;E&gt; to get E -> T)
+     * <li>Otherwise we climb the hierarchy to find a case of type equals target
+     *     to then execute the structural match, while applying possibly existing
+     *     generics contexts on the way (for example for IntRange and Collection&lt;E&gt;
+     *     to get E -> Integer, since IntRange is an AbstractList&lt;Integer&gt;)
+     * </ul>
      * Should the target not have any generics this method does nothing.
      */
     static void extractGenericsConnections(Map<GenericsTypeName, GenericsType> connections, ClassNode type, ClassNode target) {
-        if (target == null || type == target || !isUsingGenericsOrIsArrayUsingGenerics(target)) return;
+        if (target == null || target == type || !isUsingGenericsOrIsArrayUsingGenerics(target)) return;
         if (type == null || type == UNKNOWN_PARAMETER_TYPE) return;
-        if (type.isArray() && target.isArray()) {
+
+        MethodNode sam;
+
+        if (target.isGenericsPlaceHolder()) {
+            connections.put(new GenericsTypeName(target.getUnresolvedName()), new GenericsType(type));
+
+        } else if (type.isArray() && target.isArray()) {
             extractGenericsConnections(connections, type.getComponentType(), target.getComponentType());
-        } else if (target.isGenericsPlaceHolder() || type.equals(target) || !implementsInterfaceOrIsSubclassOf(type, target)) {
-            // structural match route
-            if (target.isGenericsPlaceHolder()) {
-                connections.put(new GenericsTypeName(target.getGenericsTypes()[0].getName()), new GenericsType(type));
-            } else {
-                extractGenericsConnections(connections, type.getGenericsTypes(), target.getGenericsTypes());
-            }
-        } else {
-            // have first to find matching super class or interface
-            ClassNode superClass = getSuperClass(type, target);
 
+        } else if (type.equals(CLOSURE_TYPE) && (sam = findSAM(target)) != null) { // GROOVY-9974
+            ClassNode returnType = StaticTypeCheckingVisitor.wrapTypeIfNecessary(sam.getReturnType());
+            extractGenericsConnections(connections, type.getGenericsTypes(), new GenericsType[] {new GenericsType(returnType)});
+
+        } else if (type.equals(target) || !implementsInterfaceOrIsSubclassOf(type, target)) {
+            extractGenericsConnections(connections, type.getGenericsTypes(), target.getGenericsTypes());
+
+        } else {
+            // find matching super class or interface
+            ClassNode superClass = GenericsUtils.getSuperClass(type, target);
             if (superClass != null) {
-                ClassNode corrected = getCorrectedClassNode(type, superClass, true);
-                extractGenericsConnections(connections, corrected, target);
+                if (GenericsUtils.hasUnresolvedGenerics(superClass)) {
+                    Map<String, ClassNode> spec = GenericsUtils.createGenericsSpec(type);
+                    if (!spec.isEmpty()) {
+                        superClass = GenericsUtils.correctToGenericsSpecRecurse(spec, superClass);
+                    }
+                }
+                extractGenericsConnections(connections, superClass, target);
             } else {
                 // if we reach here, we have an unhandled case
                 throw new GroovyBugError("The type " + type + " seems not to normally extend " + target + ". Sorry, I cannot handle this.");
diff --git a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
index abaca4d..64a7bcc 100644
--- a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
@@ -413,7 +413,7 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
         ''', 'Cannot find matching method'
     }
 
-    //GROOVY-6189
+    // GROOVY-6189
     void testSAMsInMethodSelection(){
         // simple direct case
         assertScript """
@@ -595,6 +595,35 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
         """, "Cannot assign"
     }
 
+    // GROOVY-9974
+    void testStreamsAPI() {
+        try {
+            Class.forName('java.util.function.Predicate')
+        } catch (ClassNotFoundException e) {
+            return
+        }
+        assertScript '''
+            import groovy.transform.*
+
+            @ToString
+            class Pogo {
+                String foo
+            }
+            @Newify(Pogo)
+            List<Pogo> m() {
+                [Pogo(foo:'bar'),Pogo(foo:'baz')]
+            }
+            String test() {
+                m().with { list ->
+                    def other = []
+                    list.stream().filter{ it.foo.startsWith('ba') }.toList()
+                }
+            }
+
+            assert test() == '[Pogo(bar), Pogo(baz)]'
+        '''
+    }
+
     // GROOVY-6238
     void testDirectMethodCallOnClosureExpression() {
         assertScript '''
@@ -686,4 +715,3 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
         }
     }
 }
-