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/09/13 21:09:57 UTC

[groovy] 01/01: GROOVY-10229, GROOVY-10230, GROOVY-10330, GROOVY-10358: LUB: generics

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

commit 934fe100091bc2aab6302bb98133d74fe24b4e55
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Sep 13 12:04:49 2022 -0500

    GROOVY-10229, GROOVY-10230, GROOVY-10330, GROOVY-10358: LUB: generics
    
    2_5_X backport
---
 .../groovy/ast/tools/WideningCategories.java       | 527 ++++++++++-----------
 .../transform/stc/StaticTypeCheckingSupport.java   |  16 +-
 src/spec/test/typing/TypeCheckingTest.groovy       |   3 +-
 src/test/gls/generics/GenericsBytecodeTest.groovy  |  28 +-
 .../groovy/transform/stc/ClosuresSTCTest.groovy    |   2 +-
 .../groovy/transform/stc/GenericsSTCTest.groovy    |  27 +-
 .../transform/stc/TernaryOperatorSTCTest.groovy    |  80 +++-
 .../groovy/ast/tools/WideningCategoriesTest.groovy |  77 +--
 8 files changed, 416 insertions(+), 344 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 a4ea1aef6a..5c11d0637f 100644
--- a/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java
+++ b/src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java
@@ -18,7 +18,6 @@
  */
 package org.codehaus.groovy.ast.tools;
 
-import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
 import org.codehaus.groovy.ast.GenericsType;
 import org.codehaus.groovy.ast.MethodNode;
@@ -45,12 +44,14 @@ import static org.codehaus.groovy.ast.ClassHelper.byte_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.char_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.double_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.float_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.getNextSuperClass;
 import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper;
 import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
 import static org.codehaus.groovy.ast.ClassHelper.int_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.isNumberType;
 import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
 import static org.codehaus.groovy.ast.ClassHelper.long_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
 import static org.codehaus.groovy.ast.ClassHelper.short_TYPE;
 
 /**
@@ -70,44 +71,24 @@ 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;
 
         {
-        put(ClassHelper.double_TYPE, 0);
-        put(ClassHelper.float_TYPE, 1);
-        put(ClassHelper.long_TYPE, 2);
-        put(ClassHelper.int_TYPE, 3);
-        put(ClassHelper.short_TYPE, 4);
-        put(ClassHelper.byte_TYPE, 5);
-    }});
-
-    /**
-     * A comparator which is used in case we generate a virtual lower upper bound class node. In that case,
-     * since a concrete implementation should be used at compile time, we must ensure that interfaces are
-     * always sorted. It is not important what sort is used, as long as the result is constant.
-     */
-    private static final Comparator<ClassNode> INTERFACE_CLASSNODE_COMPARATOR = new Comparator<ClassNode>() {
-        public int compare(final ClassNode o1, final ClassNode o2) {
-            int interfaceCountForO1 = o1.getInterfaces().length;
-            int interfaceCountForO2 = o2.getInterfaces().length;
-            if (interfaceCountForO1 > interfaceCountForO2) return -1;
-            if (interfaceCountForO1 < interfaceCountForO2) return 1;
-            int methodCountForO1 = o1.getMethods().size();
-            int methodCountForO2 = o2.getMethods().size();
-            if (methodCountForO1 > methodCountForO2) return -1;
-            if (methodCountForO1 < methodCountForO2) return 1;
-            return o1.getName().compareTo(o2.getName());
+            put(double_TYPE, 0);
+            put(float_TYPE,  1);
+            put(long_TYPE,   2);
+            put(int_TYPE,    3);
+            put(short_TYPE,  4);
+            put(byte_TYPE,   5);
         }
-    };
+    });
 
     /**
      * Used to check if a type is an int or Integer.
      * @param type the type to check
      */
-    public static boolean isInt(ClassNode type) {
+    public static boolean isInt(final ClassNode type) {
         return int_TYPE == type;
     }
 
@@ -115,7 +96,7 @@ public class WideningCategories {
      * Used to check if a type is an double or Double.
      * @param type the type to check
      */
-    public static boolean isDouble(ClassNode type) {
+    public static boolean isDouble(final ClassNode type) {
         return double_TYPE == type;
     }
 
@@ -123,7 +104,7 @@ public class WideningCategories {
      * Used to check if a type is a float or Float.
      * @param type the type to check
      */
-    public static boolean isFloat(ClassNode type) {
+    public static boolean isFloat(final ClassNode type) {
         return float_TYPE == type;
     }
 
@@ -131,49 +112,51 @@ public class WideningCategories {
      * It is of an int category, if the provided type is a
      * byte, char, short, int.
      */
-    public static boolean isIntCategory(ClassNode type) {
-        return  type==byte_TYPE     ||  type==char_TYPE     ||
-                type==int_TYPE      ||  type==short_TYPE;
+    public static boolean isIntCategory(final ClassNode type) {
+        return type == byte_TYPE || type == char_TYPE || type == int_TYPE || type == short_TYPE;
     }
+
     /**
      * It is of a long category, if the provided type is a
-     * long, its wrapper or if it is a long category. 
+     * long, its wrapper or if it is a long category.
      */
-    public static boolean isLongCategory(ClassNode type) {
-        return  type==long_TYPE     ||  isIntCategory(type);
+    public static boolean isLongCategory(final ClassNode type) {
+        return type == long_TYPE || isIntCategory(type);
     }
+
     /**
      * It is of a BigInteger category, if the provided type is a
-     * long category or a BigInteger. 
+     * long category or a BigInteger.
      */
-    public static boolean isBigIntCategory(ClassNode type) {
-        return  type==BigInteger_TYPE || isLongCategory(type);
+    public static boolean isBigIntCategory(final ClassNode type) {
+        return type == BigInteger_TYPE || isLongCategory(type);
     }
+
     /**
      * It is of a BigDecimal category, if the provided type is a
-     * BigInteger category or a BigDecimal. 
+     * BigInteger category or a BigDecimal.
      */
-    public static boolean isBigDecCategory(ClassNode type) {
-        return  type==BigDecimal_TYPE || isBigIntCategory(type);
+    public static boolean isBigDecCategory(final ClassNode type) {
+        return type == BigDecimal_TYPE || isBigIntCategory(type);
     }
+
     /**
      * It is of a double category, if the provided type is a
      * BigDecimal, a float, double. C(type)=double
      */
-    public static boolean isDoubleCategory(ClassNode type) {
-        return  type==float_TYPE    ||  type==double_TYPE   ||
-                isBigDecCategory(type);
+    public static boolean isDoubleCategory(final ClassNode type) {
+        return type == float_TYPE || type == double_TYPE || isBigDecCategory(type);
     }
 
     /**
      * It is of a floating category, if the provided type is a
      * a float, double. C(type)=float
      */
-    public static boolean isFloatingCategory(ClassNode type) {
-        return  type==float_TYPE    ||  type==double_TYPE;
+    public static boolean isFloatingCategory(final ClassNode type) {
+        return type == float_TYPE || type == double_TYPE;
     }
 
-    public static boolean isNumberCategory(ClassNode type) {
+    public static boolean isNumberCategory(final ClassNode type) {
         return isBigDecCategory(type) || type.isDerivedFrom(Number_TYPE);
     }
 
@@ -184,9 +167,11 @@ public class WideningCategories {
      * @param nodes the list of nodes for which to find the first common super type.
      * @return first common supertype
      */
-    public static ClassNode lowestUpperBound(List<ClassNode> nodes) {
-        if (nodes.size()==1) return nodes.get(0);
-        return lowestUpperBound(nodes.get(0), lowestUpperBound(nodes.subList(1, nodes.size())));
+    public static ClassNode lowestUpperBound(final List<ClassNode> nodes) {
+        int n = nodes.size();
+        if (n == 1) return nodes.get(0);
+        if (n == 2) return lowestUpperBound(nodes.get(0), nodes.get(1));
+        return lowestUpperBound(nodes.get(0), lowestUpperBound(nodes.subList(1, n)));
     }
 
     /**
@@ -206,36 +191,29 @@ public class WideningCategories {
      * @param b second class node
      * @return first common supertype
      */
-    public static ClassNode lowestUpperBound(ClassNode a, ClassNode b) {
+    public static ClassNode lowestUpperBound(final ClassNode a, final ClassNode b) {
         ClassNode lub = lowestUpperBound(a, b, null, null);
-        if (lub==null || !lub.isUsingGenerics()) return lub;
-        // types may be parameterized. If so, we must ensure that generic type arguments
+        if (lub == null || !lub.isUsingGenerics()
+                || lub.isGenericsPlaceHolder()) { // GROOVY-10330
+            return lub;
+        }
+        // types may be parameterized; if so, ensure that generic type arguments
         // are made compatible
-
         if (lub instanceof LowestUpperBoundClassNode) {
-            // no parent super class representing both types could be found
-            // or both class nodes implement common interfaces which may have
-            // been parameterized differently.
-            // We must create a classnode for which the "superclass" is potentially parameterized
-            // plus the interfaces
-            ClassNode superClass = lub.getSuperClass();
-            ClassNode psc = superClass.isUsingGenerics()?parameterizeLowestUpperBound(superClass, a, b, lub):superClass;
-
-            ClassNode[] interfaces = lub.getInterfaces();
-            ClassNode[] pinterfaces = new ClassNode[interfaces.length];
-            for (int i = 0, interfacesLength = interfaces.length; i < interfacesLength; i++) {
-                final ClassNode icn = interfaces[i];
-                if (icn.isUsingGenerics()) {
-                    pinterfaces[i] = parameterizeLowestUpperBound(icn, a, b, lub);
-                } else {
-                    pinterfaces[i] = icn;
+            ClassNode superClass = lub.getUnresolvedSuperClass();
+            if (superClass.redirect().getGenericsTypes() != null) {
+                superClass = parameterizeLowestUpperBound(superClass, a, b, lub);
+            }
+            ClassNode[] interfaces = lub.getInterfaces().clone();
+            for (int i = 0, n = interfaces.length; i < n; i += 1) {
+                ClassNode icn = interfaces[i];
+                if (icn.redirect().getGenericsTypes() != null) {
+                    interfaces[i] = parameterizeLowestUpperBound(icn, a, b, lub);
                 }
             }
-
-            return new LowestUpperBoundClassNode(((LowestUpperBoundClassNode)lub).name, psc, pinterfaces);
+            return new LowestUpperBoundClassNode(((LowestUpperBoundClassNode)lub).name, superClass, interfaces);
         } else {
             return parameterizeLowestUpperBound(lub, a, b, lub);
-
         }
     }
 
@@ -258,72 +236,63 @@ public class WideningCategories {
         // it according to the types provided by the two class nodes
         ClassNode holderForA = findGenericsTypeHolderForClass(a, lub);
         ClassNode holderForB = findGenericsTypeHolderForClass(b, lub);
-        // let's compare their generics type
+        // let's compare their generics
         GenericsType[] agt = holderForA == null ? null : holderForA.getGenericsTypes();
         GenericsType[] bgt = holderForB == null ? null : holderForB.getGenericsTypes();
-        if (agt==null || bgt==null || agt.length!=bgt.length) {
+        if (agt == null || bgt == null || agt.length != bgt.length
+                || Arrays.toString(agt).equals(Arrays.toString(bgt))) {
             return lub;
         }
-        GenericsType[] lubgt = new GenericsType[agt.length];
-        for (int i = 0; i < agt.length; i++) {
-            ClassNode t1 = agt[i].getType();
-            ClassNode t2 = bgt[i].getType();
+        int n = agt.length; GenericsType[] lubGTs = new GenericsType[n];
+        for (int i = 0; i < n; i += 1) {
+            ClassNode t1 = upperBound(agt[i]);
+            ClassNode t2 = upperBound(bgt[i]);
             ClassNode basicType;
             if (areEqualWithGenerics(t1, isPrimitiveType(a)?getWrapper(a):a) && areEqualWithGenerics(t2, isPrimitiveType(b)?getWrapper(b):b)) {
-                // we are facing a self referencing type !
-                basicType = fallback;
+                // "String implements Comparable<String>" and "StringBuffer implements Comparable<StringBuffer>"
+                basicType = fallback; // do not loop
             } else {
-                 basicType = lowestUpperBound(t1, t2);
+                basicType = lowestUpperBound(t1, t2);
             }
-            if (t1.equals(t2)) {
-                lubgt[i] = new GenericsType(basicType);
+            if (agt[i].isWildcard() || bgt[i].isWildcard() || !t1.equals(t2)) {
+                lubGTs[i] = GenericsUtils.buildWildcardType(basicType);
             } else {
-                lubgt[i] = GenericsUtils.buildWildcardType(basicType);
+                lubGTs[i] = basicType.asGenericsType();
             }
         }
-        ClassNode plain = lub.getPlainNodeReference();
-        plain.setGenericsTypes(lubgt);
-        return plain;
+        return GenericsUtils.makeClassSafe0(lub, lubGTs);
     }
 
-    private static ClassNode findGenericsTypeHolderForClass(ClassNode source, ClassNode type) {
+    private static ClassNode findGenericsTypeHolderForClass(ClassNode source, final ClassNode target) {
         if (isPrimitiveType(source)) source = getWrapper(source);
-        if (source.equals(type)) return source;
-        if (type.isInterface()) {
-            for (ClassNode interfaceNode : source.getAllInterfaces()) {
-                if (interfaceNode.equals(type)) {
-                    ClassNode parameterizedInterface = GenericsUtils.parameterizeType(source, interfaceNode);
-                    return parameterizedInterface;
+        if (source.equals(target)) {
+            return source;
+        }
+        if (target.isInterface() ? source.implementsInterface(target) : source.isDerivedFrom(target)) {
+            ClassNode sc;
+            do {
+                sc = getNextSuperClass(source, target);
+                if (GenericsUtils.hasUnresolvedGenerics(sc)) {
+                    sc = GenericsUtils.correctToGenericsSpecRecurse(GenericsUtils.createGenericsSpec(source), sc);
                 }
-            }
-        }
-        ClassNode superClass = source.getUnresolvedSuperClass();
-        // copy generic type information if available
-        if (superClass!=null && superClass.isUsingGenerics()) {
-            Map<GenericsType.GenericsTypeName, GenericsType> genericsTypeMap = GenericsUtils.extractPlaceholders(source);
-            GenericsType[] genericsTypes = superClass.getGenericsTypes();
-            if (genericsTypes!=null) {
-                GenericsType[] copyTypes = new GenericsType[genericsTypes.length];
-                for (int i = 0; i < genericsTypes.length; i++) {
-                    GenericsType genericsType = genericsTypes[i];
-                    GenericsType.GenericsTypeName gtn = new GenericsType.GenericsTypeName(genericsType.getName());
-                    if (genericsType.isPlaceholder() && genericsTypeMap.containsKey(gtn)) {
-                        copyTypes[i] = genericsTypeMap.get(gtn);
-                    } else {
-                        copyTypes[i] = genericsType;
-                    }
-                }
-                superClass = superClass.getPlainNodeReference();
-                superClass.setGenericsTypes(copyTypes);
-            }
+            } while (!(source = sc).equals(target));
+
+            return sc;
         }
-        if (superClass!=null) return findGenericsTypeHolderForClass(superClass, type);
         return null;
     }
 
-    private static ClassNode lowestUpperBound(ClassNode a, ClassNode b, List<ClassNode> interfacesImplementedByA, List<ClassNode> interfacesImplementedByB) {
+    private static ClassNode upperBound(final GenericsType gt) {
+        if (gt.isWildcard()) {
+            ClassNode[] ub = gt.getUpperBounds();
+            if (ub != null) return ub[0];
+        }
+        return gt.getType();
+    }
+
+    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) {
+        if (a == null || b == null) {
             // this is a corner case, you should not
             // compare two class nodes if one of them is null
             return null;
@@ -335,7 +304,7 @@ public class WideningCategories {
             // one of the objects is at the top of the hierarchy
             GenericsType[] gta = a.getGenericsTypes();
             GenericsType[] gtb = b.getGenericsTypes();
-            if (gta !=null && gtb !=null && gta.length==1 && gtb.length==1) {
+            if (gta != null && gtb != null && gta.length == 1 && gtb.length == 1) {
                 if (gta[0].getName().equals(gtb[0].getName())) {
                     return a;
                 }
@@ -362,20 +331,18 @@ public class WideningCategories {
         if (isPrimitiveA && isPrimitiveB) {
             Integer pa = NUMBER_TYPES_PRECEDENCE.get(a);
             Integer pb = NUMBER_TYPES_PRECEDENCE.get(b);
-            if (pa!=null && pb!=null) {
-                if (pa<=pb) return a;
-                return b;
+            if (pa != null && pb != null) {
+                return (pa <= pb ? a : b);
             }
-            return a.equals(b)?a:lowestUpperBound(getWrapper(a), getWrapper(b), null, null);
+            return a.equals(b) ? a : lowestUpperBound(getWrapper(a), getWrapper(b), null, null);
         }
         if (isNumberType(a.redirect()) && isNumberType(b.redirect())) {
             ClassNode ua = getUnwrapper(a);
             ClassNode ub = getUnwrapper(b);
             Integer pa = NUMBER_TYPES_PRECEDENCE.get(ua);
             Integer pb = NUMBER_TYPES_PRECEDENCE.get(ub);
-            if (pa!=null && pb!=null) {
-                if (pa<=pb) return a;
-                return b;
+            if (pa != null && pb != null) {
+                return (pa <= pb ? a : b);
             }
         }
 
@@ -390,19 +357,16 @@ public class WideningCategories {
             if (a.implementsInterface(b)) {
                 return b;
             }
-            // each interface may have one or more "extends", so we must find those
-            // which are common
-            ClassNode[] interfacesFromA = a.getInterfaces();
-            ClassNode[] interfacesFromB = b.getInterfaces();
-            Set<ClassNode> common = new HashSet<ClassNode>();
-            Collections.addAll(common, interfacesFromA);
-            Set<ClassNode> fromB = new HashSet<ClassNode>();
-            Collections.addAll(fromB, interfacesFromB);
-            common.retainAll(fromB);
-
-            if (common.size()==1) {
+            if (interfacesImplementedByA == null)
+                interfacesImplementedByA = GeneralUtils.getInterfacesAndSuperInterfaces(a);
+            if (interfacesImplementedByB == null)
+                interfacesImplementedByB = GeneralUtils.getInterfacesAndSuperInterfaces(b);
+
+            // each interface may have one or more "extends", so we must find those which are common
+            Collection<ClassNode> common = keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB);
+            if (common.size() == 1) {
                 return common.iterator().next();
-            } else if (common.size()>1) {
+            } else if (common.size() > 1) {
                 return buildTypeWithInterfaces(a, b, common);
             }
 
@@ -426,63 +390,53 @@ public class WideningCategories {
                 // no interface in common
                 return OBJECT_TYPE;
             }
-            if (matchingInterfaces.size()==1) {
+            if (matchingInterfaces.size() == 1) {
                 // a single match, which should be returned
                 return matchingInterfaces.get(0);
             }
-            return buildTypeWithInterfaces(a,b, matchingInterfaces);
+            return buildTypeWithInterfaces(a, b, matchingInterfaces);
         }
         // both classes do not represent interfaces
         if (a.equals(b)) {
-            return buildTypeWithInterfaces(a,b, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB));
+            return buildTypeWithInterfaces(a, b, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB));
         }
         // test if one class inherits from the other
         if (a.isDerivedFrom(b) || b.isDerivedFrom(a)) {
-            return buildTypeWithInterfaces(a,b, keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB));
+            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;
+        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));
+        // 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 (GenericsUtils.hasUnresolvedGenerics(sa))
+            sa = GenericsUtils.correctToGenericsSpecRecurse(GenericsUtils.createGenericsSpec(a), sa);
+        if (GenericsUtils.hasUnresolvedGenerics(sb))
+            sb = GenericsUtils.correctToGenericsSpecRecurse(GenericsUtils.createGenericsSpec(b), sb);
 
-        // if one superclass is derived (or equals) another
-        // then it is the common super type
+        // 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;
+    private static List<ClassNode> keepLowestCommonInterfaces(final Set<ClassNode> fromA, final Set<ClassNode> fromB) {
+        if (fromA == null || fromB == null) return Collections.EMPTY_LIST;
         Set<ClassNode> common = new HashSet<ClassNode>(fromA);
         common.retainAll(fromB);
         List<ClassNode> result = new ArrayList<ClassNode>(common.size());
@@ -492,16 +446,15 @@ public class WideningCategories {
         return result;
     }
 
-    private static void addMostSpecificInterface(ClassNode interfaceNode, List<ClassNode> nodes) {
+    private static void addMostSpecificInterface(final ClassNode interfaceNode, final List<ClassNode> nodes) {
         if (nodes.isEmpty()) nodes.add(interfaceNode);
-        for (int i = 0, nodesSize = nodes.size(); i < nodesSize; i++) {
-            final ClassNode node = nodes.get(i);
-            if (node.equals(interfaceNode)||node.implementsInterface(interfaceNode)) {
+        for (int i = 0, n = nodes.size(); i < n; i += 1) { ClassNode node = nodes.get(i);
+            if (node.equals(interfaceNode) || node.implementsInterface(interfaceNode)) {
                 // a more specific interface exists in the list, keep it
                 return;
             }
             if (interfaceNode.implementsInterface(node)) {
-                // the interface beeing added is more specific than the one in the list, replace it
+                // the interface being added is more specific than the one in the list, replace it
                 nodes.set(i, interfaceNode);
                 return;
             }
@@ -517,7 +470,7 @@ public class WideningCategories {
             for (ClassNode interfaceNode : interfaces) {
                 if (type.implementsInterface(interfaceNode)) result.add(interfaceNode);
             }
-            if (result.isEmpty() && interfaces.length>0) {
+            if (result.isEmpty() && interfaces.length > 0) {
                 // none if the direct interfaces match, but we must check "upper" in the hierarchy
                 for (ClassNode interfaceNode : interfaces) {
                     extractMostSpecificImplementedInterfaces(type, interfaceNode, result);
@@ -534,52 +487,70 @@ public class WideningCategories {
      * @param interfaces interfaces both class nodes share, which their lowest common super class do not implement.
      * @return the class node representing the lowest upper bound
      */
-    private static ClassNode buildTypeWithInterfaces(ClassNode baseType1, ClassNode baseType2, Collection<ClassNode> interfaces) {
-        boolean noInterface = interfaces.isEmpty();
-        if (noInterface) {
-            if (baseType1.equals(baseType2)) return baseType1;
-            if (baseType1.isDerivedFrom(baseType2)) return baseType2;
+    private static ClassNode buildTypeWithInterfaces(final ClassNode baseType1, final ClassNode baseType2, final Collection<ClassNode> interfaces) {
+        if (interfaces.isEmpty()) {
             if (baseType2.isDerivedFrom(baseType1)) return baseType1;
+            if (baseType1.isDerivedFrom(baseType2)) return baseType2;
         }
-        if (OBJECT_TYPE.equals(baseType1) && OBJECT_TYPE.equals(baseType2) && interfaces.size()==1) {
-            if (interfaces instanceof List) {
-                return ((List<ClassNode>) interfaces).get(0);
-            }
+
+        if (baseType1.equals(OBJECT_TYPE) && baseType2.equals(OBJECT_TYPE) && interfaces.size() == 1) {
             return interfaces.iterator().next();
         }
-        LowestUpperBoundClassNode type;
-        ClassNode superClass;
-        String name;
+
+        String name; ClassNode superClass;
         if (baseType1.equals(baseType2)) {
-            if (OBJECT_TYPE.equals(baseType1)) {
-                superClass = baseType1;
+            superClass = baseType1;
+            if (baseType1.equals(OBJECT_TYPE)) {
                 name = "Virtual$Object";
             } else {
-                superClass = baseType1;
-                name = "Virtual$"+baseType1.getName();
+                name = "Virtual$" + baseType1.getName();
             }
         } else {
-            superClass = OBJECT_TYPE;
             if (baseType1.isDerivedFrom(baseType2)) {
                 superClass = baseType2;
             } else if (baseType2.isDerivedFrom(baseType1)) {
                 superClass = baseType1;
+            } else {
+                superClass = OBJECT_TYPE;
             }
-            name = "CommonAssignOf$"+baseType1.getName()+"$"+baseType2.getName();
+            name = "CommonAssignOf$" + baseType1.getName() + "$" + baseType2.getName();
         }
-        Iterator<ClassNode> itcn = interfaces.iterator();
-        while (itcn.hasNext()) {
-            ClassNode next = itcn.next();
-            if (superClass.isDerivedFrom(next) || superClass.implementsInterface(next)) {
-                itcn.remove();
+
+        for (Iterator<ClassNode> it = interfaces.iterator(); it.hasNext(); ) {
+            if (GeneralUtils.isOrImplements(superClass, it.next())) {
+                it.remove();
             }
         }
+
+        int nInterfaces = interfaces.size();
+        if (nInterfaces == 0) return superClass;
+        if (nInterfaces == 1 && superClass.equals(OBJECT_TYPE)) return interfaces.iterator().next();
+
         ClassNode[] interfaceArray = interfaces.toArray(ClassNode.EMPTY_ARRAY);
         Arrays.sort(interfaceArray, INTERFACE_CLASSNODE_COMPARATOR);
-        type = new LowestUpperBoundClassNode(name, superClass, interfaceArray);
-        return type;
+        return new LowestUpperBoundClassNode(name, superClass, interfaceArray);
     }
 
+    /**
+     * A comparator which is used in case we generate a virtual lower upper bound class node. In that case,
+     * since a concrete implementation should be used at compile time, we must ensure that interfaces are
+     * always sorted. It is not important what sort is used, as long as the result is constant.
+     */
+    private static final Comparator<ClassNode> INTERFACE_CLASSNODE_COMPARATOR = new Comparator<ClassNode>() {
+        @Override
+        public int compare(final ClassNode o1, final ClassNode o2) {
+            int interfaceCountForO1 = o1.getInterfaces().length;
+            int interfaceCountForO2 = o2.getInterfaces().length;
+            if (interfaceCountForO1 > interfaceCountForO2) return -1;
+            if (interfaceCountForO1 < interfaceCountForO2) return 1;
+            int methodCountForO1 = o1.getMethods().size();
+            int methodCountForO2 = o2.getMethods().size();
+            if (methodCountForO1 > methodCountForO2) return -1;
+            if (methodCountForO1 < methodCountForO2) return 1;
+            return o1.getName().compareTo(o2.getName());
+        }
+    };
+
     /**
      * This {@link ClassNode} specialization is used when the lowest upper bound of two types
      * cannot be represented by an existing type. For example, if B extends A,  C extends A
@@ -592,62 +563,68 @@ public class WideningCategories {
      *
      */
     public static class LowestUpperBoundClassNode extends ClassNode {
-        private static final Comparator<ClassNode> CLASS_NODE_COMPARATOR = new Comparator<ClassNode>() {
-            public int compare(final ClassNode o1, final ClassNode o2) {
-                String n1 = o1 instanceof LowestUpperBoundClassNode?((LowestUpperBoundClassNode)o1).name:o1.getName();
-                String n2 = o2 instanceof LowestUpperBoundClassNode?((LowestUpperBoundClassNode)o2).name:o2.getName();
-                return n1.compareTo(n2);
-            }
-        };
-        private final ClassNode compileTimeClassNode;
         private final String name;
         private final String text;
-
         private final ClassNode upper;
         private final ClassNode[] interfaces;
+        private final ClassNode compileTimeClassNode;
 
-        public LowestUpperBoundClassNode(String name, ClassNode upper, ClassNode... interfaces) {
-            super(name, ACC_PUBLIC|ACC_FINAL, upper, interfaces, null);
+        public LowestUpperBoundClassNode(final String name, final ClassNode upper, final ClassNode... interfaces) {
+            super(name, ACC_PUBLIC | ACC_FINAL, upper, interfaces, null);
+            this.name = name;
             this.upper = upper;
             this.interfaces = interfaces;
-            boolean usesGenerics;
-            Arrays.sort(interfaces, CLASS_NODE_COMPARATOR);
-            compileTimeClassNode = upper.equals(OBJECT_TYPE) && interfaces.length>0?interfaces[0]:upper;
-            this.name = name;
-            usesGenerics = upper.isUsingGenerics();
-            List<GenericsType[]> genericsTypesList = new LinkedList<GenericsType[]>();
+            Arrays.sort(interfaces, new Comparator<ClassNode>() {
+                @Override
+                public int compare(final ClassNode cn1, final ClassNode cn2) {
+                    String n1 = cn1 instanceof LowestUpperBoundClassNode ? ((LowestUpperBoundClassNode) cn1).name : cn1.getName();
+                    String n2 = cn2 instanceof LowestUpperBoundClassNode ? ((LowestUpperBoundClassNode) cn2).name : cn2.getName();
+                    return n1.compareTo(n2);
+                }
+            });
+            compileTimeClassNode = upper.equals(OBJECT_TYPE) && interfaces.length > 0 ? interfaces[0] : upper;
+
+            StringBuilder sb = new StringBuilder("(");
+            if (!upper.equals(OBJECT_TYPE)) sb.append(upper.getText());
+            for (ClassNode i : interfaces) {
+                if (sb.length() > 1) {
+                    sb.append(" or ");
+                }
+                sb.append(i.getText());
+            }
+            sb.append(")");
+            this.text = sb.toString();
+
+            boolean usesGenerics = upper.isUsingGenerics();
+            List<GenericsType[]> genericsTypesList = new ArrayList<GenericsType[]>();
             genericsTypesList.add(upper.getGenericsTypes());
-			for (ClassNode anInterface : interfaces) {
+            for (ClassNode anInterface : interfaces) {
                 usesGenerics |= anInterface.isUsingGenerics();
                 genericsTypesList.add(anInterface.getGenericsTypes());
-				for (MethodNode methodNode : anInterface.getMethods()) {
+                for (MethodNode methodNode : anInterface.getMethods()) {
                     MethodNode method = addMethod(methodNode.getName(), methodNode.getModifiers(), methodNode.getReturnType(), methodNode.getParameters(), methodNode.getExceptions(), methodNode.getCode());
                     method.setDeclaringClass(anInterface); // important for static compilation!
                 }
-			}
+            }
             setUsingGenerics(usesGenerics);
             if (usesGenerics) {
-                List<GenericsType> asArrayList = new ArrayList<GenericsType>();
-                for (GenericsType[] genericsTypes : genericsTypesList) {
-                    if (genericsTypes!=null) {
-                        Collections.addAll(asArrayList, genericsTypes);
+                List<GenericsType> flatList = new ArrayList<GenericsType>();
+                for (GenericsType[] gts : genericsTypesList) {
+                    if (gts != null) {
+                        Collections.addAll(flatList, gts);
                     }
                 }
-                setGenericsTypes(asArrayList.toArray(GenericsType.EMPTY_ARRAY));
-            }
-            StringBuilder sb = new StringBuilder();
-            if (!upper.equals(OBJECT_TYPE)) sb.append(upper.getName());
-            for (ClassNode anInterface : interfaces) {
-                if (sb.length()>0) {
-                    sb.append(" or ");
-                }
-                sb.append(anInterface.getName());
+                setGenericsTypes(flatList.toArray(GenericsType.EMPTY_ARRAY));
             }
-            this.text = sb.toString();
         }
 
         public String getLubName() {
-            return this.name;
+            return name;
+        }
+
+        @Override
+        public String getText() {
+            return text;
         }
 
         @Override
@@ -662,15 +639,7 @@ public class WideningCategories {
 
         @Override
         public int hashCode() {
-            int result = super.hashCode();
-//            result = 31 * result + (compileTimeClassNode != null ? compileTimeClassNode.hashCode() : 0);
-            result = 31 * result + (name != null ? name.hashCode() : 0);
-            return result;
-        }
-
-        @Override
-        public String getText() {
-            return text;
+            return (31 * super.hashCode()) + (name != null ? name.hashCode() : 0);
         }
 
         @Override
@@ -682,21 +651,18 @@ public class WideningCategories {
                 ubs = new ClassNode[interfaces.length + 1]; ubs[0] = upper;
                 System.arraycopy(interfaces, 0, ubs, 1, interfaces.length);
             }
-            GenericsType gt = new GenericsType(ClassHelper.makeWithoutCaching("?"), ubs, null);
+            GenericsType gt = new GenericsType(makeWithoutCaching("?"), ubs, null);
             gt.setWildcard(true);
             return gt;
         }
 
         @Override
         public ClassNode getPlainNodeReference() {
-            ClassNode[] intf = interfaces==null?null:new ClassNode[interfaces.length];
-            if (intf!=null) {
-                for (int i = 0; i < interfaces.length; i++) {
-                    intf[i] = interfaces[i].getPlainNodeReference();
-                }
+            ClassNode[] faces = interfaces.clone();
+            for (int i = 0; i < interfaces.length; i += 1) {
+                faces[i] = interfaces[i].getPlainNodeReference();
             }
-            LowestUpperBoundClassNode plain = new LowestUpperBoundClassNode(name, upper.getPlainNodeReference(), intf);
-            return plain;
+            return new LowestUpperBoundClassNode(name, upper.getPlainNodeReference(), faces);
         }
     }
 
@@ -706,39 +672,42 @@ public class WideningCategories {
      * @param b
      * @return true if the class nodes are equal, false otherwise
      */
-    private static boolean areEqualWithGenerics(ClassNode a, ClassNode b) {
+    private static boolean areEqualWithGenerics(final ClassNode a, final ClassNode b) {
         if (a==null) return b==null;
         if (!a.equals(b)) return false;
         if (a.isUsingGenerics() && !b.isUsingGenerics()) return false;
         GenericsType[] gta = a.getGenericsTypes();
         GenericsType[] gtb = b.getGenericsTypes();
-        if (gta==null && gtb!=null) return false;
-        if (gtb==null && gta!=null) return false;
-        if (gta!=null && gtb!=null) {
-            if (gta.length!=gtb.length) return false;
-            for (int i = 0; i < gta.length; i++) {
-                GenericsType ga = gta[i];
-                GenericsType gb = gtb[i];
-                boolean result = ga.isPlaceholder()==gb.isPlaceholder() && ga.isWildcard()==gb.isWildcard();
-                result = result && ga.getName().equals(gb.getName());
-                result = result && areEqualWithGenerics(ga.getType(), gb.getType());
-                result = result && areEqualWithGenerics(ga.getLowerBound(), gb.getLowerBound());
-                if (result) {
-                    ClassNode[] upA = ga.getUpperBounds();
-                    if (upA!=null) {
-                        ClassNode[] upB = gb.getUpperBounds();
-                        if (upB==null || upB.length!=upA.length) return false;
-                        for (int j = 0; j < upA.length; j++) {
-                            if (!areEqualWithGenerics(upA[j],upB[j])) return false;
-                        }
-                    }
+        if (gta == null && gtb != null) return false;
+        if (gtb == null && gta != null) return false;
+        if (gta != null && gtb != null) {
+            if (gta.length != gtb.length) return false;
+            for (int i = 0, n = gta.length; i < n; i += 1) {
+                GenericsType gta_i = gta[i];
+                GenericsType gtb_i = gtb[i];
+                ClassNode[] upperA = gta_i.getUpperBounds();
+                ClassNode[] upperB = gtb_i.getUpperBounds();
+                if (gta_i.isPlaceholder() != gtb_i.isPlaceholder()
+                        || gta_i.isWildcard() != gtb_i.isWildcard()
+                        || !gta_i.getName().equals(gtb_i.getName())
+                        || !areEqualWithGenerics(gta_i.getType(), gtb_i.getType())
+                        || !areEqualWithGenerics(gta_i.getLowerBound(), gtb_i.getLowerBound())
+                        || (upperA == null ? upperB != null : !areEqualWithGenerics(upperA, upperB))) {
+                    return false;
                 }
-                if (!result) return false;
             }
         }
         return true;
     }
-    
+
+    private static boolean areEqualWithGenerics(final ClassNode[] upperA, final ClassNode[] upperB) {
+        int n; if ((n = upperA.length) != upperB.length) return false;
+        for (int i = 0; i < n; i += 1) {
+            if (!areEqualWithGenerics(upperA[i], upperB[i])) return false;
+        }
+        return true;
+    }
+
     /**
      * Determines if the source class implements an interface or subclasses the target type.
      * This method takes the {@link org.codehaus.groovy.ast.tools.WideningCategories.LowestUpperBoundClassNode lowest
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
index 45ecca7e51..0d8ebbdf86 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java
@@ -109,7 +109,6 @@ import static org.codehaus.groovy.ast.ClassHelper.make;
 import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
 import static org.codehaus.groovy.ast.ClassHelper.short_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.void_WRAPPER_TYPE;
-import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean;
 import static org.codehaus.groovy.syntax.Types.ASSIGN;
 import static org.codehaus.groovy.syntax.Types.BITWISE_AND;
 import static org.codehaus.groovy.syntax.Types.BITWISE_AND_EQUAL;
@@ -672,6 +671,11 @@ public abstract class StaticTypeCheckingSupport {
         return checkCompatibleAssignmentTypes(left, right, rightExpression, true);
     }
 
+    /**
+     * Everything that can be done by {@code castToType} should be allowed for assignment.
+     *
+     * @see org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation#castToType(Object,Class)
+     */
     public static boolean checkCompatibleAssignmentTypes(ClassNode left, ClassNode right, Expression rightExpression, boolean allowConstructorCoercion) {
         boolean rightExpressionIsNull = isNullConstant(rightExpression);
         if (rightExpressionIsNull && !isPrimitiveType(left)) {
@@ -864,7 +868,7 @@ public abstract class StaticTypeCheckingSupport {
                     return !Double.valueOf(val).equals(number);
                 }
                 default: // double
-                    return false; // no possible loose here
+                    return false; // no possible loss here
             }
         }
         return true; // possible loss of precision
@@ -930,7 +934,7 @@ public abstract class StaticTypeCheckingSupport {
         if (type.isArray() && superOrInterface.isArray()) {
             return implementsInterfaceOrIsSubclassOf(type.getComponentType(), superOrInterface.getComponentType());
         }
-        if (GROOVY_OBJECT_TYPE.equals(superOrInterface) && !type.isInterface() && isBeingCompiled(type)) {
+        if (superOrInterface.equals(GROOVY_OBJECT_TYPE) && !type.isInterface() && isBeingCompiled(type)) {
             return true;
         }
         return false;
@@ -1069,7 +1073,7 @@ public abstract class StaticTypeCheckingSupport {
      * @return zero or more results
      */
     public static List<MethodNode> chooseBestMethod(final ClassNode receiver, final Collection<MethodNode> methods, final ClassNode... argumentTypes) {
-        if (!asBoolean(methods)) {
+        if (methods == null || methods.isEmpty()) {
             return Collections.emptyList();
         }
         if (isUsingUncheckedGenerics(receiver)) {
@@ -1655,7 +1659,7 @@ public abstract class StaticTypeCheckingSupport {
     }
 
     static void applyGenericsConnections(final Map<GenericsTypeName, GenericsType> connections, final Map<GenericsTypeName, GenericsType> resolvedPlaceholders) {
-        if (!asBoolean(connections)) return;
+        if (connections == null || connections.isEmpty()) return;
 
         int count = 0;
         while (count++ < 10000) {
@@ -1947,7 +1951,7 @@ public abstract class StaticTypeCheckingSupport {
         }
         ClassNode newType = type.getPlainNodeReference();
         GenericsType[] gt = type.getGenericsTypes();
-        if (asBoolean(spec)) {
+        if (spec != null) {
             gt = applyGenericsContext(spec, gt);
         }
         newType.setGenericsTypes(gt);
diff --git a/src/spec/test/typing/TypeCheckingTest.groovy b/src/spec/test/typing/TypeCheckingTest.groovy
index 180d0b75a5..6d2018bca3 100644
--- a/src/spec/test/typing/TypeCheckingTest.groovy
+++ b/src/spec/test/typing/TypeCheckingTest.groovy
@@ -610,7 +610,8 @@ import static org.codehaus.groovy.ast.tools.WideningCategories.lowestUpperBound
                 it.exit()                                       // <9>
             }
             // end::least_upper_bound_collection_inference[]
-        ''', '[Static type checking] - Cannot find matching method Greeter or Salute#exit()'
+        ''',
+        '[Static type checking] - Cannot find matching method (Greeter or Salute)#exit()'
     }
 
     void testInstanceOfInference() {
diff --git a/src/test/gls/generics/GenericsBytecodeTest.groovy b/src/test/gls/generics/GenericsBytecodeTest.groovy
index 897149cda8..cd6bfc14fd 100644
--- a/src/test/gls/generics/GenericsBytecodeTest.groovy
+++ b/src/test/gls/generics/GenericsBytecodeTest.groovy
@@ -205,13 +205,37 @@ class GenericsBytecodeTest extends GenericsTestBase {
         ]
     }
 
+    // GROOVY-10229
+    void testWildcard3() {
+        createClassInfo '''
+            @groovy.transform.CompileStatic
+            class C {
+                Map<String,?> a() {
+                }
+                Map<String,List<?>> b() {
+                    def c = {
+                        [
+                            a(), a()
+                        ]
+                    }
+                    return null
+                }
+            }
+        '''
+        assert signatures == [
+                'a()Ljava/util/Map;'                        : '()Ljava/util/Map<Ljava/lang/String;*>;',
+                'b()Ljava/util/Map;'                        : '()Ljava/util/Map<Ljava/lang/String;Ljava/util/List<*>;>;',
+                'doCall()Ljava/util/List;'                  : '()Ljava/util/List<Ljava/util/Map<Ljava/lang/String;+Ljava/lang/Object;>;>;',
+                'doCall(Ljava/lang/Object;)Ljava/util/List;': '(Ljava/lang/Object;)Ljava/util/List<Ljava/util/Map<Ljava/lang/String;+Ljava/lang/Object;>;>;'
+        ]
+    }
+
     void testParameterAsParameterForReturnTypeAndFieldClass() {
         createClassInfo """
                class B<T> {
                    private T owner;
                    Class<T> getOwnerClass(){}
-   
-            } 
+            }
         """
         assert signatures == [
                 "class"                           : "<T:Ljava/lang/Object;>Ljava/lang/Object;Lgroovy/lang/GroovyObject;",
diff --git a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
index 4ddd49edf9..87fd1cf2cc 100644
--- a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
@@ -321,7 +321,7 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
             { -> x = 123 }
             x.charAt(0) // available in String but not available in Integer
         ''',
-        'Cannot find matching method java.io.Serializable or java.lang.Comparable'
+        'Cannot find matching method (java.io.Serializable or java.lang.Comparable',')#charAt(int)'
     }
 
     // GROOVY-9516
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index 991dd48adf..026c3751e4 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -906,7 +906,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
-    @NotYetImplemented // GROOVY-10230
+    // GROOVY-10230
     void testDiamondInferrenceFromConstructor23() {
         assertScript '''
             class A {
@@ -2417,6 +2417,8 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
 
     // GROOVY-10235
     void testCompatibleArgumentsForPlaceholders4() {
+        if (!GroovyAssert.isAtLeastJdk('1.8')) return
+
         assertScript '''
             import static java.util.concurrent.ConcurrentHashMap.newKeySet
 
@@ -2486,7 +2488,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
     // GROOVY-10100
     void testCompatibleArgumentsForPlaceholders8() {
         assertScript '''
-            import java.util.function.Function
+            import org.apache.groovy.internal.util.Function
 
             class C<T> {
                 T m(Object... args) {
@@ -3222,10 +3224,9 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
     // GROOVY-10557
     void testReturnTypeInferenceWithClosure2() {
         assertScript '''
+            import org.apache.groovy.internal.util.Function
+
             class C {
-                interface Function<T, R> {
-                    R apply(T t)
-                }
                 def <T> T m(Function<Reader,T> fn)  {
                     new StringReader("").withCloseable { reader ->
                         fn.apply(reader)
@@ -3747,11 +3748,9 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
     // GROOVY-6731
     void testContravariantMethodResolution() {
         assertScript '''
-            interface Function<T, R> {
-                R apply(T t)
-            }
+            import org.apache.groovy.internal.util.Function
 
-            public <I, O> void transform(Function<? super I, ? extends O> function) {
+            def <I, O> void transform(Function<? super I, ? extends O> function) {
                 function.apply('')
             }
 
@@ -3768,11 +3767,9 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
 
     void testContravariantMethodResolutionWithImplicitCoercion() {
         assertScript '''
-            interface Function<T, R> {
-                R apply(T t)
-            }
+            import org.apache.groovy.internal.util.Function
 
-            public <I, O> void transform(Function<? super I, ? extends O> function) {
+            def <I, O> void transform(Function<? super I, ? extends O> function) {
                 function.apply('')
             }
 
@@ -3926,9 +3923,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
     // GROOVY-9635
     void testCovariantReturnTypeInferredFromMethod3() {
         assertScript '''
-            interface Function<T, R> {
-                R apply(T t)
-            }
+            import org.apache.groovy.internal.util.Function
 
             class C<R extends Number> {
                 def <V> V m(Function<C, V> f) { // R from C is confused with R->V from Function
diff --git a/src/test/groovy/transform/stc/TernaryOperatorSTCTest.groovy b/src/test/groovy/transform/stc/TernaryOperatorSTCTest.groovy
index b477460409..5545f0a2c6 100644
--- a/src/test/groovy/transform/stc/TernaryOperatorSTCTest.groovy
+++ b/src/test/groovy/transform/stc/TernaryOperatorSTCTest.groovy
@@ -18,8 +18,6 @@
  */
 package groovy.transform.stc
 
-import groovy.transform.NotYetImplemented
-
 /**
  * Unit tests for static type checking : ternary operator.
  */
@@ -133,7 +131,41 @@ class TernaryOperatorSTCTest extends StaticTypeCheckingTestCase {
         '''
     }
 
-    @NotYetImplemented // GROOVY-10357
+    // GROOVY-10330
+    void testTypeParameterTypeParameter1() {
+        assertScript '''
+            import org.apache.groovy.internal.util.Function
+
+            class C<T> {
+                T y
+                void m(T x, Function<T, T> f) {
+                    assert f.apply(x) == 'foo'
+                }
+                void test(T x, Function<T, T> f) {
+                    @ASTTest(phase=INSTRUCTION_SELECTION, value={
+                        def type = node.getNodeMetaData(INFERRED_TYPE)
+                        assert type.isGenericsPlaceHolder()
+                        assert type.unresolvedName == 'T'
+                    })
+                    def z = true ? x : y
+                    m(z, f)
+                }
+            }
+            new C<String>().test('FOO', { it.toLowerCase() })
+        '''
+    }
+
+    // GROOVY-10363
+    void testTypeParameterTypeParameter2() {
+        assertScript '''
+            def <X extends java.util.concurrent.Callable<Number>> X m(X x, X y) {
+                X ecks = true ? x : y // infers as Callable<Object>
+            }
+            assert m(null,null) == null
+        '''
+    }
+
+    // GROOVY-10357
     void testAbstractMethodDefault() {
         assertScript '''
             import org.apache.groovy.internal.util.Function
@@ -145,13 +177,53 @@ class TernaryOperatorSTCTest extends StaticTypeCheckingTestCase {
             def a = new A() {
                 @Override
                 long m(Function<Boolean,Integer> f) {
-                    f(true).longValue()
+                    f.apply(true).longValue()
                 }
             }
             assert a.m() == 1L
         '''
     }
 
+    // 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 '''
diff --git a/src/test/org/codehaus/groovy/ast/tools/WideningCategoriesTest.groovy b/src/test/org/codehaus/groovy/ast/tools/WideningCategoriesTest.groovy
index 4be116256e..7ba751d062 100644
--- a/src/test/org/codehaus/groovy/ast/tools/WideningCategoriesTest.groovy
+++ b/src/test/org/codehaus/groovy/ast/tools/WideningCategoriesTest.groovy
@@ -69,7 +69,7 @@ final class WideningCategoriesTest extends GenericsTestCase {
 
     void testBuildCommonTypeWithTwoIncompatibleInterfaces() {
         ClassNode a = make(Set)
-        ClassNode b = make(Map)
+        ClassNode b = MAP_TYPE
         assert lowestUpperBound(a,b) == OBJECT_TYPE
         assert lowestUpperBound(b,a) == OBJECT_TYPE
     }
@@ -82,7 +82,7 @@ final class WideningCategoriesTest extends GenericsTestCase {
     }
 
     void testBuildCommonTypeWithOneClassAndNoImplementedInterface() {
-        ClassNode a = make(Map)
+        ClassNode a = MAP_TYPE
         ClassNode b = make(HashSet)
         assert lowestUpperBound(a,b) == OBJECT_TYPE
         assert lowestUpperBound(b,a) == OBJECT_TYPE
@@ -91,8 +91,8 @@ final class WideningCategoriesTest extends GenericsTestCase {
     void testBuildCommonTypeWithTwoClassesWithoutSuperClass() {
         ClassNode a = make(ClassA)
         ClassNode b = make(ClassB)
-        assert lowestUpperBound(a,b) == make(GroovyObject) // GroovyObject because Groovy classes implicitly implement GroovyObject
-        assert lowestUpperBound(b,a) == make(GroovyObject)
+        assert lowestUpperBound(a,b) == GROOVY_OBJECT_TYPE // GroovyObject because Groovy classes implicitly implement GroovyObject
+        assert lowestUpperBound(b,a) == GROOVY_OBJECT_TYPE
     }
 
     void testBuildCommonTypeWithIdenticalPrimitiveTypes() {
@@ -127,7 +127,14 @@ final class WideningCategoriesTest extends GenericsTestCase {
         assert lowestUpperBound(b,a) == make(HashSet)
     }
 
-    void testBuildCommonTypeWithTwoInterfacesSharingOneParent() {
+    void testBuildCommonTypeWithTwoInterfacesSharingOneParent0() {
+        ClassNode a = make(Set).plainNodeReference
+        ClassNode b = LIST_TYPE.plainNodeReference
+        assert lowestUpperBound(a,b).toString(false) == 'java.util.Collection <java.lang.Object>'
+        assert lowestUpperBound(b,a).toString(false) == 'java.util.Collection <java.lang.Object>'
+    }
+
+    void testBuildCommonTypeWithTwoInterfacesSharingOneParent1() {
         ClassNode a = make(InterfaceCA)
         ClassNode b = make(InterfaceDA)
         assert lowestUpperBound(a,b) == make(InterfaceA)
@@ -151,15 +158,15 @@ final class WideningCategoriesTest extends GenericsTestCase {
     void testBuildCommonTypeFromTwoClassesInDifferentBranches() {
         ClassNode a = make(ClassA1)
         ClassNode b = make(ClassB1)
-        assert lowestUpperBound(a,b) == make(GroovyObject)
-        assert lowestUpperBound(b,a) == make(GroovyObject)
+        assert lowestUpperBound(a,b) == GROOVY_OBJECT_TYPE
+        assert lowestUpperBound(b,a) == GROOVY_OBJECT_TYPE
     }
 
     void testBuildCommonTypeFromTwoClassesInDifferentBranchesAndOneCommonInterface() {
         ClassNode a = make(ClassA1_Serializable)
         ClassNode b = make(ClassB1_Serializable)
-        assert lowestUpperBound(a,b).interfaces as Set == [make(Serializable), make(GroovyObject)] as Set
-        assert lowestUpperBound(b,a).interfaces as Set == [make(Serializable), make(GroovyObject)] as Set
+        assert lowestUpperBound(a,b).interfaces as Set == [make(Serializable), GROOVY_OBJECT_TYPE] as Set
+        assert lowestUpperBound(b,a).interfaces as Set == [make(Serializable), GROOVY_OBJECT_TYPE] as Set
     }
 
     void testBuildCommonTypeFromTwoClassesWithCommonSuperClassAndOneCommonInterface() {
@@ -178,22 +185,22 @@ final class WideningCategoriesTest extends GenericsTestCase {
     // GROOVY-8111
     void testBuildCommonTypeFromTwoClassesWithTwoCommonInterfacesOneIsSelfReferential() {
         ClassNode a = boolean_TYPE
-        ClassNode b = extractTypesFromCode("${getClass().getName()}.Pair<String,String> type").type
+        ClassNode b = extractTypesFromCode("${this.class.name}.Pair<String,String> type").type
         ClassNode lub = lowestUpperBound(a, b)
 
         assert lub.superClass == OBJECT_TYPE
-        assert lub.interfaces as Set == [make(Comparable), make(Serializable)] as Set
+        assert lub.interfaces as Set == [COMPARABLE_TYPE, make(Serializable)] as Set
 
         lub = lowestUpperBound(b, a)
         assert lub.superClass == OBJECT_TYPE
-        assert lub.interfaces as Set == [make(Comparable), make(Serializable)] as Set
+        assert lub.interfaces as Set == [COMPARABLE_TYPE, make(Serializable)] as Set
     }
 
     void testStringWithGString() {
         ClassNode a = make(String)
         ClassNode b = make(GString)
         ClassNode type = lowestUpperBound(a,b)
-        assert type.interfaces as Set == [make(CharSequence), make(Comparable), make(Serializable)] as Set
+        assert type.interfaces as Set == [make(CharSequence), COMPARABLE_TYPE, make(Serializable)] as Set
     }
 
     void testDistinctPrimitiveTypes() {
@@ -210,44 +217,44 @@ final class WideningCategoriesTest extends GenericsTestCase {
     }
 
     void testLUBWithTwoInterfacesAndSameGenericArg() {
-        ClassNode a = extractTypesFromCode("List<String> type").type
-        ClassNode b = extractTypesFromCode("List<String> type").type
+        ClassNode a = extractTypesFromCode('List<String> type').type
+        ClassNode b = extractTypesFromCode('List<String> type').type
         ClassNode lub = lowestUpperBound(a,b)
-        assert lub == make(List)
+        assert lub == LIST_TYPE
         assert lub.genericsTypes.length == 1
         assert lub.genericsTypes[0].type == STRING_TYPE
     }
 
     void testLUBWithTwoInterfacesAndCommonSuperClassGenericArg() {
-        ClassNode a = extractTypesFromCode("List<Integer> type").type
-        ClassNode b = extractTypesFromCode("List<Long> type").type
+        ClassNode a = extractTypesFromCode('List<Integer> type').type
+        ClassNode b = extractTypesFromCode('List<Long> type').type
         ClassNode lub = lowestUpperBound(a,b)
-        assert lub == make(List)
+        assert lub == LIST_TYPE
         assert lub.genericsTypes.length == 1
         assert lub.genericsTypes[0].wildcard
         assert lub.genericsTypes[0].upperBounds[0].superClass == Number_TYPE
-        assert make(Comparable) in lub.genericsTypes[0].upperBounds[0].interfaces
+        assert COMPARABLE_TYPE in lub.genericsTypes[0].upperBounds[0].interfaces
     }
 
     void testLUBWithTwoInterfacesAndSingleCommonInterface() {
-        ClassNode a = extractTypesFromCode("List<Set> type").type
-        ClassNode b = extractTypesFromCode("List<List> type").type
+        ClassNode a = extractTypesFromCode('List<Set> type').type
+        ClassNode b = extractTypesFromCode('List<List> type').type
         ClassNode lub = lowestUpperBound(a,b)
-        assert lub == make(List)
+        assert lub == LIST_TYPE
         assert lub.genericsTypes.length == 1
         assert lub.genericsTypes[0].wildcard
         assert lub.genericsTypes[0].upperBounds[0] == make(Collection)
     }
 
     void testLUBWithTwoInterfacesAndNestedSingleCommonInterface() {
-        ClassNode a = extractTypesFromCode("Collection<List<Set>> type").type
-        ClassNode b = extractTypesFromCode("Collection<List<SortedSet>> type").type
+        ClassNode a = extractTypesFromCode('Collection<List<Set>> type').type
+        ClassNode b = extractTypesFromCode('Collection<List<SortedSet>> type').type
         ClassNode lub = lowestUpperBound(a,b)
         assert lub == make(Collection)
         assert lub.genericsTypes.length == 1
         def nestedType = lub.genericsTypes[0].type
-        assert nestedType == make(List)
-        assert nestedType.genericsTypes.length==1
+        assert nestedType == LIST_TYPE
+        assert nestedType.genericsTypes.length == 1
         assert nestedType.genericsTypes[0].wildcard
         assert nestedType.genericsTypes[0].upperBounds[0] == make(Set)
     }
@@ -276,18 +283,20 @@ final class WideningCategoriesTest extends GenericsTestCase {
         ClassNode b = extractTypesFromCode('org.codehaus.groovy.ast.tools.WideningCategoriesTest.PTopLong type').type
         ClassNode lub = lowestUpperBound(a,b)
         assert lub instanceof LowestUpperBoundClassNode // a virtual class which extends PTop<? extends Number> and implements Serializable
+        assert lub.interfaces == [make(Serializable)]
         assert lub.unresolvedSuperClass == make(PTop)
         assert lub.unresolvedSuperClass.genericsTypes.length == 1
         assert lub.unresolvedSuperClass.genericsTypes[0].wildcard // ? extends Number
-        ClassNode genericType = lub.unresolvedSuperClass.genericsTypes[0].upperBounds[0]
-        assert genericType == Long_TYPE
+        ClassNode upperBound = lub.unresolvedSuperClass.genericsTypes[0].upperBounds[0]
+        assert upperBound.superClass == Number_TYPE
+        assert upperBound.interfaces.contains(COMPARABLE_TYPE)
     }
 
     void testCommonAssignableType() {
         def typeA = extractTypesFromCode('LinkedList type').type
         def typeB = extractTypesFromCode('List type').type
         def superType = lowestUpperBound(typeA, typeB)
-        assert superType == make(List)
+        assert superType == LIST_TYPE
     }
 
     void testCommonAssignableType2() {
@@ -336,9 +345,8 @@ final class WideningCategoriesTest extends GenericsTestCase {
         def type = superType.genericsTypes[0]
         assert type.wildcard
         assert type.upperBounds[0] instanceof LowestUpperBoundClassNode
-        [Comparable, Serializable].each {
-            assert make(it) in type.upperBounds[0].interfaces
-        }
+        assert type.upperBounds[0].interfaces.contains(COMPARABLE_TYPE)
+        assert type.upperBounds[0].interfaces.contains(make(Serializable))
     }
 
     void testLUBOfArrayTypes() {
@@ -346,8 +354,7 @@ final class WideningCategoriesTest extends GenericsTestCase {
         def typeB = extractTypesFromCode('Integer[] type').type
         def superType = lowestUpperBound(typeA, typeB)
         assert superType.isArray()
-        def component = superType.getComponentType()
-        assert component == make(Number)
+        assert superType.componentType == Number_TYPE
     }
 
     // ---------- Classes and Interfaces used in this unit test ----------------