Example #1
0
void TypeConstraint::init() {
  if (UNLIKELY(s_typeNamesToTypes.empty())) {
    const struct Pair {
      const StringData* name;
      Type type;
    } pairs[] = {
      { makeStaticString("HH\\bool"),   { KindOfBoolean, MetaType::Precise }},
      { makeStaticString("HH\\int"),    { KindOfInt64,   MetaType::Precise }},
      { makeStaticString("HH\\float"),  { KindOfDouble,  MetaType::Precise }},
      { makeStaticString("HH\\string"), { KindOfString,  MetaType::Precise }},
      { makeStaticString("array"),      { KindOfArray,   MetaType::Precise }},
      { makeStaticString("HH\\resource"), { KindOfResource,
                                                         MetaType::Precise }},
      { makeStaticString("HH\\num"),    { KindOfDouble,  MetaType::Number }},
      { makeStaticString("self"),       { KindOfObject,  MetaType::Self }},
      { makeStaticString("parent"),     { KindOfObject,  MetaType::Parent }},
      { makeStaticString("callable"),   { KindOfObject,  MetaType::Callable }},
    };
    for (unsigned i = 0; i < sizeof(pairs) / sizeof(Pair); ++i) {
      s_typeNamesToTypes[pairs[i].name] = pairs[i].type;
    }
  }

  if (isTypeVar()) {
    // We kept the type variable type constraint to correctly check child
    // classes implementing abstract methods or interfaces.
    m_type.dt = KindOfInvalid;
    m_type.metatype = MetaType::Precise;
    return;
  }

  if (m_typeName == nullptr) {
    m_type.dt = KindOfInvalid;
    m_type.metatype = MetaType::Precise;
    return;
  }

  Type dtype;
  TRACE(5, "TypeConstraint: this %p type %s, nullable %d\n",
        this, m_typeName->data(), isNullable());
  auto const mptr = folly::get_ptr(s_typeNamesToTypes, m_typeName);
  if (mptr) dtype = *mptr;
  if (!mptr ||
      !(isHHType() || dtype.dt == KindOfArray ||
        dtype.metatype == MetaType::Parent ||
        dtype.metatype == MetaType::Self ||
        dtype.metatype == MetaType::Callable)) {
    TRACE(5, "TypeConstraint: this %p no such type %s, treating as object\n",
          this, m_typeName->data());
    m_type = { KindOfObject, MetaType::Precise };
    m_namedEntity = Unit::GetNamedEntity(m_typeName);
    TRACE(5, "TypeConstraint: NamedEntity: %p\n", m_namedEntity);
    return;
  }
  m_type = dtype;
  assert(m_type.dt != KindOfStaticString);
  assert(IMPLIES(isParent(), m_type.dt == KindOfObject));
  assert(IMPLIES(isSelf(), m_type.dt == KindOfObject));
  assert(IMPLIES(isCallable(), m_type.dt == KindOfObject));
}
Example #2
0
bool
TypeConstraint::checkPrimitive(DataType dt) const {
  assert(m_type.dt != KindOfObject);
  assert(dt != KindOfRef);
  if (isNullable() && IS_NULL_TYPE(dt)) return true;
  return equivDataTypes(m_type.dt, dt);
}
Example #3
0
void TypeConstraint::verifyFail(const Func* func, int paramNum,
                                const TypedValue* tv) const {
  Transl::VMRegAnchor _;
  std::ostringstream fname;
  fname << func->fullName()->data() << "()";
  const StringData* tn = typeName();
  if (isSelf()) {
    selfToTypeName(func, &tn);
  } else if (isParent()) {
    parentToTypeName(func, &tn);
  }

  auto const givenType = describe_actual_type(tv);

  if (isExtended()) {
    // Extended type hints raise warnings instead of recoverable
    // errors for now, to ease migration (we used to not check these
    // at all at runtime).
    assert(
      (isSoft() || isNullable()) &&
      "Only nullable and soft extended type hints are currently implemented");
    raise_debugging(
      "Argument %d to %s must be of type %s, %s given",
      paramNum + 1, fname.str().c_str(), fullName().c_str(), givenType);
  } else {
    raise_recoverable_error(
      "Argument %d passed to %s must be an instance of %s, %s given",
      paramNum + 1, fname.str().c_str(), tn->data(), givenType);
  }
}
Example #4
0
bool
TypeConstraint::checkPrimitive(DataType dt) const {
  assert(m_type.dt != KindOfObject);
  assert(dt != KindOfRef);
  if (isNullable() && dt == KindOfNull) return true;
  if (isNumber()) { return IS_INT_TYPE(dt) || IS_DOUBLE_TYPE(dt); }
  return equivDataTypes(m_type.dt, dt);
}
Example #5
0
void CMAny::calcLastPos(CMStateSet& toSet) const
{
    // If we are an epsilon node, then the last pos is an empty set
    if (isNullable())
        toSet.zeroBits();
    // Otherwise, its just the one bit of our position
    else
        toSet.setBit(fPosition);
}
Example #6
0
set<int> SyntaxSymbolAttributeCache::getFirst(const int *begin, const int *end)
{
    set<int> r;
    for (; begin != end; ++begin) {
        auto &set = getFirst(*begin);
        r.insert(set.begin(), set.end());
        if (!isNullable(*begin)) break;
    }
    return r;
}
Example #7
0
void TypeConstraint::verifyFail(const Func* func, int paramNum,
                                TypedValue* tv) const {
  JIT::VMRegAnchor _;

  const StringData* tn = typeName();
  if (isSelf()) {
    selfToTypeName(func, &tn);
  } else if (isParent()) {
    parentToTypeName(func, &tn);
  }

  auto const givenType = describe_actual_type(tv);

  auto c = tvToCell(tv);
  if (isArray() && !isSoft() && !func->mustBeRef(paramNum) &&
      c->m_type == KindOfObject && c->m_data.pobj->isCollection()) {
    // To ease migration, the 'array' type constraint will implicitly cast
    // collections to arrays, provided the type constraint is not soft and
    // the parameter is not by reference. We raise a notice to let the user
    // know that there was a type mismatch and that an implicit conversion
    // was performed.
    raise_notice(
      folly::format(
        "Argument {} to {}() must be of type {}, {} given; argument {} was "
        "implicitly cast to array",
        paramNum + 1, func->fullName()->data(), fullName(), givenType,
        paramNum + 1
      ).str()
    );
    tvCastToArrayInPlace(tv);
    return;
  }

  if (isExtended() && isSoft()) {
    // Soft extended type hints raise warnings instead of recoverable
    // errors, to ease migration.
    raise_debugging(
      "Argument %d to %s() must be of type %s, %s given",
      paramNum + 1, func->fullName()->data(), fullName().c_str(), givenType);
  } else if (isExtended() && isNullable()) {
    raise_typehint_error(
      folly::format(
        "Argument {} to {}() must be of type {}, {} given",
        paramNum + 1, func->fullName()->data(), fullName(), givenType
      ).str()
    );
  } else {
    raise_typehint_error(
      folly::format(
        "Argument {} passed to {}() must be an instance of {}, {} given",
        paramNum + 1, func->fullName()->data(), tn->data(), givenType
      ).str()
    );
  }
}
Example #8
0
Value* ColumnTinyint::createEmptyDefaultVal() const
{
    if (isNullable())
    {
        return new ValueTinyint( );
    }
    else
    {
        return new ValueTinyint( 0 );
    }
    
}
Example #9
0
Value* ColumnInteger::createEmptyDefaultVal() const
{
    if (isNullable())
    {
        return new ValueInteger( );
    }
    else
    {
        return new ValueInteger( 0 );
    }
    
}
Example #10
0
inline void CMLeaf::calcLastPos(CMStateSet& toSet) const
{
    // If we are an epsilon node, then the last pos is an empty set
    if (isNullable())
    {
        toSet.zeroBits();
        return;
    }

    // Otherwise, its just the one bit of our position
    toSet.setBit(fPosition);
}
Example #11
0
std::string TypeConstraint::displayName(const Func* func /*= nullptr*/) const {
  const StringData* tn = typeName();
  std::string name;
  if (isSoft()) {
    name += '@';
  }
  if (isNullable() && isExtended()) {
    name += '?';
  }
  if (func && isSelf()) {
    selfToTypeName(func, &tn);
    name += tn->data();
  } else if (func && isParent()) {
    parentToTypeName(func, &tn);
    name += tn->data();
  } else {
    const char* str = tn->data();
    auto len = tn->size();
    if (len > 3 && tolower(str[0]) == 'h' && tolower(str[1]) == 'h' &&
        str[2] == '\\') {
      bool strip = false;
      const char* stripped = str + 3;
      switch (len - 3) {
        case 3:
          strip = (!strcasecmp(stripped, "int") ||
                   !strcasecmp(stripped, "num"));
          break;
        case 4: strip = !strcasecmp(stripped, "bool"); break;
        case 5: strip = !strcasecmp(stripped, "float"); break;
        case 6: strip = !strcasecmp(stripped, "string"); break;
        case 8:
          strip = (!strcasecmp(stripped, "resource") ||
                   !strcasecmp(stripped, "noreturn") ||
                   !strcasecmp(stripped, "arraykey"));
          break;
        default:
          break;
      }
      if (strip) {
        str = stripped;
      }
    }
    name += str;
  }
  return name;
}
Example #12
0
DataTypePtr FunctionCoalesce::getReturnTypeImpl(const DataTypes & arguments) const
{
    /// Skip all NULL arguments. If any argument is non-Nullable, skip all next arguments.
    DataTypes filtered_args;
    filtered_args.reserve(arguments.size());
    for (const auto & arg : arguments)
    {
        if (arg->onlyNull())
            continue;

        filtered_args.push_back(arg);

        if (!arg->isNullable())
            break;
    }

    DataTypes new_args;
    for (size_t i = 0; i < filtered_args.size(); ++i)
    {
        bool is_last = i + 1 == filtered_args.size();

        if (is_last)
        {
            new_args.push_back(filtered_args[i]);
        }
        else
        {
            new_args.push_back(std::make_shared<DataTypeUInt8>());
            new_args.push_back(removeNullable(filtered_args[i]));
        }
    }

    if (new_args.empty())
        return std::make_shared<DataTypeNullable>(std::make_shared<DataTypeNothing>());
    if (new_args.size() == 1)
        return new_args.front();

    auto res = FunctionMultiIf{context}.getReturnTypeImpl(new_args);

    /// if last argument is not nullable, result should be also not nullable
    if (!new_args.back()->isNullable() && res->isNullable())
        res = removeNullable(res);

    return res;
}
Example #13
0
void TypeConstraint::init() {
  if (isTypeVar()) {
    // We kept the type variable type constraint to correctly check child
    // classes implementing abstract methods or interfaces.
    m_type.dt = folly::none;
    m_type.metatype = MetaType::Precise;
    return;
  }

  if (m_typeName == nullptr) {
    m_type.dt = folly::none;
    m_type.metatype = MetaType::Precise;
    return;
  }

  Type dtype;
  TRACE(5, "TypeConstraint: this %p type %s, nullable %d\n",
        this, m_typeName->data(), isNullable());
  auto const mptr = typeNameToType(m_typeName);
  if (mptr) dtype = *mptr;
  if (!mptr ||
      !(isHHType() || dtype.dt == KindOfArray ||
        dtype.dt == KindOfBoolean ||
        dtype.dt == KindOfString ||
        dtype.dt == KindOfInt64 ||
        dtype.dt == KindOfDouble ||
        dtype.dt == KindOfResource ||
        dtype.metatype == MetaType::ArrayKey ||
        dtype.metatype == MetaType::Number ||
        dtype.metatype == MetaType::Parent ||
        dtype.metatype == MetaType::Self ||
        dtype.metatype == MetaType::Callable)) {
    TRACE(5, "TypeConstraint: this %p no such type %s, treating as object\n",
          this, m_typeName->data());
    m_type = { KindOfObject, MetaType::Precise };
    m_namedEntity = NamedEntity::get(m_typeName);
    TRACE(5, "TypeConstraint: NamedEntity: %p\n", m_namedEntity);
    return;
  }
  m_type = dtype;
  assert(m_type.dt != KindOfStaticString);
  assert(IMPLIES(isParent(), m_type.dt == KindOfObject));
  assert(IMPLIES(isSelf(), m_type.dt == KindOfObject));
  assert(IMPLIES(isCallable(), m_type.dt == KindOfObject));
}
Example #14
0
void TypeConstraint::init() {
  if (m_typeName == nullptr || isTypeVar() || isTypeConstant()) {
    m_type = Type::Mixed;
    return;
  }
  TRACE(5, "TypeConstraint: this %p type %s, nullable %d\n",
        this, m_typeName->data(), isNullable());
  auto const mptr = nameToAnnotType(m_typeName);
  if (mptr) {
    m_type = *mptr;
    assert(getAnnotDataType(m_type) != KindOfPersistentString);
    return;
  }
  TRACE(5, "TypeConstraint: this %p no such type %s, treating as object\n",
        this, m_typeName->data());
  m_type = Type::Object;
  m_namedEntity = NamedEntity::get(m_typeName);
  TRACE(5, "TypeConstraint: NamedEntity: %p\n", m_namedEntity.get());
}
Example #15
0
bool SyntaxSymbolAttributeCache::isNullable(int sym)
{
    if (!isNonTerm(sym)) return false;
    if (m_nullable.count(sym) > 0) return m_nullable[sym];
    bool &b = m_nullable[sym];
    int pbegin, pend;
    getNonTermProductRange(sym, pbegin, pend);
    for (int pid = pbegin; pid < pend; ++pid) {
        auto &body = getProductBody(pid);
        bool nullable = true;
        for (auto i : body) {
            if (!isNullable(i)) {
                nullable = false;
                break;
            }
        }
        if (nullable) {
            b = true;
            break;
        }
    }
    return b;
}
Example #16
0
void TypeConstraint::verifyFail(const Func* func, int paramNum,
                                const TypedValue* tv) const {
  Transl::VMRegAnchor _;
  std::ostringstream fname;
  fname << func->fullName()->data() << "()";
  const StringData* tn = typeName();
  if (isSelf()) {
    selfToTypeName(func, &tn);
  } else if (isParent()) {
    parentToTypeName(func, &tn);
  }

  auto const givenType = describe_actual_type(tv);

  if (isExtended()) {
    if (isSoft()) {
      // Soft type hints raise warnings instead of recoverable
      // errors by design, to ease migration.
      raise_warning(
        "Argument %d passed to %s must be of type %s, %s given",
        paramNum + 1, fname.str().c_str(), fullName().c_str(), givenType);
    } else if (isNullable()) {
      // This error message is slightly different from the normal case
      // (fullName() vs tn)
      raise_recoverable_error(
        "Argument %d passed to %s must be of type %s, %s given",
        paramNum + 1, fname.str().c_str(), fullName().c_str(), givenType);
    } else {
      assert(false &&
        "Only nullable and soft extended type hints are currently implemented");
    }
  } else {
    raise_recoverable_error(
      "Argument %d passed to %s must be an instance of %s, %s given",
      paramNum + 1, fname.str().c_str(), tn->data(), givenType);
  }
}
Example #17
0
void TypeConstraint::verifyFail(const Func* func, TypedValue* tv,
                                int id, bool useStrictTypes) const {
  VMRegAnchor _;
  std::string name = displayName(func);
  auto const givenType = describe_actual_type(tv, isHHType());

  if (UNLIKELY(!useStrictTypes)) {
    if (auto dt = underlyingDataType()) {
      // In non-strict mode we may be able to coerce a type failure. For object
      // typehints there is no possible coercion in the failure case, but HNI
      // builtins currently only guard on kind not class so the following wil
      // generate false positives for objects.
      if (*dt != KindOfObject) {
        // HNI conversions implicitly unbox references, this behavior is wrong,
        // in particular it breaks the way type conversion works for PHP 7
        // scalar type hints
        if (tv->m_type == KindOfRef) {
          auto inner = tv->m_data.pref->var()->asTypedValue();
          if (tvCoerceParamInPlace(inner, *dt)) {
            tvAsVariant(tv) = tvAsVariant(inner);
            return;
          }
        } else {
          if (tvCoerceParamInPlace(tv, *dt)) return;
        }
      }
    }
  } else if (UNLIKELY(!func->unit()->isHHFile() &&
                      !RuntimeOption::EnableHipHopSyntax)) {
    // PHP 7 allows for a widening conversion from Int to Float. We still ban
    // this in HH files.
    if (auto dt = underlyingDataType()) {
      if (*dt == KindOfDouble && tv->m_type == KindOfInt64 &&
          tvCoerceParamToDoubleInPlace(tv)) {
        return;
      }
    }
  }

  // Handle return type constraint failures
  if (id == ReturnId) {
    std::string msg;
    if (func->isClosureBody()) {
      msg =
        folly::format(
          "Value returned from {}closure must be of type {}, {} given",
          func->isAsync() ? "async " : "",
          name,
          givenType
        ).str();
    } else {
      msg =
        folly::format(
          "Value returned from {}{} {}() must be of type {}, {} given",
          func->isAsync() ? "async " : "",
          func->preClass() ? "method" : "function",
          func->fullName(),
          name,
          givenType
        ).str();
    }
    if (RuntimeOption::EvalCheckReturnTypeHints >= 2 && !isSoft()) {
      raise_return_typehint_error(msg);
    } else {
      raise_warning_unsampled(msg);
    }
    return;
  }

  // Handle implicit collection->array conversion for array parameter type
  // constraints
  auto c = tvToCell(tv);
  if (isArray() && !isSoft() && !func->mustBeRef(id) &&
      c->m_type == KindOfObject && c->m_data.pobj->isCollection()) {
    // To ease migration, the 'array' type constraint will implicitly cast
    // collections to arrays, provided the type constraint is not soft and
    // the parameter is not by reference. We raise a notice to let the user
    // know that there was a type mismatch and that an implicit conversion
    // was performed.
    raise_notice(
      folly::format(
        "Argument {} to {}() must be of type {}, {} given; argument {} was "
        "implicitly cast to array",
        id + 1, func->fullName(), name, givenType, id + 1
      ).str()
    );
    tvCastToArrayInPlace(tv);
    return;
  }

  // Handle parameter type constraint failures
  if (isExtended() && isSoft()) {
    // Soft extended type hints raise warnings instead of recoverable
    // errors, to ease migration.
    raise_warning_unsampled(
      folly::format(
        "Argument {} to {}() must be of type {}, {} given",
        id + 1, func->fullName(), name, givenType
      ).str()
    );
  } else if (isExtended() && isNullable()) {
    raise_typehint_error(
      folly::format(
        "Argument {} to {}() must be of type {}, {} given",
        id + 1, func->fullName(), name, givenType
      ).str()
    );
  } else {
    auto cls = Unit::lookupClass(m_typeName);
    if (cls && isInterface(cls)) {
      raise_typehint_error(
        folly::format(
          "Argument {} passed to {}() must implement interface {}, {} given",
          id + 1, func->fullName(), name, givenType
        ).str()
      );
    } else {
      raise_typehint_error(
        folly::format(
          "Argument {} passed to {}() must be an instance of {}, {} given",
          id + 1, func->fullName(), name, givenType
        ).str()
      );
    }
  }
}
Example #18
0
bool TypeConstraint::check(TypedValue* tv, const Func* func) const {
  assert(hasConstraint() && !isTypeVar() && !isMixed() && !isTypeConstant());

  // This is part of the interpreter runtime; perf matters.
  if (tv->m_type == KindOfRef) {
    tv = tv->m_data.pref->tv();
  }

  if (isNullable() && tv->m_type == KindOfNull) {
    return true;
  }

  if (tv->m_type == KindOfObject) {
    // Perfect match seems common enough to be worth skipping the hash
    // table lookup.
    const Class *c = nullptr;
    if (isObject()) {
      if (m_typeName->isame(tv->m_data.pobj->getVMClass()->name())) {
        if (isProfileRequest()) InstanceBits::profile(m_typeName);
        return true;
      }
      // We can't save the Class* since it moves around from request
      // to request.
      assert(m_namedEntity);
      c = Unit::lookupClass(m_namedEntity);
    } else {
      switch (metaType()) {
        case MetaType::Self:
          selfToClass(func, &c);
          break;
        case MetaType::Parent:
          parentToClass(func, &c);
          break;
        case MetaType::Callable:
          return is_callable(tvAsCVarRef(tv));
        case MetaType::Precise:
        case MetaType::Number:
        case MetaType::ArrayKey:
        case MetaType::Dict:
        case MetaType::Vec:
          return false;
        case MetaType::Mixed:
          // We assert'd at the top of this function that the
          // metatype cannot be Mixed
          not_reached();
      }
    }
    if (isProfileRequest() && c) {
      InstanceBits::profile(c->preClass()->name());
    }
    if (c && tv->m_data.pobj->instanceof(c)) {
      return true;
    }
    return isObject() && checkTypeAliasObj(tv->m_data.pobj->getVMClass());
  }

  auto const result = annotCompat(tv->m_type, m_type, m_typeName);
  switch (result) {
    case AnnotAction::Pass: return true;
    case AnnotAction::Fail: return false;
    case AnnotAction::CallableCheck:
      return is_callable(tvAsCVarRef(tv));
    case AnnotAction::DictCheck:
      return tv->m_data.parr->isDict();
    case AnnotAction::VecCheck:
      return tv->m_data.parr->isVecArray();
    case AnnotAction::ObjectCheck:
      assert(isObject());
      return checkTypeAliasNonObj(tv);
  }
  not_reached();
}
Example #19
0
void TypeConstraint::verifyFail(const Func* func, TypedValue* tv,
                                int id) const {
  VMRegAnchor _;
  std::string name = displayName(func);
  auto const givenType = describe_actual_type(tv, isHHType());
  // Handle return type constraint failures
  if (id == ReturnId) {
    std::string msg;
    if (func->isClosureBody()) {
      msg =
        folly::format(
          "Value returned from {}closure must be of type {}, {} given",
          func->isAsync() ? "async " : "",
          name,
          givenType
        ).str();
    } else {
      msg =
        folly::format(
          "Value returned from {}{} {}() must be of type {}, {} given",
          func->isAsync() ? "async " : "",
          func->preClass() ? "method" : "function",
          func->fullName()->data(),
          name,
          givenType
        ).str();
    }
    if (RuntimeOption::EvalCheckReturnTypeHints >= 2 && !isSoft() &&
        (!func->isClosureBody() ||
         !RuntimeOption::EvalSoftClosureReturnTypeHints)) {
      raise_return_typehint_error(msg);
    } else {
      raise_debugging(msg);
    }
    return;
  }
  // Handle implicit collection->array conversion for array parameter type
  // constraints
  auto c = tvToCell(tv);
  if (isArray() && !isSoft() && !func->mustBeRef(id) &&
      c->m_type == KindOfObject && c->m_data.pobj->isCollection()) {
    // To ease migration, the 'array' type constraint will implicitly cast
    // collections to arrays, provided the type constraint is not soft and
    // the parameter is not by reference. We raise a notice to let the user
    // know that there was a type mismatch and that an implicit conversion
    // was performed.
    raise_notice(
      folly::format(
        "Argument {} to {}() must be of type {}, {} given; argument {} was "
        "implicitly cast to array",
        id + 1, func->fullName()->data(), name, givenType, id + 1
      ).str()
    );
    tvCastToArrayInPlace(tv);
    return;
  }
  // Handle parameter type constraint failures
  if (isExtended() && isSoft()) {
    // Soft extended type hints raise warnings instead of recoverable
    // errors, to ease migration.
    raise_debugging(
      folly::format(
        "Argument {} to {}() must be of type {}, {} given",
        id + 1, func->fullName()->data(), name, givenType
      ).str()
    );
  } else if (isExtended() && isNullable()) {
    raise_typehint_error(
      folly::format(
        "Argument {} to {}() must be of type {}, {} given",
        id + 1, func->fullName()->data(), name, givenType
      ).str()
    );
  } else {
    auto cls = Unit::lookupClass(m_typeName);
    if (cls && isInterface(cls)) {
      raise_typehint_error(
        folly::format(
          "Argument {} passed to {}() must implement interface {}, {} given",
          id + 1, func->fullName()->data(), name, givenType
        ).str()
      );
    } else {
      raise_typehint_error(
        folly::format(
          "Argument {} passed to {}() must be an instance of {}, {} given",
          id + 1, func->fullName()->data(), name, givenType
        ).str()
      );
    }
  }
}
Example #20
0
Def* IdentityAnalyzer::do_cknull(UnaryStmt* instr) {
  if (!isNullable(type(instr->value_in())))
    return identity(instr);
  return def_;
}
Example #21
0
bool
TypeConstraint::check(TypedValue* tv, const Func* func) const {
  assert(hasConstraint());

  // This is part of the interpreter runtime; perf matters.
  if (tv->m_type == KindOfRef) {
    tv = tv->m_data.pref->tv();
  }
  if (isNullable() && tv->m_type == KindOfNull) return true;

  if (isNumber()) {
    return IS_INT_TYPE(tv->m_type) || IS_DOUBLE_TYPE(tv->m_type);
  }

  if (tv->m_type == KindOfObject) {
    if (!isObjectOrTypeAlias()) return false;
    // Perfect match seems common enough to be worth skipping the hash
    // table lookup.
    if (m_typeName->isame(tv->m_data.pobj->getVMClass()->name())) {
      if (shouldProfile()) InstanceBits::profile(m_typeName);
      return true;
    }
    const Class *c = nullptr;
    const bool selfOrParentOrCallable = isSelf() || isParent() || isCallable();
    if (selfOrParentOrCallable) {
      if (isSelf()) {
        selfToClass(func, &c);
      } else if (isParent()) {
        parentToClass(func, &c);
      } else {
        assert(isCallable());
        return f_is_callable(tvAsCVarRef(tv));
      }
    } else {
      // We can't save the Class* since it moves around from request
      // to request.
      assert(m_namedEntity);
      c = Unit::lookupClass(m_namedEntity);
    }
    if (shouldProfile() && c) {
      InstanceBits::profile(c->preClass()->name());
    }
    if (c && tv->m_data.pobj->instanceof(c)) {
      return true;
    }
    return !selfOrParentOrCallable && checkTypeAliasObj(tv);
  }

  if (isObjectOrTypeAlias()) {
    switch (tv->m_type) {
      case KindOfArray:
        if (interface_supports_array(m_typeName)) {
          return true;
        }
        break;
      case KindOfString:
      case KindOfStaticString:
        if (interface_supports_string(m_typeName)) {
          return true;
        }
        break;
      case KindOfInt64:
        if (interface_supports_int(m_typeName)) {
          return true;
        }
        break;
      case KindOfDouble:
        if (interface_supports_double(m_typeName)) {
          return true;
        }
        break;
      default:
        break;
    }

    if (isCallable()) {
      return f_is_callable(tvAsCVarRef(tv));
    }
    return isPrecise() && checkTypeAliasNonObj(tv);
  }

  return equivDataTypes(m_type.dt, tv->m_type);
}