text: /*
     +----------------------------------------------------------------------+
     | HipHop for PHP                                                       |
     +----------------------------------------------------------------------+
     | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com)  |
     +----------------------------------------------------------------------+
     | This source file is subject to version 3.01 of the PHP license,      |
     | that is bundled with this package in the file LICENSE, and is        |
     | available through the world-wide-web at the following url:           |
     | http://www.php.net/license/3_01.txt                                  |
     | If you did not receive a copy of the PHP license and are unable to   |
     | obtain it through the world-wide-web, please send a note to          |
     | license@php.net so we can mail you a copy immediately.               |
     +----------------------------------------------------------------------+
  */
  
  #ifndef incl_HPHP_VARIANT_H_
  #define incl_HPHP_VARIANT_H_
  
  #include "hphp/runtime/base/array-data.h"
  #include "hphp/runtime/base/ref-data.h"
  #include "hphp/runtime/base/tv-conversions.h"
  #include "hphp/runtime/base/tv-mutate.h"
  #include "hphp/runtime/base/tv-refcount.h"
  #include "hphp/runtime/base/tv-val.h"
  #include "hphp/runtime/base/tv-variant.h"
  #include "hphp/runtime/base/type-array.h"
  #include "hphp/runtime/base/type-object.h"
  #include "hphp/runtime/base/type-resource.h"
  #include "hphp/runtime/base/type-string.h"
  #include "hphp/runtime/base/typed-value.h"
  
  #include <algorithm>
  #include <type_traits>
  
  namespace HPHP {
  ///////////////////////////////////////////////////////////////////////////////
  
  // Forward declare these to avoid including tv-conversions.h which has a
  // circular dependency with this file.
  void tvCastToVecInPlace(TypedValue*);
  void tvCastToDictInPlace(TypedValue*);
  void tvCastToKeysetInPlace(TypedValue*);
  void tvCastToVArrayInPlace(TypedValue*);
  void tvCastToDArrayInPlace(TypedValue*);
  
  struct OptionalVariant;
  
  /*
   * These structs are substitutes for Variant& or const Variant&, when there's
   * no actual Variant in memory to take a reference to (when working with a
   * tv_lval from an Array, for example). They should be treated with the same
   * care as normal references: when copying or storing a variant_ref or
   * const_variant_ref, think carefully about the lifetime of the underlying
   * data.
   */
  namespace variant_ref_detail {
  template<bool is_const>
  struct base {
  private:
    using tv_val_t = typename std::conditional<is_const, tv_rval, tv_lval>::type;
  
  public:
    explicit base(tv_val_t val) : m_val{val} {}
  
    DataType getType() const {
      auto const t = type(m_val);
      return isRefType(t) ? val(m_val).pref->tv()->m_type : t;
    }
    bool isNull()      const { return isNullType(getType()); }
    bool isBoolean()   const { return isBooleanType(getType()); }
    bool isInteger()   const { return isIntType(getType()); }
    bool isDouble()    const { return isDoubleType(getType()); }
    bool isString()    const { return isStringType(getType()); }
    bool isArray()     const { return isArrayLikeType(getType()); }
    bool isPHPArray()  const { return isArrayType(getType()); }
    bool isVecArray()  const { return isVectype(getType()); }
    bool isDict()      const { return isDictType(getType()); }
    bool isKeyset()    const { return isKeysetType(getType()); }
    bool isHackArray() const { return isHackArrayType(getType()); }
    bool isObject()    const { return isObjectType(getType()); }
    bool isResource()  const { return isResourceType(getType()); }
    bool isFunc()      const { return isFuncType(getType()); }
    bool isClass()     const { return isClassType(getType()); }
  
    auto toBoolean() const { return tvCastToBoolean(*m_val); }
    auto toInt64()   const { return tvCastToInt64(*m_val); }
    auto toDouble()  const { return tvCastToDouble(*m_val); }
    auto toString()  const { return HPHP::toString(m_val); }
    auto toArray()   const { return HPHP::toArray(m_val); }
  
    auto& asCStrRef() const { return HPHP::asCStrRef(m_val); }
    auto& asCArrRef() const { return HPHP::asCArrRef(m_val); }
    auto& asCObjRef() const { return HPHP::asCObjRef(m_val); }
  
    auto& toCStrRef() const { return HPHP::toCStrRef(m_val); }
    auto& toCArrRef() const { return HPHP::toCArrRef(m_val); }
    auto& toCObjRef() const { return HPHP::toCObjRef(m_val); }
  
    auto getArrayData() const {
      assertx(isArray());
      return isRefType(type(m_val)) ? val(m_val).pref->tv()->m_data.parr
                                    : val(m_val).parr;
    }
  
    auto toFuncVal() const {
      assertx(isFunc());
      return isRefType(type(m_val)) ? val(m_val).pref->tv()->m_data.pfunc
        : val(m_val).pfunc;
    }
  
    auto toClassVal() const {
      assertx(isClass());
      return isRefType(type(m_val)) ? val(m_val).pref->tv()->m_data.pclass
        : val(m_val).pclass;
    }
  
  protected:
    const tv_val_t m_val;
  };
  }
  
  struct const_variant_ref;
  
  struct variant_ref : variant_ref_detail::base<false> {
    using variant_ref_detail::base<false>::base;
  
    /* implicit */ variant_ref(Variant& v);
  
    /* implicit */ operator const_variant_ref() const;
  
    tv_lval lval() const { return m_val; }
  
    void unset() const {
      tvMove(make_tv<KindOfUninit>(), m_val);
    }
  };
  
  struct const_variant_ref : variant_ref_detail::base<true> {
    using variant_ref_detail::base<true>::base;
  
    /* implicit */ const_variant_ref(const Variant& v);
  
    /*
     * Equivalent to const_cast<Variant&>(const Variant&), so use with care.
     */
    variant_ref as_variant_ref() const {
      return variant_ref{m_val.as_lval()};
    }
  
    tv_rval rval() const { return m_val; }
  };
  
  inline variant_ref::operator const_variant_ref() const {
    return const_variant_ref{m_val};
  }
  
  /*
   * This class predates HHVM.
   *
   * In hphpc, when type inference failed to know type of a variable, we
   * would use Variant to represent the php variable in generated C++.
   *
   * Now Variant is only used in C++ extensions, and the API is mostly
   * legacy stuff.  If you're writing a C++ extension, try to avoid
   * Variant when you can (but you often can't, and we don't really have
   * a replacement yet, sorry).
   *
   * In C++ extensions, this class can be used as a generic handle to
   * one of our other data types (e.g. StringData, ArrayData), and it
   * may also be a handle to a RefData.
   *
   * Beware:
   *
   *    For historical reasons, this class does a lot of things you
   *    don't really expect in a well-behaved C++ class.
   *
   *    For example, the copy constructor is not a copy constructor (it
   *    unboxes refs and converts KindOfUninit to KindOfNull).  A
   *    similar story applies to the move constructor.  (And this means
   *    we may actually rely on whether copy elision (NRVO etc) is
   *    happening in some places for correctness.)
   *
   *    Use carefully.
   *
   */
  
  struct Variant : private TypedValue {
    // Used by VariantTraits to create a folly::Optional-like
    // optional Variant which fits in 16 bytes.
    using Optional = OptionalVariant;
  
    enum class NullInit {};
    enum class NoInit {};
    enum class CellCopy {};
    enum class CellDup {};
    enum class ArrayInitCtor {};
    enum class StrongBind {};
    enum class Attach {};
    enum class WithRefBind {};
    enum class Wrap {};
  
    Variant() noexcept { m_type = KindOfUninit; }
    explicit Variant(NullInit) noexcept { m_type = KindOfNull; }
    explicit Variant(NoInit) noexcept {}
  
    /* implicit */ Variant(bool    v) noexcept {
      m_type = KindOfBoolean; m_data.num = v;
    }
    /* implicit */ Variant(int     v) noexcept {
      m_type = KindOfInt64; m_data.num = v;
    }
    // The following two overloads will accept int64_t whether it's
    // implemented as long or long long.
    /* implicit */ Variant(long   v) noexcept {
      m_type = KindOfInt64; m_data.num = v;
    }
    /* implicit */ Variant(long long v) noexcept {
      m_type = KindOfInt64; m_data.num = v;
    }
    /* implicit */ Variant(unsigned v) noexcept {
      m_type = KindOfInt64; m_data.num = v;
    }
    /* implicit */ Variant(unsigned long v) noexcept {
      m_type = KindOfInt64; m_data.num = v;
    }
    /* implicit */ Variant(unsigned long long v) noexcept {
      m_type = KindOfInt64; m_data.num = v;
    }
    /* implicit */ Variant(double    v) noexcept {
      m_type = KindOfDouble; m_data.dbl = v;
    }
  
    /* implicit */ Variant(const char* v) {
      m_type = KindOfString;
      m_data.pstr = StringData::Make(v);
    }
    /* implicit */ Variant(const std::string &v) {
      m_type = KindOfString;
      StringData *s = StringData::Make(v.c_str(), v.size(), CopyString);
      assertx(s);
      m_data.pstr = s;
    }
    /* implicit */ Variant(const StaticString &v) noexcept {
      assertx(v.get() && !v.get()->isRefCounted());
      m_type = KindOfPersistentString;
      m_data.pstr = v.get();
    }
  
    Variant(const Variant& other, WithRefBind) {
      tvDupWithRef(*other.asTypedValue(), *asTypedValue());
      if (m_type == KindOfUninit) m_type = KindOfNull;
    }
  
    /* implicit */ Variant(const String& v) noexcept : Variant(v.get()) {}
    /* implicit */ Variant(const Array& v) noexcept : Variant(v.get()) { }
    /* implicit */ Variant(const Object& v) noexcept : Variant(v.get()) {}
    /* implicit */ Variant(const Resource& v) noexcept
    : Variant(v.hdr()) {}
  
    /* implicit */ Variant(Class* v) {
      m_type = KindOfClass;
      m_data.pclass = v;
    }
    /*
     * Explicit conversion constructors. These all manipulate ref-counts of bare
     * pointers as a side-effect, so we want to be explicit when its happening.
     */
    explicit Variant(StringData* v) noexcept;
    explicit Variant(ArrayData* v) noexcept {
      if (v) {
        m_data.parr = v;
        if (v->isRefCounted()) {
          m_type = v->toDataType();
          v->rawIncRefCount();
        } else {
          m_type = v->toPersistentDataType();
        }
      } else {
        m_type = KindOfNull;
      }
    }
    explicit Variant(ObjectData* v) noexcept {
      if (v) {
        m_type = KindOfObject;
        m_data.pobj = v;
        v->incRefCount();
      } else {
        m_type = KindOfNull;
      }
    }
    explicit Variant(RefData* r) noexcept {
      if (r) {
        m_type = KindOfRef;
        m_data.pref = r;
        r->incRefCount();
      } else {
        m_type = KindOfNull;
      }
    }
  
    explicit Variant(const Func* f) noexcept {
      assertx(f);
      m_type = KindOfFunc;
      m_data.pfunc = f;
    }
  
    template <typename T>
    explicit Variant(const req::ptr<T>& ptr) : Variant(ptr.get()) { }
    template <typename T>
    explicit Variant(req::ptr<T>&& ptr) noexcept
      : Variant(ptr.detach(), Attach{}) { }
  
    /*
     * Creation constructor from ArrayInit that avoids a null check and an
     * inc-ref.
     */
    explicit Variant(ArrayData* ad, DataType dt, ArrayInitCtor) noexcept {
      assertx(ad->toDataType() == dt);
      m_type = dt;
      m_data.parr = ad;
    }
  
    enum class PersistentArrInit {};
    Variant(const ArrayData* ad, DataType dt, PersistentArrInit) noexcept {
      assertx(ad->toPersistentDataType() == dt);
      assertx(!ad->isRefCounted());
      m_data.parr = const_cast<ArrayData*>(ad);
      m_type = dt;
    }
  
    enum class PersistentStrInit {};
    explicit Variant(const StringData *s, PersistentStrInit) noexcept {
      assertx(!s->isRefCounted());
      m_data.pstr = const_cast<StringData*>(s);
      m_type = KindOfPersistentString;
    }
  
    // These are prohibited, but declared just to prevent accidentally
    // calling the bool constructor just because we had a pointer to
    // const.
    /* implicit */ Variant(const void*) = delete;
    template<typename Ret, typename... Args>
    /* implicit */ Variant(Ret (*)(Args...)) = delete;
    template<class Class, typename Ret, typename... Args>
    /* implicit */ Variant(Ret (Class::*)(Args...)) = delete;
    template<class Class, typename Ret, typename... Args>
    /* implicit */ Variant(Ret (Class::*)(Args...) const) = delete;
  
    //////////////////////////////////////////////////////////////////////
  
    /*
     * Copy constructor and copy assignment do not semantically make
     * copies: they unbox refs and turn uninits to null.
     */
  
    Variant(const Variant& v) noexcept;
   /* implicit */  Variant(const_variant_ref v) noexcept;
  
    Variant(const Variant& v, CellCopy) noexcept {
      m_type = v.m_type;
      m_data = v.m_data;
    }
  
    Variant(const Variant& v, CellDup) noexcept {
      m_type = v.m_type;
      m_data = v.m_data;
      tvIncRefGen(*asTypedValue());
    }
  
    Variant(StrongBind, Variant& v) {
      assertx(tvIsPlausible(v));
      tvBoxIfNeeded(*v.asTypedValue());
      refDup(*v.asTypedValue(), *asTypedValue());
    }
  
    Variant& operator=(const Variant& v) noexcept {
      return assign(v);
    }
  
    /*
     * Move ctors
     *
     * Note: not semantically moves.  Like our "copy constructor", these
     * unbox refs and turn uninits to null.
     */
  
    Variant(Variant&& v) noexcept {
      if (UNLIKELY(isRefType(v.m_type))) {
        // We can't avoid the refcounting when it's a ref.  Do basically
        // what a copy would have done.
        moveRefHelper(std::move(v));
        return;
      }
  
      assertx(this != &v);
      if (v.m_type != KindOfUninit) {
        m_type = v.m_type;
        m_data = v.m_data;
        v.m_type = KindOfNull;
      } else {
        m_type = KindOfNull;
      }
    }
  
    // Move ctor for strings
    /* implicit */ Variant(String&& v) noexcept {
      StringData *s = v.get();
      if (LIKELY(s != nullptr)) {
        m_data.pstr = s;
        m_type = s->isRefCounted() ? KindOfString : KindOfPersistentString;
        v.detach();
      } else {
        m_type = KindOfNull;
      }
    }
  
    // Move ctor for arrays
    /* implicit */ Variant(Array&& v) noexcept {
      ArrayData *a = v.get();
      if (LIKELY(a != nullptr)) {
        m_data.parr = a;
        m_type = a->isRefCounted() ? a->toDataType() : a->toPersistentDataType();
        v.detach();
      } else {
        m_type = KindOfNull;
      }
    }
  
    // Move ctor for objects
    /* implicit */ Variant(Object&& v) noexcept {
      ObjectData *pobj = v.get();
      if (pobj) {
        m_type = KindOfObject;
        m_data.pobj = pobj;
        v.detach();
      } else {
        m_type = KindOfNull;
      }
    }
  
    // Move ctor for resources
    /* implicit */ Variant(Resource&& v) noexcept {
      auto hdr = v.hdr();
      if (hdr) {
        m_type = KindOfResource;
        m_data.pres = hdr;
        v.detachHdr();
      } else {
        m_type = KindOfNull;
      }
    }
  
    /*
     * Move assign
     *
     * Note: not semantically moves.  Like our "copies", these unbox
     * refs and turn uninits to null.
     */
    Variant& operator=(Variant &&rhs) noexcept {
      assertx(this != &rhs); // we end up as null on a self move-assign.
      if (isRefType(rhs.m_type)) return *this = *rhs.m_data.pref->var();
  
      Variant& lhs = isRefType(m_type) ? *m_data.pref->var() : *this;
  
      Variant goner((NoInit()));
      goner.m_data = lhs.m_data;
      goner.m_type = lhs.m_type;
  
      if (rhs.m_type == KindOfUninit) {
        lhs.m_type = KindOfNull;
      } else {
        lhs.m_type = rhs.m_type;
        lhs.m_data = rhs.m_data;
        rhs.m_type = KindOfNull;
      }
  
      return *this;
    }
  
    ALWAYS_INLINE ~Variant() noexcept {
      tvDecRefGen(asTypedValue());
      if (debug) {
        memset(this, kTVTrashFill2, sizeof(*this));
      }
    }
  
  
    //////////////////////////////////////////////////////////////////////
  
    /*
     * During sweeping, request-allocated things are not allowed to be decref'd
     * or manipulated.  This function is used to cause a Variant to go
     * into a state where its destructor will have no effects on the
     * request local heap, in cases where sweepable objects can't
     * organize things to avoid running Variant destructors.
     */
    void releaseForSweep() { m_type = KindOfNull; }
  
    //////////////////////////////////////////////////////////////////////
  
    /**
     * Break bindings and set to uninit.
     */
    void unset() {
      auto const old = *asTypedValue();
      m_type = KindOfUninit;
      tvDecRefGen(old);
    }
  
    /**
     * set to null without breaking bindings (if any), faster than v_a = null;
     */
    void setNull() noexcept {
      tvSetNull(*asTypedValue());
    }
  
    /**
     * Clear the original data, and set it to be the same as in v, and if
     * v is referenced, keep the reference.
     */
    Variant& setWithRef(TypedValue v) noexcept {
      tvSetWithRef(v, *asTypedValue());
      if (m_type == KindOfUninit) m_type = KindOfNull;
      return *this;
    }
    Variant& setWithRef(const Variant& v) noexcept {
      return setWithRef(*v.asTypedValue());
    }
  
    static Variant attach(TypedValue tv) noexcept {
      return Variant{tv, Attach{}};
    }
    static Variant attach(StringData* var) noexcept {
      return Variant{var, Attach{}};
    }
    static Variant attach(ArrayData* var) noexcept {
      return Variant{var, Attach{}};
    }
    static Variant attach(ObjectData* var) noexcept {
      return Variant{var, Attach{}};
    }
    static Variant attach(ResourceData* var) noexcept {
      return Variant{var, Attach{}};
    }
    static Variant attach(ResourceHdr* var) noexcept {
      return Variant{var, Attach{}};
    }
    static Variant attach(RefData* var) noexcept {
      return Variant{var, Attach{}};
    }
  
    static Variant wrap(TypedValue tv) noexcept {
      return Variant{tv, Wrap{}};
    }
  
  ///////////////////////////////////////////////////////////////////////////////
  // int64
  
    ALWAYS_INLINE int64_t asInt64Val() const {
      assertx(m_type == KindOfInt64);
      return m_data.num;
    }
  
    ALWAYS_INLINE int64_t toInt64Val() const {
      assertx(is(KindOfInt64));
      return
          LIKELY(m_type == KindOfInt64) ?
          m_data.num : m_data.pref->var()->m_data.num;
    }
  
  ///////////////////////////////////////////////////////////////////////////////
  // double
  
    ALWAYS_INLINE double asDoubleVal() const {
      assertx(m_type == KindOfDouble);
      return m_data.dbl;
    }
  
    ALWAYS_INLINE double toDoubleVal() const {
      assertx(is(KindOfDouble));
      return
          LIKELY(m_type == KindOfDouble) ?
          m_data.dbl : m_data.pref->var()->m_data.dbl;
    }
  
  ///////////////////////////////////////////////////////////////////////////////
  // boolean
  
    ALWAYS_INLINE bool asBooleanVal() const {
      assertx(m_type == KindOfBoolean);
      return m_data.num;
    }
  
    ALWAYS_INLINE bool toBooleanVal() const {
      assertx(is(KindOfBoolean));
      return
          LIKELY(m_type == KindOfBoolean) ?
          m_data.num : m_data.pref->var()->m_data.num;
    }
  
  ///////////////////////////////////////////////////////////////////////////////
  // string
  
    ALWAYS_INLINE const String& asCStrRef() const {
      assertx(isStringType(m_type) && m_data.pstr);
      return *reinterpret_cast<const String*>(&m_data.pstr);
    }
  
    ALWAYS_INLINE const String& toCStrRef() const {
      assertx(isString());
      assertx(isRefType(m_type)
              ? m_data.pref->var()->m_data.pstr
              : m_data.pstr);
      return *reinterpret_cast<const String*>(LIKELY(isStringType(m_type)) ?
          &m_data.pstr : &m_data.pref->tv()->m_data.pstr);
    }
  
    ALWAYS_INLINE String& asStrRef() {
      assertx(isStringType(m_type) && m_data.pstr);
      // The caller is likely going to modify the string, so we have to eagerly
      // promote KindOfPersistentString -> KindOfString.
      m_type = KindOfString;
      return *reinterpret_cast<String*>(&m_data.pstr);
    }
  
    ALWAYS_INLINE String& toStrRef() {
      assertx(isString());
      assertx(isRefType(m_type)
              ? m_data.pref->var()->m_data.pstr
              : m_data.pstr);
      // The caller is likely going to modify the string, so we have to eagerly
      // promote KindOfPersistentString -> KindOfString.
      auto tv = LIKELY(isStringType(m_type)) ? this : m_data.pref->tv();
      tv->m_type = KindOfString;
      return *reinterpret_cast<String*>(&tv->m_data.pstr);
    }
  
  ///////////////////////////////////////////////////////////////////////////////
  // array
  
    ALWAYS_INLINE const Array& asCArrRef() const {
      assertx(isArrayLikeType(m_type) && m_data.parr);
      return *reinterpret_cast<const Array*>(&m_data.parr);
    }
  
    ALWAYS_INLINE const Array& toCArrRef() const {
      assertx(isArray());
      assertx(isRefType(m_type)
              ? m_data.pref->var()->m_data.parr
              : m_data.parr);
      return *reinterpret_cast<const Array*>(LIKELY(isArrayLikeType(m_type)) ?
          &m_data.parr : &m_data.pref->tv()->m_data.parr);
    }
  
    ALWAYS_INLINE Array& asArrRef() {
      assertx(isArrayLikeType(m_type) && m_data.parr);
      m_type = m_data.parr->toDataType();
      return *reinterpret_cast<Array*>(&m_data.parr);
    }
  
    ALWAYS_INLINE Array& toArrRef() {
      assertx(isArray());
      assertx(isRefType(m_type)
              ? m_data.pref->var()->m_data.parr
              : m_data.parr);
      auto tv = LIKELY(isArrayLikeType(m_type)) ? this : m_data.pref->tv();
      tv->m_type = tv->m_data.parr->toDataType();
      return *reinterpret_cast<Array*>(&tv->m_data.parr);
    }
  
  ///////////////////////////////////////////////////////////////////////////////
  // object
  
    ALWAYS_INLINE const Object& asCObjRef() const {
      assertx(m_type == KindOfObject && m_data.pobj);
      return *reinterpret_cast<const Object*>(&m_data.pobj);
    }
  
    ALWAYS_INLINE const Object& toCObjRef() const {
      assertx(is(KindOfObject));
      assertx(isRefType(m_type)
              ? m_data.pref->var()->m_data.pobj
              : m_data.pobj);
      return *reinterpret_cast<const Object*>(LIKELY(m_type == KindOfObject) ?
          &m_data.pobj : &m_data.pref->tv()->m_data.pobj);
    }
  
    ALWAYS_INLINE Object & asObjRef() {
      assertx(m_type == KindOfObject && m_data.pobj);
      return *reinterpret_cast<Object*>(&m_data.pobj);
    }
  
    ALWAYS_INLINE const Resource& asCResRef() const {
      assertx(m_type == KindOfResource && m_data.pres);
      return *reinterpret_cast<const Resource*>(&m_data.pres);
    }
  
    ALWAYS_INLINE const Resource& toCResRef() const {
      assertx(is(KindOfResource));
      assertx(isRefType(m_type)
              ? m_data.pref->var()->m_data.pres
              : m_data.pres);
      return *reinterpret_cast<const Resource*>(LIKELY(m_type == KindOfResource) ?
          &m_data.pres : &m_data.pref->tv()->m_data.pres);
    }
  
    ALWAYS_INLINE Resource & asResRef() {
      assertx(m_type == KindOfResource && m_data.pres);
      return *reinterpret_cast<Resource*>(&m_data.pres);
    }
  
    ALWAYS_INLINE Object& toObjRef() {
      assertx(is(KindOfObject));
      assertx(isRefType(m_type)
              ? m_data.pref->var()->m_data.pobj
              : m_data.pobj);
      return *reinterpret_cast<Object*>(LIKELY(m_type == KindOfObject) ?
          &m_data.pobj : &m_data.pref->tv()->m_data.pobj);
    }
  
    /**
     * Type testing functions
     */
    DataType getType() const {
      return isRefType(m_type) ? m_data.pref->var()->m_type : m_type;
    }
    DataType getRawType() const {
      return m_type;
    }
    bool is(DataType type) const {
      return getType() == type;
    }
    bool isInitialized() const {
      return m_type != KindOfUninit;
    }
    bool isNull() const {
      return isNullType(getType());
    }
    bool isBoolean() const {
      return getType() == KindOfBoolean;
    }
    bool isInteger() const {
      return getType() == KindOfInt64;
    }
    bool isDouble() const {
      return getType() == KindOfDouble;
    }
    bool isString() const {
      return isStringType(getType());
    }
    bool isArray() const {
      return isArrayLikeType(getType());
    }
    bool isPHPArray() const {
      return isArrayType(getType());
    }
    bool isVecArray() const {
      return isVecType(getType());
    }
    bool isDict() const {
      return isDictType(getType());
    }
    bool isKeyset() const {
      return isKeysetType(getType());
    }
    bool isHackArray() const {
      return isHackArrayType(getType());
    }
    bool isObject() const {
      return getType() == KindOfObject;
    }
    bool isResource() const {
      return getType() == KindOfResource;
    }
    bool isFunc() const {
      return isFuncType(getType());
    }
    bool isClass() const {
      return isClassType(getType());
    }
  
    bool isNumeric(bool checkString = false) const noexcept;
    DataType toNumeric(int64_t &ival, double &dval, bool checkString = false)
      const;
    bool isScalar() const noexcept;
    bool isIntVal() const {
      switch (m_type) {
        case KindOfUninit:
        case KindOfNull:
        case KindOfBoolean:
        case KindOfInt64:
        case KindOfObject:
        case KindOfResource:
          return true;
        case KindOfDouble:
        case KindOfPersistentString:
        case KindOfString:
        case KindOfPersistentVec:
        case KindOfVec:
        case KindOfPersistentDict:
        case KindOfDict:
        case KindOfPersistentKeyset:
        case KindOfKeyset:
        case KindOfPersistentArray:
        case KindOfArray:
        case KindOfFunc:
        case KindOfClass:
          return false;
        case KindOfRef:
          return m_data.pref->var()->isIntVal();
      }
      not_reached();
    }
  
    // Is "define('CONSTANT', <this value>)" legal?
    bool isAllowedAsConstantValue() const;
  
    /**
     * Whether or not there are at least two variables that are strongly bound.
     */
    bool isReferenced() const {
      return isRefType(m_type) && m_data.pref->isReferenced();
    }
  
    /**
     * Get reference count of weak or strong binding. For debugging purpose.
     */
    int getRefCount() const noexcept;
  
    bool getBoolean() const {
      assertx(getType() == KindOfBoolean);
      return isRefType(m_type) ? m_data.pref->var()->m_data.num : m_data.num;
    }
    int64_t getInt64() const {
      assertx(getType() == KindOfInt64);
      return isRefType(m_type) ? m_data.pref->var()->m_data.num : m_data.num;
    }
    double getDouble() const {
      assertx(getType() == KindOfDouble);
      return isRefType(m_type) ? m_data.pref->var()->m_data.dbl : m_data.dbl;
    }
  
    /**
     * Operators
     */
    Variant& assign(const Variant& v) noexcept {
      tvSet(tvToInitCell(*v.asTypedValue()), *asTypedValue());
      return *this;
    }
    Variant& assignRef(tv_lval tv) noexcept {
      tvSetRef(tv, asTypedValue());
      return *this;
    }
    Variant& assignRef(variant_ref v) noexcept {
      return assignRef(v.lval());
    }
    Variant& assignRef(Variant& v) noexcept {
      return assignRef(v.asTypedValue());
    }
    Variant& assignRef(VRefParam v) = delete;
  
    // Generic assignment operator. Forward argument (preserving rvalue-ness and
    // lvalue-ness) to the appropriate set function, as long as its not a Variant.
    template <typename T>
    typename std::enable_if<
      !std::is_same<
        Variant,
        typename std::remove_reference<typename std::remove_cv<T>::type>::type
      >::value
      &&
      !std::is_same<
        VarNR,
        typename std::remove_reference<typename std::remove_cv<T>::type>::type
      >::value,
      Variant&
    >::type operator=(T&& v) {
      set(std::forward<T>(v));
      return *this;
    }
  
    Variant  operator +  () const = delete;
    Variant &operator += (const Variant& v) = delete;
    Variant &operator += (int     n) = delete;
    Variant &operator += (int64_t   n) = delete;
    Variant &operator += (double  n)  = delete;
  
    Variant  operator -  () const = delete;
    Variant  operator -  (const Variant& v) const = delete;
    Variant &operator -= (const Variant& v) = delete;
    Variant &operator -= (int     n) = delete;
    Variant &operator -= (int64_t   n) = delete;
    Variant &operator -= (double  n) = delete;
  
    Variant  operator *  (const Variant& v) const = delete;
    Variant &operator *= (const Variant& v) = delete;
    Variant &operator *= (int     n) = delete;
    Variant &operator *= (int64_t   n) = delete;
    Variant &operator *= (double  n) = delete;
  
    Variant  operator /  (const Variant& v) const = delete;
    Variant &operator /= (const Variant& v) = delete;
    Variant &operator /= (int     n) = delete;
    Variant &operator /= (int64_t   n) = delete;
    Variant &operator /= (double  n) = delete;
  
    int64_t    operator %  (const Variant& v) const = delete;
    Variant &operator %= (const Variant& v) = delete;
    Variant &operator %= (int     n) = delete;
    Variant &operator %= (int64_t   n) = delete;
    Variant &operator %= (double  n) = delete;
  
    Variant  operator |  (const Variant& v) const = delete;
    Variant &operator |= (const Variant& v) = delete;
    Variant  operator &  (const Variant& v) const = delete;
    Variant &operator &= (const Variant& v) = delete;
    Variant  operator ^  (const Variant& v) const = delete;
    Variant &operator ^= (const Variant& v) = delete;
    Variant &operator <<=(int64_t n) = delete;
    Variant &operator >>=(int64_t n) = delete;
  
    Variant &operator ++ () = delete;
    Variant  operator ++ (int) = delete;
    Variant &operator -- () = delete;
    Variant  operator -- (int) = delete;
  
    /*
     * Variant used to implicitly convert to all these types.  (It still
     * implicitly converts *from* most of them.)
     *
     * We're leaving these functions deleted for now because we fear the
     * possibility of changes to overload resolution by not declaring
     * them.  Eventually when fewer of these types have implicit
     * conversions we'll remove them.
     */
    /* implicit */ operator bool   () const = delete;
    /* implicit */ operator char   () const = delete;
    /* implicit */ operator short  () const = delete;
    /* implicit */ operator int    () const = delete;
    /* implicit */ operator int64_t() const = delete;
    /* implicit */ operator double () const = delete;
    /* implicit */ operator String () const = delete;
    /* implicit */ operator Array  () const = delete;
    /* implicit */ operator Object () const = delete;
  
    /**
     * Explicit type conversions
     */
    bool toBoolean() const {
      if (isNullType(m_type)) return false;
      if (hasNumData(m_type)) return m_data.num;
      return toBooleanHelper();
    }
    char toByte() const { return (char)toInt64();}
    short toInt16(int base = 10) const { return (short)toInt64(base);}
    int toInt32(int base = 10) const { return (int)toInt64(base);}
    int64_t toInt64() const {
      if (isNullType(m_type)) return 0;
      if (hasNumData(m_type)) return m_data.num;
      return toInt64Helper(10);
    }
    int64_t toInt64(int base) const {
      if (isNullType(m_type)) return 0;
      if (hasNumData(m_type)) return m_data.num;
      return toInt64Helper(base);
    }
    double toDouble() const {
      if (m_type == KindOfDouble) return m_data.dbl;
      return toDoubleHelper();
    }
  
    String toString() const& {
      return HPHP::toString(asTypedValue());
    }
  
    String toString() && {
      if (isStringType(m_type)) {
        m_type = KindOfNull;
        return String::attach(m_data.pstr);
      }
      return toString();
    }
  
    // Convert a non-array-like type to a PHP array, leaving PHP arrays and Hack
    // arrays unchanged. Use toPHPArray() if you want the result to always be a
    // PHP array.
    Array toArray() const {
      return HPHP::toArray(asTypedValue());
    }
    Array toPHPArray() const {
      if (isArrayType(m_type)) return Array(m_data.parr);
      return toPHPArrayHelper();
    }
    Object toObject() const {
      if (m_type == KindOfObject) return Object{m_data.pobj};
      return toObjectHelper();
    }
    Resource toResource() const {
      if (m_type == KindOfResource) return Resource{m_data.pres};
      return toResourceHelper();
    }
  
    Array toVecArray() const {
      if (isVecType(m_type)) return Array{m_data.parr};
      auto copy = *this;
      tvCastToVecInPlace(copy.asTypedValue());
      assertx(copy.isVecArray());
      return Array::attach(copy.detach().m_data.parr);
    }
  
    Array toDict() const {
      if (isDictType(m_type)) return Array{m_data.parr};
      auto copy = *this;
      tvCastToDictInPlace(copy.asTypedValue());
      assertx(copy.isDict());
      return Array::attach(copy.detach().m_data.parr);
    }
  
    Array toKeyset() const {
      if (isKeysetType(m_type)) return Array{m_data.parr};
      auto copy = *this;
      tvCastToKeysetInPlace(copy.asTypedValue());
      assertx(copy.isKeyset());
      return Array::attach(copy.detach().m_data.parr);
    }
  
    Array toVArray() const {
      if (RuntimeOption::EvalHackArrDVArrs) return toVecArray();
      if (isArrayType(m_type)) return asCArrRef().toVArray();
      auto copy = *this;
      tvCastToVArrayInPlace(copy.asTypedValue());
      assertx(copy.isPHPArray() && copy.asCArrRef().isVArray());
      return Array::attach(copy.detach().m_data.parr);
    }
  
    Array toDArray() const {
      if (RuntimeOption::EvalHackArrDVArrs) return toDict();
      if (isArrayType(m_type)) return asCArrRef().toDArray();
      auto copy = *this;
      tvCastToDArrayInPlace(copy.asTypedValue());
      assertx(copy.isPHPArray() && copy.asCArrRef().isDArray());
      return Array::attach(copy.detach().m_data.parr);
    }
  
    template <typename T>
    typename std::enable_if<std::is_base_of<ResourceData,T>::value, bool>::type
    isa() const {
      if (m_type == KindOfResource) {
        return m_data.pres->data()->instanceof<T>();
      }
      if (isRefType(m_type) && m_data.pref->var()->m_type == KindOfResource) {
        return m_data.pref->var()->m_data.pres->data()->instanceof<T>();
      }
      return false;
    }
  
    template <typename T>
    typename std::enable_if<std::is_base_of<ObjectData,T>::value, bool>::type
    isa() const {
      if (m_type == KindOfObject) {
        return m_data.pobj->instanceof<T>();
      }
      if (isRefType(m_type) &&
                 m_data.pref->var()->m_type == KindOfObject) {
        return m_data.pref->var()->m_data.pobj->instanceof<T>();
      }
      return false;
    }
  
    /**
     * Whether or not calling toKey() will throw a bad type exception
     */
    bool canBeValidKey() const {
      return !isArrayType(getType()) && getType() != KindOfObject;
    }
  
    /*
     * Convert to a valid key or throw an exception. If convertStrKeys is true
     * int-like string keys will be converted to int keys.
     */
    VarNR toKey(const ArrayData*) const;
  
    /* Creating a temporary Array, String, or Object with no ref-counting and
     * no type checking, use it only when we have checked the variant type and
     * we are sure the internal data will have a reference until the temporary
     * one gets out-of-scope.
     */
    StrNR toStrNR() const {
      return StrNR(getStringData());
    }
    ArrNR toArrNR() const {
      return ArrNR(getArrayData());
    }
    ObjNR toObjNR() const {
      return ObjNR(getObjectData());
    }
  
    auto toFuncVal() const {
      return const_variant_ref{*this}.toFuncVal();
    }
    auto toClassVal() const {
      return const_variant_ref{*this}.toClassVal();
    }
    /*
     * Low level access that should be restricted to internal use.
     */
    int64_t *getInt64Data() const {
      assertx(getType() == KindOfInt64);
      return isRefType(m_type) ? &m_data.pref->var()->m_data.num :
                       const_cast<int64_t*>(&m_data.num);
    }
    double *getDoubleData() const {
      assertx(getType() == KindOfDouble);
      return isRefType(m_type) ? &m_data.pref->var()->m_data.dbl :
                       const_cast<double*>(&m_data.dbl);
    }
    StringData *getStringData() const {
      assertx(isStringType(getType()));
      return isRefType(m_type) ? m_data.pref->var()->m_data.pstr : m_data.pstr;
    }
    StringData *getStringDataOrNull() const {
      // This is a necessary evil because getStringData() returns
      // an undefined result if this is a null variant
      assertx(isNull() || isString());
      return isRefType(m_type) ?
        (isNullType(m_data.pref->var()->m_type) ? nullptr :
          m_data.pref->var()->m_data.pstr) :
        (isNullType(m_type) ? nullptr : m_data.pstr);
    }
    ArrayData *getArrayData() const {
      assertx(isArray());
      return isRefType(m_type) ? m_data.pref->var()->m_data.parr : m_data.parr;
    }
    ArrayData *getArrayDataOrNull() const {
      // This is a necessary evil because getArrayData() returns
      // an undefined result if this is a null variant
      assertx(isNull() || isArray());
      return isRefType(m_type) ?
        (isNullType(m_data.pref->var()->m_type) ? nullptr :
          m_data.pref->var()->m_data.parr) :
        (isNullType(m_type) ? nullptr : m_data.parr);
    }
    ObjectData* getObjectData() const {
      assertx(is(KindOfObject));
      return isRefType(m_type) ? m_data.pref->var()->m_data.pobj : m_data.pobj;
    }
    ObjectData *getObjectDataOrNull() const {
      // This is a necessary evil because getObjectData() returns
      // an undefined result if this is a null variant
      assertx(isNull() || is(KindOfObject));
      return isRefType(m_type) ?
        (isNullType(m_data.pref->var()->m_type) ? nullptr :
          m_data.pref->var()->m_data.pobj) :
        (isNullType(m_type) ? nullptr : m_data.pobj);
    }
    Variant *getRefData() const {
      assertx(isRefType(m_type));
      return m_data.pref->var();
    }
  
    int64_t getNumData() const { return m_data.num; }
  
    /*
     * Make any Variant strings and arrays not ref counted (e.g., static).
     * Use it, for example, if you need a long-lived Variant before the Memory
     * Manager has been initialized.
     * You will still get an assertion if the Variant is an object, resource, etc.
     */
    void setEvalScalar();
  
    /*
     * Access this Variant as a TypedValue. Does not unbox refs, etc.
     */
    const TypedValue* asTypedValue() const { return this; }
          TypedValue* asTypedValue()       { return this; }
  
    /*
     * Access this Variant as a Cell.  I.e. unboxes it if it was a
     * KindOfRef.
     */
    const Cell* asCell() const { return tvToCell(asTypedValue()); }
          Cell* asCell()       { return tvToCell(asTypedValue()); }
  
    /*
     * Read this Variant as an InitCell, without incrementing the
     * reference count.  I.e. unbox if it is boxed, and turn
     * KindOfUninit into KindOfNull.
     */
    Cell asInitCellTmp() const {
      if (UNLIKELY(isRefType(m_type))) {
        return *m_data.pref->tv();
      }
      if (m_type == KindOfUninit) return make_tv<KindOfNull>();
      return *this;
    }
  
    /*
     * Access this Variant as a Ref, converting it to a Ref it isn't
     * one.
     */
    Ref* asRef() { tvBoxIfNeeded(*asTypedValue()); return this; }
  
    TypedValue detach() noexcept {
      auto tv = *asTypedValue();
      m_type = KindOfNull;
      return tv;
    }
  
   private:
    ResourceData* getResourceData() const {
      assertx(is(KindOfResource));
      return isRefType(m_type) ? m_data.pref->var()->m_data.pres->data() :
                                   m_data.pres->data();
    }
  
    ResourceData* detachResourceData() {
      assertx(is(KindOfResource));
      if (UNLIKELY(isRefType(m_type))) {
        tvUnbox(*asTypedValue());
      }
      assertx(m_type == KindOfResource);
      m_type = KindOfNull;
      return m_data.pres->data();
    }
  
    ObjectData* detachObjectData() {
      assertx(is(KindOfObject));
      if (UNLIKELY(isRefType(m_type))) {
        tvUnbox(*asTypedValue());
      }
      assertx(m_type == KindOfObject);
      m_type = KindOfNull;
      return m_data.pobj;
    }
  
    template <typename T>
    friend typename std::enable_if<
      std::is_base_of<ResourceData,T>::value,
      ResourceData*
    >::type deref(const Variant& v) { return v.getResourceData(); }
  
    template <typename T>
    friend typename std::enable_if<
      std::is_base_of<ObjectData,T>::value,
      ObjectData*
    >::type deref(const Variant& v) { return v.getObjectData(); }
  
    template <typename T>
    friend typename std::enable_if<
      std::is_base_of<ResourceData,T>::value,
      ResourceData*
    >::type detach(Variant&& v) { return v.detachResourceData(); }
  
    template <typename T>
    friend typename std::enable_if<
      std::is_base_of<ObjectData,T>::value,
      ObjectData*
    >::type detach(Variant&& v) { return v.detachObjectData(); }
  
    explicit Variant(ResourceData* v) noexcept {
      if (v) {
        m_type = KindOfResource;
        m_data.pres = v->hdr();
        v->incRefCount();
      } else {
        m_type = KindOfNull;
      }
    }
    explicit Variant(ResourceHdr* v) noexcept {
      if (v) {
        m_type = KindOfResource;
        m_data.pres = v;
        v->incRefCount();
      } else {
        m_type = KindOfNull;
      }
    }
  
    /*
     * This set of constructors act like the normal constructors for the
     * given types except that they do not increment the reference count
     * of the passed value.  They are used for the req::ptr move constructor.
     */
    Variant(StringData* var, Attach) noexcept {
      if (var) {
        m_type = var->isRefCounted() ? KindOfString : KindOfPersistentString;
        m_data.pstr = var;
      } else {
        m_type = KindOfNull;
      }
    }
    Variant(ArrayData* var, Attach) noexcept {
      if (var) {
        m_type =
          var->isRefCounted() ? var->toDataType() : var->toPersistentDataType();
        m_data.parr = var;
      } else {
        m_type = KindOfNull;
      }
    }
    Variant(ObjectData* var, Attach) noexcept {
      if (var) {
        m_type = KindOfObject;
        m_data.pobj = var;
      } else {
        m_type = KindOfNull;
      }
    }
    Variant(ResourceData* var, Attach) noexcept {
      if (var) {
        m_type = KindOfResource;
        m_data.pres = var->hdr();
      } else {
        m_type = KindOfNull;
      }
    }
    Variant(ResourceHdr* var, Attach) noexcept {
      if (var) {
        m_type = KindOfResource;
        m_data.pres = var;
      } else {
        m_type = KindOfNull;
      }
    }
    Variant(RefData* var, Attach) noexcept {
      if (var) {
        m_type = KindOfRef;
        m_data.pref = var;
      } else {
        m_type = KindOfNull;
      }
    }
    Variant(TypedValue tv, Attach) noexcept : TypedValue(tv) {}
    Variant(TypedValue tv, Wrap) noexcept : TypedValue(tv) {
      tvIncRefGen(*asTypedValue());
    }
  
    bool isPrimitive() const { return !isRefcountedType(m_type); }
    bool isObjectConvertable() {
      assertx(!isRefType(m_type));
      return isNullType(m_type) ||
        (m_type == KindOfBoolean && !m_data.num) ||
        (isStringType(m_type) && m_data.pstr->empty());
    }
  
    void set(bool    v) noexcept;
    void set(int     v) noexcept;
    void set(int64_t   v) noexcept;
    void set(double  v) noexcept;
    void set(const char* v) = delete;
    void set(const std::string & v) {
      return set(String(v));
    }
    void set(StringData  *v) noexcept;
    void set(ArrayData   *v) noexcept;
    void set(ObjectData  *v) noexcept;
    void set(ResourceHdr *v) noexcept;
    void set(ResourceData *v) noexcept { set(v->hdr()); }
    void set(const StringData *v) = delete;
    void set(const ArrayData *v) = delete;
    void set(const ObjectData *v) = delete;
    void set(const ResourceData *v) = delete;
    void set(const ResourceHdr *v) = delete;
  
    void set(const String& v) noexcept { set(v.get()); }
    void set(const StaticString & v) noexcept;
    void set(const Array& v) noexcept { set(v.get()); }
    void set(const Object& v) noexcept { set(v.get()); }
    void set(const Resource& v) noexcept { set(v.hdr()); }
  
    void set(String&& v) noexcept { steal(v.detach()); }
    void set(Array&& v) noexcept { steal(v.detach()); }
    void set(Object&& v) noexcept { steal(v.detach()); }
    void set(Resource&& v) noexcept { steal(v.detachHdr()); }
  
    template<typename T>
    void set(const req::ptr<T> &v) noexcept {
      return set(v.get());
    }
  
    template <typename T>
    void set(req::ptr<T>&& v) noexcept {
      return steal(v.detach());
    }
  
    void steal(StringData* v) noexcept;
    void steal(ArrayData* v) noexcept;
    void steal(ObjectData* v) noexcept;
    void steal(ResourceHdr* v) noexcept;
    void steal(ResourceData* v) noexcept { steal(v->hdr()); }
  
  private:
    void moveRefHelper(Variant&& v) {
      assertx(tvIsPlausible(v));
  
      assertx(isRefType(v.m_type));
      m_type = v.m_data.pref->tv()->m_type; // Can't be KindOfUninit.
      m_data = v.m_data.pref->tv()->m_data;
      tvIncRefGen(*asTypedValue());
      decRefRef(v.m_data.pref);
      v.m_type = KindOfNull;
    }
  
    bool   toBooleanHelper() const;
    int64_t  toInt64Helper(int base = 10) const;
    double toDoubleHelper() const;
    Array  toPHPArrayHelper() const;
    Object toObjectHelper() const;
    Resource toResourceHelper() const;
  
    DataType convertToNumeric(int64_t *lval, double *dval) const;
  };
  
  Variant operator+(const Variant & lhs, const Variant & rhs) = delete;
  
  ///////////////////////////////////////////////////////////////////////////////
  
  /*
   * Definitions for some members of variant_ref et al. that use Variant.
   */
  inline variant_ref::variant_ref(Variant& v)
    : variant_ref_detail::base<false>{v.asTypedValue()}
  {}
  
  inline const_variant_ref::const_variant_ref(const Variant& v)
    : variant_ref_detail::base<true>{v.asTypedValue()}
  {}
  
  ///////////////////////////////////////////////////////////////////////////////
  
  struct RefResultValue {
    const Variant& get() const { return m_var; }
  private:
    Variant m_var;
  };
  
  struct VRefParamValue {
    template <class T> /* implicit */ VRefParamValue(const T &v) : m_var(v) {}
  
    /* implicit */ VRefParamValue() : m_var(Variant::NullInit()) {}
    /* implicit */ VRefParamValue(RefResult v)
      : m_var(Variant::StrongBind{},
              const_cast<Variant&>(reinterpret_cast<const Variant&>(v))) {} // XXX
    template <typename T>
    Variant &operator=(const T &v) const = delete;
    operator const Variant&() const { return m_var; }
    const Variant *operator&() const { return &m_var; } // FIXME
    const Variant *operator->() const { return &m_var; }
  
    const Variant& wrapped() const { return m_var; }
  
    explicit operator bool   () const { return m_var.toBoolean();}
    operator int    () const { return m_var.toInt32();}
    operator int64_t() const { return m_var.toInt64();}
    operator double () const { return m_var.toDouble();}
    operator String () const { return m_var.toString();}
    operator Array  () const { return m_var.toArray();}
    operator Object () const { return m_var.toObject();}
    explicit operator Resource () const { return m_var.toResource();}
  
    bool is(DataType type) const { return m_var.is(type); }
    bool isString() const { return m_var.isString(); }
    bool isObject() const { return m_var.isObject(); }
    bool isReferenced() const { return m_var.isReferenced(); }
    bool isNull() const { return m_var.isNull(); }
    bool isRefData() const { return isRefType(m_var.asTypedValue()->m_type); }
  
    bool toBoolean() const { return m_var.toBoolean(); }
    int64_t toInt64() const { return m_var.toInt64(); }
    double toDouble() const { return m_var.toDouble(); }
    String toString() const { return m_var.toString(); }
    StringData *getStringData() const { return m_var.getStringData(); }
    Array toArray() const { return m_var.toArray(); }
    Object toObject() const { return m_var.toObject(); }
    Resource toResource() const { return m_var.toResource(); }
    ObjectData *getObjectData() const { return m_var.getObjectData(); }
  
    bool isArray() const { return m_var.isArray(); }
    bool isHackArray() const { return m_var.isHackArray(); }
    bool isPHPArray() const { return m_var.isPHPArray(); }
    ArrNR toArrNR() const { return m_var.toArrNR(); }
  
    RefData* getRefData() const {
      assertx(isRefData());
      return m_var.asTypedValue()->m_data.pref;
    }
    RefData* getRefDataOrNull() const {
      return isRefData() ? m_var.asTypedValue()->m_data.pref : nullptr;
    }
    Variant* getVariantOrNull() const {
      return isRefData() ? m_var.asTypedValue()->m_data.pref->var() : nullptr;
    }
    void assignIfRef(const Variant& other) const {
      if (auto ref = getVariantOrNull()) *ref = other;
    }
    void assignIfRef(Variant&& other) const {
      if (auto ref = getVariantOrNull()) *ref = std::move(other);
    }
  private:
    Variant m_var;
  };
  
  ///////////////////////////////////////////////////////////////////////////////
  // VarNR
  
  struct VarNR : private TypedValueAux {
    static VarNR MakeKey(const String& s) {
      if (s.empty()) return VarNR(staticEmptyString());
      int64_t n;
      if (UNLIKELY(s.get()->isStrictlyInteger(n))) {
        if (checkHACIntishCast()) raise_intish_index_cast();
        return VarNR(n);
      }
      return VarNR(s);
    }
  
    // Use to hold variant that do not need ref-counting
    explicit VarNR(bool    v) { init(KindOfBoolean); m_data.num = (v?1:0);}
    explicit VarNR(int     v) { init(KindOfInt64  ); m_data.num = v;}
    // The following two overloads will accept int64_t whether it's
    // implemented as long or long long.
    explicit VarNR(long    v) { init(KindOfInt64  ); m_data.num = v;}
    explicit VarNR(long long v) { init(KindOfInt64  ); m_data.num = v;}
    explicit VarNR(uint64_t  v) { init(KindOfInt64  ); m_data.num = v;}
    explicit VarNR(double  v) { init(KindOfDouble ); m_data.dbl = v;}
  
    explicit VarNR(const StaticString &v) {
      assertx(v.get() && !v.get()->isRefCounted());
      init(KindOfPersistentString);
      m_data.pstr = v.get();
    }
  
    explicit VarNR(const String& v);
    explicit VarNR(const Array& v);
    explicit VarNR(const Object& v);
    explicit VarNR(StringData *v);
    explicit VarNR(const StringData *v) {
      assertx(v && !v->isRefCounted());
      init(KindOfPersistentString);
      m_data.pstr = const_cast<StringData*>(v);
    }
    explicit VarNR(ArrayData *v);
    explicit VarNR(const ArrayData*) = delete;
    explicit VarNR(ObjectData *v);
    explicit VarNR(const ObjectData*) = delete;
  
    explicit VarNR(TypedValue tv) { init(tv.m_type); m_data = tv.m_data; }
  
    VarNR(const VarNR &v) : TypedValueAux(v) {}
  
    explicit VarNR() { asVariant()->asTypedValue()->m_type = KindOfUninit; }
  
    ~VarNR() {
      if (debug) {
        checkRefCount();
        memset(this, kTVTrashFill2, sizeof(*this));
      }
    }
  
    TypedValue tv() const { return *this; }
  
    operator const Variant&() const { return *asVariant(); }
  
    bool isNull() const {
      return asVariant()->isNull();
    }
  private:
    /* implicit */ VarNR(const char* v) = delete;
    /* implicit */ VarNR(const std::string & v) = delete;
  
    void init(DataType dt) {
      m_type = dt;
      if (debug) varNrFlag() = NR_FLAG;
    }
    const Variant *asVariant() const {
      return &tvAsCVarRef(static_cast<const TypedValue*>(this));
    }
    Variant* asVariant() {
      return &tvAsVariant(static_cast<TypedValue*>(this));
    }
    void checkRefCount() {
      assertx(isRefcountedType(m_type) ? varNrFlag() == NR_FLAG : true);
  
      switch (m_type) {
        DT_UNCOUNTED_CASE:
          return;
        case KindOfString:
          assertx(m_data.pstr->checkCount());
          return;
        case KindOfVec:
        case KindOfDict:
        case KindOfKeyset:
        case KindOfArray:
          assertx(m_data.parr->checkCount());
          return;
        case KindOfObject:
          assertx(m_data.pobj->checkCount());
          return;
        case KindOfResource:
          assertx(m_data.pres->checkCount());
          return;
        case KindOfRef:
          assertx(m_data.pref->checkCount());
          return;
      }
      not_reached();
    }
  };
  
  //////////////////////////////////////////////////////////////////////
  
  /*
   * The lvalBlackHole is used in array operations when a NewElem can't
   * create a new slot.  (Basically if the next integer key in an array
   * is already at the maximum integer.)
   */
  Variant& lvalBlackHole();
  
  /*
   * The lvalBlackHole has request lifetime.
   */
  void initBlackHole();
  void clearBlackHole();
  
  ///////////////////////////////////////////////////////////////////////////////
  // breaking circular dependencies
  
  inline Variant Array::operator[](Cell key) const {
    return Variant::wrap(rvalAt(key).tv());
  }
  inline Variant Array::operator[](int key) const {
    return Variant::wrap(rvalAt(key).tv());
  }
  inline Variant Array::operator[](int64_t key) const {
    return Variant::wrap(rvalAt(key).tv());
  }
  inline Variant Array::operator[](const String& key) const {
    return Variant::wrap(rvalAt(key).tv());
  }
  inline Variant Array::operator[](const Variant& key) const {
    return Variant::wrap(rvalAt(key).tv());
  }
  
  inline void Array::append(const Variant& v) {
    append(*v.asTypedValue());
  }
  inline void Array::appendWithRef(const Variant& v) {
    appendWithRef(*v.asTypedValue());
  }
  inline void Array::prepend(const Variant& v) {
    prepend(*v.asTypedValue());
  }
  
  ALWAYS_INLINE Variant uninit_null() {
    return Variant();
  }
  
  ALWAYS_INLINE Variant init_null() {
    return Variant(Variant::NullInit());
  }
  
  inline void concat_assign(Variant &v1, const char* s2) = delete;
  
  inline void concat_assign(tv_lval lhs, const String& s2) {
    lhs = tvToCell(lhs);
    if (!isStringType(type(lhs))) cellCastToStringInPlace(lhs);
    asStrRef(lhs) += s2;
  }
  
  //////////////////////////////////////////////////////////////////////
  
  // Defined here for include order reasons.
  inline RefData::~RefData() {
    assertx(m_kind == HeaderKind::Ref);
    tvAsVariant(&m_tv).~Variant();
  }
  
  //////////////////////////////////////////////////////////////////////
  
  inline Array& forceToArray(Variant& var) {
    if (!var.isArray()) var = Variant(Array::Create());
    return var.toArrRef();
  }
  
  inline Array& forceToArray(tv_lval lval) {
    auto const inner = lval.unboxed();
    if (!isArrayLikeType(inner.type())) {
      tvMove(make_tv<KindOfArray>(ArrayData::Create()), inner);
    }
    return asArrRef(inner);
  }
  
  inline Array& forceToDict(Variant& var) {
    if (!var.isDict()) var = Variant(Array::CreateDict());
    return var.toArrRef();
  }
  
  inline Array& forceToDict(tv_lval lval) {
    auto const inner = lval.unboxed();
    if (!isDictType(inner.type())) {
      tvSet(make_tv<KindOfDict>(ArrayData::CreateDict()), inner);
    }
    return asArrRef(inner);
  }
  
  inline Array& forceToDArray(Variant& var) {
    if (RuntimeOption::EvalHackArrDVArrs) return forceToDict(var);
    if (!(var.isPHPArray() && var.toCArrRef().isDArray())) {
      var = Variant(Array::CreateDArray());
    }
    return var.toArrRef();
  }
  
  inline Array& forceToDArray(tv_lval lval) {
    if (RuntimeOption::EvalHackArrDVArrs) return forceToDict(lval);
    auto const inner = lval.unboxed();
    if (!(isArrayType(inner.type()) && inner.val().parr->isDArray())) {
      tvMove(make_array_like_tv(ArrayData::CreateDArray()), inner);
    }
    return asArrRef(inner);
  }
  
  //////////////////////////////////////////////////////////////////////
  
  ALWAYS_INLINE Variant empty_string_variant() {
    return Variant(staticEmptyString(), Variant::PersistentStrInit{});
  }
  
  template <typename T>
  inline Variant toVariant(const req::ptr<T>& p) {
    return p ? Variant(p) : Variant(false);
  }
  
  template <typename T>
  inline Variant toVariant(req::ptr<T>&& p) {
    return p ? Variant(std::move(p)) : Variant(false);
  }
  
  template <>
  inline bool is_null(const Variant& v) {
    return v.isNull();
  }
  
  template <typename T>
  inline bool isa_non_null(const Variant& v) {
    return v.isa<T>();
  }
  
  // Defined here to avoid introducing a dependency cycle between type-variant
  // and type-array
  ALWAYS_INLINE Cell Array::convertKey(Cell k) const {
    return cellToKey(k, m_arr ? m_arr.get() : staticEmptyArray());
  }
  ALWAYS_INLINE Cell Array::convertKey(const Variant& k) const {
    return convertKey(*k.asCell());
  }
  
  inline VarNR Variant::toKey(const ArrayData* ad) const {
    return VarNR(tvToKey(*this, ad));
  }
  
  struct alignas(16) OptionalVariant {
    OptionalVariant() {
      m_tv.m_type = kInvalidDataType;
    }
    ~OptionalVariant() {
      clear();
    }
    OptionalVariant(const OptionalVariant& other) {
      if (other.hasValue()) {
        construct(other.value());
        return;
      }
      m_tv.m_type = kInvalidDataType;
    }
    OptionalVariant(OptionalVariant&& other) noexcept {
      if (other.hasValue()) {
        construct(std::move(other.value()));
        other.m_tv.m_type = kInvalidDataType;
        return;
      }
      m_tv.m_type = kInvalidDataType;
    }
  
    template<typename Arg>
    OptionalVariant& operator=(Arg&& arg) {
      assign(std::forward<Arg>(arg));
      return *this;
    }
    OptionalVariant& operator=(const OptionalVariant& arg) {
      assign(arg);
      return *this;
    }
    OptionalVariant& operator=(OptionalVariant&& arg) {
      assign(std::move(arg));
      return *this;
    }
  
    bool hasValue() const {
      return m_tv.m_type != kInvalidDataType;
    }
    bool has_value() const {
      return hasValue();
    }
    Variant& value() {
      assertx(hasValue());
      return tvAsVariant(&m_tv);
    }
    const Variant& value() const {
      assertx(hasValue());
      return tvAsCVarRef(&m_tv);
    }
    void clear() {
      if (hasValue()) {
        auto const old = m_tv;
        m_tv.m_type = kInvalidDataType;
        tvDecRefGen(old);
      }
    }
    template <class... Args>
    Variant& emplace(Args&&... args) {
      clear();
      construct(std::forward<Args>(args)...);
      return value();
    }
    void assign(const Variant& other) {
      if (hasValue()) {
        value() = other;
        return;
      }
      construct(other);
    }
    void assign(Variant&& other) {
      if (hasValue()) {
        value() = std::move(other);
        return;
      }
      construct(std::move(other));
    }
    void assign(const OptionalVariant& other) {
      if (other.hasValue()) return assign(other.value());
      clear();
    }
    void assign(OptionalVariant&& other) {
      if (other.hasValue()) {
        assign(std::move(other.value()));
        other.m_tv.m_type = kInvalidDataType;
        return;
      }
      clear();
    }
  
    operator bool() const { return hasValue(); }
  
    const Variant* operator->() const { return &value(); }
    Variant* operator->() { return &value(); }
  
    const Variant& operator*() const & { return value(); }
    Variant& operator*() & { return value(); }
    Variant&& operator*() && { return std::move(value()); }
  private:
    template<typename... Args>
    void construct(Args&&... args) {
      new (&m_tv) Variant(std::forward<Args>(args)...);
    }
    TypedValue m_tv;
  };
  
  //////////////////////////////////////////////////////////////////////
  
  }
  
  #endif // incl_HPHP_VARIANT_H_
  