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;
}