Type union_of()

in hphp/hhbbc/type-system.cpp [4327:4552]


Type union_of(Type a, Type b) {
  auto const combined = a.bits() | b.bits();
  auto const ham = a.m_ham | b.m_ham;

  auto const nodata = [&] { return Type { combined, ham }; };
  if (!a.hasData() && !b.hasData()) return nodata();

  auto const reuse = [&] (Type& dst) {
    dst.m_bits = combined;
    dst.m_ham = ham;
    assertx(dst.checkInvariants());
    return std::move(dst);
  };

  // Check if the given DWaitHandle or DObj is a subtype of each
  // other, returning the less specific type. Returns std::nullopt if
  // neither of them is.
  auto const whAndObj = [&] (Type& wh, Type& obj) -> Optional<Type> {
    if (obj.m_data.dobj.type == DObj::Sub &&
        obj.m_data.dobj.cls.same(wh.m_data.dwh.cls) &&
        !obj.m_data.dobj.isCtx) {
      return reuse(obj);
    }
    const DObj whDObj{ DObj::Sub, wh.m_data.dwh.cls };
    if (subtypeObj<true>(obj.m_data.dobj, whDObj)) return reuse(wh);
    if (subtypeObj<true>(whDObj, obj.m_data.dobj)) return reuse(obj);
    return std::nullopt;
  };

  // If both sides have the same specialization, check if they are
  // equal. If they are, reuse one of the sides. Otherwise if they are
  // not, return nodata() (we assume the union of two unequal
  // specializations is the full type). If only one side has a
  // specialization, check if the other side has the support bits. If
  // it does not, we can reuse the type with the specialization (since
  // it's not being unioned with anything on the other
  // side). Otherwise we have nodata(). If the union contains more
  // than one set of support bits, we can never keep any
  // specialization.

  // For wait handles, if one is a specialized object try to determine
  // if one of them is a subtype of the other. If not, demote the wait
  // handle to a DObj and use that.
  if (is_specialized_wait_handle(a)) {
    if (is_specialized_wait_handle(b)) {
      assertx(a.m_data.dwh.cls.same(b.m_data.dwh.cls));
      auto& atype = a.m_data.dwh.inner;
      auto& btype = b.m_data.dwh.inner;
      if (atype->subtypeOf(*btype)) return reuse(b);
      if (btype->subtypeOf(*atype)) return reuse(a);
      auto u = union_of(*atype, *btype);
      if (!u.strictSubtypeOf(BInitCell)) {
        a = demote_wait_handle(std::move(a));
      } else {
        *atype.mutate() = std::move(u);
      }
      return reuse(a);
    }
    if (is_specialized_obj(b)) {
      if (auto const t = whAndObj(a, b)) return *t;
      a = demote_wait_handle(std::move(a));
    } else {
      if (b.couldBe(BObj) || !subtypeOf(combined, BObj | kNonSupportBits)) {
        return nodata();
      }
      return reuse(a);
    }
  } else if (is_specialized_wait_handle(b)) {
    if (is_specialized_obj(a)) {
      if (auto const t = whAndObj(b, a)) return *t;
      b = demote_wait_handle(std::move(b));
    } else {
      if (a.couldBe(BObj) || !subtypeOf(combined, BObj | kNonSupportBits)) {
        return nodata();
      }
      return reuse(b);
    }
  }

  if (is_specialized_obj(a)) {
    if (is_specialized_obj(b)) {
      assertx(!is_specialized_wait_handle(a));
      assertx(!is_specialized_wait_handle(b));
      auto const& aobj = a.m_data.dobj;
      auto const& bobj = b.m_data.dobj;
      auto const isCtx = aobj.isCtx && bobj.isCtx;
      if (subtypeObj<false>(aobj, bobj)) {
        return setctx(reuse(b), isCtx);
      }
      if (subtypeObj<false>(bobj, aobj)) {
        return setctx(reuse(a), isCtx);
      }
      if (auto const ancestor = aobj.cls.commonAncestor(bobj.cls)) {
        // We need not to distinguish between Obj<=T and Obj=T, and
        // always return an Obj<=Ancestor, because that is the single
        // type that includes both children.
        auto ret = subObj(*ancestor);
        return setctx(reuse(ret), isCtx);
      }
      return nodata();
    }
    if (b.couldBe(BObj) || !subtypeOf(combined, BObj | kNonSupportBits)) {
      return nodata();
    }
    return reuse(a);
  } else if (is_specialized_obj(b)) {
    if (a.couldBe(BObj) || !subtypeOf(combined, BObj | kNonSupportBits)) {
      return nodata();
    }
    return reuse(b);
  }

  if (is_specialized_cls(a)) {
    if (is_specialized_cls(b)) {
      auto const& acls = a.m_data.dcls;
      auto const& bcls = b.m_data.dcls;
      auto const isCtx = acls.isCtx && bcls.isCtx;
      if (subtypeCls<false>(acls, bcls)) {
        return setctx(reuse(b), isCtx);
      }
      if (subtypeCls<false>(bcls, acls)) {
        return setctx(reuse(a), isCtx);
      }
      if (auto const ancestor = acls.cls.commonAncestor(bcls.cls)) {
        // Similar to above, this must always return an Obj<=Ancestor.
        auto ret = subCls(*ancestor);
        return setctx(reuse(ret), isCtx);
      }
      return nodata();
    }
    if (b.couldBe(BCls) || !subtypeOf(combined, BCls | kNonSupportBits)) {
      return nodata();
    }
    return reuse(a);
  } else if (is_specialized_cls(b)) {
    if (a.couldBe(BCls) || !subtypeOf(combined, BCls | kNonSupportBits)) {
      return nodata();
    }
    return reuse(b);
  }

  if (is_specialized_array_like(a)) {
    if (is_specialized_array_like(b)) {
      if (a.dualDispatchDataFn(b, DualDispatchSubtype<true>{})) return reuse(b);
      if (b.dualDispatchDataFn(a, DualDispatchSubtype<true>{})) return reuse(a);
      return a.dualDispatchDataFn(b, DualDispatchUnion{ combined, ham });
    }
    if (!subtypeOf(combined, BArrLikeN | kNonSupportBits)) return nodata();
    if (!b.couldBe(BArrLikeN)) return reuse(a);
    if (b.dispatchArrLikeNone(a, DualDispatchSubtype<true>{})) return reuse(a);
    return b.dispatchArrLikeNone(a, DualDispatchUnion{ combined, ham });
  } else if (is_specialized_array_like(b)) {
    if (!subtypeOf(combined, BArrLikeN | kNonSupportBits)) return nodata();
    if (!a.couldBe(BArrLikeN)) return reuse(b);
    if (a.dispatchArrLikeNone(b, DualDispatchSubtype<true>{})) return reuse(b);
    return a.dispatchArrLikeNone(b, DualDispatchUnion{ combined, ham });
  }

  if (is_specialized_string(a)) {
    if (is_specialized_string(b)) {
      if (a.m_data.sval == b.m_data.sval) return reuse(a);
      return nodata();
    }
    if (b.couldBe(BStr) || !subtypeOf(combined, BStr | kNonSupportBits)) {
      return nodata();
    }
    return reuse(a);
  } else if (is_specialized_string(b)) {
    if (a.couldBe(BStr) || !subtypeOf(combined, BStr | kNonSupportBits)) {
      return nodata();
    }
    return reuse(b);
  }

  if (is_specialized_lazycls(a)) {
    if (is_specialized_lazycls(b)) {
      if (a.m_data.lazyclsval == b.m_data.lazyclsval) return reuse(a);
      return nodata();
    }
    if (b.couldBe(BLazyCls) ||
        !subtypeOf(combined, BLazyCls | kNonSupportBits)) {
      return nodata();
    }
    return reuse(a);
  } else if (is_specialized_lazycls(b)) {
    if (a.couldBe(BLazyCls) ||
        !subtypeOf(combined, BLazyCls | kNonSupportBits)) {
      return nodata();
    }
    return reuse(b);
  }

  if (is_specialized_int(a)) {
    if (is_specialized_int(b)) {
      if (a.m_data.ival == b.m_data.ival) return reuse(a);
      return nodata();
    }
    if (b.couldBe(BInt) || !subtypeOf(combined, BInt | kNonSupportBits)) {
      return nodata();
    }
    return reuse(a);
  } else if (is_specialized_int(b)) {
    if (a.couldBe(BInt) || !subtypeOf(combined, BInt | kNonSupportBits)) {
      return nodata();
    }
    return reuse(b);
  }

  if (is_specialized_double(a)) {
    if (is_specialized_double(b)) {
      if (double_equals(a.m_data.dval, b.m_data.dval)) return reuse(a);
      return nodata();
    }
    if (b.couldBe(BDbl) || !subtypeOf(combined, BDbl | kNonSupportBits)) {
      return nodata();
    }
    return reuse(a);
  } else if (is_specialized_double(b)) {
    if (a.couldBe(BDbl) || !subtypeOf(combined, BDbl | kNonSupportBits)) {
      return nodata();
    }
    return reuse(b);
  }

  always_assert(false);
}