Skip to content

Rebase range iterator __reduce__ to match CPython - #8424

Draft
devyubin wants to merge 1 commit into
RustPython:mainfrom
devyubin:fix-range-iter-reduce
Draft

Rebase range iterator __reduce__ to match CPython#8424
devyubin wants to merge 1 commit into
RustPython:mainfrom
devyubin:fix-range-iter-reduce

Conversation

@devyubin

@devyubin devyubin commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

range_iterator.__reduce__ (and longrange_iterator.__reduce__) returned the original range plus the current index as the pickle state, whereas CPython returns the remaining range rebased to the current position with a None state. The round-trip result was already correct on both (RustPython restored the index via __setstate__), so this is a representational / cross-compatibility divergence, not data loss.

it = iter(range(10)); next(it); next(it); next(it)
it.__reduce__()
# before: (<built-in function iter>, (range(0, 10),), 3)
# after:  (<built-in function iter>, (range(3, 10),), None)   # matches CPython

Cause

range_iter_reduce embedded the full original range and passed index as the third tuple element. Both PyRangeIterator::__reduce__ and PyLongRangeIterator::__reduce__ go through it.

Fix

Rebase the range start by index * step and emit None for the state. The index is clamped to the length first, because RustPython's iterator increments the index unconditionally, so it can run past the length after exhaustion (unlike CPython, which stops at the length). __setstate__ is left intact, so pickles that carry an integer state still load.

Test

Verified against CPython 3.14.6 — __reduce__ output now matches for mid-iteration, fresh, reversed, exhausted (range(2, 2)), step > 1, empty, single-element, negative-step, longrange, and __setstate__-advanced iterators. pickle.loads(pickle.dumps(it)) round-trips correctly in all cases.

  • test_range: SUCCESS (29 run, 2 skipped — the two skips are a separate, pre-existing __setstate__ crash, unrelated to this change).
  • cargo build / cargo clippy -p rustpython-vm / cargo fmt --check: clean.

No @expectedFailure marker flips here: CPython's own test_range has no test asserting the __reduce__ shape, so this is a representational fix, covered for regressions by the existing pickle round-trip tests.

range_iterator.__reduce__ (and longrange_iterator) returned the original
range plus the current index as pickle state; CPython returns the range
rebased to the current position with a None state. Rebase start by
index * step (clamped to the length) and emit None. __setstate__ is kept
so pickles carrying an integer state still load.

Assisted-by: Claude Code:claude-opus-4-8
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

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 Plus

Run ID: 9621b8c5-5c87-4004-8936-892bb68fbcf3

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

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.

@moreal moreal added the z-ca-2026 Tag to track Contribution Academy 2026 label Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

z-ca-2026 Tag to track Contribution Academy 2026

Projects

None yet

Development

Successfully merging this pull request may close these issues.

range_iterator.__reduce__ uses original range + index state instead of rebased range + None

2 participants