X Tutup
Skip to content

Fix (ish) hanging tests in test_list.py#7382

Draft
ShaharNaveh wants to merge 3 commits intoRustPython:mainfrom
ShaharNaveh:list-fix-hang-cmp
Draft

Fix (ish) hanging tests in test_list.py#7382
ShaharNaveh wants to merge 3 commits intoRustPython:mainfrom
ShaharNaveh:list-fix-hang-cmp

Conversation

@ShaharNaveh
Copy link
Contributor

@ShaharNaveh ShaharNaveh commented Mar 7, 2026

Summary by CodeRabbit

  • Bug Fixes
    • Improved list comparison to handle cases where lists are modified during comparison, including early shortcuts when lengths differ, re-checking for mutations during iteration, and correct short-circuiting for equality/inequality and other comparison outcomes.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 7, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: afdbfbb5-c581-4ba4-ad42-ac938b645c68

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaced element-wise rich comparison for PyList with an index-based, mutation-aware comparison that re-reads list lengths and elements during iteration and handles Eq/Ne length shortcuts and differing-element comparisons with identical_or_equal.

Changes

Cohort / File(s) Summary
List Comparison Algorithm
crates/vm/src/builtins/list.rs
Removed PyExactSizeIterator import; replaced element-wise iteration with index-based comparison that re-reads lengths/elements to detect mutations, short-circuits Eq/Ne on length mismatch, and uses identical_or_equal for differing elements.
Spell dictionary updates
.cspell.dict/rust-more.txt
Added new tokens (oelements, olen, zelements, zlen) to the dictionary.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Fix list.__repr__ deadlock #7275: Similar changes to crates/vm/src/builtins/list.rs implementing mutation-aware re-reading during iteration (related implementation pattern).

Suggested reviewers

  • youknowone

Poem

🐇 I hop through lists with careful care,
Re-read each step and sniff the air.
If items shift while I compare,
I pause, then check—I'm always fair.
Hooray for safe equality, tail in the air! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix (ish) hanging tests in test_list.py' directly addresses the main objective of the PR, which is to fix hanging tests by implementing a mutation-aware list comparison algorithm.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2026

📦 Library Dependencies

The following Lib/ modules were modified. Here are their dependencies:

[x] test: cpython/Lib/test/test_list.py (TODO: 3)
[x] test: cpython/Lib/test/test_listcomps.py (TODO: 1)
[ ] test: cpython/Lib/test/test_userlist.py (TODO: 1)

dependencies:

dependent tests: (no tests depend on list)

Legend:

  • [+] path exists in CPython
  • [x] up-to-date, [ ] outdated

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/vm/src/builtins/list.rs`:
- Line 579: The code uses vm.bool_eq in the identity fast-path and later ignores
the requested comparator `op`; change the first-phase check to use
vm.identical_or_equal(zitem, oitem) instead of vm.bool_eq so
identical-but-unequal objects match the fast path, and in the second phase
replace the equality-only check with the proper comparator call (e.g.,
vm.bool_op(op, zitem, oitem) or the existing VM method that evaluates `op`) so
the ordered comparisons (using `op`) are honored; update the branches that
reference zitem, oitem and op accordingly.
- Around line 560-574: The comparison loop using zelements and oelements only
refreshes snapshots when lengths (zlen/olen) change, which allows in-place
mutations of elements to leave stale values; update the loop in the
equality/comparison routine (the block that uses zelf.borrow_vec(),
other.borrow_vec(), zelements, oelements, zlen, olen and the index i) so that
each iteration reads the current element(s) directly from the live lists (e.g.,
call zelf.borrow_vec() and other.borrow_vec() and index into them for the items
being compared) or otherwise refresh the snapshots for the specific indices
per-iteration instead of gating refreshes on length changes, ensuring
comparisons always use the current elements.
- Around line 565-577: The loop condition uses "while i < zlen || i < olen"
which allows indexing both snapshots when only one has an element; change it to
"while i < zlen && i < olen" so the prefix scan only runs while both zelements
and oelements have an index i; adjust no other logic — keep the existing refetch
of zelements/oelements and the subsequent size-based fallback unchanged
(identify zelements, oelements, zlen, olen, and i in the loop to locate the
change).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 6450e6c7-fcd0-4701-8cf8-bf43b2a67dc5

📥 Commits

Reviewing files that changed from the base of the PR and between f680f8a and 287e053.

⛔ Files ignored due to path filters (1)
  • Lib/test/test_list.py is excluded by !Lib/**
📒 Files selected for processing (1)
  • crates/vm/src/builtins/list.rs

Comment on lines +560 to +574
let mut zelements = zelf.borrow_vec().to_vec();
let mut oelements = other.borrow_vec().to_vec();

// Search for the first index where items are different.
let mut i = 0;
while i < zlen || i < olen {
if zelements.len() != zlen {
// Comparison mutated the list; refetch it.
zelements = zelf.borrow_vec().to_vec();
}

if oelements.len() != olen {
// Comparison mutated the list; refetch it.
oelements = other.borrow_vec().to_vec();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Length-preserving mutations still compare stale items.

The refresh logic only re-clones when len() changes. If an element comparison mutates list[i + 1] in place, the next iteration still reads the old zelements / oelements snapshots, so equality and ordering can be decided on objects that are no longer in either list. If this path is meant to be mutation-aware, reload the current items each iteration instead of gating refreshes on length changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/builtins/list.rs` around lines 560 - 574, The comparison loop
using zelements and oelements only refreshes snapshots when lengths (zlen/olen)
change, which allows in-place mutations of elements to leave stale values;
update the loop in the equality/comparison routine (the block that uses
zelf.borrow_vec(), other.borrow_vec(), zelements, oelements, zlen, olen and the
index i) so that each iteration reads the current element(s) directly from the
live lists (e.g., call zelf.borrow_vec() and other.borrow_vec() and index into
them for the items being compared) or otherwise refresh the snapshots for the
specific indices per-iteration instead of gating refreshes on length changes,
ensuring comparisons always use the current elements.

Comment on lines +565 to +577
while i < zlen || i < olen {
if zelements.len() != zlen {
// Comparison mutated the list; refetch it.
zelements = zelf.borrow_vec().to_vec();
}

if oelements.len() != olen {
// Comparison mutated the list; refetch it.
oelements = other.borrow_vec().to_vec();
}

let zitem = &zelements[i];
let oitem = &oelements[i];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Use && for the prefix scan bounds.

Line 565 currently enters the loop when only one list still has an element, but Lines 576-577 index both snapshots unconditionally. A prefix case like [1] < [1, 2] reaches i == 1 and panics before the size-based fallback at Line 592.

🛠️ Minimal fix
-        while i < zlen || i < olen {
+        while i < zlen && i < olen {
📝 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.

Suggested change
while i < zlen || i < olen {
if zelements.len() != zlen {
// Comparison mutated the list; refetch it.
zelements = zelf.borrow_vec().to_vec();
}
if oelements.len() != olen {
// Comparison mutated the list; refetch it.
oelements = other.borrow_vec().to_vec();
}
let zitem = &zelements[i];
let oitem = &oelements[i];
while i < zlen && i < olen {
if zelements.len() != zlen {
// Comparison mutated the list; refetch it.
zelements = zelf.borrow_vec().to_vec();
}
if oelements.len() != olen {
// Comparison mutated the list; refetch it.
oelements = other.borrow_vec().to_vec();
}
let zitem = &zelements[i];
let oitem = &oelements[i];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/builtins/list.rs` around lines 565 - 577, The loop condition
uses "while i < zlen || i < olen" which allows indexing both snapshots when only
one has an element; change it to "while i < zlen && i < olen" so the prefix scan
only runs while both zelements and oelements have an index i; adjust no other
logic — keep the existing refetch of zelements/oelements and the subsequent
size-based fallback unchanged (identify zelements, oelements, zlen, olen, and i
in the loop to locate the change).

let zitem = &zelements[i];
let oitem = &oelements[i];

if !vm.bool_eq(zitem, oitem)? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Use the correct comparator in both phases.

Line 579 should keep the identity fast path via vm.identical_or_equal, otherwise identical self-inequal objects stop matching. And Lines 613-619 ignore op entirely, so ordered comparisons like [1] < [2] devolve to equality and return the wrong result.

🛠️ Minimal fix
-            if !vm.bool_eq(zitem, oitem)? {
+            if !vm.identical_or_equal(zitem, oitem)? {
                 break;
             }
@@
-        let res = vm.identical_or_equal(zitem, oitem)?;
+        let res = zitem.rich_compare_bool(oitem, op, vm)?;
         Ok(PyComparisonValue::Implemented(res))

Also applies to: 613-619

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/builtins/list.rs` at line 579, The code uses vm.bool_eq in the
identity fast-path and later ignores the requested comparator `op`; change the
first-phase check to use vm.identical_or_equal(zitem, oitem) instead of
vm.bool_eq so identical-but-unequal objects match the fast path, and in the
second phase replace the equality-only check with the proper comparator call
(e.g., vm.bool_op(op, zitem, oitem) or the existing VM method that evaluates
`op`) so the ordered comparisons (using `op`) are honored; update the branches
that reference zitem, oitem and op accordingly.

@ShaharNaveh ShaharNaveh marked this pull request as draft March 7, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

X Tutup