3 #include "hphp/runtime/ext/collections/ext_collections.h"
4 #include "hphp/runtime/ext/collections/hash-collection.h"
5 #include "hphp/runtime/base/builtin-functions.h"
6 #include "hphp/runtime/base/container-functions.h"
7 #include "hphp/runtime/vm/native-data.h"
8 #include "hphp/system/systemlib.h"
11 /////////////////////////////////////////////////////////////////////////////
16 namespace collections
{
21 * BaseSet is a hash-table implementation of the Set ADT. It doesn't represent
22 * any PHP-land class. That job is delegated to its c_-prefixed child classes.
24 struct BaseSet
: HashCollection
{
25 void addAllKeysOf(TypedValue container
);
26 void addAll(const Variant
& t
);
28 void init(const Variant
& t
) {
34 template<bool raw
> void addImpl(int64_t k
);
35 template<bool raw
> void addImpl(StringData
* k
);
37 void addRaw(int64_t k
);
38 void addRaw(StringData
* k
);
39 void addRaw(TypedValue tv
) {
40 tv
= tvClassToString(tv
);
41 if (tv
.m_type
== KindOfInt64
) {
42 addRaw(tv
.m_data
.num
);
43 } else if (isStringType(tv
.m_type
)) {
44 addRaw(tv
.m_data
.pstr
);
49 void addRaw(const Variant
& v
) { addRaw(*v
.asTypedValue()); }
53 * Append an element to the Set, increffing it if it's refcounted.
56 void add(StringData
* k
);
57 void add(TypedValue tv
) {
58 tv
= tvClassToString(tv
);
59 if (tv
.m_type
== KindOfInt64
) {
61 } else if (isStringType(tv
.m_type
)) {
67 void add(const Variant
& v
) { add(*v
.asTypedValue()); }
70 * Append an element to the Set, increffing it if it's refcounted.
72 * When there is a conflict, the add() API is supposed to replace the
73 * existing element with the new element in place. However since Sets
74 * currently only support integer and string elements, there is no way
75 * user code can really tell whether the existing element was replaced
76 * so for efficiency we do nothing.
78 void SetIntMoveSkipConflict(int64_t k
, TypedValue v
);
79 void SetStrMoveSkipConflict(StringData
* k
, TypedValue v
);
82 * Prepend an element to the Set, increffing it if it's refcounted.
84 void addFront(int64_t k
);
85 void addFront(StringData
* k
);
86 void addFront(TypedValue tv
) {
87 tv
= tvClassToString(tv
);
88 if (tv
.m_type
== KindOfInt64
) {
89 addFront(tv
.m_data
.num
);
90 } else if (isStringType(tv
.m_type
)) {
91 addFront(tv
.m_data
.pstr
);
103 typename
std::enable_if
<
104 std::is_base_of
<BaseSet
, TSet
>::value
, TSet
*>::type
105 static Clone(ObjectData
* obj
);
107 template <IntishCast intishCast
= IntishCast::None
>
108 static Array
ToArray(const ObjectData
* obj
) {
109 check_collection_cast_to_array();
110 return const_cast<BaseSet
*>(
111 static_cast<const BaseSet
*>(obj
)
112 )->toPHPArrayImpl
<intishCast
>();
115 static bool ToBool(const ObjectData
* obj
);
117 template <bool throwOnMiss
>
118 static TypedValue
* OffsetAt(ObjectData
* obj
, const TypedValue
* key
) {
119 auto set
= static_cast<BaseSet
*>(obj
);
120 auto const ktv
= tvClassToString(*key
);
122 if (ktv
.m_type
== KindOfInt64
) {
123 p
= set
->find(ktv
.m_data
.num
, hash_int64(ktv
.m_data
.num
));
124 } else if (isStringType(ktv
.m_type
)) {
125 p
= set
->find(ktv
.m_data
.pstr
, ktv
.m_data
.pstr
->hash());
127 BaseSet::throwBadValueType();
129 if (LIKELY(p
!= Empty
)) {
130 return reinterpret_cast<TypedValue
*>(&set
->data()[p
].data
);
135 if (ktv
.m_type
== KindOfInt64
) {
136 collections::throwUndef(ktv
.m_data
.num
);
138 assertx(isStringType(ktv
.m_type
));
139 collections::throwUndef(ktv
.m_data
.pstr
);
142 static bool OffsetIsset(ObjectData
* obj
, const TypedValue
* key
);
143 static bool OffsetContains(ObjectData
* obj
, const TypedValue
* key
);
144 static void OffsetUnset(ObjectData
* obj
, const TypedValue
* key
);
146 static bool Equals(const ObjectData
* obj1
, const ObjectData
* obj2
);
149 template<class TVector
>
150 Object
php_values() {
151 auto vec
= req::make
<TVector
>();
152 vec
->init(VarNR(this));
153 return Object
{std::move(vec
)};
157 typename
std::enable_if
<
158 std::is_base_of
<BaseSet
, TSet
>::value
, Object
>::type
159 php_zip(const Variant
& iterable
);
162 typename
std::enable_if
<
163 std::is_base_of
<BaseSet
, TSet
>::value
, Object
>::type
164 php_take(const Variant
& n
);
167 typename
std::enable_if
<
168 std::is_base_of
<BaseSet
, TSet
>::value
, Object
>::type
169 php_skip(const Variant
& n
);
172 typename
std::enable_if
<
173 std::is_base_of
<BaseSet
, TSet
>::value
, Object
>::type
174 php_slice(const Variant
& start
, const Variant
& len
);
176 template<class TVector
>
177 typename
std::enable_if
<
178 std::is_base_of
<BaseVector
, TVector
>::value
, Object
>::type
179 php_concat(const Variant
& iterable
);
182 static typename
std::enable_if
<
183 std::is_base_of
<BaseSet
, TSet
>::value
, Object
>::type
184 fromItems(const Class
*, const Variant
& iterable
) {
185 auto set
= req::make
<TSet
>();
186 set
->addAll(iterable
);
187 return Object(std::move(set
));
191 static typename
std::enable_if
<
192 std::is_base_of
<BaseSet
, TSet
>::value
, Object
>::type
193 fromKeysOf(const Class
*, const Variant
& container
) {
194 if (container
.isNull()) {
195 return Object(req::make
<TSet
>());
197 auto const& cellContainer
= container_as_tv(container
);
198 auto target
= req::make
<TSet
>();
199 target
->addAllKeysOf(cellContainer
);
200 return Object(std::move(target
));
204 static typename
std::enable_if
<
205 std::is_base_of
<BaseSet
, TSet
>::value
, Object
>::type
206 fromArray(const Class
*, const Variant
& arr
) {
207 if (!arr
.isArray()) {
208 SystemLib::throwInvalidArgumentExceptionObject(
209 "Parameter arr must be an array");
211 auto set
= req::make
<TSet
>();
212 ArrayData
* ad
= arr
.getArrayData();
213 auto oldCap
= set
->cap();
214 set
->reserve(ad
->size()); // presume minimum collisions ...
215 ssize_t pos_limit
= ad
->iter_end();
216 for (ssize_t pos
= ad
->iter_begin(); pos
!= pos_limit
;
217 pos
= ad
->iter_advance(pos
)) {
218 set
->addRaw(ad
->nvGetVal(pos
));
220 set
->shrinkIfCapacityTooHigh(oldCap
); // ... and shrink if we were wrong
221 return Object(std::move(set
));
224 Object
getIterator();
225 bool php_contains(const Variant
& key
) {
226 auto const ktv
= tvClassToString(*key
.asTypedValue());
227 DataType t
= type(ktv
);
228 if (t
== KindOfInt64
) {
229 return contains(ktv
.m_data
.num
);
231 if (isStringType(t
)) {
232 return contains(ktv
.m_data
.pstr
);
238 [[noreturn
]] static void throwNoMutableIndexAccess();
239 [[noreturn
]] static void throwBadValueType();
242 // BaseSet is an abstract class with no additional member needing
244 using HashCollection::HashCollection
;
250 friend struct collections::CollectionsExtension
;
251 friend struct collections::SetIterator
;
252 friend struct c_Vector
;
256 static void compileTimeAssertions() {
257 // For performance, all native collection classes have their m_size field
258 // at the same offset.
259 static_assert(offsetof(BaseSet
, m_size
) ==
260 collections::FAST_SIZE_OFFSET
, "");
264 /////////////////////////////////////////////////////////////////////////////
267 struct c_Set
: BaseSet
{
268 DECLARE_COLLECTIONS_CLASS(Set
)
273 : BaseSet(c_Set::classof(), HeaderKind::Set
) { }
274 explicit c_Set(ArrayData
* arr
)
275 : BaseSet(c_Set::classof(), HeaderKind::Set
, arr
) { }
276 explicit c_Set(uint32_t cap
)
277 : BaseSet(c_Set::classof(), HeaderKind::Set
, cap
) { }
280 static c_Set
* Clone(ObjectData
* obj
);
283 friend struct collections::CollectionsExtension
;
285 Object
getImmutableCopy();
286 Object
php_add(const Variant
& val
) {
290 Object
php_addAll(const Variant
& it
) {
294 Object
php_addAllKeysOf(const Variant
& container
) {
295 if (!container
.isNull()) {
296 auto const& containerCell
= container_as_tv(container
);
297 addAllKeysOf(containerCell
);
305 Object
php_remove(const Variant
& key
) {
306 auto const ktv
= tvClassToString(*key
.asTypedValue());
307 DataType t
= type(ktv
);
308 if (t
== KindOfInt64
) {
309 remove(ktv
.m_data
.num
);
310 } else if (isStringType(t
)) {
311 remove(ktv
.m_data
.pstr
);
317 Object
php_removeAll(const Variant
& it
) {
319 ArrayIter iter
= getArrayIterHelper(it
, sz
);
320 for (; iter
; ++iter
) {
321 Variant v
= iter
.second();
322 auto const vtv
= tvClassToString(*v
.asTypedValue());
323 if (type(vtv
) == KindOfInt64
) {
324 remove(vtv
.m_data
.num
);
325 } else if (isStringType(type(vtv
))) {
326 remove(vtv
.m_data
.pstr
);
333 void php_reserve(int64_t cap
) {
334 if (UNLIKELY(cap
< 0)) {
335 SystemLib::throwInvalidArgumentExceptionObject(
336 "Parameter sz must be a non-negative integer"
343 ///////////////////////////////////////////////////////////////////////////////
346 struct c_ImmSet
: BaseSet
{
347 DECLARE_COLLECTIONS_CLASS(ImmSet
)
350 : BaseSet(c_ImmSet::classof(), HeaderKind::ImmSet
) { }
351 explicit c_ImmSet(ArrayData
* arr
)
352 : BaseSet(c_ImmSet::classof(), HeaderKind::ImmSet
, arr
) { }
353 explicit c_ImmSet(uint32_t cap
)
354 : BaseSet(c_ImmSet::classof(), HeaderKind::ImmSet
, cap
) { }
356 static c_ImmSet
* Clone(ObjectData
* obj
);
359 namespace collections
{
360 /////////////////////////////////////////////////////////////////////////////
362 extern const StaticString
367 SetIterator(const SetIterator
& src
) = delete;
368 SetIterator
& operator=(const SetIterator
& src
) {
375 static Object
newInstance() {
376 static Class
* cls
= Class::lookup(s_SetIterator
.get());
381 void setSet(BaseSet
* mp
) {
383 m_pos
= mp
->iter_begin();
386 Variant
current() const {
387 auto st
= m_obj
.get();
388 if (!st
->iter_valid_and_not_tombstone(m_pos
)) {
389 throw_iterator_not_valid();
391 return tvAsCVarRef(st
->iter_value(m_pos
));
394 Variant
key() const { return current(); }
397 return m_obj
->iter_valid_and_not_tombstone(m_pos
);
401 auto st
= m_obj
.get();
402 m_pos
= st
->iter_next(m_pos
);
406 auto st
= m_obj
.get();
407 m_pos
= st
->iter_begin();
411 req::ptr
<BaseSet
> m_obj
;
415 /////////////////////////////////////////////////////////////////////////////