From 67bf707768ac9c2c948e1ff9d932f3fd99af204f Mon Sep 17 00:00:00 2001 From: Jordan DeLong Date: Thu, 20 Jun 2013 17:08:48 -0700 Subject: [PATCH] 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. --- hphp/compiler/analysis/emitter.cpp | 33 +++-- hphp/compiler/parser/parser.cpp | 12 +- hphp/compiler/statement/typedef_statement.cpp | 4 +- hphp/compiler/statement/typedef_statement.h | 5 +- hphp/compiler/type_annotation.h | 5 + hphp/runtime/base/builtin_functions.cpp | 2 +- hphp/runtime/vm/jit/targetcache.cpp | 16 +- hphp/runtime/vm/jit/targetcache.h | 11 +- hphp/runtime/vm/jit/translator-runtime.cpp | 14 +- hphp/runtime/vm/named_entity.h | 65 +++------ hphp/runtime/vm/type_constraint.cpp | 27 ++-- hphp/runtime/vm/unit.cpp | 137 ++++++++---------- hphp/runtime/vm/unit.h | 28 +++- .../test/quick/parse_fail_type_constraint.php | 4 + ...=> parse_fail_type_constraint.php.expectf} | 2 +- hphp/test/quick/typedef_constraint2.php | 3 - hphp/test/quick/typedef_duplicate.php.expectf | 2 +- hphp/test/quick/typedef_mixed.php | 10 ++ hphp/test/quick/typedef_mixed.php.expect | 1 + hphp/test/quick/typedef_option.php | 14 ++ hphp/test/quick/typedef_option.php.expect | 8 + hphp/test/quick/typedef_option2.php | 12 ++ hphp/test/quick/typedef_option2.php.expectf | 3 + hphp/test/quick/typedef_option3.php | 12 ++ hphp/test/quick/typedef_option3.php.expectf | 3 + hphp/test/quick/typedef_option4.php | 12 ++ hphp/test/quick/typedef_option4.php.expectf | 3 + hphp/test/quick/typedef_option_fail1.php | 6 + .../quick/typedef_option_fail1.php.expectf | 1 + hphp/test/quick/typedef_option_fail2.php | 6 + .../quick/typedef_option_fail2.php.expectf | 1 + 31 files changed, 285 insertions(+), 177 deletions(-) create mode 100644 hphp/test/quick/parse_fail_type_constraint.php rename hphp/test/quick/{typedef_constraint2.php.expectf => parse_fail_type_constraint.php.expectf} (79%) delete mode 100644 hphp/test/quick/typedef_constraint2.php create mode 100644 hphp/test/quick/typedef_mixed.php create mode 100644 hphp/test/quick/typedef_mixed.php.expect create mode 100644 hphp/test/quick/typedef_option.php create mode 100644 hphp/test/quick/typedef_option.php.expect create mode 100644 hphp/test/quick/typedef_option2.php create mode 100644 hphp/test/quick/typedef_option2.php.expectf create mode 100644 hphp/test/quick/typedef_option3.php create mode 100644 hphp/test/quick/typedef_option3.php.expectf create mode 100644 hphp/test/quick/typedef_option4.php create mode 100644 hphp/test/quick/typedef_option4.php.expectf create mode 100644 hphp/test/quick/typedef_option_fail1.php create mode 100644 hphp/test/quick/typedef_option_fail1.php.expectf create mode 100644 hphp/test/quick/typedef_option_fail2.php create mode 100644 hphp/test/quick/typedef_option_fail2.php.expectf diff --git a/hphp/compiler/analysis/emitter.cpp b/hphp/compiler/analysis/emitter.cpp index 01ae33f92..7a7cc2d74 100644 --- a/hphp/compiler/analysis/emitter.cpp +++ b/hphp/compiler/analysis/emitter.cpp @@ -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); } diff --git a/hphp/compiler/parser/parser.cpp b/hphp/compiler/parser/parser.cpp index db55e15d2..51925b84c 100644 --- a/hphp/compiler/parser/parser.cpp +++ b/hphp/compiler/parser/parser.cpp @@ -13,8 +13,10 @@ | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ - #include "hphp/compiler/parser/parser.h" + +#include + #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(type.text(), TypeAnnotationPtr()); + + auto td_stmt = NEW_STMT(TypedefStatement, name.text(), annot); td_stmt->onParse(m_ar, m_file); out->stmt = td_stmt; } diff --git a/hphp/compiler/statement/typedef_statement.cpp b/hphp/compiler/statement/typedef_statement.cpp index e83401bda..2e2a7bcba 100644 --- a/hphp/compiler/statement/typedef_statement.cpp +++ b/hphp/compiler/statement/typedef_statement.cpp @@ -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() {} diff --git a/hphp/compiler/statement/typedef_statement.h b/hphp/compiler/statement/typedef_statement.h index 1466654cd..7a313ac69 100644 --- a/hphp/compiler/statement/typedef_statement.h +++ b/hphp/compiler/statement/typedef_statement.h @@ -21,6 +21,7 @@ #include #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 TypedefStatementPtr; diff --git a/hphp/compiler/type_annotation.h b/hphp/compiler/type_annotation.h index 97ba7ef14..d7b89d72f 100644 --- a/hphp/compiler/type_annotation.h +++ b/hphp/compiler/type_annotation.h @@ -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 diff --git a/hphp/runtime/base/builtin_functions.cpp b/hphp/runtime/base/builtin_functions.cpp index 560f94ea4..f6127a58d 100644 --- a/hphp/runtime/base/builtin_functions.cpp +++ b/hphp/runtime/base/builtin_functions.cpp @@ -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; } diff --git a/hphp/runtime/vm/jit/targetcache.cpp b/hphp/runtime/vm/jit/targetcache.cpp index 37f056eea..dbef19a34 100644 --- a/hphp/runtime/vm/jit/targetcache.cpp +++ b/hphp/runtime/vm/jit/targetcache.cpp @@ -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(ne)->m_cachedTypedefOffset = + allocLocked(false /* persistent */, + sizeof(TypedefReq), + alignof(TypedefReq)); + return ne->m_cachedTypedefOffset; } template diff --git a/hphp/runtime/vm/jit/targetcache.h b/hphp/runtime/vm/jit/targetcache.h index 424d62112..1c99a7585 100644 --- a/hphp/runtime/vm/jit/targetcache.h +++ b/hphp/runtime/vm/jit/targetcache.h @@ -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. diff --git a/hphp/runtime/vm/jit/translator-runtime.cpp b/hphp/runtime/vm/jit/translator-runtime.cpp index 7a22d4929..a028bdd81 100644 --- a/hphp/runtime/vm/jit/translator-runtime.cpp +++ b/hphp/runtime/vm/jit/translator-runtime.cpp @@ -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(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; + } } } diff --git a/hphp/runtime/vm/named_entity.h b/hphp/runtime/vm/named_entity.h index 109599038..f59477015 100644 --- a/hphp/runtime/vm/named_entity.h +++ b/hphp/runtime/vm/named_entity.h @@ -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 { - explicit NameDef() : m_p(0) {} - explicit NameDef(Class* c) : m_p(reinterpret_cast(c)) { - assert(!(m_p & 0x1)); - } - - explicit NameDef(Typedef* t) - : m_p(reinterpret_cast(t) | 0x1) - {} - - Class* asClass() const { - return m_p & 0x1 ? nullptr : reinterpret_cast(m_p); - } - - Typedef* asTypedef() const { - return m_p & 0x1 ? reinterpret_cast(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 diff --git a/hphp/runtime/vm/type_constraint.cpp b/hphp/runtime/vm/type_constraint.cpp index fed0bb15a..c4a61e0a8 100644 --- a/hphp/runtime/vm/type_constraint.cpp +++ b/hphp/runtime/vm/type_constraint.cpp @@ -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(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 diff --git a/hphp/runtime/vm/unit.cpp b/hphp/runtime/vm/unit.cpp index 654538af3..fb2826f0d 100644 --- a/hphp/runtime/vm/unit.cpp +++ b/hphp/runtime/vm/unit.cpp @@ -145,16 +145,22 @@ Class* NamedEntity::getCachedClass() const { return nullptr; } -void NamedEntity::setCachedNameDef(NameDef nd) { - assert(m_cachedNameDefOffset); - Transl::TargetCache::handleToRef(m_cachedNameDefOffset) = nd; +void NamedEntity::setCachedTypedef(const TypedefReq& td) { + assert(m_cachedTypedefOffset); + auto& tdReq = Transl::TargetCache::handleToRef( + m_cachedTypedefOffset + ); + tdReq = td; } -NameDef NamedEntity::getCachedNameDef() const { - if (LIKELY(m_cachedNameDefOffset != 0)) { - return Transl::TargetCache::handleToRef(m_cachedNameDefOffset); +const TypedefReq* NamedEntity::getCachedTypedef() const { + if (LIKELY(m_cachedTypedefOffset != 0)) { + auto ret = &Transl::TargetCache::handleToRef( + 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) { diff --git a/hphp/runtime/vm/unit.h b/hphp/runtime/vm/unit.h index 9d227b5b9..542670a24 100644 --- a/hphp/runtime/vm/unit.h +++ b/hphp/runtime/vm/unit.h @@ -246,20 +246,36 @@ typedef std::vector 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 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*) // diff --git a/hphp/test/quick/parse_fail_type_constraint.php b/hphp/test/quick/parse_fail_type_constraint.php new file mode 100644 index 000000000..685fe2665 --- /dev/null +++ b/hphp/test/quick/parse_fail_type_constraint.php @@ -0,0 +1,4 @@ +