Support checking option type typehints at runtime.
Adds support for checking ?Foo type hints in VerifyParamType. The parameter must have type Foo or null. Failing to pass the hint is reported as a warning instead of a recoverable error for now for migration reasons---we'll want to convert it to be the same as normal type hints later.
Esse commit está contido em:
@@ -3345,6 +3345,9 @@ public:
|
||||
std::map<std::string,int>::iterator it =
|
||||
m_gidMap.find("v:" + p->getName());
|
||||
if (it != m_gidMap.end() && it->second) {
|
||||
// NB: this is unsound if the user error handler swallows a
|
||||
// parameter typehint failure. It's opt-in via compiler
|
||||
// options, though.
|
||||
if (useDefaults && p->hasTypeHint() && !p->defaultValue()) {
|
||||
b->setBit(DataFlow::AvailIn, it->second);
|
||||
}
|
||||
|
||||
@@ -206,11 +206,11 @@ class FuncFinisher {
|
||||
public:
|
||||
FuncFinisher(EmitterVisitor* ev, Emitter& e, FuncEmitter* fe)
|
||||
: m_ev(ev), m_e(e), m_fe(fe) {
|
||||
TRACE(1, "FuncFinisher constructed: %s %p\n", m_fe->name()->data(), m_fe);
|
||||
TRACE(2, "FuncFinisher constructed: %s\n", m_fe->name()->data());
|
||||
}
|
||||
|
||||
~FuncFinisher() {
|
||||
TRACE(1, "Finishing func: %s %p\n", m_fe->name()->data(), m_fe);
|
||||
TRACE(2, "Finishing func: %s\n", m_fe->name()->data());
|
||||
m_ev->finishFunc(m_e, m_fe);
|
||||
}
|
||||
};
|
||||
@@ -5265,6 +5265,41 @@ static Attr buildAttrs(ModifierExpressionPtr mod, bool isRef = false) {
|
||||
return Attr(attrs);
|
||||
}
|
||||
|
||||
static TypeConstraint
|
||||
determine_type_constraint(const ParameterExpressionPtr& par) {
|
||||
if (par->hasTypeHint()) {
|
||||
auto ce = dynamic_pointer_cast<ConstantExpression>(par->defaultValue());
|
||||
auto flags = TypeConstraint::NoFlags;
|
||||
if (ce && ce->isNull()) {
|
||||
flags = flags|TypeConstraint::Nullable;
|
||||
}
|
||||
if (par->hhType()) {
|
||||
flags = flags|TypeConstraint::HHType;
|
||||
}
|
||||
return TypeConstraint{
|
||||
StringData::GetStaticString(par->getOriginalTypeHint()),
|
||||
flags
|
||||
};
|
||||
}
|
||||
|
||||
if (auto annot = par->annotation()) {
|
||||
if (!annot->isNullable()) return {};
|
||||
if (annot->isSoft() || annot->isFunction()) return {};
|
||||
|
||||
auto strippedName = annot->stripNullable().vanillaName();
|
||||
if (strippedName.empty()) return {};
|
||||
|
||||
return TypeConstraint{
|
||||
StringData::GetStaticString(strippedName),
|
||||
TypeConstraint::Nullable |
|
||||
TypeConstraint::ExtendedHint |
|
||||
TypeConstraint::HHType
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void EmitterVisitor::emitPostponedMeths() {
|
||||
vector<FuncEmitter*> top_fes;
|
||||
while (!m_postponedMeths.empty()) {
|
||||
@@ -5321,23 +5356,17 @@ void EmitterVisitor::emitPostponedMeths() {
|
||||
if (par->isOptional()) {
|
||||
dvInitializers.push_back(DVInitializer(i, par->defaultValue()));
|
||||
}
|
||||
// Will be fixed up later, when the DV initializers are emitted.
|
||||
|
||||
FuncEmitter::ParamInfo pi;
|
||||
if (par->hasTypeHint()) {
|
||||
ConstantExpressionPtr ce =
|
||||
dynamic_pointer_cast<ConstantExpression>(par->defaultValue());
|
||||
bool nullable = ce && ce->isNull();
|
||||
TypeConstraint tc =
|
||||
TypeConstraint(
|
||||
StringData::GetStaticString(par->getOriginalTypeHint()),
|
||||
nullable,
|
||||
par->hhType());
|
||||
pi.setTypeConstraint(tc);
|
||||
TRACE(1, "Added constraint to %s\n", fe->name()->data());
|
||||
auto const typeConstraint = determine_type_constraint(par);
|
||||
if (typeConstraint.hasConstraint()) {
|
||||
pi.setTypeConstraint(typeConstraint);
|
||||
}
|
||||
|
||||
if (par->hasUserType()) {
|
||||
pi.setUserType(StringData::GetStaticString(par->getUserTypeHint()));
|
||||
}
|
||||
|
||||
// Store info about the default value if there is one.
|
||||
if (par->isOptional()) {
|
||||
const StringData* phpCode;
|
||||
@@ -5499,10 +5528,7 @@ void EmitterVisitor::emitPostponedMeths() {
|
||||
}
|
||||
for (uint i = 0; i < fe->params().size(); i++) {
|
||||
const TypeConstraint& tc = fe->params()[i].typeConstraint();
|
||||
if (!tc.exists()) continue;
|
||||
TRACE(2, "permanent home for tc %s, param %d of func %s: %p\n",
|
||||
tc.typeName()->data(), i, fe->name()->data(), &tc);
|
||||
assert(tc.typeName()->data() != (const char*)0xdeadba5eba11f00d);
|
||||
if (!tc.hasConstraint()) continue;
|
||||
e.VerifyParamType(i);
|
||||
}
|
||||
|
||||
@@ -7333,9 +7359,11 @@ void emitAllHHBC(AnalysisResultPtr ar) {
|
||||
|
||||
/* there is a race condition in the first call to
|
||||
GetStaticString. Make sure we dont hit it */
|
||||
StringData::GetStaticString("");
|
||||
/* same for TypeConstraint */
|
||||
TypeConstraint tc;
|
||||
{
|
||||
StringData::GetStaticString("");
|
||||
/* same for TypeConstraint */
|
||||
TypeConstraint tc;
|
||||
}
|
||||
|
||||
JobQueueDispatcher<EmitterWorker::JobType, EmitterWorker>
|
||||
dispatcher(threadCount, true, 0, false, ar.get());
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
#include "hphp/compiler/expression/parameter_expression.h"
|
||||
|
||||
#include "hphp/compiler/type_annotation.h"
|
||||
#include "hphp/compiler/expression/parameter_expression.h"
|
||||
#include "hphp/compiler/analysis/function_scope.h"
|
||||
#include "hphp/compiler/analysis/file_scope.h"
|
||||
#include "hphp/compiler/analysis/variable_table.h"
|
||||
@@ -30,14 +30,23 @@ using namespace HPHP;
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// constructors/destructors
|
||||
|
||||
ParameterExpression::ParameterExpression
|
||||
(EXPRESSION_CONSTRUCTOR_PARAMETERS,
|
||||
TypeAnnotationPtr type, bool hhType, const std::string &name, bool ref,
|
||||
ExpressionPtr defaultValue, ExpressionPtr attributeList)
|
||||
: Expression(EXPRESSION_CONSTRUCTOR_PARAMETER_VALUES(ParameterExpression)),
|
||||
m_originalType(type), m_name(name), m_hhType(hhType), m_ref(ref),
|
||||
m_defaultValue(defaultValue), m_attributeList(attributeList) {
|
||||
m_type = Util::toLower(type ? type->simpleName() : "");
|
||||
ParameterExpression::ParameterExpression(
|
||||
EXPRESSION_CONSTRUCTOR_PARAMETERS,
|
||||
TypeAnnotationPtr type,
|
||||
bool hhType,
|
||||
const std::string &name,
|
||||
bool ref,
|
||||
ExpressionPtr defaultValue,
|
||||
ExpressionPtr attributeList)
|
||||
: Expression(EXPRESSION_CONSTRUCTOR_PARAMETER_VALUES(ParameterExpression))
|
||||
, m_originalType(type)
|
||||
, m_name(name)
|
||||
, m_hhType(hhType)
|
||||
, m_ref(ref)
|
||||
, m_defaultValue(defaultValue)
|
||||
, m_attributeList(attributeList)
|
||||
{
|
||||
m_type = Util::toLower(type ? type->vanillaName() : "");
|
||||
if (m_defaultValue) {
|
||||
m_defaultValue->setContext(InParameterExpression);
|
||||
}
|
||||
@@ -51,12 +60,12 @@ ExpressionPtr ParameterExpression::clone() {
|
||||
return exp;
|
||||
}
|
||||
|
||||
const std::string ParameterExpression::getOriginalTypeHint() const {
|
||||
const std::string ParameterExpression::getOriginalTypeHint() const {
|
||||
assert(hasTypeHint());
|
||||
return m_originalType->simpleName();
|
||||
return m_originalType->vanillaName();
|
||||
}
|
||||
|
||||
const std::string ParameterExpression::getUserTypeHint() const {
|
||||
const std::string ParameterExpression::getUserTypeHint() const {
|
||||
assert(hasUserType());
|
||||
return m_originalType->fullName();
|
||||
}
|
||||
@@ -286,7 +295,7 @@ void ParameterExpression::compatibleDefault() {
|
||||
// code generation functions
|
||||
|
||||
void ParameterExpression::outputPHP(CodeGenerator &cg, AnalysisResultPtr ar) {
|
||||
if (!m_type.empty()) cg_printf("%s ", m_originalType->simpleName().c_str());
|
||||
if (!m_type.empty()) cg_printf("%s ", m_originalType->vanillaName().c_str());
|
||||
if (m_ref) cg_printf("&");
|
||||
cg_printf("$%s", m_name.c_str());
|
||||
if (m_defaultValue) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define incl_HPHP_PARAMETER_EXPRESSION_H_
|
||||
|
||||
#include "hphp/compiler/expression/expression.h"
|
||||
#include "hphp/compiler/expression/constant_expression.h"
|
||||
#include "hphp/util/json.h"
|
||||
|
||||
namespace HPHP {
|
||||
@@ -46,11 +47,15 @@ public:
|
||||
ExpressionPtr defaultValue() { return m_defaultValue; }
|
||||
ExpressionPtr userAttributeList() { return m_attributeList; }
|
||||
TypePtr getTypeSpec(AnalysisResultPtr ar, bool forInference);
|
||||
|
||||
bool hasTypeHint() const { return !m_type.empty(); }
|
||||
const std::string &getTypeHint() const {
|
||||
assert(hasTypeHint());
|
||||
return m_type;
|
||||
}
|
||||
|
||||
TypeAnnotationPtr annotation() const { return m_originalType; }
|
||||
|
||||
bool hasUserType() const { return m_originalType != nullptr; }
|
||||
const std::string getOriginalTypeHint() const;
|
||||
const std::string getUserTypeHint() const;
|
||||
|
||||
@@ -916,6 +916,7 @@ void Parser::onParam(Token &out, Token *params, Token &type, Token &var,
|
||||
if (attr && attr->exp) {
|
||||
attrList = dynamic_pointer_cast<ExpressionList>(attr->exp);
|
||||
}
|
||||
|
||||
TypeAnnotationPtr typeAnnotation = type.typeAnnotation;
|
||||
expList->addElement(NEW_EXP(ParameterExpression, typeAnnotation,
|
||||
m_scanner.hipHopSyntaxEnabled(), var->text(),
|
||||
|
||||
@@ -33,7 +33,7 @@ TypeAnnotation::TypeAnnotation(const std::string &name,
|
||||
m_xhp(false),
|
||||
m_typevar(false) {}
|
||||
|
||||
const std::string TypeAnnotation::simpleName() const {
|
||||
std::string TypeAnnotation::vanillaName() const {
|
||||
// filter out types that should not be exposed to the runtime
|
||||
if (m_nullable || m_soft || m_typevar || m_function) {
|
||||
return "";
|
||||
@@ -44,7 +44,7 @@ const std::string TypeAnnotation::simpleName() const {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
const std::string TypeAnnotation::fullName() const {
|
||||
std::string TypeAnnotation::fullName() const {
|
||||
std::string name;
|
||||
if (m_nullable) {
|
||||
name += '?';
|
||||
|
||||
@@ -73,9 +73,50 @@ public:
|
||||
void setXHP() { m_xhp = true; }
|
||||
void setTypeVar() { m_typevar = true; }
|
||||
|
||||
const std::string simpleName() const;
|
||||
const std::string fullName() const;
|
||||
bool isNullable() const { return m_nullable; }
|
||||
bool isSoft() const { return m_soft; }
|
||||
bool isTuple() const { return m_tuple; }
|
||||
bool isFunction() const { return m_function; }
|
||||
bool isXHP() const { return m_xhp; }
|
||||
bool isTypeVar() const { return m_typevar; }
|
||||
|
||||
/*
|
||||
* Return a shallow copy of this TypeAnnotation, except with
|
||||
* nullability stripped.
|
||||
*/
|
||||
TypeAnnotation stripNullable() const {
|
||||
auto ret = *this;
|
||||
ret.m_nullable = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns whether this TypeAnnotation is "simple"---as described
|
||||
* above, this implies it has only one level of depth. Both the
|
||||
* type list and type args are null.
|
||||
*
|
||||
* It may however be soft or nullable, or a function type, etc.
|
||||
*/
|
||||
bool isSimple() const { return !m_typeList && !m_typeArgs; }
|
||||
|
||||
/*
|
||||
* Return a string for this annotation that is a type hint for
|
||||
* normal "vanilla" php. This means <?hh-specific annotations (such
|
||||
* as ?Foo or @Foo) are going to be stripped, as well as the deep
|
||||
* information about a type. (E.g. for Vector<string> this will
|
||||
* return "Vector".)
|
||||
*/
|
||||
std::string vanillaName() const;
|
||||
|
||||
/*
|
||||
* Returns a complete string name of this type-annotation, including
|
||||
* <?hh-specific extensions, any type parameter list, etc.
|
||||
*/
|
||||
std::string fullName() const;
|
||||
|
||||
/*
|
||||
* Add a new element to this type list for this TypeAnnotation.
|
||||
*/
|
||||
void appendToTypeList(TypeAnnotationPtr typeList);
|
||||
|
||||
private:
|
||||
|
||||
@@ -681,29 +681,6 @@ void throw_call_non_object(const char *methodName) {
|
||||
throw FatalErrorException(msg.c_str());
|
||||
}
|
||||
|
||||
void throw_unexpected_argument_type(int argNum, const char *fnName,
|
||||
const char *expected, CVarRef val) {
|
||||
const char *otype = nullptr;
|
||||
switch (val.getType()) {
|
||||
case KindOfUninit:
|
||||
case KindOfNull: otype = "null"; break;
|
||||
case KindOfBoolean: otype = "bool"; break;
|
||||
case KindOfInt64: otype = "int"; break;
|
||||
case KindOfDouble: otype = "double"; break;
|
||||
case KindOfStaticString:
|
||||
case KindOfString: otype = "string"; break;
|
||||
case KindOfArray: otype = "array"; break;
|
||||
case KindOfObject:
|
||||
otype = val.getObjectData()->o_getClassName().c_str();
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
raise_recoverable_error
|
||||
("Argument %d passed to %s must be an instance of %s, %s given",
|
||||
argNum, fnName, expected, otype);
|
||||
}
|
||||
|
||||
Object f_clone(CVarRef v) {
|
||||
if (v.isObject()) {
|
||||
Object clone = Object(v.toObject()->clone());
|
||||
|
||||
@@ -333,8 +333,6 @@ void throw_toomany_arguments_nr(const char *fn, int num, int level = 0)
|
||||
__attribute__((cold));
|
||||
void throw_wrong_arguments_nr(const char *fn, int count, int cmin, int cmax,
|
||||
int level = 0) __attribute__((cold));
|
||||
void throw_unexpected_argument_type(int argNum, const char *fnName,
|
||||
const char *expected, CVarRef val);
|
||||
|
||||
/**
|
||||
* Handler for exceptions thrown from user functions that we don't
|
||||
|
||||
@@ -394,6 +394,7 @@ public:
|
||||
kEvalVMInitialGlobalTableSizeDefault) \
|
||||
F(bool, Jit, evalJitDefault()) \
|
||||
F(bool, AllowHhas, false) \
|
||||
F(bool, CheckExtendedTypeHints, true) \
|
||||
F(bool, JitNoGdb, true) \
|
||||
F(bool, PerfPidMap, true) \
|
||||
F(bool, KeepPerfPidMap, false) \
|
||||
|
||||
@@ -423,7 +423,7 @@ static void set_function_info(Array &ret, const Func* func) {
|
||||
param.set(s_index, VarNR((int)i));
|
||||
VarNR name(func->localNames()[i]);
|
||||
param.set(s_name, name);
|
||||
const StringData* type = fpi.typeConstraint().exists() ?
|
||||
const StringData* type = fpi.typeConstraint().hasConstraint() ?
|
||||
fpi.typeConstraint().typeName() : empty_string.get();
|
||||
param.set(s_type, VarNR(type));
|
||||
const StringData* typeHint = fpi.userType() ?
|
||||
@@ -434,7 +434,7 @@ static void set_function_info(Array &ret, const Func* func) {
|
||||
param.set(s_class, VarNR(func->cls() ? func->cls()->name() :
|
||||
func->preClass()->name()));
|
||||
}
|
||||
if (!fpi.typeConstraint().exists() ||
|
||||
if (!fpi.typeConstraint().hasConstraint() ||
|
||||
fpi.typeConstraint().nullable()) {
|
||||
param.set(s_nullable, true_varNR);
|
||||
}
|
||||
|
||||
@@ -6608,7 +6608,7 @@ inline void OPTBLD_INLINE VMExecutionContext::iopVerifyParamType(PC& pc) {
|
||||
assert(param < func->numParams());
|
||||
assert(func->numParams() == int(func->params().size()));
|
||||
const TypeConstraint& tc = func->params()[param].typeConstraint();
|
||||
assert(tc.exists());
|
||||
assert(tc.hasConstraint() || !RuntimeOption::EvalCheckExtendedTypeHints);
|
||||
const TypedValue *tv = frame_local(m_fp, param);
|
||||
tc.verify(tv, func, param);
|
||||
}
|
||||
|
||||
@@ -598,7 +598,7 @@ void Func::getFuncInfo(ClassInfo::MethodInfo* mi) const {
|
||||
// owned by ParamInfo.
|
||||
pi->valueText = fpi.phpCode()->data();
|
||||
}
|
||||
pi->type = fpi.typeConstraint().exists() ?
|
||||
pi->type = fpi.typeConstraint().hasConstraint() ?
|
||||
fpi.typeConstraint().typeName()->data() : "";
|
||||
for (UserAttributeMap::const_iterator it = fpi.userAttributes().begin();
|
||||
it != fpi.userAttributes().end(); ++it) {
|
||||
|
||||
@@ -30,11 +30,21 @@ namespace HPHP {
|
||||
|
||||
TRACE_SET_MOD(runtime);
|
||||
|
||||
namespace {
|
||||
|
||||
// TODO(#2322864): this is a hack until we can get rid of the "Xhp"
|
||||
// psuedo-type.
|
||||
const StaticString s_xhp("Xhp");
|
||||
bool blacklistedName(const StringData* sd) {
|
||||
if (!sd) return false;
|
||||
return sd->isame(s_xhp.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TypeConstraint::TypeMap TypeConstraint::s_typeNamesToTypes;
|
||||
|
||||
void TypeConstraint::init() {
|
||||
const StringData* typeName = m_typeName;
|
||||
|
||||
if (UNLIKELY(s_typeNamesToTypes.empty())) {
|
||||
const struct Pair {
|
||||
const StringData* name;
|
||||
@@ -75,7 +85,16 @@ void TypeConstraint::init() {
|
||||
}
|
||||
}
|
||||
|
||||
if (typeName == nullptr) {
|
||||
if (m_typeName && isExtended()) {
|
||||
assert(nullable() &&
|
||||
"Only nullable extended type hints are implemented");
|
||||
}
|
||||
|
||||
if (blacklistedName(m_typeName) ||
|
||||
(isExtended() && !RuntimeOption::EvalCheckExtendedTypeHints)) {
|
||||
m_typeName = nullptr;
|
||||
}
|
||||
if (m_typeName == nullptr) {
|
||||
m_type.m_dt = KindOfInvalid;
|
||||
m_type.m_metatype = MetaType::Precise;
|
||||
return;
|
||||
@@ -83,14 +102,14 @@ void TypeConstraint::init() {
|
||||
|
||||
Type dtype;
|
||||
TRACE(5, "TypeConstraint: this %p type %s, nullable %d\n",
|
||||
this, typeName->data(), nullable());
|
||||
if (!mapGet(s_typeNamesToTypes, typeName, &dtype) ||
|
||||
this, m_typeName->data(), nullable());
|
||||
if (!mapGet(s_typeNamesToTypes, m_typeName, &dtype) ||
|
||||
!(hhType() || dtype.m_dt == KindOfArray || dtype.isParent() ||
|
||||
dtype.isSelf())) {
|
||||
TRACE(5, "TypeConstraint: this %p no such type %s, treating as object\n",
|
||||
this, typeName->data());
|
||||
this, m_typeName->data());
|
||||
m_type = { KindOfObject, MetaType::Precise };
|
||||
m_namedEntity = Unit::GetNamedEntity(typeName);
|
||||
m_namedEntity = Unit::GetNamedEntity(m_typeName);
|
||||
TRACE(5, "TypeConstraint: NamedEntity: %p\n", m_namedEntity);
|
||||
return;
|
||||
}
|
||||
@@ -144,7 +163,7 @@ bool TypeConstraint::checkTypedefObj(const TypedValue* tv) const {
|
||||
|
||||
bool
|
||||
TypeConstraint::check(const TypedValue* tv, const Func* func) const {
|
||||
assert(exists());
|
||||
assert(hasConstraint());
|
||||
|
||||
// This is part of the interpreter runtime; perf matters.
|
||||
if (tv->m_type == KindOfRef) {
|
||||
@@ -210,6 +229,25 @@ TypeConstraint::checkPrimitive(DataType dt) const {
|
||||
return equivDataTypes(m_type.m_dt, dt);
|
||||
}
|
||||
|
||||
static const char* describe_actual_type(const TypedValue* tv) {
|
||||
tv = tvToCell(tv);
|
||||
switch (tv->m_type) {
|
||||
case KindOfUninit:
|
||||
case KindOfNull: return "null";
|
||||
case KindOfBoolean: return "bool";
|
||||
case KindOfInt64: return "int";
|
||||
case KindOfDouble: return "double";
|
||||
case KindOfStaticString:
|
||||
case KindOfString: return "string";
|
||||
case KindOfArray: return "array";
|
||||
case KindOfObject:
|
||||
return tv->m_data.pobj->o_getClassName().c_str();
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
not_reached();
|
||||
}
|
||||
|
||||
void TypeConstraint::verifyFail(const Func* func, int paramNum,
|
||||
const TypedValue* tv) const {
|
||||
Transl::VMRegAnchor _;
|
||||
@@ -221,8 +259,23 @@ void TypeConstraint::verifyFail(const Func* func, int paramNum,
|
||||
} else if (isParent()) {
|
||||
parentToTypeName(func, &tn);
|
||||
}
|
||||
throw_unexpected_argument_type(paramNum + 1, fname.str().c_str(),
|
||||
tn->data(), tvAsCVarRef(tv));
|
||||
|
||||
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(nullable() &&
|
||||
"only nullable extended type hints are currently supported");
|
||||
raise_warning(
|
||||
"Argument %d to %s must be of type ?%s, %s given",
|
||||
paramNum + 1, fname.str().c_str(), tn->data(), 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);
|
||||
}
|
||||
}
|
||||
|
||||
void TypeConstraint::selfToClass(const Func* func, const Class **cls) const {
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
#ifndef TYPE_CONSTRAINT_H_
|
||||
#define TYPE_CONSTRAINT_H_
|
||||
#ifndef incl_HPHP_TYPE_CONSTRAINT_H_
|
||||
#define incl_HPHP_TYPE_CONSTRAINT_H_
|
||||
|
||||
#include <string>
|
||||
#include <tr1/functional>
|
||||
@@ -32,11 +32,27 @@ class TypeConstraint {
|
||||
public:
|
||||
enum Flags {
|
||||
NoFlags = 0x0,
|
||||
|
||||
/*
|
||||
* Nullable type hints check they are either the specified type,
|
||||
* or null.
|
||||
*/
|
||||
Nullable = 0x1,
|
||||
HHType = 0x2
|
||||
|
||||
/*
|
||||
* This flag indicates either EnableHipHopSyntax was true, or the
|
||||
* type came from a <?hh file and EnableHipHopSyntax was false.
|
||||
*/
|
||||
HHType = 0x2,
|
||||
|
||||
/*
|
||||
* Extended hints are hints that do not apply to normal, vanilla
|
||||
* php. For example "?Foo".
|
||||
*/
|
||||
ExtendedHint = 0x4,
|
||||
};
|
||||
|
||||
protected:
|
||||
private:
|
||||
enum class MetaType {
|
||||
Precise,
|
||||
Self,
|
||||
@@ -78,20 +94,26 @@ protected:
|
||||
public:
|
||||
void verifyFail(const Func* func, int paramNum, const TypedValue* tv) const;
|
||||
|
||||
explicit TypeConstraint(const StringData* typeName, Flags flags)
|
||||
: m_flags(flags), m_typeName(typeName), m_namedEntity(0) {
|
||||
TypeConstraint()
|
||||
: m_flags(NoFlags)
|
||||
, m_typeName(nullptr)
|
||||
, m_namedEntity(nullptr)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
explicit TypeConstraint(const StringData* typeName = nullptr,
|
||||
bool nullable = false, bool hhType = false)
|
||||
: m_flags(NoFlags), m_typeName(typeName), m_namedEntity(0) {
|
||||
if (nullable) m_flags = (Flags)(m_flags | Nullable);
|
||||
if (hhType) m_flags = (Flags)(m_flags | HHType);
|
||||
TypeConstraint(const StringData* typeName, Flags flags)
|
||||
: m_flags(flags)
|
||||
, m_typeName(typeName)
|
||||
, m_namedEntity(nullptr)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
bool exists() const { return m_typeName; }
|
||||
TypeConstraint(const TypeConstraint&) = default;
|
||||
TypeConstraint& operator=(const TypeConstraint&) = default;
|
||||
|
||||
bool hasConstraint() const { return m_typeName; }
|
||||
|
||||
const StringData* typeName() const { return m_typeName; }
|
||||
const NamedEntity* namedEntity() const { return m_namedEntity; }
|
||||
@@ -124,12 +146,21 @@ public:
|
||||
}
|
||||
|
||||
bool compat(const TypeConstraint& other) const {
|
||||
if (other.isExtended() || isExtended()) {
|
||||
/*
|
||||
* Rely on the ahead of time typechecker---checking here can
|
||||
* make it harder to convert a base class or interface to <?hh,
|
||||
* because derived classes that are still <?php would all need
|
||||
* to be modified.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
return (m_typeName == other.m_typeName
|
||||
|| (m_typeName != nullptr && other.m_typeName != nullptr
|
||||
&& m_typeName->isame(other.m_typeName)));
|
||||
}
|
||||
|
||||
inline static bool equivDataTypes(DataType t1, DataType t2) {
|
||||
static bool equivDataTypes(DataType t1, DataType t2) {
|
||||
return
|
||||
(t1 == t2) ||
|
||||
(IS_STRING_TYPE(t1) && IS_STRING_TYPE(t2)) ||
|
||||
@@ -158,12 +189,17 @@ public:
|
||||
void selfToClass(const Func* func, const Class **cls) const;
|
||||
void parentToClass(const Func* func, const Class **cls) const;
|
||||
private:
|
||||
bool isExtended() const { return m_flags & ExtendedHint; }
|
||||
|
||||
void selfToTypeName(const Func* func, const StringData **typeName) const;
|
||||
void parentToTypeName(const Func* func, const StringData **typeName) const;
|
||||
};
|
||||
|
||||
inline TypeConstraint::Flags
|
||||
operator|(TypeConstraint::Flags a, TypeConstraint::Flags b) {
|
||||
return TypeConstraint::Flags(static_cast<int>(a) | static_cast<int>(b));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif /* TYPE_CONSTRAINT_H_ */
|
||||
#endif
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
class X<A,B,C> {}
|
||||
function takes_string(string $x) { return "1\n"; }
|
||||
function takes_x(X<int> $x) { return "1\n"; }
|
||||
function takes_opt_string(?string $x) { return "2\n"; }
|
||||
echo takes_string('foo');
|
||||
echo takes_x(new X());
|
||||
echo takes_opt_string(array(42)); // allowed -- maybe is desugared to nothing
|
||||
echo takes_x("foo"); // not allowed -- should desugar to X
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
1
|
||||
1
|
||||
2
|
||||
HipHop Fatal error: Argument 1 passed to takes_x() must be an instance of X, string given in %s on line 4
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?hh
|
||||
|
||||
class Bar { public function frob() { echo "frob\n"; } }
|
||||
class Foo {}
|
||||
|
||||
function foo(?Bar $x) {
|
||||
if ($x) {
|
||||
$x->frob();
|
||||
}
|
||||
}
|
||||
|
||||
foo(new Foo);
|
||||
@@ -0,0 +1,2 @@
|
||||
HipHop Warning: Argument 1 to foo() must be of type ?Bar, Foo given in %s on line 10
|
||||
HipHop Fatal error: Call to undefined method Foo::frob from anonymous context in %s on line 8
|
||||
@@ -0,0 +1,13 @@
|
||||
<?hh
|
||||
|
||||
interface Hey {
|
||||
function wat(?Foo $x);
|
||||
}
|
||||
|
||||
class Bar implements Hey {
|
||||
public function wat($x) {
|
||||
}
|
||||
}
|
||||
|
||||
new Bar();
|
||||
echo "ok\n";
|
||||
@@ -0,0 +1 @@
|
||||
ok
|
||||
@@ -0,0 +1,11 @@
|
||||
<?hh
|
||||
|
||||
class :url {
|
||||
};
|
||||
|
||||
function foo(?:url $xhp_object): void {
|
||||
}
|
||||
|
||||
foo(<url />);
|
||||
foo(array(1,2,3));
|
||||
foo(null);
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Warning: Argument 1 to foo() must be of type ?xhp_url, array given in %s on line 7
|
||||
@@ -0,0 +1,8 @@
|
||||
<?hh
|
||||
|
||||
function foo(?(int, int) $x) {}
|
||||
|
||||
foo(null);
|
||||
foo(array(1,2));
|
||||
foo(array(1,2,3)); // ok: typechecker validates it
|
||||
foo(new stdclass);
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Warning: Argument 1 to foo() must be of type ?array, stdClass given in %s on line 3
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
../hiphop.hphp_opts
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
../hiphop.opts
|
||||
Referência em uma Nova Issue
Bloquear um usuário