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

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


The following commit(s) were added to refs/heads/GROOVY_3_0_X by this push:
     new 4693a92  GROOVY-10472: @AutoImplement is failing when covariant 
returns are involved (port to 3_0_X)
4693a92 is described below

commit 4693a92cdb0ed116e34afb66fd7715b981e23bcd
Author: Paul King <[email protected]>
AuthorDate: Fri Feb 11 14:41:19 2022 +1000

    GROOVY-10472: @AutoImplement is failing when covariant returns are involved 
(port to 3_0_X)
---
 .../apache/groovy/ast/tools/ClassNodeUtils.java    | 28 ++++---
 .../transform/AutoImplementASTTransformation.java  | 16 ++--
 .../transform/AutoImplementTransformTest.groovy    | 92 ++++++++++++++++++++++
 3 files changed, 119 insertions(+), 17 deletions(-)

diff --git a/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java 
b/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java
index b394909..8bf4a46 100644
--- a/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java
+++ b/src/main/java/org/apache/groovy/ast/tools/ClassNodeUtils.java
@@ -45,6 +45,7 @@ import java.util.Set;
 import static org.apache.groovy.ast.tools.AnnotatedNodeUtils.isGenerated;
 import static org.apache.groovy.ast.tools.AnnotatedNodeUtils.markAsGenerated;
 import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements;
 import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
 
 /**
@@ -83,11 +84,11 @@ public class ClassNodeUtils {
      * @see ClassNode#addMethod(String, int, ClassNode, Parameter[], 
ClassNode[], Statement)
      */
     public static MethodNode addGeneratedMethod(ClassNode cNode, String name,
-                                int modifiers,
-                                ClassNode returnType,
-                                Parameter[] parameters,
-                                ClassNode[] exceptions,
-                                Statement code) {
+                                                int modifiers,
+                                                ClassNode returnType,
+                                                Parameter[] parameters,
+                                                ClassNode[] exceptions,
+                                                Statement code) {
         MethodNode existing = cNode.getDeclaredMethod(name, parameters);
         if (existing != null) return existing;
         MethodNode result = new MethodNode(name, modifiers, returnType, 
parameters, exceptions, code);
@@ -165,7 +166,7 @@ public class ClassNodeUtils {
      * take precedence. Methods from interfaces visited early take precedence
      * over later ones.
      *
-     * @param cNode The ClassNode
+     * @param cNode      The ClassNode
      * @param methodsMap A map of existing methods to alter
      */
     public static void addDeclaredMethodsFromInterfaces(ClassNode cNode, 
Map<String, MethodNode> methodsMap) {
@@ -196,7 +197,7 @@ public class ClassNodeUtils {
      * Adds methods from interfaces and parent interfaces. Existing entries in 
the methods map take precedence.
      * Methods from interfaces visited early take precedence over later ones.
      *
-     * @param cNode The ClassNode
+     * @param cNode      The ClassNode
      * @param methodsMap A map of existing methods to alter
      */
     public static void addDeclaredMethodsFromAllInterfaces(ClassNode cNode, 
Map<String, MethodNode> methodsMap) {
@@ -324,7 +325,7 @@ public class ClassNodeUtils {
      * Detect whether a static property with the given name is within the class
      * or a super class.
      *
-     * @param cNode the ClassNode of interest
+     * @param cNode    the ClassNode of interest
      * @param propName the property name
      * @return the static property if found or else null
      */
@@ -350,7 +351,8 @@ public class ClassNodeUtils {
                 && !Modifier.isStatic(cNode.getModifiers());
     }
 
-    private ClassNodeUtils() { }
+    private ClassNodeUtils() {
+    }
 
     public static boolean hasNoArgConstructor(ClassNode cNode) {
         List<ConstructorNode> constructors = cNode.getDeclaredConstructors();
@@ -389,7 +391,7 @@ public class ClassNodeUtils {
     /**
      * Determine if the given ClassNode values have the same package name.
      *
-     * @param first a ClassNode
+     * @param first  a ClassNode
      * @param second a ClassNode
      * @return true if both given nodes have the same package name
      * @throws NullPointerException if either of the given nodes are null
@@ -422,4 +424,10 @@ public class ClassNodeUtils {
         }
         return null;
     }
+
+    public static boolean isSubtype(ClassNode maybeSuperclassOrInterface, 
ClassNode candidateChild) {
+        return maybeSuperclassOrInterface.isInterface() || 
candidateChild.isInterface()
+                ? isOrImplements(candidateChild, maybeSuperclassOrInterface)
+                : candidateChild.isDerivedFrom(maybeSuperclassOrInterface);
+    }
 }
diff --git 
a/src/main/java/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
 
b/src/main/java/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
index 7a94f1b..d96c620 100644
--- 
a/src/main/java/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
+++ 
b/src/main/java/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
@@ -42,6 +42,7 @@ import java.util.List;
 import java.util.Map;
 
 import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod;
+import static org.apache.groovy.ast.tools.ClassNodeUtils.isSubtype;
 import static org.apache.groovy.ast.tools.MethodNodeUtils.getPropertyName;
 import static 
org.apache.groovy.ast.tools.MethodNodeUtils.methodDescriptorWithoutReturnType;
 import static org.apache.groovy.util.BeanUtils.capitalize;
@@ -163,9 +164,7 @@ public class AutoImplementASTTransformation extends 
AbstractASTTransformation {
                     MethodNode found = 
getDeclaredMethodCorrected(genericsSpec, correctedMethod, correctedClass);
                     if (found != null) {
                         String td = methodDescriptorWithoutReturnType(found);
-                        if (result.containsKey(td) && 
!result.get(td).isAbstract()) {
-                            continue;
-                        }
+                        if (result.containsKey(td) && 
isWeakerCandidate(result.get(td), found)) continue;
                         result.put(td, found);
                     }
                 }
@@ -184,9 +183,7 @@ public class AutoImplementASTTransformation extends 
AbstractASTTransformation {
                         MethodNode found = 
getDeclaredMethodCorrected(updatedGenericsSpec, correctedMethod, 
correctedInterface);
                         if (found != null) {
                             String td = 
methodDescriptorWithoutReturnType(found);
-                            if (result.containsKey(td) && 
!result.get(td).isAbstract()) {
-                                continue;
-                            }
+                            if (result.containsKey(td) && 
isWeakerCandidate(result.get(td), found)) continue;
                             result.put(td, found);
                         }
                     }
@@ -221,10 +218,15 @@ public class AutoImplementASTTransformation extends 
AbstractASTTransformation {
                 }
             }
         }
-
         return result;
     }
 
+    private static boolean isWeakerCandidate(MethodNode existing, MethodNode 
found) {
+        return !(existing.isAbstract() && !found.isAbstract()) &&
+                // GROOVY-10472: prefer covariant method with more concrete 
type
+                isSubtype(found.getReturnType(), existing.getReturnType());
+    }
+
     private static MethodNode getDeclaredMethodCorrected(final Map<String, 
ClassNode> genericsSpec, final MethodNode origMethod, final ClassNode 
correctedClass) {
         for (MethodNode nameMatch : 
correctedClass.getDeclaredMethods(origMethod.getName())) {
             MethodNode correctedMethod = correctToGenericsSpec(genericsSpec, 
nameMatch);
diff --git 
a/src/test/org/codehaus/groovy/transform/AutoImplementTransformTest.groovy 
b/src/test/org/codehaus/groovy/transform/AutoImplementTransformTest.groovy
index ba86e77..05b108f 100644
--- a/src/test/org/codehaus/groovy/transform/AutoImplementTransformTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/AutoImplementTransformTest.groovy
@@ -216,4 +216,96 @@ final class AutoImplementTransformTest {
             assert new Foo().compare('bar', 'baz') == 0
             '''
     }
+
+    @Test // GROOVY-10472
+    void testCovariantReturnTypes() {
+        assertScript '''
+            import groovy.transform.AutoImplement
+
+            interface Super { List findAll() }
+            interface Sub extends Super { Iterable findAll() }
+
+            @AutoImplement
+            class ThisClassFails implements Sub{}
+
+            assert !(new ThisClassFails().findAll())
+        '''
+        assertScript '''
+            import groovy.transform.AutoImplement
+
+            interface Super { ArrayList findAll() }
+            interface Sub extends Super { Iterable findAll() }
+
+            @AutoImplement
+            class ThisClassFails implements Sub{}
+
+            assert !(new ThisClassFails().findAll())
+        '''
+        assertScript '''
+            import groovy.transform.AutoImplement
+
+            interface Super { Iterable findAll() }
+            interface Sub extends Super { List findAll() }
+
+            @AutoImplement
+            class ThisClassFails implements Sub{}
+
+            assert !(new ThisClassFails().findAll())
+        '''
+        assertScript '''
+            import groovy.transform.AutoImplement
+
+            interface Super { Iterable findAll() }
+            interface Sub extends Super { ArrayList findAll() }
+
+            @AutoImplement
+            class ThisClassFails implements Sub{}
+
+            assert !(new ThisClassFails().findAll())
+        '''
+        assertScript '''
+            import groovy.transform.AutoImplement
+
+            interface Super { AbstractList findAll() }
+            interface Sub extends Super { List findAll() }
+
+            @AutoImplement
+            class ThisClassFails implements Sub{}
+
+            assert !(new ThisClassFails().findAll())
+        '''
+        assertScript '''
+            import groovy.transform.AutoImplement
+
+            interface Super { List findAll() }
+            interface Sub extends Super { AbstractList findAll() }
+
+            @AutoImplement
+            class ThisClassFails implements Sub{}
+
+            assert !(new ThisClassFails().findAll())
+        '''
+        assertScript '''
+            import groovy.transform.AutoImplement
+
+            interface Super { AbstractList findAll() }
+            interface Sub extends Super { ArrayList findAll() }
+
+            @AutoImplement
+            class ThisClassFails implements Sub{}
+
+            assert !(new ThisClassFails().findAll())
+        '''
+        assertScript '''
+            import groovy.transform.AutoImplement
+
+            interface Super { ArrayList findAll() }
+            interface Sub extends Super { AbstractList findAll() }
+
+            @AutoImplement
+            class ThisClassFails implements Sub{}
+
+            assert !(new ThisClassFails().findAll())
+        '''
+    }
 }

Reply via email to