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/05/17 17:19:31 UTC

[groovy] branch GROOVY_3_0_X updated: GROOVY-10603: LUB: include transitive interfaces

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

emilles 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 751d1efbdc GROOVY-10603: LUB: include transitive interfaces
751d1efbdc is described below

commit 751d1efbdcd7eb756973563b9ea8472ddada64e8
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue May 17 11:07:09 2022 -0500

    GROOVY-10603: LUB: include transitive interfaces
---
 .../groovy/ast/tools/WideningCategories.java       | 51 +++++++---------------
 .../transform/stc/TernaryOperatorSTCTest.groovy    | 42 ++++++++++++++++++
 2 files changed, 58 insertions(+), 35 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java b/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java
index 64d9e7bf43..4c4ba12648 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java
@@ -70,8 +70,6 @@ import static org.codehaus.groovy.ast.ClassHelper.short_TYPE;
  */
 public class WideningCategories {
 
-    private static final List<ClassNode> EMPTY_CLASSNODE_LIST = Collections.emptyList();
-
     private static final Map<ClassNode, Integer> NUMBER_TYPES_PRECEDENCE = Collections.unmodifiableMap(new HashMap<ClassNode, Integer>() {
         private static final long serialVersionUID = -5178744121420941913L;
 
@@ -321,7 +319,7 @@ public class WideningCategories {
         return null;
     }
 
-    private static ClassNode lowestUpperBound(ClassNode a, ClassNode b, List<ClassNode> interfacesImplementedByA, List<ClassNode> interfacesImplementedByB) {
+    private static ClassNode lowestUpperBound(final ClassNode a, final ClassNode b, Set<ClassNode> interfacesImplementedByA, Set<ClassNode> interfacesImplementedByB) {
         // first test special cases
         if (a==null || b==null) {
             // this is a corner case, you should not
@@ -441,49 +439,32 @@ public class WideningCategories {
             return buildTypeWithInterfaces(a,b, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB));
         }
 
-        // Look at the super classes
         ClassNode sa = a.getUnresolvedSuperClass();
         ClassNode sb = b.getUnresolvedSuperClass();
 
-        // extract implemented interfaces before "going up"
-        Set<ClassNode> ifa = new HashSet<ClassNode>();
-        extractInterfaces(a, ifa);
-        Set<ClassNode> ifb = new HashSet<ClassNode>();
-        extractInterfaces(b, ifb);
-        interfacesImplementedByA = interfacesImplementedByA==null?new LinkedList<ClassNode>(ifa):interfacesImplementedByA;
-        interfacesImplementedByB = interfacesImplementedByB==null?new LinkedList<ClassNode>(ifb):interfacesImplementedByB;
-
-        // check if no superclass is defined
-        // meaning that we reached the top of the object hierarchy
-        if (sa==null || sb==null) return buildTypeWithInterfaces(OBJECT_TYPE, OBJECT_TYPE, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB));
-
-
-        // if one superclass is derived (or equals) another
-        // then it is the common super type
+        if (interfacesImplementedByA == null)
+            interfacesImplementedByA = GeneralUtils.getInterfacesAndSuperInterfaces(a);
+        if (interfacesImplementedByB == null)
+            interfacesImplementedByB = GeneralUtils.getInterfacesAndSuperInterfaces(b);
+        // check if no superclass is defined, meaning that we reached the top of the object hierarchy
+        if (sa == null || sb == null) {
+            return buildTypeWithInterfaces(OBJECT_TYPE, OBJECT_TYPE, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB));
+        }
+        // if one superclass is derived (or equals) another then it is the common super type
         if (sa.isDerivedFrom(sb) || sb.isDerivedFrom(sa)) {
             return buildTypeWithInterfaces(sa, sb, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB));
         }
-        // superclasses are on distinct hierarchy branches, so we
-        // recurse on them
+        // superclasses are on distinct hierarchy branches, so we recurse on them
         return lowestUpperBound(sa, sb, interfacesImplementedByA, interfacesImplementedByB);
     }
 
-    private static void extractInterfaces(ClassNode node, Set<ClassNode> interfaces) {
-        if (node==null) return;
-        Collections.addAll(interfaces, node.getInterfaces());
-        extractInterfaces(node.getSuperClass(), interfaces);
-    }
-    
     /**
-     * Given the list of interfaces implemented by two class nodes, returns the list of the most specific common
-     * implemented interfaces.
-     * @param fromA
-     * @param fromB
-     * @return the list of the most specific common implemented interfaces
+     * Given the interfaces implemented by two types, returns a list of the most
+     * specific common implemented interfaces.
      */
-    private static List<ClassNode> keepLowestCommonInterfaces(List<ClassNode> fromA, List<ClassNode> fromB) {
-        if (fromA==null||fromB==null) return EMPTY_CLASSNODE_LIST;
-        Set<ClassNode> common = new HashSet<ClassNode>(fromA);
+    private static List<ClassNode> keepLowestCommonInterfaces(final Set<ClassNode> fromA, final Set<ClassNode> fromB) {
+        if (fromA == null || fromB == null) return Collections.emptyList();
+        Set<ClassNode> common = new HashSet<>(fromA);
         common.retainAll(fromB);
         List<ClassNode> result = new ArrayList<ClassNode>(common.size());
         for (ClassNode classNode : common) {
diff --git a/src/test/groovy/transform/stc/TernaryOperatorSTCTest.groovy b/src/test/groovy/transform/stc/TernaryOperatorSTCTest.groovy
index 91c59fccc2..a1cdd1a22a 100644
--- a/src/test/groovy/transform/stc/TernaryOperatorSTCTest.groovy
+++ b/src/test/groovy/transform/stc/TernaryOperatorSTCTest.groovy
@@ -18,6 +18,8 @@
  */
 package groovy.transform.stc
 
+import groovy.test.NotYetImplemented
+
 /**
  * Unit tests for static type checking : ternary operator.
  */
@@ -183,6 +185,46 @@ class TernaryOperatorSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
+    @NotYetImplemented // GROOVY-10358
+    void testCommonInterface() {
+        assertScript '''
+            interface I {
+                int m(int i)
+            }
+            abstract class A implements I {
+            }
+            class B<T> extends A {
+                int m(int i) {
+                    i + 1
+                }
+            }
+            class C<T> extends A {
+                int m(int i) {
+                    i - 1
+                }
+            }
+
+            C<String> c = null; int i = 1
+            int x = (false ? c : new B<String>()).m(i) // Cannot find matching method A#m(int)
+            assert x == 2
+        '''
+    }
+
+    // GROOVY-10603
+    void testCommonInterface2() {
+        assertScript '''
+            interface I {}
+            interface J extends I {}
+            class Foo implements I {}
+            class Bar implements J {}
+
+            I test(Foo x, Bar y) {
+                true ? x : y // Cannot return value of type GroovyObject for method returning I
+            }
+            test(null, null)
+        '''
+    }
+
     // GROOVY-5523
     void testNull1() {
         assertScript '''