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);
}