Skip to content

Commit 3ebcab7

Browse files
Make mappingproxy's nb_or slot symmetric for dict | mp (RustPython#7723)
* Make mappingproxy's nb_or slot symmetric for dict | mp CPython's mappingproxy_or (Objects/descrobject.c) unwraps a mappingproxy on either side of '|' to its underlying mapping and delegates back to PyNumber_Or. This makes 'dict | mp', 'mp | dict', and 'mp | mp' all produce a plain dict result without dict's own nb_or having to know about mappingproxy. RustPython's 'or' slot in 'AsNumber for PyMappingProxy' only handled the case where the left operand is a mappingproxy. When the slot fired with a=dict, b=mp (because dict.nb_or returned NotImplemented), it returned NotImplemented again — so 'dict | mp' raised TypeError. This propagated to 'UserDict | mappingproxy' (which calls self.data | other). Rewrite the slot to unwrap a mappingproxy on either side (or both) before delegating to vm._or on the underlying mappings. The inplace_or slot is unchanged — '|=' is still rejected. Unmasks test_userdict.UserDictTest.test_mixed_or. * Unmask test_types.MappingProxyTests.test_union The slot fix in this PR also enables this test, which was marked expectedFailure for the same dict|mappingproxy TypeError. Caught as UNEXPECTED SUCCESS in CI.
1 parent 51e7200 commit 3ebcab7

3 files changed

Lines changed: 13 additions & 7 deletions

File tree

Lib/test/test_types.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1362,7 +1362,6 @@ def test_copy(self):
13621362
self.assertEqual(view['key1'], 70)
13631363
self.assertEqual(copy['key1'], 27)
13641364

1365-
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: unsupported operand type(s) for |: 'dict' and 'mappingproxy'
13661365
def test_union(self):
13671366
mapping = {'a': 0, 'b': 1, 'c': 2}
13681367
view = self.mappingproxy(mapping)

Lib/test/test_userdict.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,6 @@ class G(collections.UserDict):
244244

245245
test_repr_deep = mapping_tests.TestHashMappingProtocol.test_repr_deep
246246

247-
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: unsupported operand type(s) for |: 'UserDict' and 'mappingproxy'
248247
def test_mixed_or(self):
249248
for t in UserDict, dict, types.MappingProxyType:
250249
with self.subTest(t.__name__):

crates/vm/src/builtins/mappingproxy.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,11 +258,19 @@ impl AsNumber for PyMappingProxy {
258258
fn as_number() -> &'static PyNumberMethods {
259259
static AS_NUMBER: PyNumberMethods = PyNumberMethods {
260260
or: Some(|a, b, vm| {
261-
if let Some(a) = a.downcast_ref::<PyMappingProxy>() {
262-
a.__or__(b.to_pyobject(vm), vm)
263-
} else {
264-
Ok(vm.ctx.not_implemented())
265-
}
261+
// Mirror CPython's mappingproxy_or: when either side is a
262+
// mappingproxy, unwrap to its underlying mapping and delegate
263+
// to PyNumber_Or so `dict | mp`, `mp | dict`, and `mp | mp`
264+
// all produce a `dict` result.
265+
let a_obj = match a.downcast_ref::<PyMappingProxy>() {
266+
Some(mp) => mp.copy(vm)?,
267+
None => a.to_pyobject(vm),
268+
};
269+
let b_obj = match b.downcast_ref::<PyMappingProxy>() {
270+
Some(mp) => mp.copy(vm)?,
271+
None => b.to_pyobject(vm),
272+
};
273+
vm._or(a_obj.as_ref(), b_obj.as_ref())
266274
}),
267275
inplace_or: Some(|a, b, vm| {
268276
if let Some(a) = a.downcast_ref::<PyMappingProxy>() {

0 commit comments

Comments
 (0)