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/11/18 15:55:25 UTC

[groovy] branch GROOVY_4_0_X updated: GROOVY-10813: `parameterizeSAM`: no type parameters from method context

This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/GROOVY_4_0_X by this push:
     new 64cf479a9a GROOVY-10813: `parameterizeSAM`: no type parameters from method context
64cf479a9a is described below

commit 64cf479a9a4a7069132adfe2f8c45fffafcfc705
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Fri Nov 18 09:26:57 2022 -0600

    GROOVY-10813: `parameterizeSAM`: no type parameters from method context
---
 .../codehaus/groovy/ast/tools/GenericsUtils.java   |  91 ++++++++-----
 src/test/groovy/transform/stc/LambdaTest.groovy    |  25 ++++
 .../groovy/ast/tools/GenericsUtilsTest.groovy      | 145 ++++++++++++++-------
 3 files changed, 181 insertions(+), 80 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
index 215b3d5992..f8bcc8c6b1 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
@@ -709,6 +709,17 @@ public class GenericsUtils {
 
     private static final boolean PARAMETERIZED_TYPE_CACHE_ENABLED = Boolean.parseBoolean(getSystemPropertySafe("groovy.enable.parameterized.type.cache", "true"));
 
+    private static final EvictableCache<ParameterizedTypeCacheKey, SoftReference<ClassNode>> PARAMETERIZED_TYPE_CACHE = new ConcurrentSoftCache<>(64);
+
+    /**
+     * Clears the parameterized type cache.
+     * <p>
+     * It is useful to IDE as the type being compiled are continuously being edited/altered, see GROOVY-8675
+     */
+    public static void clearParameterizedTypeCache() {
+        PARAMETERIZED_TYPE_CACHE.clearAll();
+    }
+
     /**
      * Convenience method for {@link #findParameterizedTypeFromCache(ClassNode, ClassNode, boolean)}
      * when the {@code tryToFindExactType} boolean is {@code false}.
@@ -731,7 +742,7 @@ public class GenericsUtils {
                 new ParameterizedTypeCacheKey(genericsClass, actualType),
                 key -> new SoftReference<>(findParameterizedType(key.getGenericsClass(), key.getActualType(), tryToFindExactType)));
 
-        return sr == null ? null : sr.get();
+        return sr != null ? sr.get() : null;
     }
 
     /**
@@ -762,41 +773,34 @@ public class GenericsUtils {
         ClassNode type;
 
         while ((type = todo.poll()) != null) {
-            if (type.equals(genericsClass)) {
-                return type;
-            }
             if (done.add(type)) {
-                boolean parameterized = (type.getGenericsTypes() != null);
-                for (ClassNode cn : type.getInterfaces()) {
-                    if (parameterized)
-                        cn = parameterizeType(type, cn);
-                    todo.add(cn);
-                }
-                if (!actualType.isInterface()) {
+                if (!type.isInterface()) {
                     ClassNode cn = type.getUnresolvedSuperClass();
                     if (cn != null && cn.redirect() != ClassHelper.OBJECT_TYPE) {
-                        if (parameterized)
+                        if (hasUnresolvedGenerics(cn)) {
                             cn = parameterizeType(type, cn);
+                        }
+                        if (cn.equals(genericsClass)) {
+                            return cn;
+                        }
                         todo.add(cn);
                     }
                 }
+                for (ClassNode cn : type.getInterfaces()) {
+                    if (hasUnresolvedGenerics(cn)) {
+                        cn = parameterizeType(type, cn);
+                    }
+                    if (cn.equals(genericsClass)) {
+                        return cn;
+                    }
+                    todo.add(cn);
+                }
             }
         }
 
         return null;
     }
 
-    private static final EvictableCache<ParameterizedTypeCacheKey, SoftReference<ClassNode>> PARAMETERIZED_TYPE_CACHE = new ConcurrentSoftCache<>(64);
-
-    /**
-     * Clears the parameterized type cache.
-     * <p>
-     * It is useful to IDE as the type being compiled are continuously being edited/altered, see GROOVY-8675
-     */
-    public static void clearParameterizedTypeCache() {
-        PARAMETERIZED_TYPE_CACHE.clearAll();
-    }
-
     /**
      * map declaring generics type to actual generics type, e.g. GROOVY-7204:
      * declaring generics types:      T,      S extends Serializable
@@ -811,7 +815,7 @@ public class GenericsUtils {
      * so we need actual types:  T: String, S: Long
      */
     public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMap(final ClassNode declaringClass, final ClassNode actualReceiver) {
-        return doMakeDeclaringAndActualGenericsTypeMap(declaringClass, actualReceiver, false);
+        return correlateTypeParametersAndTypeArguments(declaringClass, actualReceiver, false);
     }
 
     /**
@@ -826,25 +830,42 @@ public class GenericsUtils {
      * @since 3.0.0
      */
     public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMapOfExactType(final ClassNode declaringClass, final ClassNode actualReceiver) {
-        return doMakeDeclaringAndActualGenericsTypeMap(declaringClass, actualReceiver, true);
+        return correlateTypeParametersAndTypeArguments(declaringClass, actualReceiver, true);
     }
 
-    private static Map<GenericsType, GenericsType> doMakeDeclaringAndActualGenericsTypeMap(final ClassNode declaringClass, final ClassNode actualReceiver, final boolean tryToFindExactType) {
-        Map<GenericsType, GenericsType> map = Collections.emptyMap();
+    private static Map<GenericsType, GenericsType> correlateTypeParametersAndTypeArguments(final ClassNode declaringClass, final ClassNode actualReceiver, final boolean tryToFindExactType) {
         ClassNode parameterizedType = findParameterizedTypeFromCache(declaringClass, actualReceiver, tryToFindExactType);
         if (parameterizedType != null && parameterizedType.isRedirectNode() && !parameterizedType.isGenericsPlaceHolder()) { // GROOVY-10166
             // declaringClass may be "List<T> -> List<E>" and parameterizedType may be "List<String> -> List<E>" or "List<> -> List<E>"
-            GenericsType[] targetGenericsTypes = parameterizedType.redirect().getGenericsTypes();
-            if (targetGenericsTypes != null) {
-                GenericsType[] sourceGenericsTypes = parameterizedType.getGenericsTypes();
-                if (sourceGenericsTypes == null) sourceGenericsTypes = EMPTY_GENERICS_ARRAY;
-                map = new LinkedHashMap<>();
-                for (int i = 0, m = sourceGenericsTypes.length, n = targetGenericsTypes.length; i < n; i += 1) {
-                    map.put(targetGenericsTypes[i], i < m ? sourceGenericsTypes[i] : targetGenericsTypes[i]);
+            final GenericsType[] typeParameters = parameterizedType.redirect().getGenericsTypes();
+            if (typeParameters != null) {
+                final GenericsType[] typeArguments = parameterizedType.getGenericsTypes();
+                final int m = typeArguments == null ? 0 : typeArguments.length;
+                final int n = typeParameters.length;
+
+                Map<GenericsType, GenericsType> map = new LinkedHashMap<>();
+                for (int i = 0; i < n; i += 1) {
+                    map.put(typeParameters[i], i < m ? typeArguments[i] : erasure(typeParameters[i]));
                 }
+                return map;
             }
         }
-        return map;
+        return Collections.emptyMap();
+    }
+
+    /**
+     * @see org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport#extractType(GenericsType)
+     */
+    private static GenericsType erasure(GenericsType gt) {
+        ClassNode cn = gt.getType().redirect(); // discard the placeholder
+
+        if (gt.getType().getGenericsTypes() != null)
+            gt = gt.getType().getGenericsTypes()[0];
+
+        if (gt.getUpperBounds() != null)
+            cn = gt.getUpperBounds()[0]; // TODO: if length > 1 then union type?
+
+        return cn.asGenericsType();
     }
 
     /**
diff --git a/src/test/groovy/transform/stc/LambdaTest.groovy b/src/test/groovy/transform/stc/LambdaTest.groovy
index bf802bfa31..c117fbac24 100644
--- a/src/test/groovy/transform/stc/LambdaTest.groovy
+++ b/src/test/groovy/transform/stc/LambdaTest.groovy
@@ -689,6 +689,31 @@ final class LambdaTest {
         }
     }
 
+    @Test // GROOVY-10813
+    void testConsumer11() {
+        ['CompileStatic', 'TypeChecked'].each { xform ->
+            assertScript """
+                @groovy.transform.${xform}
+                void test() {
+                    java.util.function.Consumer c = x -> print(x)
+                    c.accept('works')
+                }
+                test()
+            """
+            assertScript """
+                interface I<T extends CharSequence> {
+                    void accept(T t)
+                }
+                @groovy.transform.${xform}
+                void test() {
+                    I i = x -> print(x)
+                    i.accept('works')
+                }
+                test()
+            """
+        }
+    }
+
     @Test
     void testFunctionalInterface1() {
         assertScript shell, '''
diff --git a/src/test/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy b/src/test/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy
index d4b9bbfbcc..3c72244f59 100644
--- a/src/test/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy
+++ b/src/test/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy
@@ -48,12 +48,12 @@ final class GenericsUtilsTest {
         ClassNode source = findClassNode('Derived', classNodeList)
         ClassNode result = GenericsUtils.findParameterizedType(target, source)
 
-        assert 'Base' == result.name
+        assert result.name == 'Base'
         assert result.isUsingGenerics()
-        assert 2 == result.genericsTypes.length
-        assert 'java.lang.String' == result.genericsTypes[0].type.name
-        assert 'java.util.List'   == result.genericsTypes[1].type.name
-        assert target === result.redirect()
+        assert result.genericsTypes.length == 2
+        assert result.genericsTypes[0].type.name == 'java.lang.String'
+        assert result.genericsTypes[1].type.name == 'java.util.List'
+        assert result.redirect() === target
     }
 
     @Test
@@ -67,12 +67,12 @@ final class GenericsUtilsTest {
         ClassNode source = findClassNode('Derived', classNodeList)
         ClassNode result = GenericsUtils.findParameterizedType(target, source)
 
-        assert 'Base' == result.name
+        assert result.name == 'Base'
         assert result.isUsingGenerics()
-        assert 2 == result.genericsTypes.length
-        assert 'java.lang.String' == result.genericsTypes[0].type.name
-        assert 'java.util.List'   == result.genericsTypes[1].type.name
-        assert target === result.redirect()
+        assert result.genericsTypes.length == 2
+        assert result.genericsTypes[0].type.name == 'java.lang.String'
+        assert result.genericsTypes[1].type.name == 'java.util.List'
+        assert result.redirect() === target
     }
 
     @Test
@@ -87,12 +87,12 @@ final class GenericsUtilsTest {
         ClassNode source = findClassNode('Derived', classNodeList)
         ClassNode result = GenericsUtils.findParameterizedType(target, source)
 
-        assert 'Base' == result.name
+        assert result.name == 'Base'
         assert result.isUsingGenerics()
-        assert 2 == result.genericsTypes.length
-        assert 'java.lang.String' == result.genericsTypes[0].type.name
-        assert 'java.util.List'   == result.genericsTypes[1].type.name
-        assert target === result.redirect()
+        assert result.genericsTypes.length == 2
+        assert result.genericsTypes[0].type.name == 'java.lang.String'
+        assert result.genericsTypes[1].type.name == 'java.util.List'
+        assert result.redirect() === target
     }
 
     @Test
@@ -106,12 +106,12 @@ final class GenericsUtilsTest {
         ClassNode source = findClassNode('Derived', classNodeList)
         ClassNode result = GenericsUtils.findParameterizedType(target, source)
 
-        assert 'Base' == result.name
+        assert result.name == 'Base'
         assert result.isUsingGenerics()
-        assert 2 == result.genericsTypes.length
-        assert 'java.lang.String' == result.genericsTypes[0].type.name
-        assert 'java.util.List'   == result.genericsTypes[1].type.name
-        assert target === result.redirect()
+        assert result.genericsTypes.length == 2
+        assert result.genericsTypes[0].type.name == 'java.lang.String'
+        assert result.genericsTypes[1].type.name == 'java.util.List'
+        assert result.redirect() === target
     }
 
     @Test
@@ -126,12 +126,12 @@ final class GenericsUtilsTest {
         ClassNode source = findClassNode('Derived', classNodeList)
         ClassNode result = GenericsUtils.findParameterizedType(target, source)
 
-        assert 'Base' == result.name
+        assert result.name == 'Base'
         assert result.isUsingGenerics()
-        assert 2 == result.genericsTypes.length
-        assert 'java.lang.String' == result.genericsTypes[0].type.name
-        assert 'java.util.List'   == result.genericsTypes[1].type.name
-        assert target === result.redirect()
+        assert result.genericsTypes.length == 2
+        assert result.genericsTypes[0].type.name == 'java.lang.String'
+        assert result.genericsTypes[1].type.name == 'java.util.List'
+        assert result.redirect() === target
     }
 
     @Test
@@ -147,12 +147,12 @@ final class GenericsUtilsTest {
         ClassNode source = findClassNode('Derived', classNodeList)
         ClassNode result = GenericsUtils.findParameterizedType(target, source)
 
-        assert 'Base' == result.name
+        assert result.name == 'Base'
         assert result.isUsingGenerics()
-        assert 2 == result.genericsTypes.length
-        assert 'java.lang.String' == result.genericsTypes[0].type.name
-        assert 'java.util.List'   == result.genericsTypes[1].type.name
-        assert target === result.redirect()
+        assert result.genericsTypes.length == 2
+        assert result.genericsTypes[0].type.name == 'java.lang.String'
+        assert result.genericsTypes[1].type.name == 'java.util.List'
+        assert result.redirect() === target
     }
 
     @Test
@@ -169,12 +169,12 @@ final class GenericsUtilsTest {
         ClassNode source = findClassNode('Derived', classNodeList)
         ClassNode result = GenericsUtils.findParameterizedType(target, source)
 
-        assert 'Base' == result.name
+        assert result.name == 'Base'
         assert result.isUsingGenerics()
-        assert 2 == result.genericsTypes.length
-        assert 'java.lang.String' == result.genericsTypes[0].type.name
-        assert 'java.util.List'   == result.genericsTypes[1].type.name
-        assert target === result.redirect()
+        assert result.genericsTypes.length == 2
+        assert result.genericsTypes[0].type.name == 'java.lang.String'
+        assert result.genericsTypes[1].type.name == 'java.util.List'
+        assert result.redirect() === target
     }
 
     @Test // GROOVY-9945
@@ -220,9 +220,9 @@ final class GenericsUtilsTest {
 
         Map<GenericsType, GenericsType> m = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(target, source)
 
+        assert m.size() == 2
         assert m.entrySet().find { it.key.name == 'T' }.value.type.name == 'java.lang.String'
         assert m.entrySet().find { it.key.name == 'U' }.value.type.name == 'java.lang.Integer'
-        assert m.size() == 2
     }
 
     @Test
@@ -237,14 +237,14 @@ final class GenericsUtilsTest {
 
         Map<GenericsType, GenericsType> m = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(target, source)
 
+        assert m.size() == 3
         assert m.entrySet().find { it.key.name == 'R' }.value.type.name == 'java.lang.Boolean'
         assert m.entrySet().find { it.key.name == 'T' }.value.type.name == 'java.lang.Integer'
         assert m.entrySet().find { it.key.name == 'U' }.value.type.name == 'java.lang.String'
-        assert m.size() == 3
     }
 
     @Test
-    void testParameterizeSAM() {
+    void testParameterizeSAM1() {
         def classNodeList = compile '''
             import java.util.function.*
             interface T extends Function<String, Integer> {}
@@ -253,10 +253,10 @@ final class GenericsUtilsTest {
 
         def typeInfo = GenericsUtils.parameterizeSAM(samType)
 
-        assert 1 == typeInfo[0].length
-        assert ClassHelper.STRING_TYPE == typeInfo[0][0]
+        assert typeInfo[0].length == 1
+        assert typeInfo[0][0] == ClassHelper.STRING_TYPE
 
-        assert ClassHelper.Integer_TYPE == typeInfo[1]
+        assert typeInfo[1] == ClassHelper.Integer_TYPE
     }
 
     @Test
@@ -269,10 +269,65 @@ final class GenericsUtilsTest {
 
         def typeInfo = GenericsUtils.parameterizeSAM(samType)
 
-        assert 2 == typeInfo[0].length
-        assert ClassHelper.Integer_TYPE == typeInfo[0][0]
-        assert ClassHelper.Integer_TYPE == typeInfo[0][1]
+        assert typeInfo.v1.length == 2
+        assert typeInfo.v1[0] == ClassHelper.Integer_TYPE
+        assert typeInfo.v1[1] == ClassHelper.Integer_TYPE
+
+        assert typeInfo.v2 == ClassHelper.Integer_TYPE
+    }
+
+    @Test // GROOVY-10813
+    void testParameterizeSAMWithRawType() {
+        def classNodeList = compile '''
+            interface I extends java.util.function.BinaryOperator {
+            }
+        '''
+        ClassNode samType = findClassNode('I', classNodeList).interfaces.find { it.name == 'java.util.function.BinaryOperator' }
+
+        def typeInfo = GenericsUtils.parameterizeSAM(samType)
+
+        assert typeInfo.v1.length == 2
+        assert typeInfo.v1[0].toString(false) == 'java.lang.Object'
+        assert typeInfo.v1[1].toString(false) == 'java.lang.Object'
+
+        assert typeInfo.v2.toString(false) == 'java.lang.Object'
+    }
+
+    @Test
+    void testParameterizeSAMWithRawTypeWithUpperBound() {
+        def classNodeList = compile '''
+            interface I<T extends CharSequence> {
+                T apply(T input);
+            }
+            abstract class A implements I {
+            }
+        '''
+        ClassNode samType = findClassNode('A', classNodeList).interfaces.find { it.name == 'I' }
+
+        def typeInfo = GenericsUtils.parameterizeSAM(samType)
+
+        assert typeInfo.v1.length == 1
+        assert typeInfo.v1[0].toString(false) == 'java.lang.CharSequence'
+
+        assert typeInfo.v2.toString(false) == 'java.lang.CharSequence'
+    }
+
+    @Test
+    void testParameterizeSAMWithRawTypeWithUpperBounds() {
+        def classNodeList = compile '''
+            interface I<T extends CharSequence & Serializable> {
+                T apply(T input);
+            }
+            abstract class A implements I {
+            }
+        '''
+        ClassNode samType = findClassNode('A', classNodeList).interfaces.find { it.name == 'I' }
+
+        def typeInfo = GenericsUtils.parameterizeSAM(samType)
+
+        assert typeInfo.v1.length == 1
+        assert typeInfo.v1[0].toString(false) == 'java.lang.CharSequence'
 
-        assert ClassHelper.Integer_TYPE == typeInfo[1]
+        assert typeInfo.v2.toString(false) == 'java.lang.CharSequence'
     }
 }