From 0e480d5e09ef66166f75163c9c9f61692557473d Mon Sep 17 00:00:00 2001 From: Xiao Shi Date: Thu, 23 Jul 2015 15:02:36 -0700 Subject: [PATCH] resolve HH\this in TypeStructure for type constants Summary: resolve 'HH\this' in the type structures representing type constants in the runtime, to classes or interfaces. Depends on D2238974. Reviewed By: @dlreeves Differential Revision: D2250920 --- hphp/compiler/statement/class_constant.cpp | 2 +- hphp/compiler/type_annotation.cpp | 8 +- hphp/compiler/type_annotation.h | 7 +- hphp/hack/hhi/typestructure.hhi | 6 +- hphp/runtime/base/type-structure.cpp | 78 +++++++++------ hphp/runtime/base/type-structure.h | 9 +- .../ext/reflection/ext_reflection-classes.php | 4 + hphp/runtime/vm/class.cpp | 2 +- hphp/runtime/vm/preclass-inl.h | 11 ++ hphp/runtime/vm/preclass.h | 8 +- .../test/slow/type_annotation/type_annotation1.php | 2 +- .../type_annotation/type_annotation1.php.expect | 13 +-- .../type_annotation/type_annotation_shape1.php | 3 +- .../type_annotation_shape1.php.expect | 11 +- .../slow/type_annotation/type_annotation_this1.php | 19 ++++ .../type_annotation_this1.php.expect | 111 +++++++++++++++++++++ .../slow/type_annotation/type_annotation_this2.php | 11 ++ .../type_annotation_this2.php.expect | 12 +++ .../slow/type_annotation/type_annotation_this3.php | 25 +++++ .../type_annotation_this3.php.expect | 24 +++++ .../slow/type_annotation/type_annotation_this4.php | 23 +++++ .../type_annotation_this4.php.expect | 24 +++++ 22 files changed, 346 insertions(+), 67 deletions(-) create mode 100644 hphp/test/slow/type_annotation/type_annotation_this1.php create mode 100644 hphp/test/slow/type_annotation/type_annotation_this1.php.expect create mode 100644 hphp/test/slow/type_annotation/type_annotation_this2.php create mode 100644 hphp/test/slow/type_annotation/type_annotation_this2.php.expect create mode 100644 hphp/test/slow/type_annotation/type_annotation_this3.php create mode 100644 hphp/test/slow/type_annotation/type_annotation_this3.php.expect create mode 100644 hphp/test/slow/type_annotation/type_annotation_this4.php create mode 100644 hphp/test/slow/type_annotation/type_annotation_this4.php.expect diff --git a/hphp/compiler/statement/class_constant.cpp b/hphp/compiler/statement/class_constant.cpp index b141eefa036..9f265b9c730 100644 --- a/hphp/compiler/statement/class_constant.cpp +++ b/hphp/compiler/statement/class_constant.cpp @@ -40,7 +40,7 @@ ClassConstant::ClassConstant // for now only store TypeAnnotation info for type constants if (typeconst) { if (typeAnnot) { - m_typeStructure = typeAnnot->getScalarArrayRep(true); + m_typeStructure = typeAnnot->getScalarArrayRep(); } } } diff --git a/hphp/compiler/type_annotation.cpp b/hphp/compiler/type_annotation.cpp index 4537cf0b383..cf196784956 100644 --- a/hphp/compiler/type_annotation.cpp +++ b/hphp/compiler/type_annotation.cpp @@ -374,8 +374,7 @@ const StaticString s_access_list("access_list"), s_fields("fields"), s_is_cls_cns("is_cls_cns"), - s_value("value"), - s_unresolved("unresolved") + s_value("value") ; /* Turns the argsList linked list of TypeAnnotation into a positioned @@ -408,12 +407,9 @@ void TypeAnnotation::shapeFieldsToScalarArray(Array& rep, rep.add(s_fields, Variant(ArrayData::GetScalarArray(fields.get()))); } -ArrayData* TypeAnnotation::getScalarArrayRep(bool isTopLevel) const { +ArrayData* TypeAnnotation::getScalarArrayRep() const { auto rep = Array::Create(); - if (isTopLevel) { - rep.add(s_unresolved, true_varNR); - } bool nullable = (bool) m_nullable; if (nullable) { rep.add(s_nullable, true_varNR); diff --git a/hphp/compiler/type_annotation.h b/hphp/compiler/type_annotation.h index b443237ef94..c0d7049b15b 100644 --- a/hphp/compiler/type_annotation.h +++ b/hphp/compiler/type_annotation.h @@ -220,11 +220,10 @@ public: TypeAnnotationPtr getTypeArg(int n) const; TypeStructure::Kind getKind() const; + /* returns the scalar array representation (the TypeStructure) of - * this type annotation. If isTopLevel, the representation includes - * an element ['unresolved']=>bool(true), which is a temporary aid - * of TypeStructure resolution in the runtime. TODO(7657500) */ - ArrayData* getScalarArrayRep(bool isTopLevel = false) const; + * this type annotation. */ + ArrayData* getScalarArrayRep() const; private: void functionTypeName(std::string &name) const; diff --git a/hphp/hack/hhi/typestructure.hhi b/hphp/hack/hhi/typestructure.hhi index 6c8b4a95b8e..fce3bfcd422 100644 --- a/hphp/hack/hhi/typestructure.hhi +++ b/hphp/hack/hhi/typestructure.hhi @@ -30,6 +30,10 @@ enum TypeStructureKind: int { OF_ARRAY = 0; OF_GENERIC = 0; OF_SHAPE = 0; + OF_CLASS = 0; + OF_INTERFACE = 0; + OF_TRAIT = 0; + OF_ENUM = 0; } /* @@ -54,7 +58,7 @@ newtype TypeStructure = shape( 'generic_types' => ?array, // for shapes 'fields' => ?array, - // classname for classes + // classname for classes, interfaces, enums, or traits 'classname' => ?string, // name for generics (type variables) 'name' => ?string, diff --git a/hphp/runtime/base/type-structure.cpp b/hphp/runtime/base/type-structure.cpp index 576ddcb3956..a9846be5e51 100644 --- a/hphp/runtime/base/type-structure.cpp +++ b/hphp/runtime/base/type-structure.cpp @@ -49,8 +49,8 @@ const StaticString s_access_list("access_list"), s_fields("fields"), s_is_cls_cns("is_cls_cns"), - s_unresolved("unresolved"), - s_value("value") + s_value("value"), + s_this("HH\\this") ; const std::string @@ -251,6 +251,10 @@ std::string fullName (const ArrayData* arr) { case TypeStructure::Kind::T_xhp: xhpTypeName(arr, name); break; + case TypeStructure::Kind::T_class: + case TypeStructure::Kind::T_interface: + case TypeStructure::Kind::T_trait: + case TypeStructure::Kind::T_enum: case TypeStructure::Kind::T_unresolved: assert(arr->exists(s_classname)); name += arr->get(s_classname).getStringData()->toCppString(); @@ -261,15 +265,15 @@ std::string fullName (const ArrayData* arr) { return name; } -ArrayData* resolveTS(ArrayData* arr); +ArrayData* resolveTS(ArrayData* arr, const Class* typeCnsCls); -ArrayData* resolveList(ArrayData* arr) { +ArrayData* resolveList(ArrayData* arr, const Class* typeCnsCls) { auto const sz = arr->getSize(); auto newarr = Array::Create(); for (auto i = 0; i < sz; i++) { auto elem = arr->get(i).getArrayData(); - newarr.add(i, Variant(resolveTS(elem))); + newarr.add(i, Variant(resolveTS(elem, typeCnsCls))); } return ArrayData::GetScalarArray(newarr.get()); @@ -278,7 +282,7 @@ ArrayData* resolveList(ArrayData* arr) { /* Given an unresolved T_shape TypeStructure, returns the __fields__ * portion of the array with all the field names resolved to string * literals. */ -ArrayData* resolveShape(ArrayData* arr) { +ArrayData* resolveShape(ArrayData* arr, const Class* typeCnsCls) { assert(arr->exists(s_kind)); assert(static_cast(arr->get(s_kind).getNumData()) == TypeStructure::Kind::T_shape); @@ -311,14 +315,31 @@ ArrayData* resolveShape(ArrayData* arr) { } } assert(wrapper->exists(s_value)); - auto value = resolveTS(wrapper->get(s_value).getArrayData()); + auto value = resolveTS(wrapper->get(s_value).getArrayData(), typeCnsCls); newfields.add(key, Variant(value)); } return ArrayData::GetScalarArray(newfields.get()); } -ArrayData* resolveTS(ArrayData* arr) { +void resolveThis(Array& ret, const Class* typeCnsCls) { + TypeStructure::Kind resolvedKind; + if (isNormalClass(typeCnsCls)) { + resolvedKind = TypeStructure::Kind::T_class; + } else if (isInterface(typeCnsCls)) { + resolvedKind = TypeStructure::Kind::T_interface; + } else { + // trait or enum; should not reach here + raise_error("%s cannot contain a type constant " + "because it is not an interface or class", + typeCnsCls->name()->data()); + } + ret.set(s_kind, Variant(static_cast(resolvedKind))); + ret.add(s_classname, + Variant(makeStaticString(typeCnsCls->name()))); +} + +ArrayData* resolveTS(ArrayData* arr, const Class* typeCnsCls) { assert(arr->exists(s_kind)); auto const kind = static_cast( arr->get(s_kind).getNumData()); @@ -331,45 +352,50 @@ ArrayData* resolveTS(ArrayData* arr) { case TypeStructure::Kind::T_tuple: { assert(arr->exists(s_elem_types)); auto elemTypes = arr->get(s_elem_types).getArrayData(); - newarr.add(s_elem_types, Variant(resolveList(elemTypes))); + newarr.add(s_elem_types, Variant(resolveList(elemTypes, typeCnsCls))); break; } case TypeStructure::Kind::T_fun: { assert(arr->exists(s_return_type)); auto returnType = arr->get(s_return_type).getArrayData(); - newarr.add(s_return_type, Variant(resolveTS(returnType))); + newarr.add(s_return_type, Variant(resolveTS(returnType, typeCnsCls))); assert(arr->exists(s_param_types)); auto paramTypes = arr->get(s_param_types).getArrayData(); - newarr.add(s_param_types, Variant(resolveList(paramTypes))); + newarr.add(s_param_types, Variant(resolveList(paramTypes, typeCnsCls))); break; } case TypeStructure::Kind::T_array: { if (arr->exists(s_generic_types)) { auto genericTypes = arr->get(s_generic_types).getArrayData(); - newarr.add(s_generic_types, Variant(resolveList(genericTypes))); + newarr.add(s_generic_types, + Variant(resolveList(genericTypes, typeCnsCls))); } break; } case TypeStructure::Kind::T_shape: - newarr.add(s_fields, Variant(resolveShape(arr))); + newarr.add(s_fields, Variant(resolveShape(arr, typeCnsCls))); break; case TypeStructure::Kind::T_unresolved: { assert(arr->exists(s_classname)); - newarr.add(s_classname, Variant(arr->get(s_classname).getStringData())); + auto const clsName = arr->get(s_classname).getStringData(); + if (clsName->same(s_this.get())) { + resolveThis(newarr, typeCnsCls); + } else { + newarr.add(s_classname, Variant(arr->get(s_classname).getStringData())); + } if (arr->exists(s_generic_types)) { auto genericTypes = arr->get(s_generic_types).getArrayData(); - newarr.add(s_generic_types, Variant(resolveList(genericTypes))); + newarr.add(s_generic_types, + Variant(resolveList(genericTypes, typeCnsCls))); } break; } case TypeStructure::Kind::T_typevar: case TypeStructure::Kind::T_typeaccess: + // TODO(7650500): resolve this if it is the rootname. case TypeStructure::Kind::T_xhp: default: - if (!arr->exists(s_unresolved)) return arr; - newarr = Array(arr); - newarr.remove(s_unresolved); - break; + return arr; } return ArrayData::GetScalarArray(newarr.get()); @@ -380,20 +406,16 @@ ArrayData* resolveTS(ArrayData* arr) { String TypeStructure::toString(const ArrayData* arr) { if (arr->empty()) return String(); - /* When toString is called, the TypeStructure must be resolved. */ - assert(!arr->exists(s_unresolved)); - return String(fullName(arr)); } -/* Constructs a scalar array with all the shape field names resolved. */ -ArrayData* TypeStructure::resolve(ArrayData* arr) { +/* Constructs a scalar array with all the shape field names, 'this' + * resolved. TODO(7657500): resolve type access and generic types. */ +ArrayData* TypeStructure::resolve(ArrayData* arr, + const Class* typeCnsCls) { if (arr == nullptr) return arr; - if (arr->exists(s_unresolved)) return resolveTS(arr); - - assert(arr->isStatic()); - return arr; + return resolveTS(arr, typeCnsCls); } } // namespace HPHP diff --git a/hphp/runtime/base/type-structure.h b/hphp/runtime/base/type-structure.h index 7e7f75ff78d..b1d2563221e 100644 --- a/hphp/runtime/base/type-structure.h +++ b/hphp/runtime/base/type-structure.h @@ -23,6 +23,7 @@ namespace HPHP { struct String; struct ArrayData; +struct Class; /* Utility for representing full type information in the runtime. */ namespace TypeStructure { @@ -46,6 +47,12 @@ enum class Kind : uint8_t { T_typevar = 13, // corresponds to user OF_GENERIC T_shape = 14, + // These values are only used after resolution in ext_reflection.cpp + T_class = 15, + T_interface = 16, + T_trait = 17, + T_enum = 18, + /* TODO(7657500): the following kinds needs alias resolution, and * are not exposed to the users. Could resolve to a class, enum, * interface, or alias. */ @@ -56,7 +63,7 @@ enum class Kind : uint8_t { String toString(const ArrayData* arr); -ArrayData* resolve(ArrayData* arr); +ArrayData* resolve(ArrayData* arr, const Class* typeCstCls); } diff --git a/hphp/runtime/ext/reflection/ext_reflection-classes.php b/hphp/runtime/ext/reflection/ext_reflection-classes.php index a5cc4215276..5416e89cb9a 100644 --- a/hphp/runtime/ext/reflection/ext_reflection-classes.php +++ b/hphp/runtime/ext/reflection/ext_reflection-classes.php @@ -1092,6 +1092,10 @@ enum TypeStructureKind: int { OF_ARRAY = 12; OF_GENERIC = 13; OF_SHAPE = 14; + OF_CLASS = 15; + OF_INTERFACE = 16; + OF_TRAIT = 17; + OF_ENUM = 18; } <<__Native>> diff --git a/hphp/runtime/vm/class.cpp b/hphp/runtime/vm/class.cpp index 37a0e1e5c7f..c3968db7a32 100644 --- a/hphp/runtime/vm/class.cpp +++ b/hphp/runtime/vm/class.cpp @@ -929,7 +929,7 @@ Cell Class::clsCnsGet(const StringData* clsCnsName, bool includeTypeCns) const { // resolve type constant if (m_constants[clsCnsInd].isType()) { assert(clsCns->m_type == KindOfArray); - auto resTS = TypeStructure::resolve(clsCns->m_data.parr); + auto resTS = TypeStructure::resolve(clsCns->m_data.parr, this); auto tv = make_tv(resTS); tv.m_aux = clsCns->m_aux; assert(tvIsPlausible(tv)); diff --git a/hphp/runtime/vm/preclass-inl.h b/hphp/runtime/vm/preclass-inl.h index 7ad41655ae8..3ff1e1ac3fb 100644 --- a/hphp/runtime/vm/preclass-inl.h +++ b/hphp/runtime/vm/preclass-inl.h @@ -29,6 +29,10 @@ inline bool PreClass::isBuiltin() const { return m_attrs & AttrBuiltin; } +inline bool PreClass::hasConstant(const StringData* cnsName) const { + return m_constants.contains(cnsName); +} + inline bool PreClass::hasMethod(const StringData* methName) const { return m_methods.contains(methName); } @@ -37,6 +41,13 @@ inline bool PreClass::hasProp(const StringData* propName) const { return m_properties.contains(propName); } +inline const PreClass::Const* +PreClass::lookupConstant(const StringData* cnsName) const { + Slot s = m_constants.findIndex(cnsName); + assert(s != kInvalidSlot); + return &m_constants[s]; +} + inline Func* PreClass::lookupMethod(const StringData* methName) const { Func* f = m_methods.lookupDefault(methName, nullptr); assert(f != nullptr); diff --git a/hphp/runtime/vm/preclass.h b/hphp/runtime/vm/preclass.h index 1b7bd98fd45..f53848e3977 100644 --- a/hphp/runtime/vm/preclass.h +++ b/hphp/runtime/vm/preclass.h @@ -406,16 +406,18 @@ public: bool isBuiltin() const; /* - * Check whether a method or property exists on the PreClass. + * Check whether a constant, method, or property exists on the PreClass. */ + bool hasConstant(const StringData* cnsName) const; bool hasMethod(const StringData* methName) const; bool hasProp(const StringData* propName) const; /* - * Look up a method or property on the PreClass. + * Look up a constant, method, or property on the PreClass. * - * @requires: hasMethod(), hasProp(), respectively. + * @requires: hasConstant(), hasMethod(), hasProp(), respectively. */ + const Const* lookupConstant(const StringData* cnsName) const; Func* lookupMethod(const StringData* methName) const; const Prop* lookupProp(const StringData* propName) const; diff --git a/hphp/test/slow/type_annotation/type_annotation1.php b/hphp/test/slow/type_annotation/type_annotation1.php index f395a43c707..7cfccbb0481 100644 --- a/hphp/test/slow/type_annotation/type_annotation1.php +++ b/hphp/test/slow/type_annotation/type_annotation1.php @@ -2,7 +2,7 @@ class C { const type T = ?array; const type U = map>>; - const type V = (int, this, ?float, foo); + const type V = (int, ?float, foo); const type W = (function (): void); const type X = (function (mixed, resource): array); const type Y = N::M::O::P::Q; diff --git a/hphp/test/slow/type_annotation/type_annotation1.php.expect b/hphp/test/slow/type_annotation/type_annotation1.php.expect index ac8dda0d1de..7d7ac46ab3e 100644 --- a/hphp/test/slow/type_annotation/type_annotation1.php.expect +++ b/hphp/test/slow/type_annotation/type_annotation1.php.expect @@ -56,12 +56,12 @@ array(3) { } } } -string(33) "(HH\int, HH\this, ?HH\float, foo)" +string(24) "(HH\int, ?HH\float, foo)" array(2) { ["kind"]=> int(10) ["elem_types"]=> - array(4) { + array(3) { [0]=> array(1) { ["kind"]=> @@ -69,19 +69,12 @@ array(2) { } [1]=> array(2) { - ["kind"]=> - int(101) - ["classname"]=> - string(7) "HH\this" - } - [2]=> - array(2) { ["nullable"]=> bool(true) ["kind"]=> int(3) } - [3]=> + [2]=> array(2) { ["kind"]=> int(101) diff --git a/hphp/test/slow/type_annotation/type_annotation_shape1.php b/hphp/test/slow/type_annotation/type_annotation_shape1.php index 3e807bf027d..7108d61215c 100644 --- a/hphp/test/slow/type_annotation/type_annotation_shape1.php +++ b/hphp/test/slow/type_annotation/type_annotation_shape1.php @@ -4,8 +4,7 @@ class C { const type T = ?shape('field1'=>?bool, 'field2'=>int, 'field3'=>arraykey); const type U = Setstring,)>; const type V = shape('array'=>?Vector, - 'shape'=>shape('HH\\this'=>this, - 'HH\\float'=>float, + 'shape'=>shape('HH\\float'=>float, 'HH\\num'=>num), 'HH\\int'=>int, ); diff --git a/hphp/test/slow/type_annotation/type_annotation_shape1.php.expect b/hphp/test/slow/type_annotation/type_annotation_shape1.php.expect index 889ec75d752..30ca1b21d2c 100644 --- a/hphp/test/slow/type_annotation/type_annotation_shape1.php.expect +++ b/hphp/test/slow/type_annotation/type_annotation_shape1.php.expect @@ -48,7 +48,7 @@ array(3) { } } } -string(137) "HH\shape('array'=>?HH\Vector, 'shape'=>HH\shape('HH\this'=>HH\this, 'HH\float'=>HH\float, 'HH\num'=>HH\num), 'HH\int'=>HH\int)" +string(117) "HH\shape('array'=>?HH\Vector, 'shape'=>HH\shape('HH\float'=>HH\float, 'HH\num'=>HH\num), 'HH\int'=>HH\int)" array(2) { ["kind"]=> int(14) @@ -76,14 +76,7 @@ array(2) { ["kind"]=> int(14) ["fields"]=> - array(3) { - ["HH\this"]=> - array(2) { - ["kind"]=> - int(101) - ["classname"]=> - string(7) "HH\this" - } + array(2) { ["HH\float"]=> array(1) { ["kind"]=> diff --git a/hphp/test/slow/type_annotation/type_annotation_this1.php b/hphp/test/slow/type_annotation/type_annotation_this1.php new file mode 100644 index 00000000000..db17653f31d --- /dev/null +++ b/hphp/test/slow/type_annotation/type_annotation_this1.php @@ -0,0 +1,19 @@ +this, 'bar'=>Map); +} + +class Child extends Base { +} + +var_dump(type_structure(Base::class, 'T')); +var_dump(type_structure(Child::class, 'T')); + +var_dump(type_structure(Base::class, 'U')); +var_dump(type_structure(Child::class, 'U')); + +var_dump(type_structure(Base::class, 'U')); +var_dump(type_structure(Child::class, 'V')); diff --git a/hphp/test/slow/type_annotation/type_annotation_this1.php.expect b/hphp/test/slow/type_annotation/type_annotation_this1.php.expect new file mode 100644 index 00000000000..170863a75d3 --- /dev/null +++ b/hphp/test/slow/type_annotation/type_annotation_this1.php.expect @@ -0,0 +1,111 @@ +array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(4) "Base" +} +array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(5) "Child" +} +array(3) { + ["kind"]=> + int(11) + ["return_type"]=> + array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(4) "Base" + } + ["param_types"]=> + array(1) { + [0]=> + array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(4) "Base" + } + } +} +array(3) { + ["kind"]=> + int(11) + ["return_type"]=> + array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(5) "Child" + } + ["param_types"]=> + array(1) { + [0]=> + array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(5) "Child" + } + } +} +array(3) { + ["kind"]=> + int(11) + ["return_type"]=> + array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(4) "Base" + } + ["param_types"]=> + array(1) { + [0]=> + array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(4) "Base" + } + } +} +array(2) { + ["kind"]=> + int(14) + ["fields"]=> + array(2) { + ["foo"]=> + array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(5) "Child" + } + ["bar"]=> + array(3) { + ["kind"]=> + int(101) + ["classname"]=> + string(6) "HH\Map" + ["generic_types"]=> + array(2) { + [0]=> + array(1) { + ["kind"]=> + int(4) + } + [1]=> + array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(5) "Child" + } + } + } + } +} diff --git a/hphp/test/slow/type_annotation/type_annotation_this2.php b/hphp/test/slow/type_annotation/type_annotation_this2.php new file mode 100644 index 00000000000..65acdd52d07 --- /dev/null +++ b/hphp/test/slow/type_annotation/type_annotation_this2.php @@ -0,0 +1,11 @@ + + int(16) + ["classname"]=> + string(1) "I" +} +array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(1) "C" +} diff --git a/hphp/test/slow/type_annotation/type_annotation_this3.php b/hphp/test/slow/type_annotation/type_annotation_this3.php new file mode 100644 index 00000000000..301a1efbaab --- /dev/null +++ b/hphp/test/slow/type_annotation/type_annotation_this3.php @@ -0,0 +1,25 @@ + + int(15) + ["classname"]=> + string(10) "GrandChild" +} +array(2) { + ["kind"]=> + int(16) + ["classname"]=> + string(6) "IChild" +} +array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(10) "GrandChild" +} +array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(10) "GrandChild" +} diff --git a/hphp/test/slow/type_annotation/type_annotation_this4.php b/hphp/test/slow/type_annotation/type_annotation_this4.php new file mode 100644 index 00000000000..b3670b309b7 --- /dev/null +++ b/hphp/test/slow/type_annotation/type_annotation_this4.php @@ -0,0 +1,23 @@ + + int(15) + ["classname"]=> + string(9) "Foo\Child" +} +array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(14) "Foo\GrandChild" +} +array(2) { + ["kind"]=> + int(16) + ["classname"]=> + string(5) "Foo\I" +} +array(2) { + ["kind"]=> + int(15) + ["classname"]=> + string(14) "Foo\GrandChild" +} -- 2.11.4.GIT