Skip to content
Closed
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
4 changes: 4 additions & 0 deletions Lib/collections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,10 @@ def __ror__(self, other):
except ImportError:
# Leave the pure Python version in place.
pass
else:
_collections_abc.KeysView.register(type(OrderedDict().keys()))
_collections_abc.ItemsView.register(type(OrderedDict().items()))
_collections_abc.ValuesView.register(type(OrderedDict().values()))


################################################################################
Expand Down
28 changes: 20 additions & 8 deletions Lib/test/test_ordered_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,6 @@ def check_runtime_error_issue119004(self, dict1, dict2):
msg = re.escape("OrderedDict mutated during iteration")
self.assertRaisesRegex(RuntimeError, msg, operator.eq, dict1, dict2)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: RuntimeError not raised by eq
def test_issue119004_change_size_by_clear(self):
class Key(_TriggerSideEffectOnEqual):
def side_effect(self):
Expand All @@ -818,7 +817,6 @@ def side_effect(self):
self.assertDictEqual(dict1, {})
self.assertDictEqual(dict2, dict.fromkeys((0, Key(), 4.2)))

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'NoneType' object has no attribute 'key'
def test_issue119004_change_size_by_delete_key(self):
class Key(_TriggerSideEffectOnEqual):
def side_effect(self):
Expand All @@ -832,7 +830,6 @@ def side_effect(self):
self.assertDictEqual(dict1, dict.fromkeys((0, 4.2)))
self.assertDictEqual(dict2, dict.fromkeys((0, Key(), 4.2)))

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: RuntimeError not raised by eq
def test_issue119004_change_linked_list_by_clear(self):
class Key(_TriggerSideEffectOnEqual):
def side_effect(self):
Expand All @@ -846,7 +843,6 @@ def side_effect(self):
self.assertDictEqual(dict1, dict.fromkeys(('a', 'b'), 'c'))
self.assertDictEqual(dict2, dict.fromkeys((0, Key(), 4.2)))

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'NoneType' object has no attribute 'key'
def test_issue119004_change_linked_list_by_delete_key(self):
class Key(_TriggerSideEffectOnEqual):
def side_effect(self):
Expand Down Expand Up @@ -878,7 +874,6 @@ def side_effect(self):
self.assertDictEqual(dict1, dict.fromkeys((0, 4.2)))
self.assertDictEqual(dict2, dict.fromkeys((0, Key(), 4.2)))

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: RuntimeError not raised by copy
def test_issue148660_copy_clear_in_key_eq(self):
# gh-148660: od.copy() must not crash when a key's __eq__ clears od
# while copy() is inserting into the new dict.
Expand All @@ -901,7 +896,6 @@ def __eq__(self, other):
msg = "OrderedDict mutated during iteration"
self.assertRaisesRegex(RuntimeError, msg, od.copy)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: RuntimeError not raised by copy
def test_issue148660_copy_clear_in_subclass_getitem(self):
# gh-148660: od.copy() must not crash when a subclass __getitem__
# clears od.
Expand All @@ -923,6 +917,26 @@ class CPythonOrderedDictTests(OrderedDictTests,
OrderedDict = c_coll.OrderedDict
check_sizeof = support.check_sizeof

# TODO: RUSTPYTHON - RustPython shares dict storage, no separate order tracking
@unittest.expectedFailure
def test_dict_delitem(self):
super().test_dict_delitem()

# TODO: RUSTPYTHON - RustPython shares dict storage, no separate order tracking
@unittest.expectedFailure
def test_dict_pop(self):
super().test_dict_pop()

# TODO: RUSTPYTHON - RustPython shares dict storage, no separate order tracking
@unittest.expectedFailure
def test_dict_popitem(self):
super().test_dict_popitem()

# TODO: RUSTPYTHON - RustPython shares dict storage, no separate order tracking
@unittest.expectedFailure
def test_issue24347(self):
super().test_issue24347()

@support.cpython_only
def test_sizeof_exact(self):
OrderedDict = self.OrderedDict
Expand Down Expand Up @@ -956,7 +970,6 @@ def test_sizeof_exact(self):
check(iter(od.items()), itersize)
check(iter(od.values()), itersize)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: RuntimeError not raised
def test_key_change_during_iteration(self):
OrderedDict = self.OrderedDict

Expand All @@ -974,7 +987,6 @@ def test_key_change_during_iteration(self):
del od['c']
self.assertEqual(list(od), list('bdeaf'))

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_iterators_pickling(self):
OrderedDict = self.OrderedDict
pairs = [('c', 1), ('b', 2), ('a', 3), ('d', 4), ('e', 5), ('f', 6)]
Expand Down
80 changes: 70 additions & 10 deletions crates/vm/src/builtins/dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use super::{
};
use crate::common::lock::LazyLock;
use crate::object::{Traverse, TraverseFn};
use crate::stdlib::PyOrderedDictItems;
use crate::{
AsObject, Context, Py, PyExact, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult,
TryFromObject, atomic_func,
Expand Down Expand Up @@ -213,7 +214,7 @@ impl PyDict {
Ok((key.clone(), value.clone()))
}

fn update_sequence_pair(
pub(crate) fn update_sequence_pair(
element: PyObjectRef,
index: usize,
vm: &VirtualMachine,
Expand Down Expand Up @@ -496,8 +497,9 @@ impl PyDict {
}

fn __or__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
let other_dict: Result<PyDictRef, _> = other.downcast();
if let Ok(other) = other_dict {
// Accept dict subclasses that inherit this implementation. Subclasses with
// their own reflected slot, such as OrderedDict, take precedence in dispatch.
if let Ok(other) = other.downcast::<Self>() {
let self_cp = self.copy();
self_cp.merge_dict(other, true, vm)?;
return Ok(self_cp.into_pyobject(vm));
Expand Down Expand Up @@ -542,7 +544,7 @@ impl PyDict {

#[pyclass]
impl Py<PyDict> {
fn inner_cmp(
pub(crate) fn inner_cmp(
&self,
other: &Self,
op: PyComparisonOp,
Expand Down Expand Up @@ -1656,7 +1658,7 @@ impl AsNumber for PyDictItems {
static AS_NUMBER: PyNumberMethods = PyNumberMethods {
subtract: Some(set_inner_number_subtract),
and: Some(set_inner_number_and),
xor: Some(set_inner_number_xor),
xor: Some(dict_item_view_number_xor),
or: Some(set_inner_number_or),
..PyNumberMethods::NOT_IMPLEMENTED
};
Expand Down Expand Up @@ -1697,19 +1699,77 @@ where
Ok(PySet { inner: f(a, b)? }.into_pyobject(vm))
}

fn set_inner_number_subtract(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
pub(crate) fn set_inner_number_subtract(
a: &PyObject,
b: &PyObject,
vm: &VirtualMachine,
) -> PyResult {
set_inner_number_op(a, b, |a, b| a.difference(b, vm), vm)
}

fn set_inner_number_and(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
set_inner_number_op(a, b, |a, b| a.intersection(b, vm), vm)
pub(crate) fn set_inner_number_and(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
let a_is_view =
a.downcast_ref::<PyDictKeys>().is_some() || a.downcast_ref::<PyDictItems>().is_some();
let (view, other) = if a_is_view { (a, b) } else { (b, a) };
set_view_number_and(view, other, vm)
}

fn set_inner_number_xor(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
pub(crate) fn set_view_number_and(
view: &PyObject,
other: &PyObject,
vm: &VirtualMachine,
) -> PyResult {
let other = ArgIterable::<PyObjectRef>::try_from_object(vm, other.to_owned())?;
let result = PySet::default();
for item in other.iter(vm)? {
let item = item?;
if view.sequence_unchecked().contains(&item, vm)? {
result.add(item, vm)?;
}
}
Ok(result.into_pyobject(vm))
}

pub(crate) fn set_inner_number_xor(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
set_inner_number_op(a, b, |a, b| a.symmetric_difference(b, vm), vm)
}

fn set_inner_number_or(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
pub(crate) fn set_item_view_number_xor(
a: &PyObject,
b: &PyObject,
both_are_item_views: bool,
vm: &VirtualMachine,
) -> PyResult {
if !both_are_item_views {
return set_inner_number_xor(a, b, vm);
}
let result = PySet::default();
let a_iterable = ArgIterable::<PyObjectRef>::try_from_object(vm, a.to_owned())?;
for item in a_iterable.iter(vm)? {
let item = item?;
if !b.sequence_unchecked().contains(&item, vm)? {
result.add(item, vm)?;
}
}
let b_iterable = ArgIterable::<PyObjectRef>::try_from_object(vm, b.to_owned())?;
for item in b_iterable.iter(vm)? {
let item = item?;
if !a.sequence_unchecked().contains(&item, vm)? {
result.add(item, vm)?;
}
}
Ok(result.into_pyobject(vm))
}

fn dict_item_view_number_xor(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
let is_native_item_view = |obj: &PyObject| {
obj.downcast_ref::<PyDictItems>().is_some()
|| obj.downcast_ref::<PyOrderedDictItems>().is_some()
};
set_item_view_number_xor(a, b, is_native_item_view(a) && is_native_item_view(b), vm)
}

pub(crate) fn set_inner_number_or(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
set_inner_number_op(a, b, |a, b| a.union(b, vm), vm)
}

Expand Down
Loading
Loading