pyre: let a raised dict probe fail to match by identity too - #881
Conversation
`dict_keys_equal` answered `std::ptr::eq` before it consulted
`eq_error_pending()`, so the "no further comparison after a raise" rule the
function documents did not cover the identity shortcut.
That is reachable. A bucket can hold both a key whose `__eq__` raises and the
incoming key's own object; probed in that order, the raise poisons the probe
and the identity hit then still reports a match. `IndexMap::insert` therefore
REPLACES instead of appending, and `w_module_dict_store_inner_checked`'s undo —
`dict_entries_pop_last`, which assumes the failed probe appended — drops the
dict's unrelated last entry while leaving the replaced value overwritten.
Against CPython 3.14 on a module dict, storing under such a key:
q 'quiet-new' (CPython: 'quiet-old' — the store must not land)
lost ['run'] (a module-level function deleted from globals())
gained -1
`ll_dict_lookup` propagates at the first raising comparison
(`rordereddict.py:1055`), so no later key can be found however it would have
compared. Move the pending-error test above the identity shortcut, which
restores that: the probe matches nothing, `insert` appends, and the existing
`pop_last` undo is correct again.
`module_dict_invariants.py` gains the case; it fails on both backends before
this change and passes on CPython, dynasm and cranelift after.
Reported by the Codex parity review on #873 as a pre-existing mismatch. Its
stated mechanism — a later user comparison returning true — is not the live
one; `eq_error_pending()` already blocks that. The identity shortcut sitting
above the guard is what was left.
Assisted-by: Claude
WalkthroughThe dictionary equality probe now propagates pending comparison errors before identity checks, preventing partial mutation during raising ChangesDictionary mutation consistency
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pyre/extra_tests/parity_tests/module_dict_invariants.py`:
- Line 104: Replace the `assert False` statement in the exception-propagation
test with an explicit `raise AssertionError`, preserving the existing failure
message so the check remains active under optimized Python execution.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 62866e02-e62b-44c1-8eb1-89bc448930a7
📒 Files selected for processing (2)
pyre/extra_tests/parity_tests/module_dict_invariants.pypyre/pyre-object/src/dictmultiobject.rs
| except ValueError: | ||
| pass | ||
| else: | ||
| assert False, "raising __eq__ must propagate out of the store" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Replace assert False with raise AssertionError.
Under python -O, all assert statements (including this one) are stripped, silently defeating the "exception must propagate" check this test exists to enforce.
🐛 Proposed fix
- assert False, "raising __eq__ must propagate out of the store"
+ raise AssertionError("raising __eq__ must propagate out of the store")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| assert False, "raising __eq__ must propagate out of the store" | |
| raise AssertionError("raising __eq__ must propagate out of the store") |
🧰 Tools
🪛 Ruff (0.16.0)
[warning] 104-104: Assertion always fails, replace with pytest.fail()
(PT015)
[warning] 104-104: Do not assert False (python -O removes these calls), raise AssertionError()
Replace assert False
(B011)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pyre/extra_tests/parity_tests/module_dict_invariants.py` at line 104, Replace
the `assert False` statement in the exception-propagation test with an explicit
`raise AssertionError`, preserving the existing failure message so the check
remains active under optimized Python execution.
Source: Linters/SAST tools
🤖 Codex parity reviewStatic analysis of this diff vs the local RPython/PyPy sources (commit 173b990). Files in the reviewed diff1. Regressions to PyPy parity introduced by this patchNone. 2. Other mismatches introduced by this patch
3. Pre-existing mismatches (already present before this patch)
4. Structural adaptations
|
A failing dict store could delete an unrelated entry from a module dict, and
apply the store it had just reported as failed.
What was wrong
dict_keys_equalansweredstd::ptr::eqbefore it consultedeq_error_pending():The pending-error test is how pyre stands in for RPython aborting the lookup:
ll_dict_lookuppropagates at the first raising comparison(
rordereddict.py:1055), so nothing later in the bucket can be found. The RustEqcallback cannot abort anIndexMapscan, so it answersfalsefor therest of it instead — but the identity shortcut sat above that test and kept
answering
true.That is reachable. A bucket can hold both a key whose
__eq__raises and theincoming key's own object. Probed in that order the raise poisons the probe,
the identity hit still reports a match, and
IndexMap::insertthereforereplaces instead of appending.
w_module_dict_store_inner_checked's undo isdict_entries_pop_last, which assumes the failed probe appended — so it dropsthe dict's unrelated last entry, and leaves the replaced value overwritten.
Measured against CPython 3.14, storing under such a key in a module dict:
'quiet-old'— store must not land'quiet-new'['run']— a module-level function, gone fromglobals()The fix
Move the pending-error test above the identity shortcut. A poisoned probe then
matches nothing,
insertappends, and the existingpop_lastundo is correctagain — no signature or ABI changes.
Verification
module_dict_invariants.pygains the case. It fails on both backends beforethis change and passes on CPython, dynasm and cranelift after.
pyre/extra_tests/parity_tests/run.py— no pyre-side failures.cargo test -p pyre-object284,-p pyre-interpreter --features dynasm430,0 failed.
check.py— 339/340 on each of dynasm, cranelift and wasm.Note
That one
check.pyfailure issynth/str_search_index_bounds, a JIT-PANIC(
compile.rs,assert i == len(inputargs) failed (16 != 26)) identical onall three backends. It is pre-existing on this base, not from this change:
reverting the two-line reorder and rebuilding reproduces the same panic. It
arrived with #860 and is unrelated to dict key comparison.
Provenance
Reported by the Codex parity review on #873 as a pre-existing mismatch. Its
stated mechanism — a later user comparison returning true — is not the live
one;
eq_error_pending()already blocks that path. The identity shortcutsitting above the guard is what was left, and it reproduces.
🤖 Generated with Claude Code
Summary by CodeRabbit