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 2021/02/16 21:10:23 UTC

[groovy] 01/01: GROOVY-9945: findParameterizedType: fill type parameters of super types

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

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

commit 1d1696cad0303d0f4abe09fcd9b15ba88d5f5f87
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Feb 16 15:10:04 2021 -0600

    GROOVY-9945: findParameterizedType: fill type parameters of super types
---
 .../codehaus/groovy/ast/tools/GenericsUtils.java   | 153 +++----
 .../groovy/transform/stc/GenericsSTCTest.groovy    |  17 +
 .../groovy/ast/tools/GenericsUtilsTest.groovy      | 443 +++++++++------------
 3 files changed, 275 insertions(+), 338 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 bea8533..eddfb9d 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
@@ -43,11 +43,13 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -704,108 +706,78 @@ public class GenericsUtils {
 
     /**
      * Try to get the parameterized type from the cache.
-     * If no cached item found, cache and return the result of {@link #findParameterizedType(ClassNode, ClassNode, boolean)}
+     * <p>
+     * If no cached item found, cache and return the result of {@link #findParameterizedType(ClassNode,ClassNode,boolean)}
      */
     public static ClassNode findParameterizedTypeFromCache(final ClassNode genericsClass, final ClassNode actualType, boolean tryToFindExactType) {
         if (!PARAMETERIZED_TYPE_CACHE_ENABLED) {
             return findParameterizedType(genericsClass, actualType, tryToFindExactType);
         }
 
-        SoftReference<ClassNode> sr = PARAMETERIZED_TYPE_CACHE.getAndPut(new ParameterizedTypeCacheKey(genericsClass, actualType), key -> new SoftReference<>(findParameterizedType(key.getGenericsClass(), key.getActualType(), tryToFindExactType)));
+        SoftReference<ClassNode> sr = PARAMETERIZED_TYPE_CACHE.getAndPut(
+                new ParameterizedTypeCacheKey(genericsClass, actualType),
+                key -> new SoftReference<>(findParameterizedType(key.getGenericsClass(), key.getActualType(), tryToFindExactType)));
 
-        return null == sr ? null : sr.get();
+        return sr == null ? null : sr.get();
     }
 
     /**
-     * Convenience method for {@link #findParameterizedType(ClassNode, ClassNode, boolean)}
-     * when the {@code tryToFindExactType} boolean is {@code false}.
+     * Convenience method for {@link #findParameterizedType(ClassNode,ClassNode,boolean)} when {@code tryToFindExactType} is {@code false}.
      */
     public static ClassNode findParameterizedType(final ClassNode genericsClass, final ClassNode actualType) {
         return findParameterizedType(genericsClass, actualType, false);
     }
 
     /**
-     * Get the parameterized type by searching the whole class hierarchy according to generics class and actual receiver.
-     * {@link #findParameterizedTypeFromCache(ClassNode, ClassNode, boolean)} is strongly recommended for better performance.
-     *
-     * @param genericsClass the generics class
-     * @param actualType the actual type
-     * @param tryToFindExactType whether to try to find exact type
-     * @return the parameterized type
+     * Gets the parameterized type by searching the whole class hierarchy according to generics class and actual receiver.
+     * <p>
+     * {@link #findParameterizedTypeFromCache(ClassNode,ClassNode,boolean)} is strongly recommended for better performance.
      */
-    public static ClassNode findParameterizedType(ClassNode genericsClass, ClassNode actualType, boolean tryToFindExactType) {
-        ClassNode parameterizedType = null;
-
-        if (null == genericsClass.getGenericsTypes()) {
-            return parameterizedType;
+    public static ClassNode findParameterizedType(final ClassNode genericsClass, final ClassNode actualType, final boolean tryToFindExactType) {
+        final GenericsType[] genericsTypes = genericsClass.getGenericsTypes();
+        if (genericsTypes == null || genericsClass.isGenericsPlaceHolder()) {
+            return null;
         }
 
-        GenericsType[] declaringGenericsTypes = genericsClass.getGenericsTypes();
-
-        List<ClassNode> classNodeList = new LinkedList<>(getAllSuperClassesAndInterfaces(actualType));
-        classNodeList.add(0, actualType);
-
-        LinkedList<ClassNode> parameterizedTypeCandidateList = new LinkedList<>();
-
-        for (ClassNode cn : classNodeList) {
-            if (cn == genericsClass) {
-                continue;
-            }
-
-            if (tryToFindExactType && null != cn.getGenericsTypes() && hasNonPlaceHolders(cn)) {
-                parameterizedTypeCandidateList.add(cn);
-            }
+        if (actualType.equals(genericsClass)) {
+            return actualType;
+        }
 
-            if (!(genericsClass.equals(cn.redirect()))) {
-                continue;
-            }
+        LinkedList<ClassNode> todo = new LinkedList<>();
+        Set<ClassNode> done = new HashSet<>();
+        todo.add(actualType);
+        ClassNode type;
 
-            if (isGenericsTypeArraysLengthEqual(declaringGenericsTypes, cn.getGenericsTypes())) {
-                parameterizedType = cn;
-                break;
+        while ((type = todo.poll()) != null) {
+            if (type.equals(genericsClass)) {
+                return type;
             }
-        }
-
-        if (null == parameterizedType) {
-            if (!parameterizedTypeCandidateList.isEmpty()) {
-                parameterizedType = parameterizedTypeCandidateList.getLast();
+            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()) {
+                    ClassNode cn = type.getUnresolvedSuperClass();
+                    if (cn != null && cn.redirect() != ClassHelper.OBJECT_TYPE) {
+                        if (parameterized)
+                            cn = parameterizeType(type, cn);
+                        todo.add(cn);
+                    }
+                }
             }
         }
 
-        return parameterizedType;
-    }
-
-    private static boolean isGenericsTypeArraysLengthEqual(GenericsType[] declaringGenericsTypes, GenericsType[] actualGenericsTypes) {
-        return null != actualGenericsTypes && declaringGenericsTypes.length == actualGenericsTypes.length;
-    }
-
-    private static List<ClassNode> getAllSuperClassesAndInterfaces(ClassNode actualReceiver) {
-        List<ClassNode> superClassAndInterfaceList = new LinkedList<>();
-        List<ClassNode> allSuperClassNodeList = getAllUnresolvedSuperClasses(actualReceiver);
-        superClassAndInterfaceList.addAll(allSuperClassNodeList);
-        superClassAndInterfaceList.addAll(actualReceiver.getAllInterfaces());
-
-        for (ClassNode superClassNode : allSuperClassNodeList) {
-            superClassAndInterfaceList.addAll(superClassNode.getAllInterfaces());
-        }
-
-        return superClassAndInterfaceList;
-    }
-
-    private static List<ClassNode> getAllUnresolvedSuperClasses(ClassNode actualReceiver) {
-        List<ClassNode> superClassNodeList = new LinkedList<>();
-
-        for (ClassNode cn = actualReceiver.getUnresolvedSuperClass(); null != cn && ClassHelper.OBJECT_TYPE != cn; cn = cn.getUnresolvedSuperClass()) {
-            superClassNodeList.add(cn);
-        }
-
-        return superClassNodeList;
+        return null;
     }
 
     private static final EvictableCache<ParameterizedTypeCacheKey, SoftReference<ClassNode>> PARAMETERIZED_TYPE_CACHE = new ConcurrentSoftCache<>(64);
 
     /**
-     * Clear the parameterized type cache
+     * 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() {
@@ -825,7 +797,7 @@ 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) {
+    public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMap(final ClassNode declaringClass, final ClassNode actualReceiver) {
         return doMakeDeclaringAndActualGenericsTypeMap(declaringClass, actualReceiver, false).getV1();
     }
 
@@ -837,38 +809,33 @@ 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 3.0.0
      */
-    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);
+    public static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMapOfExactType(final ClassNode declaringClass, final ClassNode actualReceiver) {
+        return makeDeclaringAndActualGenericsTypeMapOfExactType(declaringClass, actualReceiver, new HashSet<>());
     }
 
-    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.getV2();
-        Map<GenericsType, GenericsType> result = resultAndParameterizedTypeTuple.getV1();
+    private static Map<GenericsType, GenericsType> makeDeclaringAndActualGenericsTypeMapOfExactType(final ClassNode declaringClass, final ClassNode actualReceiver, final Set<ClassNode> parameterizedTypes) {
+        Tuple2<Map<GenericsType, GenericsType>, ClassNode> tuple = doMakeDeclaringAndActualGenericsTypeMap(declaringClass, actualReceiver, true);
+        Map<GenericsType, GenericsType> result = tuple.getV1();
+        ClassNode parameterizedType = tuple.getV2();
 
-        if (hasPlaceHolders(parameterizedType) && !parameterizedTypeList.contains(parameterizedType)) {
-            parameterizedTypeList.add(parameterizedType);
-            result.putAll(makeDeclaringAndActualGenericsTypeMapOfExactType(parameterizedType, actualReceiver, parameterizedTypeList));
+        if (parameterizedTypes.add(parameterizedType) && hasPlaceHolders(parameterizedType)) {
+            result.putAll(makeDeclaringAndActualGenericsTypeMapOfExactType(parameterizedType, actualReceiver, parameterizedTypes));
+            result = connectGenericsTypes(result);
         }
 
-        return connectGenericsTypes(result);
+        return result;
     }
 
-    private static Tuple2<Map<GenericsType, GenericsType>, ClassNode> doMakeDeclaringAndActualGenericsTypeMap(ClassNode declaringClass, ClassNode actualReceiver, boolean tryToFindExactType) {
+    private static Tuple2<Map<GenericsType, GenericsType>, ClassNode> doMakeDeclaringAndActualGenericsTypeMap(final ClassNode declaringClass, final ClassNode actualReceiver, final boolean tryToFindExactType) {
         ClassNode parameterizedType = findParameterizedTypeFromCache(declaringClass, actualReceiver, tryToFindExactType);
-
-        if (null == parameterizedType) {
+        if (parameterizedType == null) {
             return tuple(Collections.emptyMap(), parameterizedType);
         }
 
         Map<GenericsType, GenericsType> result = new LinkedHashMap<>();
-
         result.putAll(makePlaceholderAndParameterizedTypeMap(declaringClass));
         result.putAll(makePlaceholderAndParameterizedTypeMap(parameterizedType));
 
@@ -877,7 +844,7 @@ public class GenericsUtils {
         return tuple(result, parameterizedType);
     }
 
-    private static Map<GenericsType, GenericsType> makePlaceholderAndParameterizedTypeMap(ClassNode declaringClass) {
+    private static Map<GenericsType, GenericsType> makePlaceholderAndParameterizedTypeMap(final ClassNode declaringClass) {
         if (null == declaringClass) {
             return Collections.emptyMap();
         }
@@ -897,7 +864,7 @@ public class GenericsUtils {
         return result;
     }
 
-    private static Map<GenericsType, GenericsType> connectGenericsTypes(Map<GenericsType, GenericsType> genericsTypeMap) {
+    private static Map<GenericsType, GenericsType> connectGenericsTypes(final Map<GenericsType, GenericsType> genericsTypeMap) {
         Map<GenericsType, GenericsType> result = new LinkedHashMap<>();
 
         outer:
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index 3e27826..2bab61d 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -807,6 +807,23 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    // GROOVY-9945
+    void testShouldUseMethodGenericType9() {
+        assertScript '''
+            interface I<T> {
+            }
+            class A<T> implements I<String> {
+                def m(T t) { 'works' }
+            }
+            class B<T> extends A<T> {
+            }
+
+            def bee = new B<Float>()
+            def obj = bee.m(3.14f)
+            assert obj == 'works'
+        '''
+    }
+
     // GROOVY-5516
     void testAddAllWithCollectionShouldBeAllowed() {
         assertScript '''import org.codehaus.groovy.transform.stc.ExtensionMethodNode
diff --git a/src/test/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy b/src/test/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy
index 598b5ce..1b30f71 100644
--- a/src/test/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy
+++ b/src/test/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy
@@ -19,307 +19,260 @@
 
 package org.codehaus.groovy.ast.tools
 
-import groovy.test.GroovyTestCase
 import org.codehaus.groovy.ast.ClassHelper
 import org.codehaus.groovy.ast.ClassNode
 import org.codehaus.groovy.ast.GenericsType
-import org.codehaus.groovy.control.CompilationUnit
-import org.codehaus.groovy.control.Phases
+import org.codehaus.groovy.control.CompilePhase
+import org.junit.Test
 
-import java.util.function.BiFunction
+final class GenericsUtilsTest {
 
-import static groovy.lang.Tuple.tuple
+    private static List<ClassNode> compile(String code) {
+        def compiler = new org.codehaus.groovy.ast.builder.AstStringCompiler()
+        compiler.compile(code, CompilePhase.SEMANTIC_ANALYSIS, false).tail()
+    }
+
+    private static ClassNode findClassNode(String name, List<ClassNode> list) {
+        list.find { it.name == name }
+    }
 
-class GenericsUtilsTest extends GroovyTestCase {
+    //--------------------------------------------------------------------------
+
+    @Test
     void testFindParameterizedType1() {
-        def code = '''
-        class Base<T, S> {}
-        class Derived extends Base<String, List> {}
+        def classNodeList = compile '''
+            class Base<T, S> {}
+            class Derived extends Base<String, List> {}
         '''
-        def ast = new CompilationUnit().tap {
-            addSource 'hello.groovy', code
-            compile Phases.SEMANTIC_ANALYSIS
-        }.ast
-
-        def classNodeList = ast.getModules()[0].getClasses()
-        ClassNode genericsClass = findClassNode('Base', classNodeList)
-        ClassNode actualReceiver = findClassNode('Derived', classNodeList)
-
-        ClassNode parameterizedClass = GenericsUtils.findParameterizedType(genericsClass, actualReceiver, false)
-        assert parameterizedClass.isUsingGenerics()
-        assert 'Base' == parameterizedClass.name
-        GenericsType[] genericsTypes = parameterizedClass.getGenericsTypes()
-        assert 2 == genericsTypes.length
-        assert 'java.lang.String' == genericsTypes[0].type.name
-        assert 'java.util.List' == genericsTypes[1].type.name
-        assert genericsClass.is(parameterizedClass.redirect())
+        ClassNode target = findClassNode('Base', classNodeList)
+        ClassNode source = findClassNode('Derived', classNodeList)
+        ClassNode result = GenericsUtils.findParameterizedType(target, source)
+
+        assert 'Base' == result.name
+        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()
     }
 
+    @Test
     void testFindParameterizedType2() {
-        def code = '''
-        class Base<T, S> {}
-        class Derived2 extends Base<String, List> {}
-        class Derived extends Derived2 {}
+        def classNodeList = compile '''
+            class Base<T, S> {}
+            class Derived2 extends Base<String, List> {}
+            class Derived extends Derived2 {}
         '''
-        def ast = new CompilationUnit().tap {
-            addSource 'hello.groovy', code
-            compile Phases.SEMANTIC_ANALYSIS
-        }.ast
-
-        def classNodeList = ast.getModules()[0].getClasses()
-        ClassNode genericsClass = findClassNode('Base', classNodeList)
-        ClassNode actualReceiver = findClassNode('Derived', classNodeList)
-
-        ClassNode parameterizedClass = GenericsUtils.findParameterizedType(genericsClass, actualReceiver, false)
-        assert parameterizedClass.isUsingGenerics()
-        assert 'Base' == parameterizedClass.name
-        GenericsType[] genericsTypes = parameterizedClass.getGenericsTypes()
-        assert 2 == genericsTypes.length
-        assert 'java.lang.String' == genericsTypes[0].type.name
-        assert 'java.util.List' == genericsTypes[1].type.name
-        assert genericsClass.is(parameterizedClass.redirect())
+        ClassNode target = findClassNode('Base', classNodeList)
+        ClassNode source = findClassNode('Derived', classNodeList)
+        ClassNode result = GenericsUtils.findParameterizedType(target, source)
+
+        assert 'Base' == result.name
+        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()
     }
 
+    @Test
     void testFindParameterizedType3() {
-        def code = '''
-        class Base0 {}
-        class Base<T, S> extends Base0 {}
-        class Derived2 extends Base<String, List> {}
-        class Derived extends Derived2 {}
+        def classNodeList = compile '''
+            class Base0 {}
+            class Base<T, S> extends Base0 {}
+            class Derived2 extends Base<String, List> {}
+            class Derived extends Derived2 {}
         '''
-        def ast = new CompilationUnit().tap {
-            addSource 'hello.groovy', code
-            compile Phases.SEMANTIC_ANALYSIS
-        }.ast
-
-        def classNodeList = ast.getModules()[0].getClasses()
-        ClassNode genericsClass = findClassNode('Base', classNodeList)
-        ClassNode actualReceiver = findClassNode('Derived', classNodeList)
-
-        ClassNode parameterizedClass = GenericsUtils.findParameterizedType(genericsClass, actualReceiver, false)
-        assert parameterizedClass.isUsingGenerics()
-        assert 'Base' == parameterizedClass.name
-        GenericsType[] genericsTypes = parameterizedClass.getGenericsTypes()
-        assert 2 == genericsTypes.length
-        assert 'java.lang.String' == genericsTypes[0].type.name
-        assert 'java.util.List' == genericsTypes[1].type.name
-        assert genericsClass.is(parameterizedClass.redirect())
+        ClassNode target = findClassNode('Base', classNodeList)
+        ClassNode source = findClassNode('Derived', classNodeList)
+        ClassNode result = GenericsUtils.findParameterizedType(target, source)
+
+        assert 'Base' == result.name
+        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()
     }
 
+    @Test
     void testFindParameterizedType4() {
-        def code = '''
-        interface Base<T, S> {}
-        class Derived2 implements Base<String, List> {}
-        class Derived extends Derived2 {}
+        def classNodeList = compile '''
+            interface Base<T, S> {}
+            class Derived2 implements Base<String, List> {}
+            class Derived extends Derived2 {}
         '''
-        def ast = new CompilationUnit().tap {
-            addSource 'hello.groovy', code
-            compile Phases.SEMANTIC_ANALYSIS
-        }.ast
-
-        def classNodeList = ast.getModules()[0].getClasses()
-        ClassNode genericsClass = findClassNode('Base', classNodeList)
-        ClassNode actualReceiver = findClassNode('Derived', classNodeList)
-
-        ClassNode parameterizedClass = GenericsUtils.findParameterizedType(genericsClass, actualReceiver, false)
-        assert parameterizedClass.isUsingGenerics()
-        assert 'Base' == parameterizedClass.name
-        GenericsType[] genericsTypes = parameterizedClass.getGenericsTypes()
-        assert 2 == genericsTypes.length
-        assert 'java.lang.String' == genericsTypes[0].type.name
-        assert 'java.util.List' == genericsTypes[1].type.name
-        assert genericsClass.is(parameterizedClass.redirect())
+        ClassNode target = findClassNode('Base', classNodeList)
+        ClassNode source = findClassNode('Derived', classNodeList)
+        ClassNode result = GenericsUtils.findParameterizedType(target, source)
+
+        assert 'Base' == result.name
+        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()
     }
 
+    @Test
     void testFindParameterizedType5() {
-        def code = '''
-        interface Base<T, S> {}
-        interface Base2 extends Base<String, List> {}
-        class Derived2 implements Base2 {}
-        class Derived extends Derived2 {}
+        def classNodeList = compile '''
+            interface Base<T, S> {}
+            interface Base2 extends Base<String, List> {}
+            class Derived2 implements Base2 {}
+            class Derived extends Derived2 {}
         '''
-        def ast = new CompilationUnit().tap {
-            addSource 'hello.groovy', code
-            compile Phases.SEMANTIC_ANALYSIS
-        }.ast
-
-        def classNodeList = ast.getModules()[0].getClasses()
-        ClassNode genericsClass = findClassNode('Base', classNodeList)
-        ClassNode actualReceiver = findClassNode('Derived', classNodeList)
-
-        ClassNode parameterizedClass = GenericsUtils.findParameterizedType(genericsClass, actualReceiver, false)
-        assert parameterizedClass.isUsingGenerics()
-        assert 'Base' == parameterizedClass.name
-        GenericsType[] genericsTypes = parameterizedClass.getGenericsTypes()
-        assert 2 == genericsTypes.length
-        assert 'java.lang.String' == genericsTypes[0].type.name
-        assert 'java.util.List' == genericsTypes[1].type.name
-        assert genericsClass.is(parameterizedClass.redirect())
+        ClassNode target = findClassNode('Base', classNodeList)
+        ClassNode source = findClassNode('Derived', classNodeList)
+        ClassNode result = GenericsUtils.findParameterizedType(target, source)
+
+        assert 'Base' == result.name
+        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()
     }
 
+    @Test
     void testFindParameterizedType6() {
-        def code = '''
-        interface Base<T, S> {}
-        interface Base2 extends Base<String, List> {}
-        class Derived2 implements Base2 {}
-        class Derived3 extends Derived2 {}
-        class Derived extends Derived3 {}
+        def classNodeList = compile '''
+            interface Base<T, S> {}
+            interface Base2 extends Base<String, List> {}
+            class Derived2 implements Base2 {}
+            class Derived3 extends Derived2 {}
+            class Derived extends Derived3 {}
         '''
-        def ast = new CompilationUnit().tap {
-            addSource 'hello.groovy', code
-            compile Phases.SEMANTIC_ANALYSIS
-        }.ast
-
-        def classNodeList = ast.getModules()[0].getClasses()
-        ClassNode genericsClass = findClassNode('Base', classNodeList)
-        ClassNode actualReceiver = findClassNode('Derived', classNodeList)
-
-        ClassNode parameterizedClass = GenericsUtils.findParameterizedType(genericsClass, actualReceiver, false)
-        assert parameterizedClass.isUsingGenerics()
-        assert 'Base' == parameterizedClass.name
-        GenericsType[] genericsTypes = parameterizedClass.getGenericsTypes()
-        assert 2 == genericsTypes.length
-        assert 'java.lang.String' == genericsTypes[0].type.name
-        assert 'java.util.List' == genericsTypes[1].type.name
-        assert genericsClass.is(parameterizedClass.redirect())
+        ClassNode target = findClassNode('Base', classNodeList)
+        ClassNode source = findClassNode('Derived', classNodeList)
+        ClassNode result = GenericsUtils.findParameterizedType(target, source)
+
+        assert 'Base' == result.name
+        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()
     }
 
+    @Test
     void testFindParameterizedType7() {
-        def code = '''
-        interface Base0 {}
-        interface Base<T, S> extends Base0 {}
-        interface Base2 extends Base<String, List> {}
-        class Derived2 implements Base2 {}
-        class Derived3 extends Derived2 {}
-        class Derived extends Derived3 {}
+        def classNodeList = compile '''
+            interface Base0 {}
+            interface Base<T, S> extends Base0 {}
+            interface Base2 extends Base<String, List> {}
+            class Derived2 implements Base2 {}
+            class Derived3 extends Derived2 {}
+            class Derived extends Derived3 {}
         '''
-        def ast = new CompilationUnit().tap {
-            addSource 'hello.groovy', code
-            compile Phases.SEMANTIC_ANALYSIS
-        }.ast
-
-        def classNodeList = ast.getModules()[0].getClasses()
-        ClassNode genericsClass = findClassNode('Base', classNodeList)
-        ClassNode actualReceiver = findClassNode('Derived', classNodeList)
-
-        ClassNode parameterizedClass = GenericsUtils.findParameterizedType(genericsClass, actualReceiver, false)
-        assert parameterizedClass.isUsingGenerics()
-        assert 'Base' == parameterizedClass.name
-        GenericsType[] genericsTypes = parameterizedClass.getGenericsTypes()
-        assert 2 == genericsTypes.length
-        assert 'java.lang.String' == genericsTypes[0].type.name
-        assert 'java.util.List' == genericsTypes[1].type.name
-        assert genericsClass.is(parameterizedClass.redirect())
+        ClassNode target = findClassNode('Base', classNodeList)
+        ClassNode source = findClassNode('Derived', classNodeList)
+        ClassNode result = GenericsUtils.findParameterizedType(target, source)
+
+        assert 'Base' == result.name
+        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()
     }
 
-    void testMakeDeclaringAndActualGenericsTypeMapOfExactType() {
-        def code = '''
-        import java.util.function.*
-        interface Derived extends BinaryOperator<Integer> {}
+    @Test // GROOVY-9945
+    void testFindParameterizedType8() {
+        def classNodeList = compile '''
+            interface I<T> {}
+            class A<T> implements I<String> {}
+            class B<T> extends A<T> {}
+            class C extends B<Number> {}
         '''
-        def ast = new CompilationUnit().tap {
-            addSource 'hello.groovy', code
-            compile Phases.SEMANTIC_ANALYSIS
-        }.ast
+        ClassNode target = findClassNode('A', classNodeList)
+        ClassNode source = findClassNode('C', classNodeList)
+        ClassNode result = GenericsUtils.findParameterizedType(target, source)
 
-        def classNodeList = ast.getModules()[0].getClasses()
-        ClassNode genericsClass = ClassHelper.makeWithoutCaching(BiFunction.class)
-        ClassNode actualReceiver = findClassNode('Derived', classNodeList)
+        assert result.toString(false) == 'A <java.lang.Number>'
+    }
+
+    @Test
+    void testMakeDeclaringAndActualGenericsTypeMapOfExactType() {
+        def classNodeList = compile '''
+            import java.util.function.*
+            interface Derived extends BinaryOperator<Integer> {}
+        '''
+        ClassNode target = ClassHelper.makeWithoutCaching(java.util.function.BiFunction.class)
+        ClassNode source = findClassNode('Derived', classNodeList)
 
-        Map<GenericsType, GenericsType> m = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(genericsClass, actualReceiver)
+        Map<GenericsType, GenericsType> m = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(target, source)
 
-        assert m.entrySet().every { it.value.type.getTypeClass() == Integer }
-        assert m.entrySet().grep { it.key.name == 'T' }[0].value.type.getTypeClass() == Integer
-        assert m.entrySet().grep { it.key.name == 'U' }[0].value.type.getTypeClass() == Integer
-        assert m.entrySet().grep { it.key.name == 'R' }[0].value.type.getTypeClass() == Integer
+        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.Integer'
+        assert m.entrySet().find { it.key.name == 'R' }.value.type.name == 'java.lang.Integer'
     }
 
+    @Test
     void testMakeDeclaringAndActualGenericsTypeMapOfExactType2() {
-        def code = '''
-        interface IBase<T, U> {}
-        class Base<U> implements IBase<String, U> {}
-        class Derived extends Base<Integer> {}
+        def classNodeList = compile '''
+            interface IBase<T, U> {}
+            class Base<U> implements IBase<String, U> {}
+            class Derived extends Base<Integer> {}
         '''
-        def ast = new CompilationUnit().tap {
-            addSource 'hello.groovy', code
-            compile Phases.SEMANTIC_ANALYSIS
-        }.ast
+        ClassNode target = findClassNode('IBase', classNodeList)
+        ClassNode source = findClassNode('Derived', classNodeList)
 
-        def classNodeList = ast.getModules()[0].getClasses()
-        ClassNode genericsClass = findClassNode('IBase', classNodeList)
-        ClassNode actualReceiver = findClassNode('Derived', classNodeList)
+        Map<GenericsType, GenericsType> m = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(target, source)
 
-        Map<GenericsType, GenericsType> m = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(genericsClass, actualReceiver)
-
-        assert m.entrySet().grep { it.key.name == 'T' }[0].value.type.getTypeClass() == String
-        assert m.entrySet().grep { it.key.name == 'U' }[0].value.type.getTypeClass() == Integer
+        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
     void testMakeDeclaringAndActualGenericsTypeMapOfExactType3() {
-        def code = '''
-        interface IBase<T, U, R> {}
-        class Base<X, Y> implements IBase<Y, String, X> {}
-        class Derived extends Base<Boolean, Integer> {}
+        def classNodeList = compile '''
+            interface IBase<T, U, R> {}
+            class Base<X,Y> implements IBase<Y,String,X> {}
+            class Derived extends Base<Boolean, Integer> {}
         '''
-        def ast = new CompilationUnit().tap {
-            addSource 'hello.groovy', code
-            compile Phases.SEMANTIC_ANALYSIS
-        }.ast
-
-        def classNodeList = ast.getModules()[0].getClasses()
-        ClassNode genericsClass = findClassNode('IBase', classNodeList)
-        ClassNode actualReceiver = findClassNode('Derived', classNodeList)
-
-        Map<GenericsType, GenericsType> m = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(genericsClass, actualReceiver)
-        println m
-
-        assert m.entrySet().grep { it.key.name == 'X' }[0].value.type.getTypeClass() == Boolean
-        assert m.entrySet().grep { it.key.name == 'Y' }[0].value.type.getTypeClass() == Integer
-        assert m.entrySet().grep { it.key.name == 'T' }[0].value.type.getTypeClass() == Integer
-        assert m.entrySet().grep { it.key.name == 'U' }[0].value.type.getTypeClass() == String
-        assert m.entrySet().grep { it.key.name == 'R' }[0].value.type.getTypeClass() == Boolean
-    }
+        ClassNode target = findClassNode('IBase', classNodeList)
+        ClassNode source = findClassNode('Derived', classNodeList)
+
+        Map<GenericsType, GenericsType> m = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(target, source)
 
-    static ClassNode findClassNode(String name, List<ClassNode> classNodeList) {
-        return classNodeList.find { it.name == name }
+        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() {
-        def code = '''
-        import java.util.function.*
-        interface T extends Function<String, Integer> {}
+        def classNodeList = compile '''
+            import java.util.function.*
+            interface T extends Function<String, Integer> {}
         '''
-        def ast = new CompilationUnit().tap {
-            addSource 'hello.groovy', code
-            compile Phases.SEMANTIC_ANALYSIS
-        }.ast
-
-        def classNodeList = ast.getModules()[0].getClasses()
-        ClassNode parameterizedClassNode = findClassNode('T', classNodeList).getAllInterfaces().find { it.name.equals('java.util.function.Function') }
-
-        Tuple2<ClassNode[], ClassNode> typeInfo = GenericsUtils.parameterizeSAM(parameterizedClassNode)
-        assert 1 == typeInfo.getV1().length
-        assert ClassHelper.STRING_TYPE == typeInfo.getV1()[0]
-        assert ClassHelper.Integer_TYPE == typeInfo.getV2()
+        ClassNode samType = findClassNode('T', classNodeList).interfaces.find { it.name == 'java.util.function.Function' }
+
+        def typeInfo = GenericsUtils.parameterizeSAM(samType)
+
+        assert 1 == typeInfo[0].length
+        assert ClassHelper.STRING_TYPE == typeInfo[0][0]
+
+        assert ClassHelper.Integer_TYPE == typeInfo[1]
     }
 
+    @Test
     void testParameterizeSAM2() {
-        def code = '''
-        import java.util.function.*
-        interface T extends BinaryOperator<Integer> {}
+        def classNodeList = compile '''
+            import java.util.function.*
+            interface T extends BinaryOperator<Integer> {}
         '''
-        def ast = new CompilationUnit().tap {
-            addSource 'hello.groovy', code
-            compile Phases.SEMANTIC_ANALYSIS
-        }.ast
-
-        def classNodeList = ast.getModules()[0].getClasses()
-        ClassNode parameterizedClassNode = findClassNode('T', classNodeList).getAllInterfaces().find { it.name.equals('java.util.function.BinaryOperator') }
-
-        Tuple2<ClassNode[], ClassNode> typeInfo = GenericsUtils.parameterizeSAM(parameterizedClassNode)
-        assert 2 == typeInfo.getV1().length
-        assert ClassHelper.Integer_TYPE == typeInfo.getV1()[0]
-        assert ClassHelper.Integer_TYPE == typeInfo.getV1()[1]
-        assert ClassHelper.Integer_TYPE == typeInfo.getV2()
+        ClassNode samType = findClassNode('T', classNodeList).interfaces.find { it.name == 'java.util.function.BinaryOperator' }
+
+        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 ClassHelper.Integer_TYPE == typeInfo[1]
     }
 }