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 17:51:07 UTC

[groovy] branch GROOVY_2_5_X updated: GROOVY-10813: `makeDeclaringAndActualGenericsTypeMapOfExactType` erasure

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 46013ada78 GROOVY-10813: `makeDeclaringAndActualGenericsTypeMapOfExactType` erasure
46013ada78 is described below

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

    GROOVY-10813: `makeDeclaringAndActualGenericsTypeMapOfExactType` erasure
    
    2_5_X backport
---
 .../codehaus/groovy/ast/tools/GenericsUtils.java   |  67 ++-
 src/test/groovy/bugs/Groovy7204.groovy             | 559 +++++++++++++++++++++
 .../groovy/ast/tools/GenericsUtilsTest.groovy      | 369 ++++++++------
 3 files changed, 814 insertions(+), 181 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 25a4205a19..10167026ce 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/GenericsUtils.java
@@ -759,24 +759,28 @@ 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);
+                }
             }
         }
 
@@ -827,7 +831,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);
     }
 
     /**
@@ -844,25 +848,42 @@ public class GenericsUtils {
      * @since 2.5.9
      */
     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.<GenericsType, GenericsType>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<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]);
+            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/bugs/Groovy7204.groovy b/src/test/groovy/bugs/Groovy7204.groovy
new file mode 100644
index 0000000000..50deba1584
--- /dev/null
+++ b/src/test/groovy/bugs/Groovy7204.groovy
@@ -0,0 +1,559 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.bugs
+
+import groovy.transform.NotYetImplemented
+import org.junit.Test
+
+import static groovy.test.GroovyAssert.assertScript
+
+final class Groovy7204 {
+
+    @Test
+    void testTypeChecked1() {
+        assertScript '''import groovy.transform.*
+            @TypeChecked
+            public class MyClass {
+                static MyRepository factory() {
+                    return new MyRepositoryImpl()
+                }
+
+                static void main(String[] args) {
+                    MyRepository r = factory()
+                    r.delete('foo')
+                }
+            }
+
+            @TypeChecked
+            interface CrudRepository<T, S extends Serializable> {
+                void delete(T arg)
+                void delete(S arg)
+            }
+
+            @TypeChecked
+            interface MyRepository extends CrudRepository<String, Long> {
+            }
+
+            @TypeChecked
+            class MyRepositoryImpl implements MyRepository {
+                @Override
+                public void delete(String arg) {
+                    assert true
+                }
+
+                @Override
+                public void delete(Long arg) {
+                    assert false: 'wrong method invoked'
+                }
+            }
+        '''
+    }
+
+    @Test
+    void testTypeChecked2() {
+        assertScript '''import groovy.transform.*
+            @TypeChecked
+            public class MyClass {
+                static MyRepository factory() {
+                    return new MyRepositoryImpl()
+                }
+
+                static void main(String[] args) {
+                    MyRepository r = factory()
+                    r.delete('foo')
+                }
+            }
+
+            @TypeChecked
+            abstract class CrudRepository<T, S extends Serializable> {
+                abstract void delete(T arg)
+                abstract void delete(S arg)
+            }
+
+            @TypeChecked
+            abstract class MyRepository extends CrudRepository<String, Long> {
+            }
+
+            @TypeChecked
+            class MyRepositoryImpl extends MyRepository {
+                @Override
+                public void delete(String arg) {
+                    assert true
+                }
+
+                @Override
+                public void delete(Long arg) {
+                    assert false: 'wrong method invoked'
+                }
+            }
+        '''
+    }
+
+    @Test
+    void testTypeChecked3() {
+        assertScript '''import groovy.transform.*
+            @TypeChecked
+            public class MyClass {
+                static MyRepository factory() {
+                    return new MyRepositoryImpl()
+                }
+
+                static void main(String[] args) {
+                    MyRepository r = factory()
+                    r.delete('foo')
+                }
+            }
+
+            @TypeChecked
+            interface CrudRepository<T, S extends Serializable> {
+                void delete(T arg)
+                void delete(S arg)
+            }
+
+            @TypeChecked
+            interface MyRepository2 extends CrudRepository<String, Long> {
+            }
+
+            @TypeChecked
+            interface MyRepository extends MyRepository2 {
+            }
+
+            @TypeChecked
+            class MyRepositoryImpl implements MyRepository {
+                @Override
+                public void delete(String arg) {
+                    assert true
+                }
+
+                @Override
+                public void delete(Long arg) {
+                    assert false: 'wrong method invoked'
+                }
+            }
+        '''
+    }
+
+    @Test
+    void testTypeChecked4() {
+        assertScript '''import groovy.transform.*
+            @TypeChecked
+            public class MyClass {
+                static MyRepository factory() {
+                    return new MyRepositoryImpl()
+                }
+
+                static void main(String[] args) {
+                    MyRepository r = factory()
+                    r.delete('foo')
+                }
+            }
+
+            @TypeChecked
+            abstract class CrudRepository<T, S extends Serializable> {
+                abstract void delete(T arg)
+                abstract void delete(S arg)
+            }
+
+            @TypeChecked
+            abstract class MyRepository2 extends CrudRepository<String, Long> {
+            }
+
+            @TypeChecked
+            abstract class MyRepository extends MyRepository2 {
+            }
+
+            @TypeChecked
+            class MyRepositoryImpl extends MyRepository {
+                @Override
+                public void delete(String arg) {
+                    assert true
+                }
+
+                @Override
+                public void delete(Long arg) {
+                    assert false: 'wrong method invoked'
+                }
+            }
+        '''
+    }
+
+    @Test
+    void testTypeChecked5() {
+        assertScript '''import groovy.transform.*
+            @TypeChecked
+            public class MyClass {
+                static MyRepository factory() {
+                    return new MyRepositoryImpl()
+                }
+
+                static void main(String[] args) {
+                    MyRepository r = factory()
+                    r.delete('foo')
+                }
+            }
+
+            @TypeChecked
+            interface CrudRepository<T, S extends Serializable> {
+                void delete(T arg)
+                void delete(S arg)
+            }
+
+            @TypeChecked
+            abstract class MyRepository2 implements CrudRepository<String, Long> {
+            }
+
+            @TypeChecked
+            abstract class MyRepository extends MyRepository2 {
+            }
+
+            @TypeChecked
+            class MyRepositoryImpl extends MyRepository {
+                @Override
+                public void delete(String arg) {
+                    assert true
+                }
+
+                @Override
+                public void delete(Long arg) {
+                    assert false: 'wrong method invoked'
+                }
+            }
+        '''
+    }
+
+    @NotYetImplemented @Test
+    void testTypeChecked6() {
+        assertScript '''import groovy.transform.*
+            class Repository<T, S extends Serializable> {
+                void delete(T arg) { assert true }
+                void delete(S arg) { assert false: 'wrong method invoked' }
+            }
+
+            @TypeChecked
+            def test() {
+                Repository<String, Long> r = new Repository<String, Long>()
+                r.delete('foo')
+            }
+
+            test()
+        '''
+    }
+
+    //
+
+    @Test
+    void testCompileStatic1() {
+        assertScript '''import groovy.transform.*
+            @CompileStatic
+            public class MyClass {
+                static MyRepository factory() {
+                    return new MyRepositoryImpl()
+                }
+
+                static void main(String[] args) {
+                    MyRepository r = factory()
+                    r.delete('foo')
+                }
+            }
+
+            @CompileStatic
+            interface CrudRepository<T, S extends Serializable> {
+                void delete(T arg)
+                void delete(S arg)
+            }
+
+            @CompileStatic
+            interface MyRepository extends CrudRepository<String, Long> {
+            }
+
+            @CompileStatic
+            class MyRepositoryImpl implements MyRepository {
+                @Override
+                public void delete(String arg) {
+                    assert true
+                }
+
+                @Override
+                public void delete(Long arg) {
+                    assert false: 'wrong method invoked'
+                }
+            }
+        '''
+    }
+
+    @Test
+    void testCompileStatic2() {
+        assertScript '''import groovy.transform.*
+            @CompileStatic
+            public class MyClass {
+                static MyRepository factory() {
+                    return new MyRepositoryImpl()
+                }
+
+                static void main(String[] args) {
+                    MyRepository r = factory()
+                    r.delete('foo')
+                }
+            }
+
+            @CompileStatic
+            abstract class CrudRepository<T, S extends Serializable> {
+                abstract void delete(T arg)
+                abstract void delete(S arg)
+            }
+
+            @CompileStatic
+            abstract class MyRepository extends CrudRepository<String, Long> {
+            }
+
+            @CompileStatic
+            class MyRepositoryImpl extends MyRepository {
+                @Override
+                public void delete(String arg) {
+                    assert true
+                }
+
+                @Override
+                public void delete(Long arg) {
+                    assert false: 'wrong method invoked'
+                }
+            }
+        '''
+    }
+
+    @Test
+    void testCompileStatic3() {
+        assertScript '''import groovy.transform.*
+            @CompileStatic
+            public class MyClass {
+                static MyRepository factory() {
+                    return new MyRepositoryImpl()
+                }
+
+                static void main(String[] args) {
+                    MyRepository r = factory()
+                    r.delete('foo')
+                }
+            }
+
+            @CompileStatic
+            interface CrudRepository<T, S extends Serializable> {
+                void delete(T arg)
+                void delete(S arg)
+            }
+
+            @CompileStatic
+            interface MyRepository2 extends CrudRepository<String, Long> {
+            }
+
+            @CompileStatic
+            interface MyRepository extends MyRepository2 {
+            }
+
+            @CompileStatic
+            class MyRepositoryImpl implements MyRepository {
+                @Override
+                public void delete(String arg) {
+                    assert true
+                }
+
+                @Override
+                public void delete(Long arg) {
+                    assert false: 'wrong method invoked'
+                }
+            }
+        '''
+    }
+
+    @Test
+    void testCompileStatic4() {
+        assertScript '''import groovy.transform.*
+            @CompileStatic
+            public class MyClass {
+                static MyRepository factory() {
+                    return new MyRepositoryImpl()
+                }
+
+                static void main(String[] args) {
+                    MyRepository r = factory()
+                    r.delete('foo')
+                }
+            }
+
+            @CompileStatic
+            abstract class CrudRepository<T, S extends Serializable> {
+                abstract void delete(T arg)
+                abstract void delete(S arg)
+            }
+
+            @CompileStatic
+            abstract class MyRepository2 extends CrudRepository<String, Long> {
+            }
+
+            @CompileStatic
+            abstract class MyRepository extends MyRepository2 {
+            }
+
+            @CompileStatic
+            class MyRepositoryImpl extends MyRepository {
+                @Override
+                public void delete(String arg) {
+                    assert true
+                }
+
+                @Override
+                public void delete(Long arg) {
+                    assert false: 'wrong method invoked'
+                }
+            }
+        '''
+    }
+
+    @Test
+    void testCompileStatic5() {
+        assertScript '''import groovy.transform.*
+            @CompileStatic
+            public class MyClass {
+                static MyRepository factory() {
+                    return new MyRepositoryImpl()
+                }
+
+                static void main(String[] args) {
+                    MyRepository r = factory()
+                    r.delete('foo')
+                }
+            }
+
+            @CompileStatic
+            interface CrudRepository<T, S extends Serializable> {
+                void delete(T arg)
+                void delete(S arg)
+            }
+
+            @CompileStatic
+            abstract class MyRepository2 implements CrudRepository<String, Long> {
+            }
+
+            @CompileStatic
+            abstract class MyRepository extends MyRepository2 {
+            }
+
+            @CompileStatic
+            class MyRepositoryImpl extends MyRepository {
+                @Override
+                public void delete(String arg) {
+                    assert true
+                }
+
+                @Override
+                public void delete(Long arg) {
+                    assert false: 'wrong method invoked'
+                }
+            }
+        '''
+    }
+
+    @Test
+    void testCompileStatic6() {
+        assertScript '''import groovy.transform.*
+            class Repository<T, S extends Serializable> {
+                void delete(T arg) { assert true }
+                void delete(S arg) { assert false: 'wrong method invoked' }
+            }
+
+            @CompileStatic
+            def test() {
+                Repository<String, Long> r = new Repository<String, Long>()
+                r.delete('foo')
+            }
+
+            test()
+        '''
+    }
+
+    @Test // GROOVY-8059
+    void testCompileStatic7() {
+        assertScript '''import groovy.transform.*
+            abstract class A<K extends Serializable, V> {
+                void delete(K key) {}
+                void delete(V val) {}
+            }
+            class C extends A<String, Integer> {
+            }
+
+            @CompileStatic
+            class Test {
+                Test() {
+                    def obj = new C()
+                    obj.delete(Integer.valueOf(1))
+                }
+            }
+
+            new Test()
+        '''
+    }
+
+    @Test
+    void testCompileStatic8() {
+        assertScript '''import groovy.transform.*
+            class Trie<T> {
+            }
+
+            @CompileStatic
+            class Base<T> {
+                protected List<Trie<T>> list = []
+                Base() {
+                    list.add(new Trie<String>()) // should fail!!
+                }
+            }
+
+            @CompileStatic
+            class Test extends Base<String> {
+                Trie<String> getFirstElement() {
+                    list.get(0)
+                }
+            }
+
+            assert new Test().firstElement instanceof Trie
+        '''
+    }
+
+    @Test
+    void testCompileStatic9() {
+        assertScript '''import groovy.transform.*
+            class Trie<T> {
+            }
+
+            class Base<T> extends ArrayList<Trie<T>> {
+            }
+
+            @CompileStatic
+            class Test extends Base<String> {
+                Test() {
+                    add(new Trie<String>())
+                }
+                Trie<String> getFirstElement() {
+                    get(0)
+                }
+            }
+
+            assert new Test().firstElement instanceof Trie
+        '''
+    }
+}
diff --git a/src/test/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy b/src/test/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy
index cf2fb49a7c..4255f2a9f2 100644
--- a/src/test/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy
+++ b/src/test/org/codehaus/groovy/ast/tools/GenericsUtilsTest.groovy
@@ -21,192 +21,245 @@ package org.codehaus.groovy.ast.tools
 
 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
 
-class GenericsUtilsTest extends GroovyTestCase {
+final class GenericsUtilsTest {
+
+    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 }
+    }
+
+    //--------------------------------------------------------------------------
+
+    @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)
-        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 result.name == 'Base'
+        assert result.isUsingGenerics()
+        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().is(target)
     }
 
+    @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)
-        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 result.name == 'Base'
+        assert result.isUsingGenerics()
+        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().is(target)
     }
 
+    @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)
-        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 result.name == 'Base'
+        assert result.isUsingGenerics()
+        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().is(target)
     }
 
+    @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)
-        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 result.name == 'Base'
+        assert result.isUsingGenerics()
+        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().is(target)
     }
 
+    @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)
-        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 result.name == 'Base'
+        assert result.isUsingGenerics()
+        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().is(target)
     }
 
+    @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)
-        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 result.name == 'Base'
+        assert result.isUsingGenerics()
+        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().is(target)
     }
 
+    @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 {}
+        '''
+        ClassNode target = findClassNode('Base', classNodeList)
+        ClassNode source = findClassNode('Derived', classNodeList)
+        ClassNode result = GenericsUtils.findParameterizedType(target, source)
+
+        assert result.name == 'Base'
+        assert result.isUsingGenerics()
+        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().is(target)
+    }
+
+    @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> {}
+        '''
+        ClassNode target = findClassNode('A', classNodeList)
+        ClassNode source = findClassNode('C', classNodeList)
+        ClassNode result = GenericsUtils.findParameterizedType(target, source)
+
+        assert result.toString(false) == 'A <java.lang.Number>'
+    }
+
+    //
+
+    @Test
+    void testMakeDeclaringAndActualGenericsTypeMapOfExactType1() {
+        def classNodeList = compile '''
+            interface BiFunction<T,U,R> { R apply(T t, U u) }
+            interface BinaryOperator<T> extends BiFunction<T,T,T> {}
+            interface AnExtension extends BinaryOperator<Integer> {}
+        '''
+        ClassNode target = findClassNode('BiFunction', classNodeList)
+        ClassNode source = findClassNode('AnExtension', classNodeList)
+
+        Map<GenericsType, GenericsType> m = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(target, source)
+
+        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 classNodeList = compile '''
+            interface I<T, U> {}
+            class Base<U> implements I<String,U> {}
+            class Derived extends Base<Integer > {}
+        '''
+        ClassNode target = findClassNode('I', classNodeList)
+        ClassNode source = findClassNode('Derived', classNodeList)
+
+        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'
+    }
+
+    @Test
+    void testMakeDeclaringAndActualGenericsTypeMapOfExactType3() {
+        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('Base', classNodeList)
-        ClassNode actualReceiver = findClassNode('Derived', classNodeList)
-
-        ClassNode parameterizedClass = GenericsUtils.findParameterizedType(genericsClass, actualReceiver)
-        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('IBase', classNodeList)
+        ClassNode source = findClassNode('Derived', classNodeList)
+
+        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'
     }
 
-    static ClassNode findClassNode(String name, List<ClassNode> classNodeList) {
-        return classNodeList.find { it.name == name }
+    @Test // GROOVY-10813
+    void testMakeDeclaringAndActualGenericsTypeMapOfExactType4() {
+        def classNodeList = compile '''
+            interface BiFunction<T,U,R> { R apply(T t, U u) }
+            interface BinaryOperator<T> extends BiFunction<T,T,T> {}
+            interface I extends BinaryOperator /* <-- raw type */ {}
+        '''
+        ClassNode target = findClassNode('BiFunction', classNodeList)
+        ClassNode source = findClassNode('I', classNodeList)
+
+        def m = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(target, source)
+
+        assert m.size() == 3
+        assert m.entrySet().find { it.key.name == 'T' }.value.type.name == 'java.lang.Object'
+        assert m.entrySet().find { it.key.name == 'U' }.value.type.name == 'java.lang.Object'
+        assert m.entrySet().find { it.key.name == 'R' }.value.type.name == 'java.lang.Object'
     }
 }