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/07/19 22:49:12 UTC

[groovy] branch GROOVY_2_5_X updated: GROOVY-10166, GROOVY-10282: GenericsUtils: don't mix type param contexts collect for BiFunction yields [T:U, U:? super T, R:U] not [T:? super T, U:? super T, R:? super T]

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 8eed4fe9a4 GROOVY-10166, GROOVY-10282: GenericsUtils: don't mix type param contexts collect for BiFunction<U, ? super T, U> yields [T:U, U:? super T, R:U]   not [T:? super T, U:? super T, R:? super T]
8eed4fe9a4 is described below

commit 8eed4fe9a417ae287021732dac970a28562bd92b
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Jul 19 17:38:21 2022 -0500

    GROOVY-10166, GROOVY-10282: GenericsUtils: don't mix type param contexts
    collect for BiFunction<U, ? super T, U> yields [T:U, U:? super T, R:U]
      not [T:? super T, U:? super T, R:? super T]
    
    2_5_X backport
---
 .../codehaus/groovy/ast/tools/GenericsUtils.java   | 214 ++++++++-------------
 .../groovy/transform/stc/GenericsSTCTest.groovy    | 109 ++++++++++-
 2 files changed, 184 insertions(+), 139 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 379ccf559b..db1a12b1b7 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
@@ -20,7 +20,6 @@ package org.codehaus.groovy.ast.tools;
 
 import antlr.RecognitionException;
 import antlr.TokenStreamException;
-import groovy.lang.Tuple2;
 import groovy.transform.stc.IncorrectTypeHintException;
 import org.apache.groovy.internal.util.Predicate;
 import org.apache.groovy.util.SystemUtil;
@@ -556,19 +555,11 @@ public class GenericsUtils {
         }
     }
 
-    public static ClassNode getSuperClass(ClassNode type, ClassNode target) {
-        ClassNode superClass = ClassHelper.getNextSuperClass(type, target);
-
-        if (superClass == null) {
-            if (ClassHelper.isPrimitiveType(type)) {
-                superClass = ClassHelper.getNextSuperClass(ClassHelper.getWrapper(type), target);
-            }
-        }
-
-        return superClass;
+    public static ClassNode getSuperClass(final ClassNode type, final ClassNode target) {
+        return ClassHelper.getNextSuperClass(ClassHelper.isPrimitiveType(type) ? ClassHelper.getWrapper(type) : type, target);
     }
 
-    private static void extractSuperClassGenerics(GenericsType[] usage, GenericsType[] declaration, Map<String, ClassNode> spec) {
+    private static void extractSuperClassGenerics(final GenericsType[] usage, final GenericsType[] declaration, final Map<String, ClassNode> spec) {
         // if declaration does not provide generics, there is no connection to make
         if (declaration == null || declaration.length == 0) return;
 
@@ -739,32 +730,6 @@ public class GenericsUtils {
         return newTypes;
     }
 
-    private static final boolean PARAMETERIZED_TYPE_CACHE_ENABLED = Boolean.parseBoolean(SystemUtil.getSystemPropertySafe("groovy.enable.parameterized.type.cache", "true"));
-
-    /**
-     * Try to get the parameterized type from the cache.
-     * If no cached item found, cache and return the result of {@link #findParameterizedType(ClassNode, ClassNode)}
-     */
-    public static ClassNode findParameterizedTypeFromCache(final ClassNode genericsClass, final ClassNode actualType) {
-        return findParameterizedType(genericsClass, actualType, false);
-    }
-
-    // Backported from 3.0.0
-    private static ClassNode findParameterizedTypeFromCache(final ClassNode genericsClass, final ClassNode actualType, final boolean tryToFindExactType) {
-        if (!PARAMETERIZED_TYPE_CACHE_ENABLED) {
-            return findParameterizedType(genericsClass, actualType, tryToFindExactType);
-        }
-
-        SoftReference<ClassNode> sr = PARAMETERIZED_TYPE_CACHE.getAndPut(new ParameterizedTypeCacheKey(genericsClass, actualType), new EvictableCache.ValueProvider<ParameterizedTypeCacheKey, SoftReference<ClassNode>>() {
-            @Override
-            public SoftReference<ClassNode> provide(ParameterizedTypeCacheKey key) {
-                return new SoftReference<>(findParameterizedType(key.getGenericsClass(), key.getActualType(), tryToFindExactType));
-            }
-        });
-
-        return null == sr ? null : sr.get();
-    }
-
     /**
      * Get the parameterized type by search the whole class hierarchy according to generics class and actual receiver.
      * {@link #findParameterizedTypeFromCache(ClassNode, ClassNode)} is strongly recommended for better performance.
@@ -848,16 +813,6 @@ public class GenericsUtils {
         return false;
     }
 
-    private static final EvictableCache<ParameterizedTypeCacheKey, SoftReference<ClassNode>> PARAMETERIZED_TYPE_CACHE = new ConcurrentSoftCache<>(64);
-
-    /**
-     * Clear the parameterized type cache
-     * 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
@@ -871,8 +826,8 @@ public class GenericsUtils {
      * The resolved types can not help us to choose methods correctly if the argument is a string:  T: Object, S: Serializable
      * so we need actual types:  T: String, S: Long
      */
-    public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMap(ClassNode declaringClass, ClassNode actualReceiver) {
-        return doMakeDeclaringAndActualGenericsTypeMap(declaringClass, actualReceiver, false).getFirst();
+    public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMap(final ClassNode declaringClass, final ClassNode actualReceiver) {
+        return doMakeDeclaringAndActualGenericsTypeMap(declaringClass, actualReceiver, false);
     }
 
     /**
@@ -885,71 +840,38 @@ public class GenericsUtils {
      * @param declaringClass the generics class node declaring the generics types
      * @param actualReceiver the sub-class class node
      * @return the placeholder-to-actualtype mapping
+     *
      * @since 2.5.9
      */
-    public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMapOfExactType(ClassNode declaringClass, ClassNode actualReceiver) {
-        List<ClassNode> parameterizedTypeList = new LinkedList<>();
-
-        Map<GenericsType, GenericsType> result = makeDeclaringAndActualGenericsTypeMapOfExactType(declaringClass, actualReceiver, parameterizedTypeList);
-
-        return connectGenericsTypes(result);
-    }
-
-    private static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMapOfExactType(ClassNode declaringClass, ClassNode actualReceiver, List<ClassNode> parameterizedTypeList) {
-        Tuple2<Map<GenericsType, GenericsType>, ClassNode> resultAndParameterizedTypeTuple = doMakeDeclaringAndActualGenericsTypeMap(declaringClass, actualReceiver, true);
-        ClassNode parameterizedType = resultAndParameterizedTypeTuple.getSecond();
-        Map<GenericsType, GenericsType> result = resultAndParameterizedTypeTuple.getFirst();
-
-        if (hasPlaceHolders(parameterizedType) && !parameterizedTypeList.contains(parameterizedType)) {
-            parameterizedTypeList.add(parameterizedType);
-            result.putAll(makeDeclaringAndActualGenericsTypeMapOfExactType(parameterizedType, actualReceiver, parameterizedTypeList));
-        }
-
-        return connectGenericsTypes(result);
+    public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMapOfExactType(final ClassNode declaringClass, final ClassNode actualReceiver) {
+        return doMakeDeclaringAndActualGenericsTypeMap(declaringClass, actualReceiver, true);
     }
 
-    private static Tuple2<Map<GenericsType, GenericsType>, ClassNode> doMakeDeclaringAndActualGenericsTypeMap(ClassNode declaringClass, ClassNode actualReceiver, boolean tryToFindExactType) {
+    private static Map<GenericsType, GenericsType> doMakeDeclaringAndActualGenericsTypeMap(final ClassNode declaringClass, final ClassNode actualReceiver, final boolean tryToFindExactType) {
+        Map<GenericsType, GenericsType> map = Collections.<GenericsType, GenericsType>emptyMap();
         ClassNode parameterizedType = findParameterizedTypeFromCache(declaringClass, actualReceiver, tryToFindExactType);
-
-        if (parameterizedType == null) {
-            return new Tuple2<>(Collections.<GenericsType,GenericsType>emptyMap(), parameterizedType);
-        }
-
-        Map<GenericsType, GenericsType> result = new LinkedHashMap<>();
-        result.putAll(makePlaceholderAndParameterizedTypeMap(declaringClass));
-        result.putAll(makePlaceholderAndParameterizedTypeMap(parameterizedType));
-
-        result = connectGenericsTypes(result);
-
-        return new Tuple2<>(result, parameterizedType);
-    }
-
-    private static Map<GenericsType, GenericsType> connectGenericsTypes(Map<GenericsType, GenericsType> genericsTypeMap) {
-        Map<GenericsType, GenericsType> result = new LinkedHashMap<>();
-
-        outer:
-        for (Map.Entry<GenericsType, GenericsType> entry : genericsTypeMap.entrySet()) {
-            GenericsType key = entry.getKey();
-            GenericsType value = entry.getValue();
-
-            if (value.isPlaceholder()) {
-                for (Map.Entry<GenericsType, GenericsType> genericsTypeMapEntry : genericsTypeMap.entrySet()) {
-                    GenericsType genericsTypeMapEntryValue = genericsTypeMapEntry.getValue();
-                    if (!genericsTypeMapEntryValue.isPlaceholder() && (genericsTypeMapEntry.getKey().getName().equals(value.getName()))) {
-                        result.put(key, genericsTypeMapEntryValue); // connected to actual type
-                        continue outer;
-                    }
+        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<GenericsType, GenericsType>();
+                for (int i = 0, m = sourceGenericsTypes.length, n = targetGenericsTypes.length; i < n; i += 1) {
+                    map.put(targetGenericsTypes[i], i < m ? sourceGenericsTypes[i] : targetGenericsTypes[i]);
                 }
             }
-
-            result.put(key, value);
         }
-
-        return result;
+        return map;
     }
 
-    public static boolean hasPlaceHolders(ClassNode parameterizedType) {
-        return checkPlaceHolders(parameterizedType, new Predicate<GenericsType>() {
+    /**
+     * Checks if the type has any placeholder (aka unresolved) generics.
+     *
+     * @since 2.5.10
+     */
+    public static boolean hasPlaceHolders(final ClassNode type) {
+        return checkPlaceHolders(type, new Predicate<GenericsType>() {
             @Override
             public boolean test(GenericsType genericsType) {
                 return genericsType.isPlaceholder();
@@ -957,48 +879,28 @@ public class GenericsUtils {
         });
     }
 
-    private static boolean checkPlaceHolders(ClassNode parameterizedType, Predicate<GenericsType> p) {
-        if (parameterizedType == null) return false;
-
-        GenericsType[] genericsTypes = parameterizedType.getGenericsTypes();
-
-        if (genericsTypes == null) return false;
-
-        for (GenericsType genericsType : genericsTypes) {
-            if (p.test(genericsType)) {
-                return true;
+    private static boolean checkPlaceHolders(final ClassNode type, final Predicate<GenericsType> p) {
+        if (type != null) {
+            GenericsType[] genericsTypes = type.getGenericsTypes();
+            if (genericsTypes != null) {
+                for (GenericsType genericsType : genericsTypes) {
+                    if (p.test(genericsType)) {
+                        return true;
+                    }
+                }
             }
         }
-
         return false;
     }
 
-    private static Map<GenericsType, GenericsType> makePlaceholderAndParameterizedTypeMap(ClassNode declaringClass) {
-        if (declaringClass == null) {
-            return Collections.emptyMap();
-        }
-
-        ClassNode redirectDeclaringClass = declaringClass.redirect();
-        GenericsType[] declaringGenericsTypes = declaringClass.getGenericsTypes();
-        GenericsType[] redirectDeclaringGenericsTypes = redirectDeclaringClass.getGenericsTypes();
-
-        Map<GenericsType, GenericsType> result = new LinkedHashMap<>();
-        if (declaringGenericsTypes != null && redirectDeclaringGenericsTypes != null) {
-            for (int i = 0, n = declaringGenericsTypes.length; i < n; i += 1) {
-                result.put(redirectDeclaringGenericsTypes[i], declaringGenericsTypes[i]);
-            }
-        }
-        return result;
-    }
-
     /**
-     * Get the actual type according to the placeholder name
+     * Gets the actual type according to the placeholder name.
      *
-     * @param placeholderName the placeholder name, e.g. T, E
+     * @param placeholderName the placeholder name (i.e. "T", "E", etc.)
      * @param genericsPlaceholderAndTypeMap the result of {@link #makeDeclaringAndActualGenericsTypeMap(ClassNode, ClassNode)}
      * @return the actual type
      */
-    public static ClassNode findActualTypeByGenericsPlaceholderName(String placeholderName, Map<GenericsType, GenericsType> genericsPlaceholderAndTypeMap) {
+    public static ClassNode findActualTypeByGenericsPlaceholderName(final String placeholderName, final Map<GenericsType, GenericsType> genericsPlaceholderAndTypeMap) {
         for (Map.Entry<GenericsType, GenericsType> entry : genericsPlaceholderAndTypeMap.entrySet()) {
             if (placeholderName.equals(entry.getKey().getName())) {
                 GenericsType gt = entry.getValue();
@@ -1016,6 +918,44 @@ public class GenericsUtils {
         return null;
     }
 
+    //--------------------------------------------------------------------------
+
+    /**
+     * 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();
+    }
+
+    /**
+     * Try to get the parameterized type from the cache. If no cached item found,
+     * cache and return the result of {@link #findParameterizedType(ClassNode, ClassNode)}
+     */
+    public static ClassNode findParameterizedTypeFromCache(final ClassNode genericsClass, final ClassNode actualType) {
+        return findParameterizedType(genericsClass, actualType, false);
+    }
+
+    private static ClassNode findParameterizedTypeFromCache(final ClassNode genericsClass, final ClassNode actualType, final boolean tryToFindExactType) {
+        if (!PARAMETERIZED_TYPE_CACHE_ENABLED) {
+            return findParameterizedType(genericsClass, actualType, tryToFindExactType);
+        }
+
+        SoftReference<ClassNode> sr = PARAMETERIZED_TYPE_CACHE.getAndPut(new ParameterizedTypeCacheKey(genericsClass, actualType), new EvictableCache.ValueProvider<ParameterizedTypeCacheKey, SoftReference<ClassNode>>() {
+            @Override
+            public SoftReference<ClassNode> provide(ParameterizedTypeCacheKey key) {
+                return new SoftReference<>(findParameterizedType(key.getGenericsClass(), key.getActualType(), tryToFindExactType));
+            }
+        });
+
+        return sr == null ? null : sr.get();
+    }
+
+    private static final EvictableCache<ParameterizedTypeCacheKey, SoftReference<ClassNode>> PARAMETERIZED_TYPE_CACHE = new ConcurrentSoftCache<>(64);
+
+    private static final boolean PARAMETERIZED_TYPE_CACHE_ENABLED = Boolean.parseBoolean(SystemUtil.getSystemPropertySafe("groovy.enable.parameterized.type.cache", "true"));
+
     private static class ParameterizedTypeCacheKey {
         private ClassNode genericsClass;
         private ClassNode actualType;
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index 1f3f814b16..1a3b869509 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -2027,8 +2027,95 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
-    // GROOVY-5893
-    @NotYetImplemented
+    // GROOVY-10166
+    void testShouldFindMethodEvenWithRepeatNames1() {
+        assertScript '''
+            abstract class A<T extends C> {
+                T getC() {
+                }
+                Map toMap() {
+                    c.getMap(this)
+                }
+            }
+
+            class C<T extends A> {
+                Map getMap(T a) {
+                }
+            }
+
+            new C()
+        '''
+    }
+
+    // GROOVY-10166
+    void testShouldFindMethodEvenWithRepeatNames2() {
+        assertScript '''
+            abstract class A<T extends C> {
+                T getC() {
+                }
+            }
+
+            class C<T extends A> {
+                T get() {
+                    A a = null
+                    a.c.get(1)
+                }
+                T get(int i) {
+                }
+            }
+
+            new C()
+        '''
+    }
+
+    // GROOVY-10196
+    void testShouldFindMethodEvenWithRepeatNames3() {
+        assertScript '''
+            interface M<K,V> {
+            }
+            interface MM<K,V> extends M<K,List<V>> {
+                void add(K key, V val)
+            }
+            class MMA<K,V> implements MM<K,V> {
+                @Override
+                void add(K key, V val) {
+                }
+            }
+            class LMM<K,V> extends MMA<K,V> {
+            }
+
+            MM<String,String> map = new LMM<>()
+            map.add('foo','bar')
+        '''
+    }
+
+    @NotYetImplemented // GROOVY-10322
+    void testShouldFindMethodEvenWithRepeatNames4() {
+        assertScript '''
+            class C<T> {
+                def <T> T m(T t) { // this T hides T from C<T>
+                    return t
+                }
+            }
+            int x = new C<String>().m(1) // no error, param and return types are `int` not `String`
+            assert x == 1
+        '''
+    }
+
+    // GROOVY-10337
+    void testShouldFindMethodEvenWithRepeatNames5() {
+        assertScript '''
+            class C<X,Y> {
+                C(C<Y,? extends Y> that) {
+                }
+            }
+            def <T> void test() {
+                new C<Number,T>((C<T,T>)null) // cannot call ctor with argument C<T,T>
+            }
+        '''
+    }
+
+    @NotYetImplemented // GROOVY-5893
     void testPlusInClosure() {
         assertScript '''
         def list = [1, 2, 3]
@@ -3138,6 +3225,24 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    // GROOVY-10282
+    void testBiFunctionAndBinaryOperatorWithSharedTypeParameter() {
+        assertScript '''
+            interface BiFunction<T,U,R> {
+                R apply(T t, U u);
+            }
+            interface BinaryOperator<T> extends BiFunction<T,T,T> {
+            }
+            interface Stream<T> {
+                def <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
+            }
+
+            void test(Stream<Integer> stream) {
+                stream.reduce('', { s, i  -> s + '-' }, String.&concat)
+            }
+        '''
+    }
+
     // GROOVY-7804
     void testParameterlessClosureToGenericSAMTypeArgumentCoercion() {
         assertScript '''