Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 81 additions & 24 deletions crates/vm/src/builtins/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,21 @@ impl PySetInner {
Ok(res)
}

fn intersection_multi(
&self,
mut others: impl core::iter::Iterator<Item = ArgIterable>,
vm: &VirtualMachine,
) -> PyResult<Self> {
let Some(other) = others.next() else {
return Ok(self.copy());
};
let mut result = self.intersection(other, vm)?;
for other in others {
result = result.intersection(other, vm)?;
}
Ok(result)
}

fn len(&self) -> usize {
self.content.len()
}
Expand All @@ -252,8 +267,7 @@ impl PySetInner {
Self::wrap_unhashable_error(result, needle, vm)
}

/// [`Self::contains`] with a known hash. Such a needle came out of a
/// set/dict, so it is hashable and needs no frozenset retry.
/// Look up a key whose hash is already known, without a frozenset retry.
fn contains_known_hash(
&self,
needle: &PyObject,
Expand All @@ -272,12 +286,12 @@ impl PySetInner {
}

let (superset, subset) = match op {
PyComparisonOp::Lt | PyComparisonOp::Le => (other, self),
PyComparisonOp::Lt | PyComparisonOp::Le | PyComparisonOp::Eq => (other, self),
_ => (self, other),
};

for key in subset.elements() {
if !superset.contains(&key, vm)? {
for (key, hash) in subset.content.keys_with_hashes() {
if !superset.contains_known_hash(&key, hash, vm)? {
return Ok(false);
}
}
Expand All @@ -301,19 +315,36 @@ impl PySetInner {
}

pub(super) fn intersection(&self, other: ArgIterable, vm: &VirtualMachine) -> PyResult<Self> {
let (target, elements) = if let Some(other_set) = extract_set(other.as_object()) {
if core::ptr::eq(self, other_set) {
return Ok(self.copy());
}
let (target, source) = if self.len() < other_set.len() {
(other_set, self)
} else {
(self, other_set)
};
(target, Some(source.content.keys_with_hashes()))
} else {
(self, None)
};
let set = Self::default();
if let Some(elements) = Self::cached_hashes(other.as_object(), vm) {
if let Some(elements) = elements {
for (obj, hash) in elements {
if self.contains_known_hash(&obj, hash, vm)? {
if target.contains_known_hash(&obj, hash, vm)? {
set.add_known_hash(&obj, hash, vm)?;
}
}
return Ok(set);
}
for item in other.iter(vm)? {
let obj = item?;
if self.contains(&obj, vm)? {
set.add(&obj, vm)?;
let hash = obj.hash(vm)?;
if self.contains_known_hash(&obj, hash, vm)? {
set.add_known_hash(&obj, hash, vm)?;
if set.len() >= self.len() {
break;
}
}
}
Ok(set)
Expand Down Expand Up @@ -361,6 +392,9 @@ impl PySetInner {
}

fn issuperset(&self, other: ArgIterable, vm: &VirtualMachine) -> PyResult<bool> {
if let Some(other_set) = extract_set(other.as_object()) {
return self.compare(other_set, PyComparisonOp::Ge, vm);
}
for item in other.iter(vm)? {
if !self.contains(&*item?, vm)? {
return Ok(false);
Expand All @@ -370,11 +404,32 @@ impl PySetInner {
}

fn issubset(&self, other: ArgIterable, vm: &VirtualMachine) -> PyResult<bool> {
let other_set = Self::from_iter(other.iter(vm)?, vm)?;
self.compare(&other_set, PyComparisonOp::Le, vm)
if let Some(other_set) = extract_set(other.as_object()) {
return self.compare(other_set, PyComparisonOp::Le, vm);
}
Ok(self.intersection(other, vm)?.len() == self.len())
}

pub(super) fn isdisjoint(&self, other: ArgIterable, vm: &VirtualMachine) -> PyResult<bool> {
if let Some(other_set) = extract_set(other.as_object()) {
if core::ptr::eq(self, other_set) {
return Ok(self.len() == 0);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
let other_type = other.as_object().class();
if other_type.is(vm.ctx.types.set_type) || other_type.is(vm.ctx.types.frozenset_type) {
let (target, source) = if self.len() < other_set.len() {
(other_set, self)
} else {
(self, other_set)
};
for (key, hash) in source.content.keys_with_hashes() {
if target.contains_known_hash(&key, hash, vm)? {
return Ok(false);
}
}
return Ok(true);
}
}
for item in other.iter(vm)? {
if self.contains(&*item?, vm)? {
return Ok(false);
Expand Down Expand Up @@ -486,7 +541,7 @@ impl PySetInner {
others: impl core::iter::Iterator<Item = ArgIterable>,
vm: &VirtualMachine,
) -> PyResult<()> {
let temp_inner = self.fold_op(others, Self::intersection, vm)?;
let temp_inner = self.intersection_multi(others, vm)?;
self.clear();
for (obj, hash) in temp_inner.content.keys_with_hashes() {
self.add_known_hash(&obj, hash, vm)?;
Expand Down Expand Up @@ -694,7 +749,9 @@ impl PySet {
others: PosArgs<ArgIterable, NameOthers>,
vm: &VirtualMachine,
) -> PyResult<Self> {
self.fold_op(others.into_iter(), PySetInner::intersection, vm)
Ok(Self {
inner: self.inner.intersection_multi(others.into_iter(), vm)?,
})
}

#[pymethod]
Expand Down Expand Up @@ -748,11 +805,9 @@ impl PySet {
vm: &VirtualMachine,
) -> PyResult<PyArithmeticValue<Self>> {
if let Ok(other) = AnySet::try_from_object(vm, other) {
Ok(PyArithmeticValue::Implemented(self.op(
other,
PySetInner::intersection,
vm,
)?))
Ok(PyArithmeticValue::Implemented(Self {
inner: self.inner.intersection(other.into_iterable(vm)?, vm)?,
}))
} else {
Ok(PyArithmeticValue::NotImplemented)
}
Expand Down Expand Up @@ -1221,7 +1276,10 @@ impl PyFrozenSet {
others: PosArgs<ArgIterable, NameOthers>,
vm: &VirtualMachine,
) -> PyResult<Self> {
self.fold_op(others.into_iter(), PySetInner::intersection, vm)
Ok(Self {
inner: self.inner.intersection_multi(others.into_iter(), vm)?,
..Default::default()
})
}

#[pymethod]
Expand Down Expand Up @@ -1275,11 +1333,10 @@ impl PyFrozenSet {
vm: &VirtualMachine,
) -> PyResult<PyArithmeticValue<Self>> {
if let Ok(other) = AnySet::try_from_object(vm, other) {
Ok(PyArithmeticValue::Implemented(self.op(
other,
PySetInner::intersection,
vm,
)?))
Ok(PyArithmeticValue::Implemented(Self {
inner: self.inner.intersection(other.into_iterable(vm)?, vm)?,
..Default::default()
}))
} else {
Ok(PyArithmeticValue::NotImplemented)
}
Expand Down
13 changes: 7 additions & 6 deletions crates/vm/src/dict_inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -485,9 +485,9 @@ impl<T: Clone> Dict<T> {

/// Store a key whose hash the caller already knows.
///
/// `hash` must equal `key.key_hash(vm)`; a wrong one lands the entry in a
/// bucket no lookup probes, silently losing the key. Only pass a hash from
/// [`Self::keys_with_hashes`] on a container holding this same key.
/// `hash` must be computed for `key` or read from [`Self::keys_with_hashes`]
/// on a container holding this same key. A wrong hash lands the entry in a
/// bucket no lookup probes, silently losing the key.
pub(crate) fn insert_known_hash<K>(
&self,
vm: &VirtualMachine,
Expand Down Expand Up @@ -1330,6 +1330,7 @@ pub trait DictKey {
}
fn key_hash(&self, vm: &VirtualMachine) -> PyResult<HashValue>;
fn key_is(&self, other: &PyObject) -> bool;
/// Compare the stored entry (`other_key`) with this lookup key, in that order.
fn key_eq(&self, vm: &VirtualMachine, other_key: &PyObject) -> PyResult<bool>;
fn key_as_isize(&self, vm: &VirtualMachine) -> PyResult<isize>;
}
Expand Down Expand Up @@ -1369,7 +1370,7 @@ impl DictKey for PyObject {
let b = unsafe { other_key.downcast_unchecked_ref::<PyInt>() };
return Ok(a.as_bigint() == b.as_bigint());
}
vm.bool_eq(self, other_key)
vm.bool_eq(other_key, self)
}

#[inline]
Expand Down Expand Up @@ -1401,7 +1402,7 @@ impl DictKey for Py<PyStr> {
} else if let Some(pystr) = other_key.downcast_ref_if_exact::<PyStr>(vm) {
Ok(self.as_wtf8() == pystr.as_wtf8())
} else {
vm.bool_eq(self.as_object(), other_key)
vm.bool_eq(other_key, self.as_object())
}
}

Expand Down Expand Up @@ -1709,7 +1710,7 @@ impl DictKey for usize {
}
} else {
let int = vm.ctx.new_int(*self);
vm.bool_eq(int.as_ref(), other_key)
vm.bool_eq(other_key, int.as_ref())
}
}

Expand Down
47 changes: 47 additions & 0 deletions extra_tests/snippets/builtin_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,3 +568,50 @@ def __missing__(self, key):
assert ("ports", [80, 443]) in lookup_items
assert ("ports", [8080]) not in lookup_items
assert ("missing", []) not in lookup_items


class LookupOrderKey:
calls = []

def __init__(self, name, equal):
self.name = name
self.equal = equal

def __hash__(self):
return 17

def __eq__(self, other):
self.calls.append(self.name)
return self.equal


stored = LookupOrderKey("stored", True)
lookup = LookupOrderKey("lookup", False)
mapping = {stored: 1}
assert mapping[lookup] == 1
mapping[lookup] = 2
assert len(mapping) == 1
assert next(iter(mapping)) is stored
del mapping[lookup]
assert not mapping
assert LookupOrderKey.calls == ["stored", "stored", "stored"]

LookupOrderKey.calls.clear()
stored = LookupOrderKey("stored", NotImplemented)
lookup = LookupOrderKey("lookup", True)
assert {stored: 1}[lookup] == 1
assert LookupOrderKey.calls == ["stored", "lookup"]


class LookupOrderSubclass(LookupOrderKey):
__hash__ = LookupOrderKey.__hash__

def __eq__(self, other):
return super().__eq__(other)


LookupOrderKey.calls.clear()
stored = LookupOrderKey("stored", False)
lookup = LookupOrderSubclass("lookup", True)
assert {stored: 1}[lookup] == 1
assert LookupOrderKey.calls == ["lookup"]
Loading
Loading