private static ClassNode lowestUpperBound()

in src/main/java/org/codehaus/groovy/ast/tools/WideningCategories.java [314:436]


    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
            // compare two class nodes if one of them is null
            return null;
        }
        if (a.isArray() && b.isArray()) {
            return lowestUpperBound(a.getComponentType(), b.getComponentType(), interfacesImplementedByA, interfacesImplementedByB).makeArray();
        }
        if (isObjectType(a) || isObjectType(b)) {
            // 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[0].getName().equals(gtb[0].getName())) {
                    return a;
                }
            }
            return OBJECT_TYPE;
        }
        if (isPrimitiveVoid(a) || isPrimitiveVoid(b)) {
            if (!b.equals(a)) {
                // one class is void, the other is not
                return OBJECT_TYPE;
            }
            return VOID_TYPE;
        }

        // handle primitives
        boolean aIsPrimitive = isPrimitiveType(a);
        boolean bIsPrimitive = isPrimitiveType(b);
        if (aIsPrimitive || bIsPrimitive) {
            if (a.equals(b)) return a;
            // GROOVY-8965, GROOVY-11014, et al.
            Integer pa = NUMBER_TYPES_PRECEDENCE.get(aIsPrimitive ? a : getUnwrapper(a));
            Integer pb = NUMBER_TYPES_PRECEDENCE.get(bIsPrimitive ? b : getUnwrapper(b));
            ClassNode wa = aIsPrimitive ? getWrapper(a) : a;
            ClassNode wb = bIsPrimitive ? getWrapper(b) : b;
            if (pa != null && pb != null) { // coercion
                if (pa.compareTo(pb) <= 0) {
                    return bIsPrimitive ? a : wa;
                } else {
                    return aIsPrimitive ? b : wb;
                }
            }
            return lowestUpperBound(wa, wb, null, null);
        }

        // handle interfaces
        boolean aIsInterface = a.isInterface();
        boolean bIsInterface = b.isInterface();
        if (aIsInterface && bIsInterface) {
            if (a.equals(b) || b.implementsInterface(a)) {
                return a;
            }
            if (a.implementsInterface(b)) {
                return b;
            }

            // GROOVY-11189: find common interface(s)
            interfacesImplementedByA = GeneralUtils.getInterfacesAndSuperInterfaces(a);
            interfacesImplementedByB = GeneralUtils.getInterfacesAndSuperInterfaces(b);
            Collection<ClassNode> common = keepLowestCommonInterfaces(interfacesImplementedByA, interfacesImplementedByB);

            if (common.size() == 1) {
                return common.iterator().next();
            } else if (common.size() > 1) {
                return buildTypeWithInterfaces(a, b, common);
            } else {
                return OBJECT_TYPE; // no common interface, so Object is implied
            }
        } else if (bIsInterface) {
            return lowestUpperBound(b, a, null, null);
        } else if (aIsInterface) {
            // a is an interface, b is not

            // a ClassNode superclass for an interface is not
            // another interface but always Object. This implies that
            // "extends" for an interface is understood as "implements"
            // for a ClassNode. Therefore, even if b doesn't implement
            // interface a, a could "implement" other interfaces that b
            // implements too, so we must create a list of matching interfaces
            List<ClassNode> matchingInterfaces = new LinkedList<>();
            extractMostSpecificImplementedInterfaces(b, a, matchingInterfaces);
            if (matchingInterfaces.isEmpty()) {
                // no interface in common
                return OBJECT_TYPE;
            }
            if (matchingInterfaces.size() == 1) {
                // a single match, which should be returned
                return matchingInterfaces.get(0);
            }
            return buildTypeWithInterfaces(a, b, matchingInterfaces);
        }
        // both classes do not represent interfaces
        if (a.equals(b)) {
            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));
        }

        ClassNode sa = a.getUnresolvedSuperClass();
        ClassNode sb = b.getUnresolvedSuperClass();

        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
        return lowestUpperBound(sa, sb, interfacesImplementedByA, interfacesImplementedByB);
    }