runtime/set-builtins.cpp (1,079 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "set-builtins.h"
#include "builtins.h"
#include "dict-builtins.h"
#include "frame.h"
#include "globals.h"
#include "objects.h"
#include "runtime.h"
#include "thread.h"
#include "type-builtins.h"
namespace py {
// Data tuple layout
static const word kHashOffset = 0;
static const word kValueOffset = kHashOffset + 1;
static const word kNumPointers = kValueOffset + 1;
static word getIndex(RawTuple data, word hash) {
DCHECK(SmallInt::isValid(hash), "hash out of range");
word nitems = data.length() / kNumPointers;
DCHECK(Utils::isPowerOfTwo(nitems), "%ld not a power of 2", nitems);
return (hash & (nitems - 1)) * kNumPointers;
}
static bool isEmpty(RawTuple data, word index) {
return data.at(index + kHashOffset).isNoneType();
}
static bool isFull(RawTuple data, word index) {
return data.at(index + kHashOffset).isSmallInt();
}
static bool isTombstone(RawTuple data, word index) {
return data.at(index + kHashOffset).isUnbound();
}
static word itemHash(RawTuple data, word index) {
return SmallInt::cast(data.at(index + kHashOffset)).value();
}
static RawObject itemValue(RawTuple data, word index) {
return data.at(index + kValueOffset);
}
static void itemAtPut(RawTuple data, word index, word hash, RawObject value) {
data.atPut(index + kHashOffset, SmallInt::fromWordTruncated(hash));
data.atPut(index + kValueOffset, value);
}
static void itemAtPutTombstone(RawTuple data, word index) {
data.atPut(index + kHashOffset, Unbound::object());
data.atPut(index + kValueOffset, NoneType::object());
}
static word nextItemIndex(RawTuple data, word* index) {
for (word i = *index; i < data.length(); i += kNumPointers) {
if (!isFull(data, i)) continue;
*index = i + kNumPointers;
return i;
}
return -1;
}
bool setNextItem(const SetBase& set, word* index, RawObject* value_out) {
RawTuple data = RawTuple::cast(set.data());
word next_item_index = nextItemIndex(data, index);
if (next_item_index < 0) {
return false;
}
*value_out = itemValue(data, next_item_index);
return true;
}
bool setNextItemHash(const SetBase& set, word* index, RawObject* value_out,
word* hash_out) {
DCHECK(hash_out != nullptr, "hash_out must not be null");
RawTuple data = RawTuple::cast(set.data());
word next_item_index = nextItemIndex(data, index);
if (next_item_index < 0) {
return false;
}
*value_out = itemValue(data, next_item_index);
*hash_out = itemHash(data, next_item_index);
return true;
}
static word entryMatches(Thread* thread, RawTuple data, word entry,
word hash_value, const Object& key) {
DCHECK(isFull(data, entry), "entry cannot be a tombstone");
if (itemHash(data, entry) == hash_value) {
RawObject start_key = itemValue(data, entry);
RawObject eq = Runtime::objectEquals(thread, start_key, *key);
if (eq == Bool::trueObj()) {
return entry;
}
if (eq.isErrorException()) {
UNIMPLEMENTED("exception in value comparison");
}
return -1;
}
return -1;
}
static const word kNumLinearProbes = 9;
static const word kPerturbShift = 5;
static word setLookup(Thread* thread, const Tuple& data, const Object& key,
word hash_value) {
word length = data.length();
if (length == 0) {
return -1;
}
uword perturb = static_cast<uword>(hash_value);
word mask = length - 1;
word i = hash_value & mask;
word entry = getIndex(*data, i);
if (isEmpty(*data, entry)) {
return -1;
}
for (;;) {
if (isFull(*data, entry)) {
word match = entryMatches(thread, *data, entry, hash_value, key);
if (match != -1) {
return match;
}
}
if (entry + kNumLinearProbes * kNumPointers <= mask) {
for (int j = 1; j <= kNumLinearProbes; j++) {
entry += kNumPointers;
if (isEmpty(*data, entry)) {
return -1;
}
if (!isTombstone(*data, entry)) {
word match = entryMatches(thread, *data, entry, hash_value, key);
if (match != -1) {
return match;
}
}
}
}
perturb >>= kPerturbShift;
i = (i * 5 + 1 + perturb) & mask;
entry = getIndex(*data, i);
if (isEmpty(*data, entry)) {
return -1;
}
}
}
static word setLookupForInsertion(Thread* thread, const Tuple& data,
const Object& key, word hash_value) {
word length = data.length();
if (length == 0) {
return -1;
}
uword perturb = static_cast<uword>(hash_value);
word mask = length - 1;
word i = hash_value & mask;
word entry = getIndex(*data, i);
if (isEmpty(*data, entry)) {
return entry;
}
for (word freeslot = -1;;) {
if (isFull(*data, entry)) {
word match = entryMatches(thread, *data, entry, hash_value, key);
if (match != -1) {
return match;
}
} else if (isTombstone(*data, entry)) {
freeslot = entry;
}
if (entry + kNumLinearProbes * kNumPointers <= mask) {
for (int j = 1; j <= kNumLinearProbes; j++) {
entry += kNumPointers;
if (isEmpty(*data, entry)) {
return entry;
}
if (!isTombstone(*data, entry)) {
word match = entryMatches(thread, *data, entry, hash_value, key);
if (match != -1) {
return match;
}
} else if (isTombstone(*data, entry)) {
freeslot = entry;
}
}
}
perturb >>= kPerturbShift;
i = (i * 5 + 1 + perturb) & mask;
entry = getIndex(*data, i);
if (isEmpty(*data, entry)) {
return (freeslot == -1) ? entry : freeslot;
}
}
}
static RawTuple setGrow(Thread* thread, const SetBase& set) {
HandleScope scope(thread);
Tuple data(&scope, set.data());
word new_length = data.length() * Runtime::kSetGrowthFactor;
if (new_length == 0) {
new_length = Runtime::kInitialSetCapacity * kNumPointers;
}
MutableTuple new_data(&scope, thread->runtime()->newMutableTuple(new_length));
new_data.fill(NoneType::object());
// Re-insert items
Object value(&scope, NoneType::object());
for (word i = 0, hash; setNextItemHash(set, &i, &value, &hash);) {
word index = setLookupForInsertion(thread, new_data, value, hash);
DCHECK(index != -1, "unexpected index %ld", index);
itemAtPut(*new_data, index, hash, *value);
}
set.setNumFilled(set.numItems());
return *new_data;
}
RawObject setAdd(Thread* thread, const SetBase& set, const Object& value,
word hash) {
HandleScope scope(thread);
Tuple data(&scope, set.data());
word index = setLookup(thread, data, value, hash);
if (index != -1) {
return itemValue(*data, index);
}
Tuple new_data(&scope, *data);
if (data.length() == 0 || 10 * set.numFilled() >= 3 * data.length()) {
new_data = setGrow(thread, set);
}
index = setLookupForInsertion(thread, new_data, value, hash);
DCHECK(index != -1, "unexpected index %ld", index);
set.setData(*new_data);
itemAtPut(*new_data, index, hash, *value);
set.setNumItems(set.numItems() + 1);
set.setNumFilled(set.numFilled() + 1);
return *value;
}
bool setIncludes(Thread* thread, const SetBase& set, const Object& key,
word hash) {
HandleScope scope(thread);
Tuple data(&scope, set.data());
return setLookup(thread, data, key, hash) != -1;
}
RawObject setIntersection(Thread* thread, const SetBase& set,
const Object& iterable) {
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
SetBase dst(&scope, runtime->isInstanceOfSet(*set) ? runtime->newSet()
: runtime->newFrozenSet());
Object value(&scope, NoneType::object());
// Special case for sets
if (runtime->isInstanceOfSetBase(*iterable)) {
SetBase self(&scope, *set);
SetBase other(&scope, *iterable);
if (set.numItems() == 0 || other.numItems() == 0) {
return *dst;
}
// Iterate over the smaller set
if (set.numItems() > other.numItems()) {
self = *iterable;
other = *set;
}
Tuple other_data(&scope, other.data());
for (word i = 0, hash; setNextItemHash(self, &i, &value, &hash);) {
if (setLookup(thread, other_data, value, hash) != -1) {
setAdd(thread, dst, value, hash);
}
}
return *dst;
}
// Generic case
Object iter_method(&scope,
Interpreter::lookupMethod(thread, iterable, ID(__iter__)));
if (iter_method.isError()) {
return thread->raiseWithFmt(LayoutId::kTypeError, "object is not iterable");
}
Object iterator(&scope,
Interpreter::callMethod1(thread, iter_method, iterable));
if (iterator.isError()) {
return thread->raiseWithFmt(LayoutId::kTypeError, "object is not iterable");
}
Object next_method(&scope,
Interpreter::lookupMethod(thread, iterator, ID(__next__)));
if (next_method.isError()) {
return thread->raiseWithFmt(LayoutId::kTypeError,
"iter() returned a non-iterator");
}
if (set.numItems() == 0) {
return *dst;
}
Tuple data(&scope, set.data());
Object hash_obj(&scope, NoneType::object());
for (;;) {
value = Interpreter::callMethod1(thread, next_method, iterator);
if (value.isError()) {
if (thread->clearPendingStopIteration()) break;
return *value;
}
hash_obj = Interpreter::hash(thread, value);
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
if (setLookup(thread, data, value, hash) != -1) {
setAdd(thread, dst, value, hash);
}
}
return *dst;
}
bool setRemove(Thread* thread, const Set& set, const Object& key, word hash) {
HandleScope scope(thread);
Tuple data(&scope, set.data());
word index = setLookup(thread, data, key, hash);
if (index != -1) {
itemAtPutTombstone(*data, index);
set.setNumItems(set.numItems() - 1);
return true;
}
return false;
}
RawObject setUpdate(Thread* thread, const SetBase& dst,
const Object& iterable) {
HandleScope scope(thread);
Object elt(&scope, NoneType::object());
Object hash_obj(&scope, NoneType::object());
// Special case for lists
if (iterable.isList()) {
List src(&scope, *iterable);
for (word i = 0; i < src.numItems(); i++) {
elt = src.at(i);
hash_obj = Interpreter::hash(thread, elt);
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
setAdd(thread, dst, elt, hash);
}
return *dst;
}
// Special case for lists iterators
if (iterable.isListIterator()) {
ListIterator list_iter(&scope, *iterable);
List src(&scope, list_iter.iterable());
for (word i = 0; i < src.numItems(); i++) {
elt = src.at(i);
hash_obj = Interpreter::hash(thread, elt);
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
setAdd(thread, dst, elt, hash);
}
}
// Special case for tuples
if (iterable.isTuple()) {
Tuple tuple(&scope, *iterable);
if (tuple.length() > 0) {
for (word i = 0; i < tuple.length(); i++) {
elt = tuple.at(i);
hash_obj = Interpreter::hash(thread, elt);
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
setAdd(thread, dst, elt, hash);
}
}
return *dst;
}
// Special case for built-in set types
if (thread->runtime()->isInstanceOfSetBase(*iterable)) {
SetBase src(&scope, *iterable);
if (src.numItems() > 0) {
for (word i = 0, hash; setNextItemHash(src, &i, &elt, &hash);) {
// take hash from data to avoid recomputing it.
setAdd(thread, dst, elt, hash);
}
}
return *dst;
}
// Special case for dicts
if (iterable.isDict()) {
Dict dict(&scope, *iterable);
for (word i = 0, hash; dictNextKeyHash(dict, &i, &elt, &hash);) {
setAdd(thread, dst, elt, hash);
}
return *dst;
}
// Generic case
Object iter_method(&scope,
Interpreter::lookupMethod(thread, iterable, ID(__iter__)));
if (iter_method.isError()) {
return thread->raiseWithFmt(LayoutId::kTypeError,
"'%T' object is not iterable", &iterable);
}
Object iterator(&scope,
Interpreter::callMethod1(thread, iter_method, iterable));
if (iterator.isError()) {
return thread->raiseWithFmt(LayoutId::kTypeError,
"'%T' object is not iterable", &iterable);
}
Object next_method(&scope,
Interpreter::lookupMethod(thread, iterator, ID(__next__)));
if (next_method.isError()) {
return thread->raiseWithFmt(LayoutId::kTypeError,
"iter() returned a non-iterator");
}
for (;;) {
elt = Interpreter::callMethod1(thread, next_method, iterator);
if (elt.isError()) {
if (thread->clearPendingStopIteration()) break;
return *elt;
}
hash_obj = Interpreter::hash(thread, elt);
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
setAdd(thread, dst, elt, hash);
}
return *dst;
}
RawSmallInt frozensetHash(Thread* thread, const Object& frozenset) {
HandleScope scope(thread);
FrozenSet set(&scope, *frozenset);
uword result = 0;
Object value(&scope, NoneType::object());
for (word i = 0, value_hash; setNextItemHash(set, &i, &value, &value_hash);) {
uword h = static_cast<uword>(value_hash);
result ^= ((h ^ uword{89869747}) ^ (h << 16)) * uword{3644798167};
}
result ^= static_cast<uword>(set.numItems() + 1) * uword{1927868237};
result ^= (result >> 11) ^ (result >> 25);
result = result * uword{69069} + uword{907133923};
// cpython replaces `-1` results with -2, because -1 is used as an
// "uninitialized hash" marker in some situations. We do not use the same
// marker, but do the same to match behavior.
if (result == static_cast<uword>(word{-1})) {
result--;
}
return SmallInt::fromWordTruncated(result);
}
static RawObject dunderLenImpl(Thread* thread, Arguments args, SymbolId id) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfSetBase(*self)) {
return thread->raiseRequiresType(self, id);
}
SetBase set(&scope, *self);
return SmallInt::fromWord(set.numItems());
}
static RawObject dunderContainsImpl(Thread* thread, Arguments args,
SymbolId id) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfSetBase(*self)) {
return thread->raiseRequiresType(self, id);
}
SetBase set(&scope, *self);
Object key(&scope, args.get(1));
Object hash_obj(&scope, Interpreter::hash(thread, key));
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
return Bool::fromBool(setIncludes(thread, set, key, hash));
}
static RawObject dunderIterImpl(Thread* thread, Arguments args, SymbolId id) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfSetBase(*self)) {
return thread->raiseRequiresType(self, id);
}
return thread->runtime()->newSetIterator(self);
}
static RawObject isdisjointImpl(Thread* thread, Arguments args, SymbolId id) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object other(&scope, args.get(1));
Object value(&scope, NoneType::object());
if (!thread->runtime()->isInstanceOfSetBase(*self)) {
return thread->raiseRequiresType(self, id);
}
SetBase a(&scope, *self);
if (a.numItems() == 0) {
return Bool::trueObj();
}
if (thread->runtime()->isInstanceOfSetBase(*other)) {
SetBase b(&scope, *other);
if (b.numItems() == 0) {
return Bool::trueObj();
}
// Iterate over the smaller set
if (a.numItems() > b.numItems()) {
a = *other;
b = *self;
}
for (word i = 0, hash; setNextItemHash(a, &i, &value, &hash);) {
if (setIncludes(thread, b, value, hash)) {
return Bool::falseObj();
}
}
return Bool::trueObj();
}
// Generic iterator case
Object iter_method(&scope,
Interpreter::lookupMethod(thread, other, ID(__iter__)));
if (iter_method.isError()) {
return thread->raiseWithFmt(LayoutId::kTypeError, "object is not iterable");
}
Object iterator(&scope, Interpreter::callMethod1(thread, iter_method, other));
if (iterator.isError()) {
return thread->raiseWithFmt(LayoutId::kTypeError, "object is not iterable");
}
Object next_method(&scope,
Interpreter::lookupMethod(thread, iterator, ID(__next__)));
if (next_method.isError()) {
return thread->raiseWithFmt(LayoutId::kTypeError,
"iter() returned a non-iterator");
}
Object hash_obj(&scope, NoneType::object());
for (;;) {
value = Interpreter::callMethod1(thread, next_method, iterator);
if (value.isError()) {
if (thread->clearPendingStopIteration()) break;
return *value;
}
hash_obj = Interpreter::hash(thread, value);
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
if (setIncludes(thread, a, value, hash)) {
return Bool::falseObj();
}
}
return Bool::trueObj();
}
static RawObject intersectionImpl(Thread* thread, Arguments args, SymbolId id) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfSetBase(*self)) {
return thread->raiseRequiresType(self, id);
}
SetBase set(&scope, *self);
Object other(&scope, args.get(1));
return setIntersection(thread, set, other);
}
static RawObject dunderEqImpl(Thread* thread, Arguments args, SymbolId id) {
Runtime* runtime = thread->runtime();
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object other(&scope, args.get(1));
if (!runtime->isInstanceOfSetBase(*self)) {
return thread->raiseRequiresType(self, id);
}
if (!runtime->isInstanceOfSetBase(*other)) {
return NotImplementedType::object();
}
SetBase set(&scope, *self);
SetBase other_set(&scope, *other);
return Bool::fromBool(setEquals(thread, set, other_set));
}
static RawObject dunderNeImpl(Thread* thread, Arguments args, SymbolId id) {
Runtime* runtime = thread->runtime();
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object other(&scope, args.get(1));
if (!runtime->isInstanceOfSetBase(*self)) {
return thread->raiseRequiresType(self, id);
}
if (!runtime->isInstanceOfSetBase(*other)) {
return NotImplementedType::object();
}
SetBase set(&scope, *self);
SetBase other_set(&scope, *other);
return Bool::fromBool(!setEquals(thread, set, other_set));
}
static RawObject dunderLeImpl(Thread* thread, Arguments args, SymbolId id) {
Runtime* runtime = thread->runtime();
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object other(&scope, args.get(1));
if (!runtime->isInstanceOfSetBase(*self)) {
return thread->raiseRequiresType(self, id);
}
if (!runtime->isInstanceOfSetBase(*other)) {
return NotImplementedType::object();
}
SetBase set(&scope, *self);
SetBase other_set(&scope, *other);
return Bool::fromBool(setIsSubset(thread, set, other_set));
}
static RawObject dunderLtImpl(Thread* thread, Arguments args, SymbolId id) {
Runtime* runtime = thread->runtime();
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object other(&scope, args.get(1));
if (!runtime->isInstanceOfSetBase(*self)) {
return thread->raiseRequiresType(self, id);
}
if (!runtime->isInstanceOfSetBase(*other)) {
return NotImplementedType::object();
}
SetBase set(&scope, *self);
SetBase other_set(&scope, *other);
return Bool::fromBool(setIsProperSubset(thread, set, other_set));
}
static RawObject dunderGeImpl(Thread* thread, Arguments args, SymbolId id) {
Runtime* runtime = thread->runtime();
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object other(&scope, args.get(1));
if (!runtime->isInstanceOfSetBase(*self)) {
return thread->raiseRequiresType(self, id);
}
if (!runtime->isInstanceOfSetBase(*other)) {
return NotImplementedType::object();
}
SetBase set(&scope, *self);
SetBase other_set(&scope, *other);
return Bool::fromBool(setIsSubset(thread, other_set, set));
}
static RawObject dunderGtImpl(Thread* thread, Arguments args, SymbolId id) {
Runtime* runtime = thread->runtime();
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object other(&scope, args.get(1));
if (!runtime->isInstanceOfSetBase(*self)) {
return thread->raiseRequiresType(self, id);
}
if (!runtime->isInstanceOfSetBase(*other)) {
return NotImplementedType::object();
}
SetBase set(&scope, *self);
SetBase other_set(&scope, *other);
return Bool::fromBool(setIsProperSubset(thread, other_set, set));
}
static const BuiltinAttribute kFrozenSetAttributes[] = {
{ID(_frozenset__data), RawFrozenSet::kDataOffset, AttributeFlags::kHidden},
{ID(_frozenset__num_items), RawFrozenSet::kNumItemsOffset,
AttributeFlags::kHidden},
{ID(_frozenset__num_filled), RawFrozenSet::kNumFilledOffset,
AttributeFlags::kHidden},
};
RawObject METH(frozenset, __and__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object other(&scope, args.get(1));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfFrozenSet(*self)) {
return thread->raiseRequiresType(self, ID(frozenset));
}
if (!runtime->isInstanceOfSetBase(*other)) {
return NotImplementedType::object();
}
FrozenSet set(&scope, *self);
SetBase other_set(&scope, *other);
return setIntersection(thread, set, other_set);
}
RawObject METH(frozenset, __contains__)(Thread* thread, Arguments args) {
return dunderContainsImpl(thread, args, ID(frozenset));
}
RawObject METH(frozenset, __eq__)(Thread* thread, Arguments args) {
return dunderEqImpl(thread, args, ID(frozenset));
}
RawObject METH(frozenset, __ge__)(Thread* thread, Arguments args) {
return dunderGeImpl(thread, args, ID(frozenset));
}
RawObject METH(frozenset, __gt__)(Thread* thread, Arguments args) {
return dunderGtImpl(thread, args, ID(frozenset));
}
RawObject METH(frozenset, __hash__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfFrozenSet(*self)) {
return thread->raiseRequiresType(self, ID(frozenset));
}
FrozenSet set(&scope, *self);
return frozensetHash(thread, set);
}
RawObject METH(frozenset, __iter__)(Thread* thread, Arguments args) {
return dunderIterImpl(thread, args, ID(frozenset));
}
RawObject METH(frozenset, __le__)(Thread* thread, Arguments args) {
return dunderLeImpl(thread, args, ID(frozenset));
}
RawObject METH(frozenset, __len__)(Thread* thread, Arguments args) {
return dunderLenImpl(thread, args, ID(frozenset));
}
RawObject METH(frozenset, __lt__)(Thread* thread, Arguments args) {
return dunderLtImpl(thread, args, ID(frozenset));
}
RawObject METH(frozenset, __ne__)(Thread* thread, Arguments args) {
return dunderNeImpl(thread, args, ID(frozenset));
}
RawObject METH(frozenset, __new__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object type_obj(&scope, args.get(0));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfType(*type_obj)) {
return thread->raiseRequiresType(type_obj, ID(type));
}
Type type(&scope, *type_obj);
if (type.builtinBase() != LayoutId::kFrozenSet) {
return thread->raiseWithFmt(LayoutId::kTypeError,
"not a subtype of frozenset");
}
if (args.get(1).isUnbound()) {
// Iterable not provided
if (type.isBuiltin() && type.builtinBase() == LayoutId::kFrozenSet) {
// Called with exact frozenset type, should return singleton
return runtime->emptyFrozenSet();
}
// Not called with exact frozenset type, should return new distinct
// frozenset
Layout layout(&scope, type.instanceLayout());
FrozenSet result(&scope, runtime->newInstance(layout));
result.setNumItems(0);
result.setNumFilled(0);
result.setData(runtime->emptyTuple());
return *result;
}
// Called with iterable, so iterate
Object iterable(&scope, args.get(1));
// frozenset(f) where f is a frozenset is idempotent
if (iterable.isFrozenSet()) {
return *iterable;
}
Object dunder_iter(&scope,
Interpreter::lookupMethod(thread, iterable, ID(__iter__)));
if (dunder_iter.isError()) {
return thread->raiseWithFmt(
LayoutId::kTypeError,
"frozenset.__new__ must be called with an iterable");
}
if (type.isBuiltin() && type.builtinBase() == LayoutId::kFrozenSet) {
// Called with exact frozenset type
FrozenSet result(&scope, runtime->newFrozenSet());
RawObject maybe_error = setUpdate(thread, result, iterable);
if (maybe_error.isError()) return maybe_error;
result = maybe_error;
if (result.numItems() == 0) {
return runtime->emptyFrozenSet();
}
return *result;
}
// Not called with exact frozenset type, should return new frozenset subclass
Layout layout(&scope, type.instanceLayout());
FrozenSet result(&scope, runtime->newInstance(layout));
result.setNumItems(0);
result.setNumFilled(0);
result.setData(runtime->emptyTuple());
return setUpdate(thread, result, iterable);
}
RawObject METH(frozenset, __or__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self_obj(&scope, args.get(0));
Object other(&scope, args.get(1));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfFrozenSet(*self_obj)) {
return thread->raiseRequiresType(self_obj, ID(frozenset));
}
if (!runtime->isInstanceOfSetBase(*other)) {
return NotImplementedType::object();
}
FrozenSet self(&scope, *self_obj);
if (*self == *other) {
// Make a shallow copy; we can alias the underlying immutable data
// structure
FrozenSet result(&scope, runtime->newFrozenSet());
result.setData(self.data());
result.setNumItems(self.numItems());
result.setNumFilled(self.numFilled());
return *result;
}
FrozenSet result(&scope, runtime->newFrozenSet());
Object maybe_error(&scope, setUpdate(thread, result, self));
if (maybe_error.isError()) return *maybe_error;
result = *maybe_error;
return setUpdate(thread, result, other);
}
RawObject METH(frozenset, copy)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfFrozenSet(*self)) {
return thread->raiseRequiresType(self, ID(frozenset));
}
FrozenSet set(&scope, *self);
if (set.isFrozenSet()) {
return *set;
}
return setCopy(thread, set);
}
RawObject METH(frozenset, intersection)(Thread* thread, Arguments args) {
return intersectionImpl(thread, args, ID(frozenset));
}
RawObject METH(frozenset, isdisjoint)(Thread* thread, Arguments args) {
return isdisjointImpl(thread, args, ID(frozenset));
}
RawObject setCopy(Thread* thread, const SetBase& set) {
word num_items = set.numItems();
Runtime* runtime = thread->runtime();
if (num_items == 0) {
return runtime->isInstanceOfSet(*set) ? runtime->newSet()
: runtime->emptyFrozenSet();
}
HandleScope scope(thread);
SetBase new_set(&scope, runtime->isInstanceOfSet(*set)
? runtime->newSet()
: runtime->newFrozenSet());
Tuple data(&scope, set.data());
MutableTuple new_data(&scope, runtime->newMutableTuple(data.length()));
new_data.fill(NoneType::object());
Object value(&scope, NoneType::object());
for (word i = 0, hash; setNextItemHash(set, &i, &value, &hash);) {
word dst_index = i - kNumPointers;
itemAtPut(*new_data, dst_index, hash, *value);
}
new_set.setData(*new_data);
new_set.setNumItems(set.numItems());
new_set.setNumFilled(set.numItems());
return *new_set;
}
bool setIsSubset(Thread* thread, const SetBase& set, const SetBase& other) {
HandleScope scope(thread);
Object value(&scope, NoneType::object());
for (word i = 0, hash; setNextItemHash(set, &i, &value, &hash);) {
if (!setIncludes(thread, other, value, hash)) {
return false;
}
}
return true;
}
bool setIsProperSubset(Thread* thread, const SetBase& set,
const SetBase& other) {
if (set.numItems() == other.numItems()) {
return false;
}
return setIsSubset(thread, set, other);
}
bool setEquals(Thread* thread, const SetBase& set, const SetBase& other) {
if (set.numItems() != other.numItems()) {
return false;
}
if (*set == *other) {
return true;
}
return setIsSubset(thread, set, other);
}
RawObject setPop(Thread* thread, const Set& set) {
HandleScope scope(thread);
Tuple data(&scope, set.data());
word num_items = set.numItems();
if (num_items != 0) {
Object value(&scope, NoneType::object());
for (word i = 0; setNextItem(set, &i, &value);) {
itemAtPutTombstone(*data, i - kNumPointers);
set.setNumItems(num_items - 1);
return *value;
}
}
// num_items == 0 or all items were found empty
return thread->raiseWithFmt(LayoutId::kKeyError, "pop from an empty set");
}
RawObject setIteratorNext(Thread* thread, const SetIterator& iter) {
word idx = iter.index();
HandleScope scope(thread);
SetBase underlying(&scope, iter.iterable());
Object value(&scope, NoneType::object());
// Find the next non empty item
if (!setNextItem(underlying, &idx, &value)) {
return Error::noMoreItems();
}
iter.setConsumedCount(iter.consumedCount() + 1);
iter.setIndex(idx);
return *value;
}
static const BuiltinAttribute kSetAttributes[] = {
{ID(_set__data), RawSet::kDataOffset, AttributeFlags::kHidden},
{ID(_set__num_items), RawSet::kNumItemsOffset, AttributeFlags::kHidden},
{ID(_set__num_filled), RawSet::kNumFilledOffset, AttributeFlags::kHidden},
};
RawObject METH(set, __and__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object other(&scope, args.get(1));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfSet(*self)) {
return thread->raiseRequiresType(self, ID(set));
}
if (!runtime->isInstanceOfSetBase(*other)) {
return NotImplementedType::object();
}
Set set(&scope, *self);
SetBase other_set(&scope, *other);
return setIntersection(thread, set, other_set);
}
RawObject METH(set, __contains__)(Thread* thread, Arguments args) {
return dunderContainsImpl(thread, args, ID(set));
}
RawObject METH(set, __eq__)(Thread* thread, Arguments args) {
return dunderEqImpl(thread, args, ID(set));
}
RawObject METH(set, __ge__)(Thread* thread, Arguments args) {
return dunderGeImpl(thread, args, ID(set));
}
RawObject METH(set, __gt__)(Thread* thread, Arguments args) {
return dunderGtImpl(thread, args, ID(set));
}
RawObject METH(set, __iand__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object other(&scope, args.get(1));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfSet(*self)) {
return thread->raiseRequiresType(self, ID(set));
}
if (!runtime->isInstanceOfSet(*other)) {
return NotImplementedType::object();
}
Set set(&scope, *self);
Object intersection(&scope, setIntersection(thread, set, other));
if (intersection.isError()) {
return *intersection;
}
RawSet intersection_set = Set::cast(*intersection);
set.setData(intersection_set.data());
set.setNumItems(intersection_set.numItems());
set.setNumFilled(intersection_set.numFilled());
return *set;
}
RawObject METH(set, __init__)(Thread* thread, Arguments args) {
Runtime* runtime = thread->runtime();
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!runtime->isInstanceOfSet(*self)) {
return thread->raiseRequiresType(self, ID(set));
}
Set set(&scope, *self);
Object iterable(&scope, args.get(1));
Object result(&scope, setUpdate(thread, set, iterable));
if (result.isError()) {
return *result;
}
return NoneType::object();
}
RawObject METH(set, __iter__)(Thread* thread, Arguments args) {
return dunderIterImpl(thread, args, ID(set));
}
RawObject METH(set, __le__)(Thread* thread, Arguments args) {
return dunderLeImpl(thread, args, ID(set));
}
RawObject METH(set, __len__)(Thread* thread, Arguments args) {
return dunderLenImpl(thread, args, ID(set));
}
RawObject METH(set, __lt__)(Thread* thread, Arguments args) {
return dunderLtImpl(thread, args, ID(set));
}
RawObject METH(set, __ne__)(Thread* thread, Arguments args) {
return dunderNeImpl(thread, args, ID(set));
}
RawObject METH(set, __new__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Object type_obj(&scope, args.get(0));
if (!runtime->isInstanceOfType(*type_obj)) {
return thread->raiseRequiresType(type_obj, ID(type));
}
Type type(&scope, *type_obj);
if (type.builtinBase() != LayoutId::kSet) {
return thread->raiseWithFmt(LayoutId::kTypeError, "not a subtype of set");
}
Layout layout(&scope, type.instanceLayout());
Set result(&scope, runtime->newInstance(layout));
result.setNumItems(0);
result.setNumFilled(0);
result.setData(runtime->emptyTuple());
return *result;
}
RawObject METH(set, add)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfSet(*self)) {
return thread->raiseRequiresType(self, ID(set));
}
Set set(&scope, *self);
Object value(&scope, args.get(1));
Object hash_obj(&scope, Interpreter::hash(thread, value));
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
Object result(&scope, setAdd(thread, set, value, hash));
if (result.isError()) return *result;
return NoneType::object();
}
RawObject METH(set, clear)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfSet(*self)) {
return thread->raiseRequiresType(self, ID(set));
}
Set set(&scope, *self);
if (set.numItems() == 0) {
return NoneType::object();
}
set.setNumItems(0);
set.setNumFilled(0);
MutableTuple data(&scope, set.data());
data.fill(NoneType::object());
return NoneType::object();
}
RawObject METH(set, copy)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfSet(*self)) {
return thread->raiseRequiresType(self, ID(set));
}
Set set(&scope, *self);
return setCopy(thread, set);
}
RawObject METH(set, discard)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self_obj(&scope, args.get(0));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfSet(*self_obj)) {
return thread->raiseRequiresType(self_obj, ID(set));
}
Set self(&scope, *self_obj);
Object key(&scope, args.get(1));
Object hash_obj(&scope, Interpreter::hash(thread, key));
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
setRemove(thread, self, key, hash);
return NoneType::object();
}
RawObject METH(set, intersection)(Thread* thread, Arguments args) {
return intersectionImpl(thread, args, ID(set));
}
RawObject METH(set, isdisjoint)(Thread* thread, Arguments args) {
return isdisjointImpl(thread, args, ID(set));
}
RawObject METH(set, pop)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfSet(*self)) {
return thread->raiseRequiresType(self, ID(set));
}
Set set(&scope, args.get(0));
return setPop(thread, set);
}
RawObject METH(set, remove)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Runtime* runtime = thread->runtime();
if (!runtime->isInstanceOfSet(*self)) {
return thread->raiseRequiresType(self, ID(set));
}
Set set(&scope, *self);
Object key(&scope, args.get(1));
Object hash_obj(&scope, Interpreter::hash(thread, key));
if (hash_obj.isErrorException()) return *hash_obj;
word hash = SmallInt::cast(*hash_obj).value();
if (!setRemove(thread, set, key, hash)) {
return thread->raise(LayoutId::kKeyError, *key);
}
return NoneType::object();
}
RawObject METH(set, update)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Object self_obj(&scope, args.get(0));
if (!runtime->isInstanceOfSet(*self_obj)) {
return thread->raiseRequiresType(self_obj, ID(set));
}
Set self(&scope, *self_obj);
Object starargs_obj(&scope, args.get(1));
if (!runtime->isInstanceOfTuple(*starargs_obj)) {
return thread->raiseRequiresType(starargs_obj, ID(tuple));
}
Tuple starargs(&scope, tupleUnderlying(*starargs_obj));
Object result(&scope, NoneType::object());
for (word i = 0; i < starargs.length(); i++) {
Object other(&scope, starargs.at(i));
result = setUpdate(thread, self, other);
if (result.isError()) {
return *result;
}
}
return NoneType::object();
}
static const BuiltinAttribute kSetIteratorAttributes[] = {
{ID(_set_iterator__iterable), RawSetIterator::kIterableOffset,
AttributeFlags::kHidden},
{ID(_set_iterator__index), RawSetIterator::kIndexOffset,
AttributeFlags::kHidden},
{ID(_set_iterator__consumed_count), RawSetIterator::kConsumedCountOffset,
AttributeFlags::kHidden},
};
RawObject METH(set_iterator, __iter__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isSetIterator()) {
return thread->raiseRequiresType(self, ID(set_iterator));
}
return *self;
}
RawObject METH(set_iterator, __next__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self_obj(&scope, args.get(0));
if (!self_obj.isSetIterator()) {
return thread->raiseRequiresType(self_obj, ID(set_iterator));
}
SetIterator self(&scope, *self_obj);
Object value(&scope, setIteratorNext(thread, self));
if (value.isError()) {
return thread->raise(LayoutId::kStopIteration, NoneType::object());
}
return *value;
}
RawObject METH(set_iterator, __length_hint__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
if (!self.isSetIterator()) {
return thread->raiseRequiresType(self, ID(set_iterator));
}
SetIterator set_iterator(&scope, *self);
Set set(&scope, set_iterator.iterable());
return SmallInt::fromWord(set.numItems() - set_iterator.consumedCount());
}
void initializeSetTypes(Thread* thread) {
addBuiltinType(thread, ID(set), LayoutId::kSet,
/*superclass_id=*/LayoutId::kObject, kSetAttributes,
Set::kSize, /*basetype=*/true);
addBuiltinType(thread, ID(frozenset), LayoutId::kFrozenSet,
/*superclass_id=*/LayoutId::kObject, kFrozenSetAttributes,
FrozenSet::kSize, /*basetype=*/true);
addBuiltinType(thread, ID(set_iterator), LayoutId::kSetIterator,
/*superclass_id=*/LayoutId::kObject, kSetIteratorAttributes,
SetIterator::kSize, /*basetype=*/false);
}
} // namespace py