bool checkTypeStructureMatchesTVImpl()

in hphp/runtime/base/type-structure-helpers.cpp [518:804]


bool checkTypeStructureMatchesTVImpl(
  const Array& ts,
  TypedValue c1,
  std::string& givenType,
  std::string& expectedType,
  std::string& errorKey,
  bool& warn,
  bool isOrAsOp
) {
  auto const errOnLen = [&givenType](auto cell, auto len) {
    if (!gen_error) return;
    givenType = folly::sformat("{} of length {}",
      describe_actual_type(&cell), len);
  };

  auto const errOnKey = [&errorKey](TypedValue key) {
    if (!gen_error) return;
    auto const escapedKey = [key] {
      if (isStringType(type(key))) {
        return folly::sformat("\"{}\"",
          folly::cEscape<std::string>(val(key).pstr->toCppString()));
      }
      assertx(isIntType(type(key)));
      return std::to_string(val(key).num);
    }();
    errorKey = folly::sformat("[{}]{}", escapedKey, errorKey);
  };

  auto const type = c1.m_type;
  auto const data = c1.m_data;

  auto const tv = ts.lookup(s_nullable);
  if (isNullType(type) && tv.is_init() && tv.val().num) {
    return true;
  }
  assertx(ts.exists(s_kind));

  auto const ts_kind = static_cast<TypeStructure::Kind>(
    ts[s_kind].toInt64Val()
  );

  auto const result = [&] {
    switch (ts_kind) {
    case TypeStructure::Kind::T_void:
    case TypeStructure::Kind::T_null:
      return isNullType(type);
    case TypeStructure::Kind::T_nonnull:
      return !isNullType(type);
    case TypeStructure::Kind::T_int:
      return isIntType(type);
    case TypeStructure::Kind::T_bool:
      return isBoolType(type);
    case TypeStructure::Kind::T_float:
      return isDoubleType(type);
    case TypeStructure::Kind::T_num:
      return isIntType(type) || isDoubleType(type);
    case TypeStructure::Kind::T_arraykey:
      if (isClassType(type) || isLazyClassType(type)) {
        if (RO::EvalClassIsStringNotices) {
          raise_notice("Class used in is_string");
        }
        return true;
      }
      return isIntType(type) || isStringType(type);

    case TypeStructure::Kind::T_string:
      if (isClassType(type) || isLazyClassType(type)) {
        if (RO::EvalClassIsStringNotices) {
          raise_notice("Class used in is_string");
        }
        return true;
      }
      return isStringType(type);

    case TypeStructure::Kind::T_resource:
      return isResourceType(type) &&
             !reinterpret_cast<const Resource*>(&data.pres)->isInvalid();

    case TypeStructure::Kind::T_darray:
      return isOrAsOp ? is_dict(&c1) : is_vec(&c1) || is_dict(&c1);

    case TypeStructure::Kind::T_varray:
      return isOrAsOp ? is_vec(&c1) : is_vec(&c1) || is_dict(&c1);

    case TypeStructure::Kind::T_varray_or_darray:
      return is_vec(&c1) || is_dict(&c1);

    case TypeStructure::Kind::T_dict:
      return is_dict(&c1);

    case TypeStructure::Kind::T_vec:
      return is_vec(&c1);

    case TypeStructure::Kind::T_vec_or_dict:
      return is_vec(&c1) || is_dict(&c1);

    case TypeStructure::Kind::T_keyset:
      return is_keyset(&c1);

    case TypeStructure::Kind::T_any_array:
      return isArrayLikeType(type);

    case TypeStructure::Kind::T_enum: {
      assertx(ts.exists(s_classname));
      auto const cls = Class::load(ts[s_classname].asCStrRef().get());
      if (!isOrAsOp) {
        if (auto const dt = cls ? cls->enumBaseTy() : std::nullopt) {
          return equivDataTypes(*dt, type);
        }
        return isIntType(type) || isStringType(type);
      }
      return cls && enumHasValue(cls, &c1);
    }

    case TypeStructure::Kind::T_trait:
      // For is/as, we will not get here since we'll throw an error on the
      // resolution pass.
      // For parameter/return type verification, we treat it as a class type.
    case TypeStructure::Kind::T_class:
    case TypeStructure::Kind::T_interface:
    case TypeStructure::Kind::T_xhp: {
      assertx(ts.exists(s_classname));
      auto const name = ts[s_classname].asCStrRef().get();
      return
        tvInstanceOfImpl(&c1, [&] { return Class::load(name); }) &&
        checkReifiedGenericsMatch(ts, c1, name, warn, isOrAsOp);
    }

    case TypeStructure::Kind::T_tuple: {
      if (!isVecType(type)) return false;
      if (!isOrAsOp) return true;

      auto const ad = data.parr;
      assertx(ts.exists(s_elem_types));
      auto const ts_arr = ts[s_elem_types].getArrayData();
      if (ad->size() != ts_arr->size()) {
        errOnLen(c1, ad->size());
        return false;
      }

      auto const matches_tuple = [&] {
        bool keys_match = true; // are keys contiguous and zero-indexed?
        bool vals_match = true; // do vals match the type structure types?
        int index = 0;

        IterateKV(ad,
          [&](TypedValue k, TypedValue v) {
            if (!isIntType(k.m_type) || k.m_data.num != index++) {
              keys_match = false;
              return true;
            }
            auto const tv = ts_arr->get(k.m_data.num);
            auto const& ts2 = asCArrRef(&tv);
            auto thisElemWarns = false;
            if (!checkTypeStructureMatchesTVImpl<gen_error>(
              ts2, v, givenType, expectedType, errorKey, thisElemWarns, isOrAsOp
            )) {
              errOnKey(k);
              if (thisElemWarns) {
                warn = true;
                return false;
              }
              vals_match = false;
              return true;
            }
            return false;
          }
        );
        // If there is an error, ignore `warn`.
        if (!vals_match || !keys_match) warn = false;

        if (!keys_match || warn) return false;
        return vals_match;
      }();

      return matches_tuple;
    }

    case TypeStructure::Kind::T_shape: {
      if (!isDictType(type)) return false;
      if (!isOrAsOp) return true;

      auto const ad = data.parr;
      assertx(ts.exists(s_fields));
      auto const ts_arr = ts[s_fields].getArrayData();

      auto const numRequiredFields = [&] {
        auto n = 0;
        IterateV(
          ts_arr,
          [&](TypedValue v) {
            assertx(isArrayLikeType(v.m_type));
            n += !isOptionalShapeField(v.m_data.parr);
          }
        );
        return n;
      }();

      if (ad->size() < numRequiredFields) {
        errOnLen(c1, ad->size());
        return false;
      }

      auto const allowsUnknownFields = shapeAllowsUnknownFields(ts);
      if (!allowsUnknownFields && ad->size() > ts_arr->size()) {
        errOnLen(c1, ad->size());
        return false;
      }

      auto fieldsDidMatch = true;
      auto numExpectedFields = 0;

      IterateKV(
        ts_arr,
        [&](TypedValue k, TypedValue v) {
          assertx(isArrayLikeType(v.m_type));
          auto const tsFieldData = v.m_data.parr;
          if (!ad->exists(k)) {
            if (isOptionalShapeField(tsFieldData)) {
              return false;
            }
            fieldsDidMatch = false;
            errOnKey(k);
            return true;
          }
          auto const tsField = getShapeFieldElement(v);
          auto const field = ad->at(k);
          bool thisFieldWarns = false;
          numExpectedFields++;
          if (!checkTypeStructureMatchesTVImpl<gen_error>(
            ArrNR(tsField), field, givenType,
            expectedType, errorKey, thisFieldWarns, isOrAsOp
          )) {
            errOnKey(k);
            if (thisFieldWarns) {
              warn = true;
              return false;
            }
            fieldsDidMatch = false;
            return true;
          }
          return false;
        }
      );
      if (!fieldsDidMatch ||
          !(allowsUnknownFields || ad->size() == numExpectedFields)) {
        warn = false;
        return false;
      }
      return !warn;
    }

    case TypeStructure::Kind::T_nothing:
    case TypeStructure::Kind::T_noreturn:
    case TypeStructure::Kind::T_unresolved:
    case TypeStructure::Kind::T_typeaccess:
      return false;

    case TypeStructure::Kind::T_mixed:
    case TypeStructure::Kind::T_dynamic:
      return true;

    case TypeStructure::Kind::T_fun:
    case TypeStructure::Kind::T_typevar:
      // For is/as, we will not get here since we'll throw an error on the
      // resolution pass.
      // For parameter/return type verification, we don't check these types, so
      // just return true.
      return true;

    case TypeStructure::Kind::T_reifiedtype:
      // This type should have been removed in the resolution phase.
      always_assert(false);
    }
    not_reached();
  }();
  if (!warn && is_ts_soft(ts.get())) warn = true;
  if (gen_error && !result) {
    if (givenType.empty()) givenType = describe_actual_type(&c1);
    if (expectedType.empty()) {
      expectedType =
        TypeStructure::toString(ts,
          TypeStructure::TSDisplayType::TSDisplayTypeUser).toCppString();
    }
  }
  return result;
}