Make mappingproxy's nb_or slot symmetric for dict | mp#7723
Make mappingproxy's nb_or slot symmetric for dict | mp#7723youknowone merged 2 commits intoRustPython:mainfrom
Conversation
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.
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (1)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including ⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThe Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
📦 Library DependenciesThe following Lib/ modules were modified. Here are their dependencies: (module 'types test_userdict' not found) Legend:
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/vm/src/builtins/mappingproxy.rs (1)
265-272: Extract duplicated mappingproxy-unwrapping logic into one helper.
a_objandb_objuse identical match logic. Please extract once and call it for both operands to keep this slot easier to maintain.♻️ Suggested refactor
or: Some(|a, b, vm| { // Mirror CPython's mappingproxy_or: when either side is a // mappingproxy, unwrap to its underlying mapping and delegate // to PyNumber_Or so `dict | mp`, `mp | dict`, and `mp | mp` // all produce a `dict` result. - let a_obj = match a.downcast_ref::<PyMappingProxy>() { - Some(mp) => mp.copy(vm)?, - None => a.to_pyobject(vm), - }; - let b_obj = match b.downcast_ref::<PyMappingProxy>() { - Some(mp) => mp.copy(vm)?, - None => b.to_pyobject(vm), - }; + let unwrap_mappingproxy = |obj: &PyObject| -> PyResult<PyObjectRef> { + match obj.downcast_ref::<PyMappingProxy>() { + Some(mp) => mp.copy(vm), + None => Ok(obj.to_pyobject(vm)), + } + }; + let a_obj = unwrap_mappingproxy(a)?; + let b_obj = unwrap_mappingproxy(b)?; vm._or(a_obj.as_ref(), b_obj.as_ref()) }),As per coding guidelines, when branches differ only in a value but share common logic, extract the differing value first, then call the common logic once to avoid duplicate code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/vm/src/builtins/mappingproxy.rs` around lines 265 - 272, Extract the duplicated match into a small helper (e.g., unwrap_mappingproxy_or_convert) that takes the operand (the value currently named `a` or `b`) and `vm` and returns the same result type as the current branches, using `downcast_ref::<PyMappingProxy>()` to call `mp.copy(vm)?` or falling back to `to_pyobject(vm)`; then call that helper for both `a` and `b` to produce `a_obj` and `b_obj`, preserving the `?` error propagation and return types so all existing callers (`PyMappingProxy`, `copy`, `to_pyobject`) behave identically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@crates/vm/src/builtins/mappingproxy.rs`:
- Around line 265-272: Extract the duplicated match into a small helper (e.g.,
unwrap_mappingproxy_or_convert) that takes the operand (the value currently
named `a` or `b`) and `vm` and returns the same result type as the current
branches, using `downcast_ref::<PyMappingProxy>()` to call `mp.copy(vm)?` or
falling back to `to_pyobject(vm)`; then call that helper for both `a` and `b` to
produce `a_obj` and `b_obj`, preserving the `?` error propagation and return
types so all existing callers (`PyMappingProxy`, `copy`, `to_pyobject`) behave
identically.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 40f31df5-08cf-4a0d-a3f8-9e4dfe5cfeb0
⛔ Files ignored due to path filters (1)
Lib/test/test_userdict.pyis excluded by!Lib/**
📒 Files selected for processing (1)
crates/vm/src/builtins/mappingproxy.rs
CI failing because of that, can you please remove that test patch? |
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.
|
Done — removed the decorator on |
my bad thanks for catching ! updated with correct test patch :) |
Don't worry. Nobody expects you run entire test on local machine. This is perfectly common workflow. |
Background
CPython's
mappingproxy_or(Objects/descrobject.c) unwraps amappingproxyon either side of|to its underlying mapping and delegates back toPyNumber_Or. This makesdict | mp,mp | dict, andmp | mpall produce a plaindictresult without dict's ownnb_orhaving to know aboutmappingproxy.RustPython's
orslot inAsNumber for PyMappingProxyonly handled the case where the left operand is a mappingproxy. When the slot fired witha = dict, b = mp(becausedict.nb_orreturnedNotImplemented), it returnedNotImplementedagain — sodict | mpraisedTypeError. This propagated toUserDict | mappingproxy(which callsself.data | other).Repro
Fix
Rewrite the
orslot to unwrap amappingproxyon either side (or both) before delegating tovm._oron the underlying mappings. Mirrors CPython'smappingproxy_or. Theinplace_orslot is unchanged —|=is explicitly rejected on a mappingproxy.Tests unmasked
test_userdict.UserDictTest.test_mixed_orVerification
dict | mp,mp | dict,mp | mp,UserDict | mp,mp | UserDict, plus subclass propagation:UserDict | Sub,Sub | UserDict,Sub | Sub2)mp |= dictstill raisesTypeError(inplace_orslot unchanged)mp | listandlist | mpstill raiseTypeError(non-mapping operands)C.__dict__ | dict) works for the otherMappingProxyInnervariant tootest_userdict,test_dict,test_collections,test_descr,test_class,test_typing,test_userlist,test_ordered_dict,test_setSummary by CodeRabbit
Bug Fixes
|(or) operator behavior with mapping proxies. Operations combining dictionaries and mapping proxies (dict | proxy,proxy | dict,proxy | proxy) now consistently return dictionaries as expected.