ClassSpec ClassSpec::operator&(const ClassSpec& rhs) const { auto const& lhs = *this; if (lhs <= rhs) return lhs; if (rhs <= lhs) return rhs; assertx(lhs.cls() && rhs.cls()); // If neither class is an interface, their intersection is trivial. if (isNormalClass(lhs.cls()) && isNormalClass(rhs.cls())) { return Bottom; } // If either is an interface, we'd need to explore all implementing classes // in the program to know if they have a non-empty intersection. Instead, // we'll just try to take the "better" of the two. We consider a normal // class better than an interface, because it might influence important // things like method dispatch or property accesses better than an interface // type could. if (isNormalClass(lhs.cls())) return lhs; if (isNormalClass(rhs.cls())) return rhs; // If they are both interfaces, we have to pick one arbitrarily, but we must // do so in a way that is stable regardless of which one was passed as lhs or // rhs (to guarantee that operator& is commutative). We use the class name // in this case to ensure that the ordering is dependent only on the source // program (Class* or something like that seems less desirable). return lhs.cls()->name()->compare(rhs.cls()->name()) < 0 ? lhs : rhs; }
static folly::Optional<ClassInfo> commonAncestor(ClassInfo a, ClassInfo b) { if (!isNormalClass(a.get()) || !isNormalClass(b.get())) return folly::none; if (auto result = a.get()->commonAncestor(b.get())) { return ClassInfo(result, ClassTag::Sub); } return folly::none; }
ClassSpec ClassSpec::operator|(const ClassSpec& rhs) const { auto const& lhs = *this; if (lhs <= rhs) return rhs; if (rhs <= lhs) return lhs; assertx(lhs.cls() && rhs.cls()); // We're unwilling to unify with interfaces, so just return Top. if (!isNormalClass(lhs.cls()) || !isNormalClass(rhs.cls())) { return Top; } // Unify to a common ancestor if possible. if (auto cls = lhs.cls()->commonAncestor(rhs.cls())) { return ClassSpec(cls, ClassSpec::SubTag{}); } return Top; }