◐ Shell
reader mode source ↗
Skip to content

Add except* support#6530

Merged
youknowone merged 7 commits into
RustPython:mainfrom
youknowone:except-star
Dec 26, 2025
Merged

Add except* support#6530
youknowone merged 7 commits into
RustPython:mainfrom
youknowone:except-star

Conversation

@youknowone

@youknowone youknowone commented Dec 25, 2025

Copy link
Copy Markdown
Member

fix #4479
close #6522

@ShaharNaveh Does this looking better?

Summary by CodeRabbit

Release Notes

  • New Features

    • Implemented support for exception groups with the except* exception handling syntax, enabling proper matching and filtering of exceptions across multiple handlers. Non-group exceptions are automatically wrapped when caught with except*.
  • Tests

    • Added test coverage for exception group creation and exception matching behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 25, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR implements runtime support for Python 3.11's except* exception handling syntax. Changes span bytecode instruction definitions, compiler except* statement handling, VM exception matching and reraising logic, frame instruction execution, and supporting tests for exception group normalization.

Changes

Cohort / File(s) Summary
Bytecode Infrastructure
crates/compiler-core/src/bytecode.rs
Added PrepReraiseStar intrinsic function variant and CheckEgMatch instruction variant; updated stack effects, encoding/decoding, and disassembly paths to handle the new instruction.
Exception Handling Runtime
crates/vm/src/exceptions.rs
Added three public functions: exception_group_match() to match exceptions against except* handlers, prep_reraise_star() to prepare exceptions for reraise, and is_same_exception_metadata() helper to identify matching exceptions within groups.
Compiler except* Support
crates/codegen/src/compile.rs
Renamed compile_try_star_statement() to compile_try_star_except() and replaced NotImplementedYet placeholder with full except* handler implementation, including exception type matching, handler binding/evaluation, and control flow for orelse/finally blocks.
Frame Execution
crates/vm/src/frame.rs
Added dispatch logic in execute_instruction() for CheckEgMatch instruction and PrepReraiseStar intrinsic, delegating to new exception matching and reraising functions.
Test & Dictionary
extra_tests/snippets/builtin_exceptions.py, .cspell.dict/python-more.txt
Added test validating that except* wraps non-group exceptions into ExceptionGroup; added "reraised" term to spellcheck dictionary.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ShaharNaveh

Poem

🐰 Whiskers twitch with joy so bright,
Except-star exceptions handled right!
Groups are matched, handlers dance,
Reraising with a clever prance!
Python 3.11 takes its flight!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The pull request adds 'reraised' to the spell-check dictionary, which is a minor change unrelated to the core except* implementation objectives. Remove the spell-check dictionary change or explain its necessity; it appears to be a supporting change for the new code rather than a core requirement.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The pull request implements except* exception handling as specified in issues #4479 and #6522, including exception group matching, reraise logic, bytecode instructions, and validation tests.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title 'Add except* support' directly and accurately summarizes the main change in the pull request, which implements except* handling for exception groups in Python 3.11 syntax.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 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.

@youknowone youknowone force-pushed the except-star branch 2 times, most recently from f6ded50 to fd03d97 Compare December 26, 2025 00:49
Copilot AI and others added 6 commits December 26, 2025 09: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>
@youknowone youknowone marked this pull request as ready for review December 26, 2025 06:30

@coderabbitai coderabbitai Bot 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.

Hide 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

📥 Commits

Reviewing files that changed from the base of the PR and between c9bf8df and 8c2cddc.

⛔ Files ignored due to path filters (1)
  • Lib/test/test_compile.py is excluded by !Lib/**
📒 Files selected for processing (6)
  • .cspell.dict/python-more.txt
  • crates/codegen/src/compile.rs
  • crates/compiler-core/src/bytecode.rs
  • crates/vm/src/exceptions.rs
  • crates/vm/src/frame.rs
  • extra_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 running cargo fmt to 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.rs
  • crates/compiler-core/src/bytecode.rs
  • crates/vm/src/frame.rs
  • crates/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.rs
  • crates/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 entry reraised looks correct

Spelling 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 ExceptionGroup wrapping a naked ValueError("x") and preserving the inner exception and its arguments align with Python 3.11’s documented except* behavior and PEP 654 examples. No issues spotted here.

If you also run extra_tests under CPython, please confirm they only execute on a 3.11+ interpreter so the except* syntax is always accepted.

crates/vm/src/frame.rs (2)

694-702: CheckEgMatch VM handler is stack‑correct and aligns with CPython

The handler pops match_type then exc_value, calls exception_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’s CHECK_EG_MATCH contract 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_MATCHPOP_JUMP_IF_NONE pattern on both naked exceptions and nested ExceptionGroups, comparing behavior against CPython 3.11+.


2506-2511: PrepReraiseStar intrinsic wiring is correct

CallIntrinsic2 already pops arg2 then arg1 and forwards (arg1, arg2) into call_intrinsic_2; here those are correctly treated as (orig, excs) and delegated to exceptions::prep_reraise_star. This matches CPython’s binary intrinsic prep_reraise_star(orig, excs) signature.

Please confirm via tests mirroring CPython’s _testinternalcapi.PrepReraiseStar cases (nested groups, combinations of raised vs reraised exceptions) that the intrinsic is only ever invoked with excs as a list and orig as the original exception/group.

crates/compiler-core/src/bytecode.rs (2)

655-657: CheckEgMatch instruction is consistently integrated

  • The new Instruction::CheckEgMatch variant, its stack effect (0, pop 2/push 2), and disassembly label CHECK_EG_MATCH are all wired consistently.
  • This matches CPython’s CHECK_EG_MATCH behavior and the new VM handler in frame.rs.

Once the compiler emits CheckEgMatch for except* bodies, please run disassembly on a small except* 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 reordered

IntrinsicFunction2 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 Instruction enum (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_match correctly implements CHECK_EG_MATCH semantics

The logic closely mirrors CPython’s _PyEval_ExceptionGroupMatch:

  • None input returns (None, None).
  • Naked exceptions that match match_type are wrapped in an ExceptionGroup("", (exc,)) and have their traceback copied, matching documented “naked exception” behavior for except*.
  • Exception groups delegate to .split(match_type) with robust error checking, and you return (rest, matched) in the order expected by the VM’s CHECK_EG_MATCH handler.
  • Non‑matching values fall through to (exc_value.clone(), None).

This should give correct behavior for both bare exceptions and nested ExceptionGroups in simple except* 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) the None case to mirror CPython’s _testinternalcapi coverage.

crates/codegen/src/compile.rs (2)

2166-2181: LGTM! Syntax validation correctly handles except requirements.*

The implementation properly validates:

  1. Multiple exception types must be parenthesized - the source range comparison correctly detects except* A, B: vs except* (A, B):
  2. except* must specify an exception type - correctly enforces this PEP 654 requirement

2184-2184: CheckEgMatch instruction is properly implemented and tested.

The CheckEgMatch instruction (defined in crates/compiler-core/src/bytecode.rs:657) has a complete VM implementation in crates/vm/src/frame.rs:694-702 that calls exception_group_match() from crates/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-380 verify except* handling with both naked and grouped exceptions.

@youknowone youknowone changed the title Except star Dec 26, 2025

@ShaharNaveh ShaharNaveh 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.

Hide comment

Looks great!

Hide details View details @youknowone youknowone merged commit 4a6e8fb into RustPython:main Dec 26, 2025
13 checks passed
@youknowone youknowone deleted the except-star branch December 26, 2025 12:54
@coderabbitai coderabbitai Bot mentioned this pull request Dec 27, 2025
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.

Two CPython 3.11 syntax changes involving * operator are unsupported

3 participants