Add except* support#6530
Conversation
📝 WalkthroughWalkthroughThis PR implements runtime support for Python 3.11's Changes
Sequence DiagramsequenceDiagram
participant Try as Try Block
participant Match as Exception Matcher
participant Handler as Handler Executor
participant Reraise as Reraise Logic
participant Finally as Finally Block
Try->>Match: Exception occurs
Match->>Match: exception_group_match()
alt Exception matches handler type
Match->>Handler: Wrap in ExceptionGroup if needed
Handler->>Handler: Bind exception name
Handler->>Handler: Execute handler body
Handler->>Reraise: Prepare unhandled exceptions
else No match
Match->>Reraise: Exception doesn't match
end
Reraise->>Reraise: prep_reraise_star()
Reraise->>Finally: Construct/combine exceptions
Finally->>Finally: Execute finally block
Finally->>Finally: Raise combined exception if needed
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ 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 |
Sorry, something went wrong.
f6ded50 to
fd03d97
Compare
December 26, 2025 00:49
Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>
Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>
Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>
Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>
fd03d97 to
1e2fa6b
Compare
December 26, 2025 00:49
1e2fa6b to
8c2cddc
Compare
December 26, 2025 01:19
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
crates/codegen/src/compile.rs (1)
2220-2243: Consider adding inline comments documenting stack state transitions.The logic is correct, but the stack state transitions through the unhandled exception check are complex:
- Line 2224: [rest] → [rest, rest]
- Line 2225: [rest, rest] → [rest, bool]
- Line 2226: [rest, bool] → [rest] (if truthy) or jump to handled_block (if falsy)
- Line 2236: Raise with [rest] on stack
- Lines 2242-2243: Pop rest and exception state in handled_block
Adding stack state comments (similar to line 2155) would improve maintainability for future developers working on exception handling.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
Lib/test/test_compile.pyis excluded by!Lib/**
📒 Files selected for processing (6)
.cspell.dict/python-more.txtcrates/codegen/src/compile.rscrates/compiler-core/src/bytecode.rscrates/vm/src/exceptions.rscrates/vm/src/frame.rsextra_tests/snippets/builtin_exceptions.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.rs: Follow the default rustfmt code style by runningcargo fmtto format Rust code
Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints introduced by changes
Follow Rust best practices for error handling and memory management
Use the macro system (pyclass,pymodule,pyfunction, etc.) when implementing Python functionality in Rust
Files:
crates/vm/src/exceptions.rscrates/compiler-core/src/bytecode.rscrates/vm/src/frame.rscrates/codegen/src/compile.rs
**/*.py
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.py: In most cases, Python code should not be edited; bug fixes should be made through Rust code modifications only
Follow PEP 8 style for custom Python code
Use ruff for linting Python code
Files:
extra_tests/snippets/builtin_exceptions.py
🧠 Learnings (1)
📚 Learning: 2025-12-09T08:46:58.660Z
Learnt from: youknowone
Repo: RustPython/RustPython PR: 6358
File: crates/vm/src/exception_group.rs:173-185
Timestamp: 2025-12-09T08:46:58.660Z
Learning: In crates/vm/src/exception_group.rs, the derive() method intentionally always creates a BaseExceptionGroup instance rather than preserving the original exception class type. This is a deliberate design decision that differs from CPython's behavior.
Applied to files:
crates/vm/src/exceptions.rscrates/vm/src/frame.rs
🧬 Code graph analysis (1)
crates/vm/src/frame.rs (1)
crates/vm/src/exceptions.rs (3)
exc_value(2462-2462)exception_group_match(2438-2491)prep_reraise_star(2494-2538)
🔇 Additional comments (9)
.cspell.dict/python-more.txt (1)
202-202: Dictionary entryreraisedlooks correctSpelling and alphabetical placement are consistent with the rest of the cspell word list; matches new exception reraising terminology.
extra_tests/snippets/builtin_exceptions.py (1)
371-380: except normalization test matches CPython semantics*The assertions about
ExceptionGroupwrapping a nakedValueError("x")and preserving the inner exception and its arguments align with Python 3.11’s documentedexcept*behavior and PEP 654 examples. No issues spotted here.If you also run
extra_testsunder CPython, please confirm they only execute on a 3.11+ interpreter so theexcept*syntax is always accepted.crates/vm/src/frame.rs (2)
694-702:CheckEgMatchVM handler is stack‑correct and aligns with CPythonThe handler pops
match_typethenexc_value, callsexception_group_match, and pushes(rest, matched)in that order, giving a net stack effect of 0 as declared in the bytecode metadata. This matches CPython’sCHECK_EG_MATCHcontract where the stack shape becomes[..., rest_or_exc, match?]with the match value on top. No issues found.It would still be good to add/extend a VM test that exercises the full
CHECK_EG_MATCH→POP_JUMP_IF_NONEpattern on both naked exceptions and nestedExceptionGroups, comparing behavior against CPython 3.11+.
2506-2511:PrepReraiseStarintrinsic wiring is correct
CallIntrinsic2already popsarg2thenarg1and forwards(arg1, arg2)intocall_intrinsic_2; here those are correctly treated as(orig, excs)and delegated toexceptions::prep_reraise_star. This matches CPython’s binary intrinsicprep_reraise_star(orig, excs)signature.Please confirm via tests mirroring CPython’s
_testinternalcapi.PrepReraiseStarcases (nested groups, combinations of raised vs reraised exceptions) that the intrinsic is only ever invoked withexcsas a list andorigas the original exception/group.crates/compiler-core/src/bytecode.rs (2)
655-657:CheckEgMatchinstruction is consistently integrated
- The new
Instruction::CheckEgMatchvariant, its stack effect (0, pop 2/push 2), and disassembly labelCHECK_EG_MATCHare all wired consistently.- This matches CPython’s
CHECK_EG_MATCHbehavior and the new VM handler inframe.rs.Once the compiler emits
CheckEgMatchforexcept*bodies, please run disassembly on a smallexcept*snippet under both CPython 3.11+ and RustPython to confirm opcode sequences and stack effects agree.Also applies to: 1727-1728, 1894-1895
577-583: IntrinsicFunction2 was newly added, not reorderedIntrinsicFunction2 with discriminants PrepReraiseStar=1 through SetTypeparamDefault=5 is a new enum, not a reordering of existing variants. The actual bytecode compatibility concern was the alphabetical sorting of the
Instructionenum (commit d287d1e), which changed discriminant values for all instructions. FORMAT_VERSION remains at 4 despite this structural change.Frozen bytecode (embedded stdlib) is regenerated on each build and deserialized at runtime, so persisted frozen modules are not affected. RustPython's standard VM does not actively implement .pyc caching, making this a non-issue for typical deployments.
crates/vm/src/exceptions.rs (1)
2436-2491:exception_group_matchcorrectly implementsCHECK_EG_MATCHsemanticsThe logic closely mirrors CPython’s
_PyEval_ExceptionGroupMatch:
Noneinput returns(None, None).- Naked exceptions that match
match_typeare wrapped in anExceptionGroup("", (exc,))and have their traceback copied, matching documented “naked exception” behavior forexcept*.- Exception groups delegate to
.split(match_type)with robust error checking, and you return(rest, matched)in the order expected by the VM’sCHECK_EG_MATCHhandler.- Non‑matching values fall through to
(exc_value.clone(), None).This should give correct behavior for both bare exceptions and nested
ExceptionGroups in simpleexcept*cases.Consider adding tests that hit: (a) partial matches on nested
ExceptionGroups, (b) invalid.split()implementations on custom group subclasses (ensuring your TypeError path triggers), and (c) theNonecase to mirror CPython’s_testinternalcapicoverage.crates/codegen/src/compile.rs (2)
2166-2181: LGTM! Syntax validation correctly handles except requirements.*The implementation properly validates:
- Multiple exception types must be parenthesized - the source range comparison correctly detects
except* A, B:vsexcept* (A, B):- except* must specify an exception type - correctly enforces this PEP 654 requirement
2184-2184: CheckEgMatch instruction is properly implemented and tested.The
CheckEgMatchinstruction (defined incrates/compiler-core/src/bytecode.rs:657) has a complete VM implementation incrates/vm/src/frame.rs:694-702that callsexception_group_match()fromcrates/vm/src/exceptions.rs:2438-2499. This function:
- Splits exception groups using the
split()method to separate matching and non-matching exceptions- Returns
(rest, match)tuples as documented- Handles nested exception groups correctly through the split mechanism
- Wraps naked exceptions in ExceptionGroup when they match the type
Tests in
extra_tests/snippets/builtin_exceptions.py:371-380verify except* handling with both naked and grouped exceptions.
Sorry, something went wrong.
ShaharNaveh
left a comment
There was a problem hiding this comment.
Looks great!
Sorry, something went wrong.
4a6e8fb
into
RustPython:main
Dec 26, 2025
fix #4479
close #6522
@ShaharNaveh Does this looking better?
Summary by CodeRabbit
Release Notes
New Features
except*exception handling syntax, enabling proper matching and filtering of exceptions across multiple handlers. Non-group exceptions are automatically wrapped when caught withexcept*.Tests
✏️ Tip: You can customize this high-level summary in your review settings.