Skip to content

Store StopIteration.value in a struct field instead of the instance dict#8301

Merged
youknowone merged 3 commits into
RustPython:mainfrom
kangdora:fix-stopiteration-value
Jul 19, 2026
Merged

Store StopIteration.value in a struct field instead of the instance dict#8301
youknowone merged 3 commits into
RustPython:mainfrom
kangdora:fix-stopiteration-value

Conversation

@kangdora

@kangdora kangdora commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

StopIteration.value was stored in the instance __dict__, so StopIteration().__dict__ was non-empty and clearing the dict lost the value, unlike CPython. It's now a struct field on PyStopIteration (#[repr(C)] + base field, the same approach as OSError and SystemExit in #8282), exposed via #[pygetset] — writable, deletable, and independent from args, with an empty __dict__ as in CPython.

new_stop_iteration is also routed through the full constructor path instead of building a PyBaseException with a manual dict. This fixes a crash caused by the added payload (the old path allocated a base-sized object) and removes the per-instance dict allocation on this hot path.

e = StopIteration(5)
e.__dict__.clear()
print(e.value)
# before: None   after: 5   (matches CPython)

Test plan

  • Verified against CPython 3.14.6: value for StopIteration() / (5) / (1, 2), write/delete, dict.clear(), vars(), empty __dict__, and generator return value delivery (incl. yield from).
  • test_generators, test_generator_stop, test_yield_from: all pass. test_exceptions / extra_tests/snippets/builtin_exceptions.py: no regressions (the single test_badisinstance failure is pre-existing on main).
  • cargo fmt / cargo clippy: clean (no new warnings).
  • Unmarks test_pydoc's test_member_descriptor: it now passes because StopIteration.value is a real descriptor whose pydoc summary line renders as value (it was previously a plain None class attribute).
    Note this does not add __doc__ support to getset descriptors — StopIteration.value.__doc__ still differs from CPython, which is a separate pre-existing gap in PyGetSet.

Assisted-by: Claude Code:claude-fable-5

Summary by CodeRabbit

Summary by CodeRabbit

  • Bug Fixes
    • Improved StopIteration exception handling and initialization to make the exception’s value reliably accessible and updatable.
    • Standardized how StopIteration is constructed, ensuring the return payload is consistently preserved whether a value is provided or not.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 3cdcc3ba-2ade-40c2-99fc-acc3a426ec91

📥 Commits

Reviewing files that changed from the base of the PR and between ee34610 and bf33c19.

⛔ Files ignored due to path filters (1)
  • Lib/test/test_pydoc/test_pydoc.py is excluded by !Lib/**
📒 Files selected for processing (1)
  • crates/vm/src/vm/vm_new.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/vm/src/vm/vm_new.rs

📝 Walkthrough

Walkthrough

StopIteration now stores value in a dedicated field with explicit accessors and initializes it without writing to the instance dictionary. Its VM helper constructs StopIteration through the standard exception invocation path using zero or one constructor argument.

Changes

StopIteration storage and construction

Layer / File(s) Summary
StopIteration representation and accessors
crates/vm/src/exceptions.rs
PyStopIteration stores value in a dedicated field, initializes it internally, and exposes explicit getter/setter accessors.
StopIteration construction path
crates/vm/src/vm/vm_new.rs
new_stop_iteration passes an empty or single-value argument list through invoke_exception.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant VirtualMachine
  participant invoke_exception
  participant PyStopIteration
  VirtualMachine->>VirtualMachine: build args from value
  VirtualMachine->>invoke_exception: create StopIteration with args
  invoke_exception->>PyStopIteration: initialize instance
  PyStopIteration-->>VirtualMachine: return exception
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: moving StopIteration.value from the instance dict to a struct field.
Linked Issues check ✅ Passed The code moves StopIteration.value into a struct field and routes construction through the full exception path, matching the issue goals.
Out of Scope Changes check ✅ Passed The changes stay focused on StopIteration storage and construction, with no unrelated features visible in the summary.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 Jul 17, 2026
@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

📦 Library Dependencies

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

[x] lib: cpython/Lib/pydoc.py
[x] lib: cpython/Lib/pydoc_data
[ ] test: cpython/Lib/test/test_pydoc (TODO: 33)

dependencies:

  • pydoc

dependent tests: (5 tests)

  • pydoc: test_enum test_pydoc
    • pdb: test_pdb
    • xmlrpc.server: test_docxmlrpc test_xmlrpc

[x] lib: cpython/Lib/csv.py
[x] test: cpython/Lib/test/test_csv.py (TODO: 24)

dependencies:

  • csv

dependent tests: (4 tests)

  • csv: test_csv test_genericalias
    • importlib.metadata: test_importlib test_zoneinfo

[x] lib: cpython/Lib/weakref.py
[x] lib: cpython/Lib/_weakrefset.py
[x] test: cpython/Lib/test/test_weakref.py (TODO: 13)
[x] test: cpython/Lib/test/test_weakset.py

dependencies:

  • weakref

dependent tests: (222 tests)

  • weakref: test_array test_ast test_asyncio test_code test_concurrent_futures test_context test_contextlib test_copy test_ctypes test_deque test_descr test_dict test_enum test_exceptions test_file test_fileio test_finalization test_frame test_functools test_gc test_generators test_genericalias test_importlib test_inspect test_io test_ipaddress test_itertools test_logging test_memoryio test_memoryview test_mmap test_ordered_dict test_pickle test_picklebuffer test_queue test_re test_scope test_set test_slice test_socket test_sqlite3 test_ssl test_struct test_sys test_tempfile test_thread test_threading test_threading_local test_type_params test_types test_typing test_unittest test_uuid test_weakref test_weakset test_xml_etree
    • asyncio: test_asyncio test_external_inspection test_os test_pdb test_unittest
    • bdb: test_bdb
    • concurrent.futures.process: test_compileall test_concurrent_futures
    • copy: test_bytes test_codecs test_collections test_copyreg test_coroutines test_csv test_decimal test_defaultdict test_dictviews test_email test_fractions test_http_cookies test_minidom test_opcache test_optparse test_platform test_plistlib test_posix test_site test_statistics test_structseq test_super test_sysconfig test_tomllib test_urllib2 test_xml_dom_minicompat test_zlib
      • argparse: test_argparse
      • collections: test_annotationlib test_bisect test_builtin test_c_locale_coercion test_call test_configparser test_contains test_ctypes test_embed test_exception_group test_fileinput test_funcattrs test_hash test_httpservers test_iter test_iterlen test_json test_math test_monitoring test_pathlib test_patma test_pprint test_pydoc test_random test_reprlib test_richcmp test_shelve test_sqlite3 test_string test_traceback test_tuple test_urllib test_userdict test_userlist test_userstring test_with
      • dataclasses: test__colorize test_ctypes test_regrtest test_zoneinfo
      • email.generator: test_email
      • gettext: test_gettext test_tools
      • http.cookiejar: test_http_cookiejar
      • http.server: test_robotparser test_urllib2_localnet test_xmlrpc
      • logging.handlers: test_pkgutil
      • mailbox: test_mailbox
      • smtplib: test_smtplib test_smtpnet
      • tarfile: test_shutil test_tarfile
      • webbrowser: test_webbrowser
    • inspect: test_abc test_asyncgen test_buffer test_clinic test_grammar test_ntpath test_operator test_posixpath test_signal test_turtle test_type_annotations test_yield_from test_zipimport test_zipimport_support
      • ast: test_compile test_compiler_codegen test_dis test_fstring test_future_stmt test_peepholer test_peg_generator test_type_comments test_ucn test_unparse
      • cmd: test_cmd
      • importlib.metadata: test_importlib
      • pkgutil: test_pyrepl test_runpy
      • rlcompleter: test_pyrepl test_rlcompleter
      • trace: test_trace
    • logging: test_hashlib test_support test_urllib2net
      • hashlib: test_hmac test_unicodedata
      • multiprocessing.util: test_concurrent_futures
      • venv: test_venv
    • multiprocessing: test_fcntl test_multiprocessing_main_handling
    • symtable: test_symtable
    • tempfile: test_bz2 test_cmd_line test_cprofile test_ctypes test_doctest test_ensurepip test_faulthandler test_filecmp test_generated_cases test_importlib test_launcher test_linecache test_modulefinder test_peg_generator test_pkg test_pstats test_py_compile test_pyrepl test_selectors test_string_literals test_subprocess test_sys_settrace test_tabnanny test_termios test_threadedtempfile test_tokenize test_urllib_response test_winconsoleio test_zipapp test_zipfile test_zipfile64 test_zstd
      • ctypes.util: test_ctypes
      • urllib.request: test_sax test_urllibnet

[ ] test: cpython/Lib/test/test_set.py (TODO: 5)

dependencies:

dependent tests: (no tests depend on set)

Legend:

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

@moreal moreal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍🏻

Comment thread crates/vm/src/vm/vm_new.rs Outdated
self.ctx.exceptions.stop_iteration.to_owned(),
Some(dict),
)
exc.unwrap()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not suggesting we fix this in this PR, but I'm leaving it here for the record.

The reason Err is returned in invoke_exception is because the conversion to PyBaseException fails. However, since PyStopException has PyException as its base, it seems like this error shouldn't occur. That said, having to call unwrap due to the code structure feels a bit awkward, and there seems to be room for improvement to resolve this at the type level.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

at least let's use expect instead of unwrap.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I agree. invoke_exception returns Err only when the downcast to
PyBaseException fails, and that can't happen for a type whose base is
PyException, so the unwrap() is asserting what the type system already
knows. Solving it at the type level would be cleaner.

Also related: invoke_exception goes through a full PyType::call, and
new_stop_iteration is on a hot path, so I wonder if constructing via
py_new directly would be better here. The // TODO: fast-path built-in exceptions comment in invoke_exception seems to point at the same thing.

I'll keep both out of this PR and open a follow-up issue. I'd like to work
on it if that direction sounds good to you.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@youknowone Done — thanks!

Comment thread crates/vm/src/vm/vm_new.rs Outdated
self.ctx.exceptions.stop_iteration.to_owned(),
Some(dict),
)
exc.unwrap()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

at least let's use expect instead of unwrap.

@youknowone youknowone left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

thank you!

Comment on lines +1705 to +1708
pub struct PyStopIteration {
base: PyException,
value: PyAtomicRef<Option<PyObject>>,
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

👍

@youknowone
youknowone merged commit 39f1630 into RustPython:main Jul 19, 2026
27 checks passed
@youknowone

Copy link
Copy Markdown
Member

I created #8320 while reviewing this PR. if you are interested in, please look in it

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.

StopIteration.value is stored in the instance dict instead of as a member

3 participants