Fixes and improvements for type aliases
Replace NameDef with a new struct of runtime-resolved typedef information. This needs to include more than Class* or Typedef*, because we might have nullable type aliases, or a non-nullable alias to a nullable typedef, or vice versa. Switch to the new TypeAnnotation stuff in TypedefStatement instead of just strings so support for this isn't weird (shapes are outside of this for now though---see the hack in parser.cpp). Also fixes support for type aliases to mixed.
Esse commit está contido em:
@@ -6143,28 +6143,33 @@ void EmitterVisitor::emitClassUseTrait(PreClassEmitter* pce,
|
||||
}
|
||||
|
||||
void EmitterVisitor::emitTypedef(Emitter& e, TypedefStatementPtr td) {
|
||||
auto kind = !strcasecmp(td->value.c_str(), "array") ? KindOfArray :
|
||||
!strcasecmp(td->value.c_str(), "int") ? KindOfInt64 :
|
||||
!strcasecmp(td->value.c_str(), "integer") ? KindOfInt64 :
|
||||
!strcasecmp(td->value.c_str(), "bool") ? KindOfBoolean :
|
||||
!strcasecmp(td->value.c_str(), "boolean") ? KindOfBoolean :
|
||||
!strcasecmp(td->value.c_str(), "string") ? KindOfString :
|
||||
!strcasecmp(td->value.c_str(), "real") ? KindOfDouble :
|
||||
!strcasecmp(td->value.c_str(), "float") ? KindOfDouble :
|
||||
!strcasecmp(td->value.c_str(), "double") ? KindOfDouble :
|
||||
KindOfObject;
|
||||
auto const nullable = td->annot->isNullable();
|
||||
auto const valueStr = td->annot->stripNullable().vanillaName();
|
||||
auto const kind =
|
||||
td->annot->stripNullable().isMixed() ? KindOfAny :
|
||||
!strcasecmp(valueStr.c_str(), "array") ? KindOfArray :
|
||||
!strcasecmp(valueStr.c_str(), "int") ? KindOfInt64 :
|
||||
!strcasecmp(valueStr.c_str(), "integer") ? KindOfInt64 :
|
||||
!strcasecmp(valueStr.c_str(), "bool") ? KindOfBoolean :
|
||||
!strcasecmp(valueStr.c_str(), "boolean") ? KindOfBoolean :
|
||||
!strcasecmp(valueStr.c_str(), "string") ? KindOfString :
|
||||
!strcasecmp(valueStr.c_str(), "real") ? KindOfDouble :
|
||||
!strcasecmp(valueStr.c_str(), "float") ? KindOfDouble :
|
||||
!strcasecmp(valueStr.c_str(), "double") ? KindOfDouble :
|
||||
KindOfObject;
|
||||
|
||||
// We have to merge the strings as litstrs to ensure namedentity
|
||||
// creation.
|
||||
auto const name = StringData::GetStaticString(td->name);
|
||||
auto const value = StringData::GetStaticString(td->value);
|
||||
auto const value = StringData::GetStaticString(valueStr);
|
||||
m_ue.mergeLitstr(name);
|
||||
m_ue.mergeLitstr(value);
|
||||
|
||||
Typedef record;
|
||||
record.m_name = name;
|
||||
record.m_value = value;
|
||||
record.m_kind = kind;
|
||||
record.name = name;
|
||||
record.value = value;
|
||||
record.kind = kind;
|
||||
record.nullable = nullable;
|
||||
Id id = m_ue.addTypedef(record);
|
||||
e.DefTypedef(id);
|
||||
}
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#include "hphp/compiler/parser/parser.h"
|
||||
|
||||
#include <boost/make_shared.hpp>
|
||||
|
||||
#include "hphp/compiler/type_annotation.h"
|
||||
#include "hphp/util/parser/hphp.tab.hpp"
|
||||
#include "hphp/compiler/analysis/file_scope.h"
|
||||
@@ -1635,7 +1637,13 @@ void Parser::onGoto(Token &out, Token &label, bool limited) {
|
||||
}
|
||||
|
||||
void Parser::onTypedef(Token& out, const Token& name, const Token& type) {
|
||||
auto td_stmt = NEW_STMT(TypedefStatement, name.text(), type.text());
|
||||
// Note: we don't always get TypeAnnotations (e.g. for shape types
|
||||
// currently).
|
||||
auto const annot = type.typeAnnotation
|
||||
? type.typeAnnotation
|
||||
: boost::make_shared<TypeAnnotation>(type.text(), TypeAnnotationPtr());
|
||||
|
||||
auto td_stmt = NEW_STMT(TypedefStatement, name.text(), annot);
|
||||
td_stmt->onParse(m_ar, m_file);
|
||||
out->stmt = td_stmt;
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ namespace HPHP {
|
||||
TypedefStatement::TypedefStatement(
|
||||
STATEMENT_CONSTRUCTOR_PARAMETERS,
|
||||
const std::string& name,
|
||||
const std::string& value)
|
||||
const TypeAnnotationPtr& annot)
|
||||
: Statement(STATEMENT_CONSTRUCTOR_PARAMETER_VALUES(TypedefStatement))
|
||||
, name(name)
|
||||
, value(value)
|
||||
, annot(annot)
|
||||
{}
|
||||
|
||||
TypedefStatement::~TypedefStatement() {}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "hphp/compiler/statement/statement.h"
|
||||
#include "hphp/compiler/type_annotation.h"
|
||||
|
||||
namespace HPHP {
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@@ -28,7 +29,7 @@ namespace HPHP {
|
||||
struct TypedefStatement : Statement, IParseHandler {
|
||||
explicit TypedefStatement(STATEMENT_CONSTRUCTOR_PARAMETERS,
|
||||
const std::string& name,
|
||||
const std::string& value);
|
||||
const TypeAnnotationPtr& typeAnnotation);
|
||||
~TypedefStatement();
|
||||
|
||||
DECLARE_STATEMENT_VIRTUAL_FUNCTIONS;
|
||||
@@ -38,7 +39,7 @@ public: // IParseHandler
|
||||
|
||||
public:
|
||||
const std::string name;
|
||||
const std::string value;
|
||||
const TypeAnnotationPtr annot;
|
||||
};
|
||||
|
||||
typedef boost::shared_ptr<TypedefStatement> TypedefStatementPtr;
|
||||
|
||||
@@ -90,6 +90,11 @@ public:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return whether this TypeAnnotation is equal to the "mixed" type.
|
||||
*/
|
||||
bool isMixed() const { return !m_name.compare("mixed"); }
|
||||
|
||||
/*
|
||||
* Returns whether this TypeAnnotation is "simple"---as described
|
||||
* above, this implies it has only one level of depth. Both the
|
||||
|
||||
@@ -1095,7 +1095,7 @@ bool AutoloadHandler::autoloadType(CStrRef name) {
|
||||
return !m_map.isNull() &&
|
||||
loadFromMap(name, s_type, true,
|
||||
[] (CStrRef name) {
|
||||
return !!Unit::GetNamedEntity(name.get())->getCachedNameDef();
|
||||
return Unit::GetNamedEntity(name.get())->getCachedTypedef() != nullptr;
|
||||
}
|
||||
) != Failure;
|
||||
}
|
||||
|
||||
@@ -731,12 +731,18 @@ CacheHandle allocFixedFunction(const StringData* name) {
|
||||
return allocFixedFunction(Unit::GetNamedEntity(name), false);
|
||||
}
|
||||
|
||||
CacheHandle allocNameDef(const NamedEntity* ne) {
|
||||
if (ne->m_cachedNameDefOffset) {
|
||||
return ne->m_cachedNameDefOffset;
|
||||
CacheHandle allocTypedef(const NamedEntity* ne) {
|
||||
if (ne->m_cachedTypedefOffset) {
|
||||
return ne->m_cachedTypedefOffset;
|
||||
}
|
||||
return allocFuncOrClass(&ne->m_cachedNameDefOffset,
|
||||
false /* persistent */); // TODO(#2103214): support persistent
|
||||
|
||||
Lock l(s_handleMutex);
|
||||
// TODO(#2103214): support persistent
|
||||
const_cast<NamedEntity*>(ne)->m_cachedTypedefOffset =
|
||||
allocLocked(false /* persistent */,
|
||||
sizeof(TypedefReq),
|
||||
alignof(TypedefReq));
|
||||
return ne->m_cachedTypedefOffset;
|
||||
}
|
||||
|
||||
template<bool checkOnly>
|
||||
|
||||
@@ -342,14 +342,13 @@ CacheHandle allocFixedFunction(const NamedEntity* ne, bool persistent);
|
||||
CacheHandle allocFixedFunction(const StringData* name);
|
||||
|
||||
/*
|
||||
* NameDefs.
|
||||
* Type aliases.
|
||||
*
|
||||
* Request-private values for typedef names. When a typedef is
|
||||
* defined, a NameDef for it is cached here. If it is a typedef for a
|
||||
* class, the NameDef will be for the target class. Otherwise it's a
|
||||
* NameDef pointing to the Typedef*.
|
||||
* Request-private values for type aliases (typedefs). When a typedef
|
||||
* is defined, the entry for it is cached. This reserves enough space
|
||||
* for a TypedefReq struct.
|
||||
*/
|
||||
CacheHandle allocNameDef(const NamedEntity* name);
|
||||
CacheHandle allocTypedef(const NamedEntity* name);
|
||||
|
||||
/*
|
||||
* Constants.
|
||||
|
||||
@@ -285,16 +285,22 @@ void VerifyParamTypeSlow(const Class* cls,
|
||||
// object, so if it's a typedef for something non-objecty we're
|
||||
// failing anyway.
|
||||
if (auto namedEntity = expected->namedEntity()) {
|
||||
NameDef def = namedEntity->getCachedNameDef();
|
||||
auto def = namedEntity->getCachedTypedef();
|
||||
if (UNLIKELY(!def)) {
|
||||
VMRegAnchor _;
|
||||
String nameStr(const_cast<StringData*>(expected->typeName()));
|
||||
if (AutoloadHandler::s_instance->autoloadType(nameStr)) {
|
||||
def = namedEntity->getCachedNameDef();
|
||||
def = namedEntity->getCachedTypedef();
|
||||
}
|
||||
}
|
||||
if (def && (constraint = def.asClass()) && cls->classof(constraint)) {
|
||||
return;
|
||||
if (def) {
|
||||
// There's no need to handle nullable typedefs specially here:
|
||||
// we already know we're checking a non-null object with the
|
||||
// class `cls'.
|
||||
if (def->kind == KindOfObject) {
|
||||
constraint = def->klass;
|
||||
if (constraint && cls->classof(constraint)) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,60 +28,29 @@
|
||||
namespace HPHP {
|
||||
|
||||
class Class;
|
||||
class Typedef;
|
||||
struct Typedef;
|
||||
struct TypedefReq;
|
||||
class Func;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* A NameDef is a definition of a type for a given name. This
|
||||
* currently means it is either a Class*, a Typedef*, or null.
|
||||
*/
|
||||
struct NameDef : private boost::equality_comparable<NameDef> {
|
||||
explicit NameDef() : m_p(0) {}
|
||||
explicit NameDef(Class* c) : m_p(reinterpret_cast<uintptr_t>(c)) {
|
||||
assert(!(m_p & 0x1));
|
||||
}
|
||||
|
||||
explicit NameDef(Typedef* t)
|
||||
: m_p(reinterpret_cast<uintptr_t>(t) | 0x1)
|
||||
{}
|
||||
|
||||
Class* asClass() const {
|
||||
return m_p & 0x1 ? nullptr : reinterpret_cast<Class*>(m_p);
|
||||
}
|
||||
|
||||
Typedef* asTypedef() const {
|
||||
return m_p & 0x1 ? reinterpret_cast<Typedef*>(m_p - 1) : nullptr;
|
||||
}
|
||||
|
||||
explicit operator bool() const { return !!m_p; }
|
||||
|
||||
bool operator==(NameDef other) const {
|
||||
return m_p == other.m_p;
|
||||
}
|
||||
|
||||
private:
|
||||
uintptr_t m_p;
|
||||
};
|
||||
|
||||
/*
|
||||
* NamedEntity represents a user-defined name that may map to
|
||||
* different objects (NameDefs) in different requests. Classes and
|
||||
* functions are in separate namespaces, so we have a targetcache
|
||||
* offset for resolving each.
|
||||
* different objects in different requests. Classes and functions are
|
||||
* in separate namespaces, so we have a targetcache offset for
|
||||
* resolving each.
|
||||
*
|
||||
* Classes and typedefs are in the same namespace when we're naming
|
||||
* types, but different namespaces at sites that allocate classes. If
|
||||
* a typedef is defined for a given name, we'll cache a NameDef in
|
||||
* each request at m_cachedNameDefOffset. Classes are always cached
|
||||
* at m_cachedClassOffset. See TargetCache::allocNameDef.
|
||||
* a typedef is defined for a given name, we'll cache it in each
|
||||
* request at m_cachedTypedefOffset. Classes are always cached at
|
||||
* m_cachedClassOffset.
|
||||
*/
|
||||
struct NamedEntity {
|
||||
explicit NamedEntity()
|
||||
: m_cachedClassOffset(0)
|
||||
, m_cachedFuncOffset(0)
|
||||
, m_cachedNameDefOffset(0)
|
||||
, m_cachedTypedefOffset(0)
|
||||
, m_clsList(nullptr)
|
||||
{}
|
||||
|
||||
@@ -89,7 +58,7 @@ struct NamedEntity {
|
||||
// read them without locks.
|
||||
unsigned m_cachedClassOffset;
|
||||
unsigned m_cachedFuncOffset;
|
||||
unsigned m_cachedNameDefOffset;
|
||||
unsigned m_cachedTypedefOffset;
|
||||
|
||||
void setCachedFunc(Func *f);
|
||||
Func* getCachedFunc() const;
|
||||
@@ -97,8 +66,8 @@ struct NamedEntity {
|
||||
void setCachedClass(Class* c);
|
||||
Class* getCachedClass() const;
|
||||
|
||||
NameDef getCachedNameDef() const;
|
||||
void setCachedNameDef(NameDef);
|
||||
const TypedefReq* getCachedTypedef() const;
|
||||
void setCachedTypedef(const TypedefReq&);
|
||||
|
||||
Class* clsList() const {
|
||||
return m_clsList;
|
||||
@@ -115,6 +84,16 @@ private:
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* Lookup a Typedef* for the supplied NamedEntity (which must be the
|
||||
* NamedEntity for `name'), if necessary invoking autoload for types
|
||||
* but not classes.
|
||||
*/
|
||||
const TypedefReq* getTypedefWithAutoload(const NamedEntity* ne,
|
||||
const StringData* name);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct ahm_string_data_isame {
|
||||
bool operator()(const StringData *s1, const StringData *s2) const {
|
||||
// ahm uses -1, -2, -3 as magic values
|
||||
|
||||
@@ -128,15 +128,15 @@ void TypeConstraint::init() {
|
||||
* autoload typedefs because they can affect whether the
|
||||
* VerifyParamType would succeed.
|
||||
*/
|
||||
static NameDef getNameDefWithAutoload(const NamedEntity* ne,
|
||||
const StringData* name) {
|
||||
NameDef def = ne->getCachedNameDef();
|
||||
const TypedefReq* getTypedefWithAutoload(const NamedEntity* ne,
|
||||
const StringData* name) {
|
||||
auto def = ne->getCachedTypedef();
|
||||
if (!def) {
|
||||
String nameStr(const_cast<StringData*>(name));
|
||||
if (!AutoloadHandler::s_instance->autoloadType(nameStr)) {
|
||||
return NameDef();
|
||||
return nullptr;
|
||||
}
|
||||
def = ne->getCachedNameDef();
|
||||
def = ne->getCachedTypedef();
|
||||
}
|
||||
return def;
|
||||
}
|
||||
@@ -145,20 +145,21 @@ bool TypeConstraint::checkTypedefNonObj(const TypedValue* tv) const {
|
||||
assert(tv->m_type != KindOfObject); // this checks when tv is not an object
|
||||
assert(!isSelf() && !isParent());
|
||||
|
||||
NameDef def = getNameDefWithAutoload(m_namedEntity, m_typeName);
|
||||
if (!def) return false;
|
||||
Typedef* td = def.asTypedef();
|
||||
return td && equivDataTypes(td->m_kind, tv->m_type);
|
||||
auto const td = getTypedefWithAutoload(m_namedEntity, m_typeName);
|
||||
if (!td) return false;
|
||||
if (td->nullable && IS_NULL_TYPE(tv->m_type)) return true;
|
||||
return td->kind == KindOfAny || equivDataTypes(td->kind, tv->m_type);
|
||||
}
|
||||
|
||||
bool TypeConstraint::checkTypedefObj(const TypedValue* tv) const {
|
||||
assert(tv->m_type == KindOfObject); // this checks when tv is an object
|
||||
assert(!isSelf() && !isParent() && !isCallable());
|
||||
|
||||
NameDef def = getNameDefWithAutoload(m_namedEntity, m_typeName);
|
||||
if (!def) return false;
|
||||
Class* c = def.asClass();
|
||||
return c && tv->m_data.pobj->instanceof(c);
|
||||
auto const td = getTypedefWithAutoload(m_namedEntity, m_typeName);
|
||||
if (!td) return false;
|
||||
if (td->nullable && IS_NULL_TYPE(tv->m_type)) return true;
|
||||
if (td->kind != KindOfObject) return false;
|
||||
return td->klass && tv->m_data.pobj->instanceof(td->klass);
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
+63
-74
@@ -145,16 +145,22 @@ Class* NamedEntity::getCachedClass() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NamedEntity::setCachedNameDef(NameDef nd) {
|
||||
assert(m_cachedNameDefOffset);
|
||||
Transl::TargetCache::handleToRef<NameDef>(m_cachedNameDefOffset) = nd;
|
||||
void NamedEntity::setCachedTypedef(const TypedefReq& td) {
|
||||
assert(m_cachedTypedefOffset);
|
||||
auto& tdReq = Transl::TargetCache::handleToRef<TypedefReq>(
|
||||
m_cachedTypedefOffset
|
||||
);
|
||||
tdReq = td;
|
||||
}
|
||||
|
||||
NameDef NamedEntity::getCachedNameDef() const {
|
||||
if (LIKELY(m_cachedNameDefOffset != 0)) {
|
||||
return Transl::TargetCache::handleToRef<NameDef>(m_cachedNameDefOffset);
|
||||
const TypedefReq* NamedEntity::getCachedTypedef() const {
|
||||
if (LIKELY(m_cachedTypedefOffset != 0)) {
|
||||
auto ret = &Transl::TargetCache::handleToRef<const TypedefReq>(
|
||||
m_cachedTypedefOffset
|
||||
);
|
||||
return ret->name ? ret : nullptr;
|
||||
}
|
||||
return NameDef();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NamedEntity::pushClass(Class* cls) {
|
||||
@@ -527,20 +533,15 @@ Class* Unit::defClass(const PreClass* preClass,
|
||||
* Raise a fatal unless the existing class definition is identical to the
|
||||
* one this invocation would create.
|
||||
*/
|
||||
if (NameDef current = nameList->getCachedNameDef()) {
|
||||
auto name = current.asTypedef()
|
||||
? current.asTypedef()->m_name
|
||||
: current.asClass()->name();
|
||||
|
||||
if (auto current = nameList->getCachedTypedef()) {
|
||||
FrameRestore fr(preClass);
|
||||
raise_error("Cannot declare class with the same name (%s) as an "
|
||||
"existing type", name->data());
|
||||
"existing type", current->name->data());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If it's compatible, the class must have been declared as a
|
||||
// DefClass, not a typedef. So we don't need to check the NameDef
|
||||
// for a class, only the cached class offset.
|
||||
// If there was already a class declared with DefClass, check if
|
||||
// it's compatible.
|
||||
if (Class* cls = nameList->getCachedClass()) {
|
||||
if (cls->preClass() != preClass) {
|
||||
if (failIsFatal) {
|
||||
@@ -651,33 +652,19 @@ bool Unit::aliasClass(Class* original, const StringData* alias) {
|
||||
void Unit::defTypedef(Id id) {
|
||||
assert(id < m_typedefs.size());
|
||||
auto thisType = &m_typedefs[id];
|
||||
auto nameList = GetNamedEntity(thisType->m_name);
|
||||
const StringData* typeName = thisType->m_value;
|
||||
|
||||
auto checkExistingClass = [&] (Class* cls) {
|
||||
if (thisType->m_kind != KindOfObject ||
|
||||
!cls->name()->isame(typeName)) {
|
||||
raise_error("The type %s is already defined to a different class (%s)",
|
||||
thisType->m_name->data(),
|
||||
cls->name()->data());
|
||||
}
|
||||
};
|
||||
auto nameList = GetNamedEntity(thisType->name);
|
||||
const StringData* typeName = thisType->value;
|
||||
|
||||
/*
|
||||
* Check if this name already has a NameDef, and if so make sure it
|
||||
* is compatible.
|
||||
* Check if this name already was defined as a type alias, and if so
|
||||
* make sure it is compatible.
|
||||
*/
|
||||
if (NameDef current = nameList->getCachedNameDef()) {
|
||||
if (Class* cls = current.asClass()) {
|
||||
checkExistingClass(cls);
|
||||
return;
|
||||
}
|
||||
Typedef* td = current.asTypedef();
|
||||
assert(td);
|
||||
if (thisType->m_kind != td->m_kind ||
|
||||
!td->m_value->isame(typeName)) {
|
||||
if (auto current = nameList->getCachedTypedef()) {
|
||||
if (thisType->kind != current->kind ||
|
||||
thisType->nullable != current->nullable ||
|
||||
Unit::lookupClass(typeName) != current->klass) {
|
||||
raise_error("The type %s is already defined to an incompatible type",
|
||||
thisType->m_name->data());
|
||||
thisType->name->data());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -685,56 +672,58 @@ void Unit::defTypedef(Id id) {
|
||||
// There might also be a class with this name already.
|
||||
if (Class* cls = nameList->getCachedClass()) {
|
||||
raise_error("The name %s is already defined as a class",
|
||||
thisType->m_name->data());
|
||||
thisType->name->data());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nameList->m_cachedNameDefOffset) {
|
||||
nameList->m_cachedNameDefOffset =
|
||||
Transl::TargetCache::allocNameDef(nameList);
|
||||
if (!nameList->m_cachedTypedefOffset) {
|
||||
nameList->m_cachedTypedefOffset =
|
||||
Transl::TargetCache::allocTypedef(nameList);
|
||||
}
|
||||
|
||||
/*
|
||||
* The cached NameDef for this typedef will be the actual Class* if
|
||||
* it is a typedef for a class type, otherwise it is a pointer to a
|
||||
* Typedef structure.
|
||||
*
|
||||
* If this typedef is a KindOfObject and the name on the right hand
|
||||
* side was another typedef, we will bind the name to the other side
|
||||
* for this request. We need to inspect the right hand side and
|
||||
* figure out what it was first.
|
||||
* for this request (i.e. resolve that typedef now).
|
||||
*
|
||||
* We need to inspect the right hand side and figure out what it was
|
||||
* first.
|
||||
*
|
||||
* If the right hand side was a class, we need to autoload and
|
||||
* ensure it exists at this point.
|
||||
*/
|
||||
|
||||
if (thisType->m_kind != KindOfObject) {
|
||||
nameList->setCachedNameDef(NameDef(thisType));
|
||||
return;
|
||||
}
|
||||
if (auto klass = Unit::loadClass(typeName)) {
|
||||
nameList->setCachedNameDef(NameDef(klass));
|
||||
if (thisType->kind != KindOfObject) {
|
||||
nameList->setCachedTypedef(
|
||||
TypedefReq { thisType->kind,
|
||||
thisType->nullable,
|
||||
nullptr,
|
||||
thisType->name }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
auto targetNameList = GetNamedEntity(typeName);
|
||||
NameDef target = targetNameList->getCachedNameDef();
|
||||
if (!target) {
|
||||
String normName = normalizeNS(typeName);
|
||||
if (normName) {
|
||||
typeName = normName.get();
|
||||
targetNameList = GetNamedEntity(typeName);
|
||||
target = targetNameList->getCachedNameDef();
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
AutoloadHandler::s_instance->autoloadType(typeName->data());
|
||||
target = targetNameList->getCachedNameDef();
|
||||
if (!target) {
|
||||
raise_error("Unknown type or class %s", typeName->data());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (auto targetTd = getTypedefWithAutoload(targetNameList, typeName)) {
|
||||
nameList->setCachedTypedef(
|
||||
TypedefReq { targetTd->kind,
|
||||
thisType->nullable || targetTd->nullable,
|
||||
targetTd->klass,
|
||||
thisType->name }
|
||||
);
|
||||
return;
|
||||
}
|
||||
assert(target);
|
||||
nameList->setCachedNameDef(target);
|
||||
if (auto klass = Unit::loadClass(typeName)) {
|
||||
nameList->setCachedTypedef(
|
||||
TypedefReq { KindOfObject,
|
||||
thisType->nullable,
|
||||
klass,
|
||||
thisType->name }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
raise_error("Unknown type or class %s", typeName->data());
|
||||
}
|
||||
|
||||
void Unit::renameFunc(const StringData* oldName, const StringData* newName) {
|
||||
|
||||
@@ -246,20 +246,36 @@ typedef std::vector<FuncEntry> FuncTable;
|
||||
* just a name. At runtime we still might resolve this name to
|
||||
* another typedef, becoming a typedef for KindOfArray or something in
|
||||
* that request.
|
||||
*
|
||||
* For the per-request struct, see TypedefReq below.
|
||||
*/
|
||||
struct Typedef {
|
||||
const StringData* m_name;
|
||||
const StringData* m_value;
|
||||
DataType m_kind;
|
||||
const StringData* name;
|
||||
const StringData* value;
|
||||
DataType kind;
|
||||
bool nullable; // Null is allowed; for ?Foo typedefs
|
||||
|
||||
template<class SerDe> void serde(SerDe& sd) {
|
||||
sd(m_name)
|
||||
(m_value)
|
||||
(m_kind)
|
||||
sd(name)
|
||||
(value)
|
||||
(kind)
|
||||
(nullable)
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* In a given request, a defined typedef is turned into a TypedefReq
|
||||
* struct. This contains the information needed to validate parameter
|
||||
* type hints for a typedef at runtime.
|
||||
*/
|
||||
struct TypedefReq {
|
||||
DataType kind; // may be KindOfAny for "mixed"
|
||||
bool nullable; // for option types, like ?Foo
|
||||
Class* klass; // nullptr if kind != KindOfObject
|
||||
const StringData* name; // needed for error messages; nullptr if not defined
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// (const StringData*) versus (StringData*)
|
||||
//
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?hh
|
||||
|
||||
// non newtypes can't have constraints
|
||||
type Foo as int = int; // error
|
||||
+1
-1
@@ -1 +1 @@
|
||||
HipHop Fatal error: syntax error, unexpected T_AS, expecting '=' in %s on line 3
|
||||
HipHop Fatal error: syntax error, unexpected T_AS, expecting '=' in %s on line 4
|
||||
@@ -1,3 +0,0 @@
|
||||
<?hh
|
||||
|
||||
type Foo as int = int;
|
||||
@@ -1 +1 @@
|
||||
HipHop Fatal error: The type MyType is already defined to a different class (MyClass) in %s on line 10
|
||||
HipHop Fatal error: The type MyType is already defined to an incompatible type in %s on line 10
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
type Foo = mixed;
|
||||
|
||||
function foo(Foo $x) {
|
||||
echo $x;
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
foo(12);
|
||||
@@ -0,0 +1 @@
|
||||
12
|
||||
@@ -0,0 +1,14 @@
|
||||
<?hh
|
||||
|
||||
type foo = ?int;
|
||||
function bar(foo $k) { var_dump($k); }
|
||||
bar(12);
|
||||
bar(null);
|
||||
bar(42);
|
||||
|
||||
class something {}
|
||||
type blah = ?something;
|
||||
function bar2(blah $k) { var_dump($k); }
|
||||
bar2(new something());
|
||||
bar2(null);
|
||||
bar2(new something());
|
||||
@@ -0,0 +1,8 @@
|
||||
int(12)
|
||||
NULL
|
||||
int(42)
|
||||
object(something)#1 (0) {
|
||||
}
|
||||
NULL
|
||||
object(something)#1 (0) {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?hh
|
||||
|
||||
type foo = int;
|
||||
type bar = ?foo;
|
||||
|
||||
function wat(bar $x) {
|
||||
var_dump($x);
|
||||
}
|
||||
|
||||
wat(null);
|
||||
wat(2);
|
||||
wat("fail"); // ends test
|
||||
@@ -0,0 +1,3 @@
|
||||
NULL
|
||||
int(2)
|
||||
HipHop Fatal error: Argument 1 passed to wat() must be an instance of bar, string given in %s on line 8
|
||||
@@ -0,0 +1,12 @@
|
||||
<?hh
|
||||
|
||||
type foo = ?int;
|
||||
type bar = foo;
|
||||
|
||||
function wat(bar $x) {
|
||||
var_dump($x);
|
||||
}
|
||||
|
||||
wat(null);
|
||||
wat(2);
|
||||
wat("fail"); // ends test
|
||||
@@ -0,0 +1,3 @@
|
||||
NULL
|
||||
int(2)
|
||||
HipHop Fatal error: Argument 1 passed to wat() must be an instance of bar, string given in %s on line 8
|
||||
@@ -0,0 +1,12 @@
|
||||
<?hh
|
||||
|
||||
type one = ?int;
|
||||
type two = ?int;
|
||||
|
||||
function yeah(two $x) {
|
||||
var_dump($x);
|
||||
}
|
||||
|
||||
yeah(12);
|
||||
yeah(null);
|
||||
yeah("fail"); // ends test
|
||||
@@ -0,0 +1,3 @@
|
||||
int(12)
|
||||
NULL
|
||||
HipHop Fatal error: Argument 1 passed to yeah() must be an instance of two, string given in %s on line 8
|
||||
@@ -0,0 +1,6 @@
|
||||
<?hh
|
||||
|
||||
type foo = ?int;
|
||||
function bar(foo $k) {}
|
||||
bar("fail");
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: Argument 1 passed to bar() must be an instance of foo, string given in %s on line 4
|
||||
@@ -0,0 +1,6 @@
|
||||
<?hh
|
||||
|
||||
class something {}
|
||||
type blah = ?something;
|
||||
function bar2(blah $k) {}
|
||||
bar2("fail");
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: Argument 1 passed to bar2() must be an instance of blah, string given in %s on line 5
|
||||
Referência em uma Nova Issue
Bloquear um usuário