-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Implement more except* #6545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement more except* #6545
Conversation
📝 WalkthroughWalkthroughReimplements except* support across compiler and VM: new SetExcInfo bytecode and frame handling, multi-block codegen for except*/reraise, plus VM exception-group validation, projection, and reraise helpers. No public API signatures changed. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Codegen as Codegen (compile.rs)
participant BC as Bytecode (bytecode.rs)
participant Frame as VM Frame (frame.rs)
participant Exc as Exceptions (exceptions.rs)
rect rgb(235, 245, 255)
Note over Codegen,BC: Compile-time except* lowering
Codegen->>Codegen: Analyze except* patterns (validate no break/continue/return)
Codegen->>BC: Emit multi-block flow, PrepReraiseStar, SetExcInfo
end
rect rgb(235, 255, 235)
Note over Frame,Exc: Runtime matching and reraise
Frame->>Frame: Execute handler entry (SET_EXC_INFO reads TOS)
Frame->>Exc: Call exception_group_match / prep_reraise_star
Exc->>Exc: Validate types, split/project, collect leaf ids
Exc-->>Frame: Return matched/unmatched (exception or ExceptionGroup)
Frame->>Frame: Route to matched handler, else, finally, or reraise path
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 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 |
8a2161b to
ea4e001
Compare
.github/copilot-instructions.md
Outdated
| When you modify bytecode instructions, a full clean is required: | ||
|
|
||
| ```bash | ||
| rm -rf target/debug/build/rustpython-* && find . | grep -E "(__pycache__|\.pyc$)" | xargs rm -rf |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| rm -rf target/debug/build/rustpython-* && find . | grep -E "(__pycache__|\.pyc$)" | xargs rm -rf | |
| rm -r target/debug/build/rustpython-* && find . | grep -E "(__pycache__|\.pyc$)" | xargs r 8000 m -r |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/vm/src/exceptions.rs (1)
2538-2604:prep_reraise_starleaf‑ID projection matches the design; update thederive()commentThe revamped
prep_reraise_starlooks robust:
- Handles the non‑group “naked exception” case early, returning the first non‑
Noneentry.- Classifies items in
excsinto “raised” vs “reraised” by walking exception‑group leaves and comparing object IDs, which is a stable way to track identity throughsplit()/ restructuring.- Uses
exception_group_projection+split_by_leaf_idsto project reraised leaves back onto the original group’s shape, and then combines that result with any newly raised exceptions, returning either a single exception or a newExceptionGroupas appropriate.- All paths handle
Nonesafely and short‑circuit when there’s nothing to reraise.One small inconsistency is the comment in
split_by_leaf_ids:// Reconstruct using derive() to preserve the exception group typePer the existing design in
exception_group::types::PyBaseExceptionGroup::derive, the helper intentionally always constructs aBaseExceptionGrouprather than preserving the original subclass type, so this comment is misleading for future readers. Consider rephrasing to something like “reconstruct usingderive()to preserve the structure (but not necessarily the concrete group subclass).”Based on learnings,
derive()is intentionally not type‑preserving.Also applies to: 2606-2712
🧹 Nitpick comments (2)
crates/vm/src/exceptions.rs (1)
2436-2468: except match-type validation andsplit()contract look sound*The new
check_except_star_type_validhelper and its use inexception_group_matchcorrectly enforce:
- match types must derive from
BaseException, including tuple elements.ExceptionGroup/BaseExceptionGroupare rejected forexcept*in favor of plainexcept.The added runtime checks around
exc_value.split(match_type)(type must betuple, length at least 2) are defensive and prevent misbehaving custom groups from corrupting control flow. Only minor nit: the error message says “2‑tuple” but the code only rejects tuples with length< 2; not wrong in practice since builtinExceptionGroup.splitalways returns length‑2, but could be tightened if you want the contract to be exact.Also applies to: 2484-2486, 2514-2519
crates/vm/src/frame.rs (1)
4-5: VM handling ofSetExcInfocorrectly updates the active exceptionThe new arm in
execute_instruction:
- Peeks at TOS without popping and, when it is a
PyBaseException, sets it as the current exception viavm.set_exception(Some(exc.to_owned())).- No‑ops cleanly when TOS is not an exception, which keeps the VM stable even if codegen misfires.
Behavior is correct for except* handlers that want bare
raiseto re‑raise the matched exception. If you’d like stricter debugging, you could add adebug_assert!(exc.downcast_ref::<PyBaseException>().is_some())around these sites to catch invalidSetExcInfoemissions during development, but it’s not required for correctness.Also applies to: 1358-1365
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
.github/copilot-instructions.mdcrates/codegen/src/compile.rscrates/codegen/src/error.rscrates/compiler-core/src/bytecode.rscrates/vm/src/exceptions.rscrates/vm/src/frame.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.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/codegen/src/error.rscrates/codegen/src/compile.rscrates/vm/src/frame.rscrates/compiler-core/src/bytecode.rscrates/vm/src/exceptions.rs
🧠 Learnings (10)
📓 Common learnings
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.
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/test/**/*.py : When modifying test files in `Lib/test/`, use `unittest.skip("TODO: RustPython <reason>")` or `unittest.expectedFailure` with `# TODO: RUSTPYTHON <reason>` comment to mark unsupported features
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Run `cargo test --workspace --exclude rustpython_wasm` to execute Rust unit tests
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/test/**/*.py : Only acceptable modifications to test files are: (1) Removing `unittest.expectedFailure` decorators and upper TODO comments when tests actually pass, (2) Adding `unittest.expectedFailure` decorators when tests cannot be fixed
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/**/*.py : Add a `# TODO: RUSTPYTHON` comment when modifications are made to files in the `Lib/` directory
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/test/**/*.py : NEVER comment out or delete test code lines except for removing `unittest.expectedFailure` decorators and upper TODO comments
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Use `cargo run -- script.py` instead of the standard `python` command to run and test Python code with RustPython
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to **/*.py : In most cases, Python code should not be edited; bug fixes should be made through Rust code modifications only
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/**/*.py : Minimize modifications to CPython standard library files in the `Lib/` directory; modifications should be minimal and only to work around RustPython limitations
Applied to files:
.github/copilot-instructions.md
📚 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
8000
differs from CPython's behavior.
Applied to files:
crates/codegen/src/compile.rscrates/vm/src/frame.rscrates/vm/src/exceptions.rs
🧬 Code graph analysis (3)
crates/vm/src/frame.rs (1)
crates/stdlib/src/openssl.rs (1)
exc(3740-3740)
crates/compiler-core/src/bytecode.rs (1)
crates/vm/src/exceptions.rs (2)
w(1730-1730)w(1731-1731)
crates/vm/src/exceptions.rs (1)
crates/vm/src/exception_group.rs (1)
exception_group(35-40)
🔇 Additional comments (7)
.github/copilot-instructions.md (1)
76-82: Clean-build instructions for bytecode edits are clear and consistentThe added section gives a precise, reproducible clean command for bytecode changes and fits well with the surrounding Lib/ guidance. No issues from a tooling or safety perspective.
crates/codegen/src/error.rs (1)
91-92: NewBreakContinueReturnInExceptStarerror variant is wired correctlyThe enum variant and its
Displayarm are added consistently with existing error types, with a clear diagnostic string that will surface nicely at compile time. No structural or behavioral issues.Also applies to: 153-158
crates/compiler-core/src/bytecode.rs (1)
868-871:SetExcInfoopcode is cleanly integrated into the bytecode model
- Adding
SetExcInfoat the end ofInstructionand updatingLAST_INSTRUCTIONkeeps the marshaling range consistent.stack_effectreturns0, matching the “peek‑only” semantics.- Disassembly uses
SET_EXC_INFO, which aligns with the other mnemonics.No additional wiring issues spotted.
Also applies to: 875-875, 1749-1750, 1982-1983
crates/codegen/src/compile.rs (4)
1583-1617: LGTM: Break statement validation for except blocks.*The control flow correctly validates that
breakcannot occur inside anExceptionGroupHandlerblock before reaching a loop. TheResult<Option<BlockIdx>, ()>approach cleanly handles the three cases: found loop, no loop, and forbidden context.
1620-1657: LGTM: Continue statement validation mirrors break handling.The implementation correctly mirrors the break validation logic, using
fb_block(loop start) for continue vsfb_exit(loop end) for break.
1665-1676: LGTM: Return statement check in except blocks.*Unlike break/continue which need to find a loop first,
returnis unconditionally forbidden anywhere within anExceptionGroupHandler, and this implementation correctly checks for any such handler in the fblock stack.
2156-2426: The except* implementation correctly handles exception group matching, handler execution, exception accumulation, and final reraise. All ListAppend index calculations are correct and properly target the exception results list based on the documented stack states.
ea4e001 to
d8cda04
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
crates/codegen/src/compile.rs (2)
1582-1619: Break inside except now correctly rejected while preserving nested-loop behavior*The new fblock scan for
break:
- Correctly targets the innermost
WhileLoop/ForLoopwhen present, even inside anexcept*handler with inner loops.- Emits
InvalidBreakwhen no loop exists, preserving existing semantics.- Emits
BreakContinueReturnInExceptStarwhen anExceptionGroupHandleris encountered before any loop, which matches the CPython restriction for top‑levelbreakin anexcept*suite.This looks right and integrates cleanly with the existing
FBlockTypeusage. If you like, thefound_looppattern here and inContinuecould be factored into a small helper to avoid duplication, but that’s optional.
1622-1657: Continue inside except is validated symmetrically to break*The
continuehandling mirrors the newbreaklogic:
- Uses the same reverse fblock scan to find the innermost loop block.
- Yields
InvalidContinueoutside any loop.- Produces
BreakContinueReturnInExceptStarwhen the first relevant fblock is anExceptionGroupHandler, while still allowingcontinuein loops nested inside the handler.The symmetry with the
breakimplementation keeps behavior predictable and should match CPython’s constraints onexcept*blocks.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
Lib/test/test_except_star.pyis excluded by!Lib/**
📒 Files selected for processing (6)
.github/copilot-instructions.mdcrates/codegen/src/compile.rscrates/codegen/src/error.rscrates/compiler-core/src/bytecode.rscrates/vm/src/exceptions.rscrates/vm/src/frame.rs
🚧 Files skipped from review as they are similar to previous changes (2)
- crates/codegen/src/error.rs
- crates/vm/src/frame.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.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/compiler-core/src/bytecode.rscrates/codegen/src/compile.rscrates/vm/src/exceptions.rs
🧠 Learnings (10)
📓 Common learnings
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.
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Run `cargo test --workspace --exclude rustpython_wasm` to execute Rust unit tests
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/test/**/*.py : When modifying test files in `Lib/test/`, use `unittest.skip("TODO: RustPython <reason>")` or `unittest.expectedFailure` with `# TODO: RUSTPYTHON <reason>` comment to mark unsupported features
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/test/**/*.py : Only acceptable modifications to test files are: (1) Removing `unittest.expectedFailure` decorators and upper TODO comments when tests actually pass, (2) Adding `unittest.expectedFailure` decorators when tests cannot be fixed
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPy
8000
thon PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/**/*.py : Add a `# TODO: RUSTPYTHON` comment when modifications are made to files in the `Lib/` directory
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/test/**/*.py : NEVER comment out or delete test code lines except for removing `unittest.expectedFailure` decorators and upper TODO comments
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Use `cargo run -- script.py` instead of the standard `python` command to run and test Python code with RustPython
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to **/*.py : In most cases, Python code should not be edited; bug fixes should be made through Rust code modifications only
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/**/*.py : Minimize modifications to CPython standard library files in the `Lib/` directory; modifications should be minimal and only to work around RustPython limitations
Applied to files:
.github/copilot-instructions.md
📚 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/codegen/src/compile.rscrates/vm/src/exceptions.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Run snippets and cpython tests on wasm-wasi
- GitHub Check: Run snippets and cpython tests (windows-latest)
- GitHub Check: Run snippets and cpython tests (ubuntu-latest)
- GitHub Check: Run snippets and cpython tests (macos-latest)
- GitHub Check: Run rust tests (windows-latest)
- GitHub Check: Run rust tests (macos-latest)
- GitHub Check: Run tests under miri
- GitHub Check: Run rust tests (ubuntu-latest)
- GitHub Check: Check the WASM package and demo
- GitHub Check: Check Rust code with clippy
- GitHub Check: Ensure compilation on various targets
🔇 Additional comments (14)
crates/vm/src/exceptions.rs (10)
2436-2468: LGTM!The validation logic correctly enforces except* semantics: match types must be BaseException subclasses but cannot be ExceptionGroup/BaseExceptionGroup. Error messages are clear and match Python's behavior.
2484-2485: Good addition of validation.The early validation ensures that except* handlers reject ExceptionGroup types, which correctly implements Python 3.11+ semantics.
2513-2519: Defensive type checking added.This validation ensures that user-defined split() methods return the correct type, catching errors early with a clear message.
2548-2559: Correct handling of edge cases.The early return for empty lists and special handling for naked (non-group) exceptions correctly implements the except* semantics where only one except* clause can execute for a naked exception.
2569-2575: Good use of leaf ID comparison.Using
is_exception_from_origto distinguish raised from reraised exceptions is a robust approach that relies on object identity rather than mutable metadata.
2582-2603: Correct projection and combination logic.The projection preserves the original nested structure for reraised exceptions, and the combination logic correctly handles single vs. multiple exceptions, matching Python's except* behavior.
2606-2624: LGTM!The leaf ID comparison approach correctly identifies reraised exceptions by checking if all of the exception's leaf IDs are present in the original group, relying on the fact that
split()preserves leaf exception identity.
2626-2650: LGTM!The recursive collection correctly identifies leaf exceptions by checking if an exception is not an exception group, and safely traverses nested structures using if-let chains.
2652-2672: LGTM!The projection function correctly collects leaf IDs from the keep list and delegates to
split_by_leaf_idsfor the actual recursive splitting, maintaining a clean separation of concerns.
2674-2712: LGTM!The recursive splitting correctly preserves the nested structure using the
derive()method. Per the learnings,derive()intentionally creates BaseExceptionGroup instances rather than preserving the original exception class type, which is a deliberate design decision.Based on learnings, this design differs from CPython but is intentional in RustPython.
.github/copilot-instructions.md (1)
76-82: Clean-build instructions look good and match prior suggested workflowThe new “Clean Build” snippet is accurate for forcing a rebuild after bytecode instruction changes (wiping rustpython build artifacts and Python bytecode). No issues from a tooling or safety perspective; this should help avoid stale-instruction failures.
crates/compiler-core/src/bytecode.rs (1)
868-876: SetExcInfo opcode is wired consistently (enum, marshaling, stack effect, disassembly)The new
SetExcInfoinstruction is correctly integrated:
- Added as the last
Instructionvariant andLAST_INSTRUCTIONupdated, keepingTryFrom<u8>/marshaling valid.stack_effectreturns0, matching the “does not pop the value” behavior.- Disassembler prints
SET_EXC_INFO, consistent with other op names.No issues spotted here.
Also applies to: 1749-1750, 1982-1983
crates/codegen/src/compile.rs (2)
1665-1676: Return is correctly disallowed anywhere within an except handler frame*The added check for
FBlockType::ExceptionGroupHandlerbefore emitting areturn:
- Ensures
returnis rejected anywhere in the dynamic extent of anexcept*handler (including inside nested loops), which aligns with CPython’s semantics.- Still permits
returnin the try body,else,finally, and in nested functions defined inside anexcept*since those have their ownCodeInfo/fblock stacks.This is a good, minimal way to enforce the restriction using the existing fblock mechanism.
2156-2423: except compilation flow matches CPython’s multi-block design and maintains stack invariants*The reworked
compile_try_star_exceptlooks internally consistent and closely follows CPython’s strategy:
Stack protocol:
- At handler entry,
[exc] → [orig, list, rest]viaBUILD_LIST(0)andCOPY(2), giving a stableorigand mutablerestplus an aggregation list.- For each handler,
CHECK_EG_MATCHtransforms[orig, list, rest, type]into[orig, list, new_rest, match], and all subsequent paths (no match, normal handler completion, and handler raising) end with[orig, list, new_rest], ready for the next handler.- After the last handler,
ListAppend { i: 0 }appends the finalrestto the list, andPrepReraiseStar(orig, list)is invoked with exactly two arguments, returning a singleresult.Handler semantics:
- Unparenthesized tuple types and
except*without a type are rejected with clearSyntaxErrors during codegen.SetExcInfois issued before running the handler body so bareraisere-raises the correctmatch.- Handlers are wrapped in an inner
SetupExceptwith a dedicatedhandler_except_block:
- Normal completion: no new exception;
matchis dropped andNoneis appended withListAppend { i: 1 }.- Raised in handler: the raised exception is appended with
ListAppend { i: 2 }andmatchis dropped.- Name binding (
as name) is cleaned up in both normal and exceptional paths by resetting toNoneand deleting the variable, matching standardexceptbehavior.FBlockType::ExceptionGroupHandleris pushed only around the handler body and popped immediately afterward, givingbreak/continue/returnthe necessary context while not leaking into surrounding code.Reraise / no‑reraise paths:
- The
result is not Nonetest uses identity (is not None) viaIsOp(Invert::Yes), and if true branches to aRaise { kind: Raise }withresultstill on the stack—effectivelyraise result.- When
result is None,resultis popped and the original try* exception is cleaned up withPopException, after which (if afinallyexists) the code performsPopBlock+EnterFinallyand jumps to a sharedend_block.Else / finally integration:
- The try-
elsepath is unchanged structurally: on success,SetupExceptis popped,else_blockis executed, and for a presentfinallywe again usePopBlock+EnterFinallybefore jumping toend_block.end_blockserves as a common join point for “no re‑raise” and “else” paths;finally_blockis chained after it and runsfinalbodyfollowed byEndFinally.Given how subtle these flows are, especially with nested
except*,finally, and different ways of raising (bareraise,raise e, new exceptions from handlers), it’s worth making sure tests cover:
try/except*with and withoutelse/finally,- handlers that both handle and re-raise parts of an exception group,
- bare
raiseinsideexcept*,- and combinations of multiple
except*handlers.Structurally, though, this implementation looks sound and matches the expected stack behavior.
.github/copilot-instructions.md
Outdated
| When you modify bytecode instructions, a full clean is required: | ||
|
|
||
| ```bash | ||
| rm -r target/debug/build/rustpython-* && find . | grep -E "(__pycache__|\.pyc$)" | xargs rm -r |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deleting only the pyc is enough
| YieldValue, | ||
| /// Set the current exception to TOS (for except* handlers). | ||
| /// Does not pop the value. | ||
| SetExcInfo, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move to the proper place please:)
Should be between SetAdd and SetFunctionAttribute
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fixed other parts, but let's keep this here until we have to really break bytecode next time. Please move it to proper place when you are going to remove some instructions.
This is a sort of tiny effort not to break bytecode compatibility without a good reason
d8cda04 to
9dda34c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
.github/copilot-instructions.md (1)
76-82: Consider a more robust shell command for clean builds.The current pipeline has a few potential issues:
xargs rm -rwill error if no.pycfiles are foundfind . | grepis less efficient than using find's built-in filtering- The
-rflag is unnecessary when removing individual.pycfiles🔎 Proposed safer command
-rm -r target/debug/build/rustpython-* && find . | grep -E "\.pyc$" | xargs rm -r +rm -rf target/debug/build/rustpython-* && find . -name "*.pyc" -type f -deleteThis version:
- Uses
find -deleteto avoid the xargs pipeline entirely- Adds
-type fto ensure only files are matched- Uses
-rfon the first command to avoid errors if directories don't exist- Is more efficient and handles edge cases gracefully
crates/codegen/src/compile.rs (1)
2236-2246: Clarify comment - the identity check is for "is None", not "is not None".The comment on line 2236 states "Check if match is not None" but the code performs:
emit!(self, Instruction::IsOp(bytecode::Invert::No)); // is None? emit!(self, Instruction::PopJumpIfTrue { target: no_match_block });This checks if
match is None(usingInvert::Nowhich meansis, notis not) and jumps tono_match_blockwhen the result is True. The logic is correct, but the comment is misleading.Suggested fix:
-// Check if match is not None (use identity check, not truthiness) +// Check if match is None and jump to no_match_block (use identity check, not truthiness)crates/vm/src/exceptions.rs (1)
2537-2604: Consider adding more inline comments for the projection algorithm.The reimplemented logic correctly handles the complex cases (naked exceptions, raised vs reraised, projection onto original structure), but the algorithm is intricate. Adding a few more inline comments explaining the high-level strategy would improve maintainability.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
Lib/test/test_except_star.pyis excluded by!Lib/**
📒 Files selected for processing (6)
.github/copilot-instructions.mdcrates/codegen/src/compile.rscrates/codegen/src/error.rscrates/compiler-core/src/bytecode.rscrates/vm/src/exceptions.rscrates/vm/src/frame.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/compiler-core/src/bytecode.rs
🧰 Additional context used
📓 Path-based instructions (1)
**/*.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/codegen/src/error.rscrates/codegen/src/compile.rscrates/vm/src/frame.rscrates/vm/src/exceptions.rs
🧠 Learnings (10)
📓 Common learnings
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.
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Run `cargo test --workspace --exclude rustpython_wasm` to execute Rust unit tests
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/test/**/*.py : When modifying test files in `Lib/test/`, use `unittest.skip("TODO: RustPython <reason>")` or `unittest.expectedFailure` with `# TODO: RUSTPYTHON <reason>` comment to mark unsupported features
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/**/*.py : Add a `# TODO: RUSTPYTHON` comment when modifications are made to files in the `Lib/` directory
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructio
A3E2
ns.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/test/**/*.py : Only acceptable modifications to test files are: (1) Removing `unittest.expectedFailure` decorators and upper TODO comments when tests actually pass, (2) Adding `unittest.expectedFailure` decorators when tests cannot be fixed
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/test/**/*.py : NEVER comment out or delete test code lines except for removing `unittest.expectedFailure` decorators and upper TODO comments
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Use `cargo run -- script.py` instead of the standard `python` command to run and test Python code with RustPython
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to **/*.py : In most cases, Python code should not be edited; bug fixes should be made through Rust code modifications only
Applied to files:
.github/copilot-instructions.md
📚 Learning: 2025-11-29T12:17:28.606Z
Learnt from: CR
Repo: RustPython/RustPython PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T12:17:28.606Z
Learning: Applies to Lib/**/*.py : Minimize modifications to CPython standard library files in the `Lib/` directory; modifications should be minimal and only to work around RustPython limitations
Applied to files:
.github/copilot-instructions.md
📚 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/frame.rscrates/vm/src/exceptions.rs
🧬 Code graph analysis (2)
crates/vm/src/frame.rs (1)
crates/stdlib/src/openssl.rs (1)
exc(3740-3740)
crates/vm/src/exceptions.rs (1)
crates/vm/src/exception_group.rs (1)
exception_group(35-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Run snippets and cpython tests (ubuntu-latest)
- GitHub Check: Run rust tests (windows-latest)
- GitHub Check: Run snippets and cpython tests (macos-latest)
- GitHub Check: Run snippets and cpython tests (windows-latest)
- GitHub Check: Run rust tests (ubuntu-latest)
- GitHub Check: Run tests under miri
- GitHub Check: Run snippets and cpython tests on wasm-wasi
- GitHub Check: Run rust tests (macos-latest)
- GitHub Check: Check Rust code with clippy
- GitHub Check: Ensure compilation on various targets
- GitHub Check: Check the WASM package and demo
🔇 Additional comments (9)
crates/codegen/src/error.rs (1)
91-92: LGTM! Clear error handling for except* control flow restrictions.The new error variant and its display message correctly capture the semantic restriction that
break,continue, andreturnstatements cannot appear in except* blocks. The implementation follows established patterns in the codebase.Also applies to: 153-158
crates/codegen/src/compile.rs (4)
1583-1618: LGTM - Break statement validation in except blocks implemented correctly.*The logic properly scans the fblock stack to detect if a
breakstatement occurs inside anexcept*handler before reaching a loop, and raises the appropriateBreakContinueReturnInExceptStarerror as required by PEP 654.
1620-1657: LGTM - Continue statement validation mirrors break handling.The implementation correctly prevents
continuestatements insideexcept*blocks using the same pattern asbreakvalidation.
1665-1676: LGTM - Return statement validation completes the except control flow restrictions.*Correctly prevents
returninsideexcept*handlers, completing the PEP 654 requirement that break/continue/return are not allowed in exception group handlers.
2156-2426: Comprehensive except implementation looks solid.*The rewritten
compile_try_star_exceptmethod correctly implements PEP 654 exception group handling with:
- Proper stack layout management (
[orig, list, rest])- SetExcInfo instruction to support bare
raisein handlers- ExceptionGroupHandler fblock to prevent break/continue/return
- PrepReraiseStar intrinsic for exception group reconstruction
- Correct multi-block control flow for matched, non-matched, and reraise paths
The stack manipulation and ListAppend index calculations are correct, and the cleanup logic properly handles both normal and exceptional exits.
crates/vm/src/exceptions.rs (4)
2436-2468: Validation logic looks correct.The function properly enforces except* semantics by rejecting ExceptionGroup types and ensuring BaseException subclasses. Error messages are clear.
As per coding guidelines, ensure
cargo fmtandcargo clippyhave been run on this file.
2484-2519: Good defensive type checking for split() result.The added validation ensures that exception groups return a properly-typed tuple from their
split()method, with clear error messages when they don't. This prevents downstream type errors.
2652-2712: Projection logic correctly uses derive() for structure preservation.The recursive splitting and reconstruction properly preserves the nested exception group structure. The comment at line 2709 correctly notes that subclass type may not be preserved—this aligns with the design decision documented in the learnings about derive() behavior.
Based on learnings, the derive() method intentionally creates BaseExceptionGroup instances, which is the expected behavior here.
2643-2648: No issues found. The chainedif letsyntax is fully compatible with the project's MSRV of Rust 1.89.0 (well above the required 1.53.0).
| bytecode::Instruction::SetExcInfo => { | ||
| // Set the current exception to TOS (for except* handlers) | ||
| // This updates sys.exc_info() so bare 'raise' will reraise the matched exception | ||
| let exc = self.top_value(); | ||
| if let Some(exc) = exc.downcast_ref::<PyBaseException>() { | ||
| vm.set_exception(Some(exc.to_owned())); | ||
| } | ||
| Ok(None) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for similar instruction handlers with type validation patterns
rg -n -A 5 -B 2 'bytecode::Instruction::(ListAppend|SetAdd|MapAdd)' crates/vm/src/frame.rs | head -60Repository: RustPython/RustPython
Length of output: 1251
🏁 Script executed:
#!/bin/bash
# Also search for SetExcInfo usage and context
rg -n -B 5 -A 10 'SetExcInfo' crates/vm/src/Repository: RustPython/RustPython
Length of output: 1256
🏁 Script executed:
#!/bin/bash
# Search for except* handler context to understand if SetExcInfo should always receive PyBaseException
rg -n -A 3 -B 3 'except\*' crates/vm/src/ --type py --type rs | head -80Repository: RustPython/RustPython
Length of output: 91
🏁 Script executed:
#!/bin/bash
# Search for what generates SetExcInfo bytecode instruction
rg -n 'SetExcInfo' crates/vm/src/ -A 3 -B 3Repository: RustPython/RustPython
Length of output: 603
🏁 Script executed:
#!/bin/bash
# Check if there's any compiler/codegen logic that produces SetExcInfo
rg -n 'SetExcInfo' crates/compiler/Repository: RustPython/RustPython
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Look at the pattern - check if downcast_ref with if-let is used elsewhere for similar operations
rg -n 'downcast_ref.*if let' crates/vm/src/frame.rs | head -20Repository: RustPython/RustPython
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Search for instructions that use safe downcast with if let (like SetExcInfo)
rg -n 'if let Some.*downcast_ref' crates/vm/src/frame.rs -B 3 -A 3Repository: RustPython/RustPython
Length of output: 945
🏁 Script executed:
#!/bin/bash
# Search for bytecode generation of SetExcInfo in compiler module (broader search)
rg -n 'SetExcInfo' crates/ --type-list | head -20Repository: RustPython/RustPython
Length of output: 454
🏁 Script executed:
#!/bin/bash
# Look for where SetExcInfo bytecode instruction is defined
rg -n 'SetExcInfo' crates/vm/src/bytecode.rs -B 2 -A 2Repository: RustPython/RustPython
Length of output: 130
🏁 Script executed:
#!/bin/bash
# Check compiler for ExceptionGroup handling which might generate SetExcInfo
rg -n 'except\*|ExceptionGroup' crates/compiler/src/Repository: RustPython/RustPython
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Let's find the bytecode definition file
fd -name "*.rs" crates/ | xargs grep -l 'enum Instruction' | head -5Repository: RustPython/RustPython
Length of output: 295
🏁 Script executed:
#!/bin/bash
# Check how JumpIfNotExcMatch (which uses similar safe downcast pattern) is generated
rg -n 'JumpIfNotExcMatch' crates/compiler/src/ -B 3 -A 3Repository: RustPython/RustPython
Length of output: 47
🏁 Script executed:
#!/bin/bash
# See if there are comments or tests explaining SetExcInfo behavior
rg -n 'SetExcInfo' crates/ -B 5 -A 5Repository: RustPython/RustPython
Length of output: 4926
🏁 Script executed:
#!/bin/bash
# Look at the except* handler compilation context more closely
rg -n 'except\*|handler_except_block' crates/codegen/src/compile.rs -B 5 -A 10 | head -100Repository: RustPython/RustPython
Length of output: 4266
🏁 Script executed:
#!/bin/bash
# Check if there are any tests for SetExcInfo
rg -n 'SetExcInfo' crates/wasm-lib/tests/ crates/*/tests/Repository: RustPython/RustPython
Length of output: 127
🏁 Script executed:
#!/bin/bash
# Look at what comes before SetExcInfo in the stack - is it guaranteed to be BaseException?
rg -n 'emit.*SetExcInfo
67DE
' crates/codegen/src/compile.rs -B 20 -A 5Repository: RustPython/RustPython
Length of output: 1293
🏁 Script executed:
#!/bin/bash
# Look at how CheckEgMatch is implemented - what does it guarantee?
rg -n 'CheckEgMatch' crates/vm/src/frame.rs -B 3 -A 15 | head -50Repository: RustPython/RustPython
Length of output: 1046
🏁 Script executed:
#!/bin/bash
# Check if there's any documentation or comments about why safe downcast is used for exception handling
rg -n 'downcast_ref.*PyBaseException' crates/vm/src/frame.rs -B 5 -A 5Repository: RustPython/RustPython
Length of output: 663
🏁 Script executed:
#!/bin/bash
# See if SetExcInfo ever handles cases where the value is NOT a BaseException
rg -n 'CheckEgMatch\|SetExcInfo' crates/codegen/src/compile.rs -B 2 -A 2 | head -40Repository: RustPython/RustPython
Length of output: 47
🏁 Script executed:
#!/bin/bash
# Check the exception_group_match function to see what it guarantees
rg -n 'fn exception_group_match' crates/vm/src/ -A 30Repository: RustPython/RustPython
Length of output: 2448
🏁 Script executed:
#!/bin/bash
# Look at exception_group.rs derive method mentioned in learnings to understand exception handling design
rg -n 'fn derive' crates/vm/src/exception_group.rs -B 3 -A 30 | head -80Repository: RustPython/RustPython
Length of output: 2532
Consider consistency with similar bytecode handlers for exception type validation.
SetExcInfo receives guaranteed non-None exception values from CheckEgMatch (the None case is eliminated at lines 2238-2245 in compile.rs). However, it uses a safe downcast_ref with silent failure, which differs from similar stack-based instructions:
- ListAppend/SetAdd/MapAdd use
unsafe downcast_unchecked_ref()with "SAFETY: trust compiler" comments - Other exception instructions like JumpIfNotExcMatch also use the safe downcast pattern
Either document why exception handlers intentionally use defensive safe downcast (as opposed to trusting the compiler like other instructions), or standardize on downcast_unchecked_ref() for consistency.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.