Skip to content

Add basic capi error support#7787

Open
bschoenmaeckers wants to merge 15 commits intoRustPython:mainfrom
bschoenmaeckers:capi-errors
Open

Add basic capi error support#7787
bschoenmaeckers wants to merge 15 commits intoRustPython:mainfrom
bschoenmaeckers:capi-errors

Conversation

@bschoenmaeckers
Copy link
Copy Markdown
Contributor

@bschoenmaeckers bschoenmaeckers commented May 5, 2026

This is the next step in adding c-api support. It also contains utilities to handle errors for implementing other c-api functions.

Summary by CodeRabbit

  • New Features

    • Expanded C API for exception handling, object/type inspection (type flags, type retrieval/is-type), runtime constant access, and thread/state restore entrypoints.
  • Refactor

    • Interpreter startup now initializes and registers global exception state when the main interpreter is created.
    • Several VM/type APIs made public to support C-API usage.
  • Chores

    • Centralized FFI result handling and error propagation for native interop.
  • Tests / CI

    • C-API tests run in a dedicated CI step.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Initializes and exposes C-API exception statics and object/type helpers, adds FFI result conversions and a with_vm helper, makes VM exception APIs public, registers the main interpreter via set_main_interpreter, and wires/tests C-API exception handling and CI test steps.

Changes

C‑API Exception & Object Bridge

Layer / File(s) Summary
Data Shape / Statics
crates/capi/src/pyerrors.rs
Adds define_exception_statics! and many #[unsafe(no_mangle)] pub static mut ...: *mut PyObject; introduces pub(crate) unsafe fn init_exception_statics(zoo: &'static ExceptionZoo).
FFI Result Conversion
crates/capi/src/util.rs
Adds pub(crate) trait FfiResult<Output=Self> with ERR_VALUE and into_output(self, vm); implements conversions for pointers, numerics, bools, floats, usize->isize, and PyResult<T> that set VM exceptions on error.
VM Surface / Visibility
crates/vm/src/vm/mod.rs, crates/vm/src/builtins/type.rs
Promotes VirtualMachine::current_exception and set_exception to pub; adds pub fn take_raised_exception(&self) -> Option<PyBaseExceptionRef>; makes Py<PyType>::is_subtype public.
FFI Function Layer
crates/capi/src/pyerrors.rs, crates/capi/src/pystate.rs
Implements PyErr_* FFI functions (PyErr_Occurred, PyErr_GetRaisedException, PyErr_SetRaisedException, PyErr_SetObject, PyErr_SetString, PyErr_PrintEx, PyErr_DisplayException, PyErr_WriteUnraisable, PyErr_NewException, PyErr_NewExceptionWithDoc, PyErr_GivenExceptionMatches) using with_vm; adds PyEval_RestoreThread stub.
Initialization / Wiring
crates/capi/src/lib.rs, crates/capi/src/pylifecycle.rs, src/lib.rs
Adds pub fn set_main_interpreter(interpreter: Interpreter) that asserts unset, calls unsafe init_exception_statics(&Context::genesis().exceptions), and stores the interpreter; Py_InitializeEx also calls init_exception_statics before creating the interpreter; run() now calls rustpython_capi::set_main_interpreter(interp).
Object helpers / Constants
crates/capi/src/object.rs
Adds pub type PyTypeObject = Py<PyType> and C-ABI functions Py_TYPE, Py_IS_TYPE, PyType_GetFlags, and Py_GetConstantBorrowed.
Tests / Misc / CI / Manifest
crates/capi/src/pyerrors.rs, .cspell.dict/cpython.txt, .github/workflows/ci.yaml, Cargo.toml
Adds unit tests for raised exceptions and type checks; adds pyerrors to cspell; excludes rustpython-capi from workspace tests and adds dedicated CI step to run crates/capi tests; switches workspace pyo3 dependency to the PyO3 git repository.

Sequence Diagram

sequenceDiagram
    participant C as C caller
    participant CApi as rustpython-capi
    participant VM as VirtualMachine
    participant Exc as ExceptionStack

    C->>CApi: PyErr_SetObject(exc_type, value)
    CApi->>VM: with_vm -> &VirtualMachine
    VM->>VM: normalize_exception(type, value) -> PyBaseExceptionRef
    VM-->>CApi: PyBaseExceptionRef
    CApi->>Exc: vm.set_exception(Some(exc))

    C->>CApi: PyErr_Occurred()
    CApi->>VM: with_vm -> vm.current_exception()
    VM-->>CApi: Option<PyBaseExceptionRef>
    CApi-->>C: *mut PyObject or NULL

    C->>CApi: PyErr_GetRaisedException()
    CApi->>VM: with_vm -> vm.take_raised_exception()
    VM-->>CApi: Option<PyBaseExceptionRef>
    CApi-->>C: *mut PyObject or NULL

    C->>CApi: PyErr_PrintEx()
    CApi->>VM: with_vm -> vm.take_raised_exception()
    VM->>VM: print_exception(exc)
    VM-->>CApi: done
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Suggested Reviewers

  • youknowone

Poem

🐰
I hopped through statics, pointers, and thread,
I bridged PyErrs from C into the VM bed.
With careful paws and tiny leaps,
Exceptions wake from borrowed sleeps.
A carrot for tests — may reviews be sped.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add basic capi error support' accurately describes the primary changes, which comprehensively add C-API error handling infrastructure including exception statics, FFI functions, and utilities.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/capi/src/pyerrors.rs`:
- Around line 138-145: Replace all use of expect!/panic!/assert! in the FFI
entrypoints so they fail gracefully through the Python/C-API: validate C string
UTF-8 in PyErr_SetString and on error create and set a Python Unicode/ValueError
via the VM and return the C-API sentinel value for that function; check for
missing exception types in PyErr_PrintEx and PyErr_WriteUnraisable and on
failure set an appropriate Python exception and return early; handle downcast
failures in PyErr_DisplayException by setting a TypeError and returning the
function's sentinel; and in PyErr_NewException replace expect/panic/assert with
input validation that sets a Python exception (TypeError/ValueError) via the VM
and returns NULL or the proper error sentinel. Locate and update the functions
named PyErr_SetString, PyErr_PrintEx, PyErr_DisplayException,
PyErr_WriteUnraisable, and PyErr_NewException to perform these checks and use
the VM to set exception state instead of aborting.
- Around line 99-105: PyErr_Occurred is returning the exception instance instead
of the exception type; change the mapping inside PyErr_Occurred to return the
exception's type object pointer rather than exc.as_object().as_raw(). Locate the
closure in the PyErr_Occurred function that calls vm.current_exception() and
replace the mapping to use the exception's class/type accessor (e.g.,
exc.class().as_object().as_raw() or the equivalent method that returns the
exception type object) while keeping the unwrap_or_default fallback.
- Around line 118-123: PyErr_SetRaisedException currently invokes undefined
behavior by calling NonNull::new_unchecked(exc) when exc can be NULL; change the
handling to use NonNull::new(exc) and branch on the Option: if NULL then clear
the VM error indicator (return Ok or equivalent) otherwise construct the
PyObjectRef via PyObjectRef::from_raw(...) and downcast_unchecked as before and
return Err(exception). Ensure you update the closure passed to with_vm to handle
the None case (clear/ok) and the Some(nonnull) case to build and return the
Err(exception).

In `@crates/capi/src/util.rs`:
- Around line 79-84: The impl of FfiResult for usize currently does an unchecked
cast in into_output, which can silently overflow; update into_output on the
usize impl to perform a checked conversion to isize (compare self to isize::MAX
or use try_from) and return ERR_VALUE if it would overflow while also setting a
Python exception on the provided VirtualMachine (use the VM API to raise an
OverflowError or ValueError), otherwise return the safely-cast isize; keep
ERR_VALUE as -1 and only use it when an exception was set.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: a85d532a-6303-4655-944d-c31fdb0ed9f0

📥 Commits

Reviewing files that changed from the base of the PR and between 22d4f43 and fbb09f2.

📒 Files selected for processing (8)
  • crates/capi/src/lib.rs
  • crates/capi/src/pyerrors.rs
  • crates/capi/src/pylifecycle.rs
  • crates/capi/src/pystate.rs
  • crates/capi/src/util.rs
  • crates/vm/src/builtins/type.rs
  • crates/vm/src/vm/mod.rs
  • src/lib.rs

Comment thread crates/capi/src/pyerrors.rs
Comment thread crates/capi/src/pyerrors.rs
Comment thread crates/capi/src/pyerrors.rs
Comment thread crates/capi/src/util.rs
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
crates/capi/src/util.rs (1)

79-85: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Avoid panicking conversion in the FFI output path (usizeisize).

Line [82]-Line [85] uses expect, which can panic instead of setting a Python exception and returning the sentinel. In this bridge, conversion overflow should map to ERR_VALUE and set VM exception.

Proposed fix
 impl FfiResult<isize> for usize {
     const ERR_VALUE: isize = -1;
 
-    fn into_output(self, _vm: &VirtualMachine) -> isize {
-        self.try_into()
-            .expect("Output value is too large to fit into target type")
+    fn into_output(self, vm: &VirtualMachine) -> isize {
+        match isize::try_from(self) {
+            Ok(value) => value,
+            Err(_) => {
+                vm.set_exception(Some(
+                    vm.new_overflow_error("value does not fit in Py_ssize_t".to_owned()),
+                ));
+                Self::ERR_VALUE
+            }
+        }
     }
 }

As per coding guidelines, "Follow Rust best practices for error handling and memory management."

#!/bin/bash
set -euo pipefail

# Verify panic-prone conversion in this impl and inspect neighboring error-mapping patterns.
rg -n --type=rust -C3 'impl\s+FfiResult<isize>\s+for\s+usize|expect\(|new_overflow_error\(|set_exception\(Some\(' crates/capi/src/util.rs

# Verify the overflow-error constructor naming used elsewhere in RustPython.
rg -n --type=rust -C2 '\bnew_overflow_error\s*\('
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/capi/src/util.rs` around lines 79 - 85, The impl FfiResult<isize> for
usize currently panics in into_output via expect; change it to handle overflow
by attempting self.try_into() and, on Err, call the VM overflow-error path and
return the ERR_VALUE sentinel instead of panicking. Concretely, update
into_output to match on self.try_into(): on Ok(v) return v, on Err(_)
create/obtain an overflow exception via VirtualMachine (e.g. new_overflow_error
or the project's overflow constructor) and set it on the vm
(vm.set_exception(...) or the project's equivalent), then return
Self::ERR_VALUE; keep the FfiResult trait, into_output function name, ERR_VALUE
constant and VirtualMachine reference to locate the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@crates/capi/src/util.rs`:
- Around line 79-85: The impl FfiResult<isize> for usize currently panics in
into_output via expect; change it to handle overflow by attempting
self.try_into() and, on Err, call the VM overflow-error path and return the
ERR_VALUE sentinel instead of panicking. Concretely, update into_output to match
on self.try_into(): on Ok(v) return v, on Err(_) create/obtain an overflow
exception via VirtualMachine (e.g. new_overflow_error or the project's overflow
constructor) and set it on the vm (vm.set_exception(...) or the project's
equivalent), then return Self::ERR_VALUE; keep the FfiResult trait, into_output
function name, ERR_VALUE constant and VirtualMachine reference to locate the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: fc955e62-f835-48d4-974d-f7bec9602b20

📥 Commits

Reviewing files that changed from the base of the PR and between fbb09f2 and 2562f9d.

📒 Files selected for processing (8)
  • crates/capi/src/lib.rs
  • crates/capi/src/pyerrors.rs
  • crates/capi/src/pylifecycle.rs
  • crates/capi/src/pystate.rs
  • crates/capi/src/util.rs
  • crates/vm/src/builtins/type.rs
  • crates/vm/src/vm/mod.rs
  • src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • crates/capi/src/pystate.rs
  • crates/capi/src/pylifecycle.rs
  • crates/capi/src/lib.rs
  • crates/vm/src/vm/mod.rs
  • crates/capi/src/pyerrors.rs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a 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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/capi/src/object.rs`:
- Around line 70-80: The extern "C" function Py_GetConstantBorrowed currently
panics on invalid constant_id which will abort the process; update the match
default arm to return a NULL pointer instead of calling panic!. Inside the
with_vm closure (using vm and ctx), replace the _ => panic!(...) branch with a
branch that returns std::ptr::null_mut() (a *mut PyObject) so the C API gets
NULL for invalid args; ensure the match arms all yield the same *mut PyObject
type and remove the panic invocation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 32705d29-6198-436c-8c01-f12f08d4c9c6

📥 Commits

Reviewing files that changed from the base of the PR and between 2562f9d and 40d97ea.

📒 Files selected for processing (2)
  • crates/capi/src/lib.rs
  • crates/capi/src/object.rs

Comment thread crates/capi/src/object.rs
@bschoenmaeckers bschoenmaeckers force-pushed the capi-errors branch 2 times, most recently from 7714ba4 to ce9322b Compare May 5, 2026 20:49
@bschoenmaeckers
Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 6, 2026

✅ Actions performed

Reviews resumed.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yaml:
- Around line 119-123: The `if` condition on the "run c-api tests" step
incorrectly checks for 'windows-2025'; update the condition `if: runner.os !=
'windows-2025'` to use the actual runner OS value `if: runner.os != 'Windows'`
so the step skips on Windows runners (match the existing pattern used elsewhere
in the file).

In `@crates/capi/src/object.rs`:
- Around line 39-66: The code casts ty.slots.flags.bits() (u64) to c_ulong which
is 32-bit on MSVC, causing truncation of internal flags (e.g.
HAS_DICT/HAS_WEAKREF at bits 40/41); fix by masking and asserting: compute let
raw = ty.slots.flags.bits(); debug_assert_eq!(raw & !0xffff_ffff, 0, "unexpected
high bits set (HAS_DICT/HAS_WEAKREF)"); let flags = (raw & 0xffff_ffff) as
c_ulong; return that—this ensures consistent behavior across platforms and makes
unintended high-bit use detectable; update the code around where flags is built
(references: ty.slots.flags.bits(), PY_TPFLAGS_* constants,
HAS_DICT/HAS_WEAKREF).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: e60c2948-0736-4795-a30b-29055716fa2c

📥 Commits

Reviewing files that changed from the base of the PR and between 6b79cb5 and d34ef6f.

📒 Files selected for processing (3)
  • .github/workflows/ci.yaml
  • crates/capi/src/object.rs
  • crates/capi/src/pystate.rs

Comment thread .github/workflows/ci.yaml
Comment thread crates/capi/src/object.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a 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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yaml:
- Line 115: The CI workflow currently hardcodes "--exclude rustpython-capi" into
the cargo test command instead of adding it to the shared env variable, causing
clippy to still include that crate on Windows; update the WORKSPACE_EXCLUDES
environment variable to include "rustpython-capi" (so it is respected by all
jobs) and remove the inline "--exclude rustpython-capi" from the cargo test
invocation (the line containing "run: cargo test --workspace --exclude
rustpython-capi ...") so tests and the clippy job both consume ${{
env.WORKSPACE_EXCLUDES }} consistently; also verify the clippy command that uses
${{ env.WORKSPACE_EXCLUDES }} remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: ecdbda11-4d2e-401d-99da-81702c01e3ad

📥 Commits

Reviewing files that changed from the base of the PR and between d34ef6f and 8432c17.

📒 Files selected for processing (1)
  • .github/workflows/ci.yaml

Comment thread .github/workflows/ci.yaml
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a 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

♻️ Duplicate comments (1)
crates/capi/src/pyerrors.rs (1)

195-239: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

PyErr_NewException* still rejects or drops supported CPython inputs.

PyErr_NewException() is documented to accept an optional dict and either a single base class or a tuple of base classes, and PyErr_NewExceptionWithDoc() should use a non-NULL doc as the new class docstring. In the current code, a non-null dict hits assert!, _doc is ignored entirely, and malformed name / base inputs still go through expect! / panic!() instead of failing via the normal C-API error path. That makes this exported surface observably incompatible for extensions that rely on these arguments. (docs.python.org)

As per coding guidelines, "Follow Rust best practices for error handling and memory management".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/capi/src/pyerrors.rs` around lines 195 - 239, PyErr_NewException and
PyErr_NewExceptionWithDoc currently panic/expect on bad inputs, ignore the _doc
arg, and assert on a non-null dict; change them to validate inputs and fail via
the C-API error path (return null pointer after setting a Python exception)
instead of panicking. Specifically: in PyErr_NewException, replace the
CStr::to_str()/rsplit_once and base parsing expect/panic paths with graceful
checks that set an appropriate vm exception and return std::ptr::null_mut() on
error; accept a non-null dict by using it to populate the new type’s dict
(instead of assert!), and when base is NULL treat it as using the default base
per CPython semantics or validate tuple contents and error out cleanly. In
PyErr_NewExceptionWithDoc, forward name/base/dict but use the non-NULL _doc to
set the new type’s __doc__ (convert C string safely and set an error/return null
on failure) before returning the new exception type created by
vm.ctx.new_exception_type. Ensure all error paths use the VM/C-API error-setting
mechanism rather than panic!/expect! and return null pointers on failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/capi/src/pystate.rs`:
- Around line 50-51: The current stubs PyEval_SaveThread and
PyEval_RestoreThread must be implemented to bridge RustPython's threading API to
the C-API: change PyEval_SaveThread (signature returning *mut PyThreadState) to
call the equivalent of releasing the GIL (use
allow_threads()/release_current_thread() semantics) and return a pointer-sized
token representing the saved thread state (PyThreadState) that can later be
used; implement PyEval_RestoreThread to accept that token and re-acquire/restore
the thread state by calling the attach/re-acquire logic
(attach_current_thread()/acquire GIL) using the passed PyThreadState token.
Ensure both functions reference the exact symbols PyEval_SaveThread,
PyEval_RestoreThread and type PyThreadState so C extensions using
Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS get correct GIL release/restore
behavior.

---

Duplicate comments:
In `@crates/capi/src/pyerrors.rs`:
- Around line 195-239: PyErr_NewException and PyErr_NewExceptionWithDoc
currently panic/expect on bad inputs, ignore the _doc arg, and assert on a
non-null dict; change them to validate inputs and fail via the C-API error path
(return null pointer after setting a Python exception) instead of panicking.
Specifically: in PyErr_NewException, replace the CStr::to_str()/rsplit_once and
base parsing expect/panic paths with graceful checks that set an appropriate vm
exception and return std::ptr::null_mut() on error; accept a non-null dict by
using it to populate the new type’s dict (instead of assert!), and when base is
NULL treat it as using the default base per CPython semantics or validate tuple
contents and error out cleanly. In PyErr_NewExceptionWithDoc, forward
name/base/dict but use the non-NULL _doc to set the new type’s __doc__ (convert
C string safely and set an error/return null on failure) before returning the
new exception type created by vm.ctx.new_exception_type. Ensure all error paths
use the VM/C-API error-setting mechanism rather than panic!/expect! and return
null pointers on failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 543a9385-81ea-4e71-b76b-eb98502f92bc

📥 Commits

Reviewing files that changed from the base of the PR and between 130321e and 30ac061.

📒 Files selected for processing (2)
  • crates/capi/src/pyerrors.rs
  • crates/capi/src/pystate.rs

Comment thread crates/capi/src/pystate.rs
@bschoenmaeckers
Copy link
Copy Markdown
Contributor Author

This is ready for final review.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
crates/capi/src/object.rs (1)

14-21: Py_IS_TYPE: type conversion is handled correctly; with_vm necessity remains unresolved.

The closure's bool return value is converted to c_int via the FfiResult<c_int> for bool implementation (util.rs:104–110), which casts bool to c_int directly. This compiles successfully.

However, Py_TYPE (line 10–12) performs similar class-pointer access without a with_vm wrapper, while Py_IS_TYPE wraps the is() comparison inside with_vm. This may be intentional if is() requires VM context for type comparison; if not, the with_vm wrapper could be removed for consistency and simplicity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/capi/src/object.rs` around lines 14 - 21, The Py_IS_TYPE function
unnecessarily wraps the class pointer comparison in with_vm while Py_TYPE
accesses op.class() directly; if obj.class().is(ty) does not require VM state,
remove the with_vm wrapper and mirror Py_TYPE’s pattern (use unsafe deref of op
and ty and call obj.class().is(ty) directly) so the behavior and calling
convention are consistent; if the is() path actually needs VM context, instead
keep with_vm but add a brief inline comment on why Py_IS_TYPE requires VM state
while Py_TYPE does not.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/capi/src/object.rs`:
- Around line 14-21: The Py_IS_TYPE function unnecessarily wraps the class
pointer comparison in with_vm while Py_TYPE accesses op.class() directly; if
obj.class().is(ty) does not require VM state, remove the with_vm wrapper and
mirror Py_TYPE’s pattern (use unsafe deref of op and ty and call
obj.class().is(ty) directly) so the behavior and calling convention are
consistent; if the is() path actually needs VM context, instead keep with_vm but
add a brief inline comment on why Py_IS_TYPE requires VM state while Py_TYPE
does not.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 4f54f4da-4ccd-4a88-99a3-36ccadfa4635

📥 Commits

Reviewing files that changed from the base of the PR and between 30ac061 and 4e4f7df.

📒 Files selected for processing (1)
  • crates/capi/src/object.rs

Copy link
Copy Markdown
Member

@youknowone youknowone left a comment

Choose a reason for hiding this comment

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

I will try to properly review this soon. Sorry for delay

Comment thread .github/workflows/ci.yaml Outdated
- name: run c-api tests
working-directory: crates/capi
run: cargo test
if: runner.os != 'Windows' # Requires pyo3 0.29+ on Windows
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.

anything wrong about this? we can pick proper pyo3 version for windows

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a 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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Cargo.toml`:
- Line 106: The Cargo.toml currently references pyo3 and pyo3-ffi as unpinned
git dependencies which breaks reproducible builds; update both pyo3 and pyo3-ffi
entries in Cargo.toml to pin them to the known-good commit
901b032bfb8ec8eb05b39be3b98ef880427cf15a by adding the git URL and rev (or tag)
fields for each dependency so Cargo always fetches that commit (ensure you
update both the pyo3 and pyo3-ffi dependency entries).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: e8e653be-0610-4f3a-a46e-c6deecd09545

📥 Commits

Reviewing files that changed from the base of the PR and between 4e4f7df and b2c2f69.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • .github/workflows/ci.yaml
  • Cargo.toml
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/ci.yaml

Comment thread Cargo.toml

[patch.crates-io]
parking_lot_core = { git = "https://github.com/youknowone/parking_lot", branch = "rustpython" }
pyo3-ffi = { git = "https://github.com/PyO3/pyo3" }
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Get the current HEAD commit of the PyO3 repository

echo "Fetching latest PyO3 commit from main branch..."
git ls-remote https://github.com/PyO3/pyo3.git HEAD | awk '{print $1}'

Repository: RustPython/RustPython

Length of output: 154


🌐 Web query:

PyO3 Windows test fixes May 2026

💡 Result:

As of 2026-05-09, the most concrete PyO3 “Windows test fixes” that landed in 2026 are: 1) Windows linking strategy fix used by PyO3 tests: switch to raw-dylib (eliminate import libs), plus an x86-only workaround PyO3 introduced a change to use #[link(kind = "raw-dylib")] for Windows linking (via the pyo3-ffi layer), removing the need for Windows import libraries in the general case [1]. The PR also documents an x86-specific workaround: without an extra leading underscore for certain CPython private _Py* symbols, rustc generates wrong imports and the test binaries fail to start with STATUS_ENTRYPOINT_NOT_FOUND [1]. 2) Follow-on ecosystem work so tooling understands the “Windows import lib support” change (reducing CI/test failures) Maturin added logic to treat PyO3 0.29.0+ as having Windows import-lib support using the raw-dylib behavior, and notes that the upcoming 0.29.0 release will use raw-dylib linking on Windows, eliminating the need for the generate-import-lib feature [2]. This directly targets Windows build/test failures caused by mismatched expectations around import-library generation [2]. 3) CI coverage addition for Windows debug builds (stopping regressions from slipping through) PyO3 also added CI to test the minimum supported debug build of Python (including Windows debug builds in the CI matrix), by pinning the standalone debug binaries it downloads [3]. This is a test-related fix in the sense of preventing Windows debug ABI/linking issues from reappearing unnoticed [3]. Additional related Windows test/CI issue you may care about (but not specifically “May 2026 fixes”) A PyO3 issue discusses “pytests crash on windows free-threaded interpreter” involving module init failures and/or mismatched libpython naming on Windows [4]. The thread indicates Windows-specific debugging around which DLLs are loaded and conditional checks around sysconfig flags [4]. If you tell me what you mean by “May 2026” (e.g., issues opened in May 2026 vs. changes released in May 2026 vs. your local failing test log), I can narrow this to only the May-specific items; with the evidence I found, the strongest 2026 Windows test reliability fixes are centered on the raw-dylib/import-lib change and CI debug coverage rather than a single May-only patch.

Citations:


🏁 Script executed:

# Check the Cargo.toml file to understand the full context
head -300 Cargo.toml | tail -200

Repository: RustPython/RustPython

Length of output: 7007


🏁 Script executed:

# Look for pyo3 related entries in Cargo.toml to understand the full dependency structure
rg -n "pyo3" Cargo.toml -A 2 -B 1

Repository: RustPython/RustPython

Length of output: 688


🌐 Web query:

PyO3 0.29.0 release Windows raw-dylib linking 2026

💡 Result:

PyO3 0.29.0 release status (as of 2026-05-09) I could not find a published PyO3 v0.29.0 release tag/release notes in the official PyO3 GitHub releases list; the changelog/release streams I found show up through 0.28.3 in 2026. [1][2][3] Windows “raw-dylib” linking for PyO3 0.29.0 Even though a v0.29.0 release was not found, upstream development clearly indicates that the upcoming PyO3 0.29.0 would switch Windows linking to Rust’s #[link(kind = "raw-dylib")] approach (“raw-dylib linking”), eliminating the need to generate Windows Python import libraries (.lib) via the previous generate-import-lib/python3-dll-a path. 1) Core change in pyo3-ffi (Windows raw-dylib) PyO3 PR #5866 (“pyo3-ffi: use raw-dylib for Windows linking, eliminating import libraries”) describes replacing extern blocks with a macro that adds #[link(kind = "raw-dylib")] on Windows targets, using a build-script emitted pyo3_dll cfg to select the target DLL name. [4] It also states that the raw-dylib feature was stabilized in Rust 1.71 for Windows targets and that PyO3’s MSRV is 1.83, so this is compatible. [4] 2) Related “import lib support” detection in maturin for pyo3 0.29.0+ A maturin commit (#3145, Apr 18 2026) explicitly says: “Upcoming pyo3 0.29.0 release will use raw-dylib linking on Windows, eliminating the need for the generate-import-lib feature,” and adjusts logic to treat pyo3 0.29.0+ as always having Windows import lib support via raw-dylib (so import library generation is no longer needed). [5] 3) PyO3 user guide confirmation that Windows uses raw-dylib (cross-compiling case) PyO3’s building/distribution docs state: “On Windows, this variable is not needed because PyO3 uses raw-dylib linking.” [6] What to take away for “raw-dylib linking Windows” - The raw-dylib linking strategy is documented in PyO3’s Windows cross-compilation guidance. [6] - Upstream implementation work to eliminate import library generation is documented in PyO3 PR #5866. [4] - Downstream build tooling (maturin) explicitly expects PyO3 0.29.0+ to use raw-dylib on Windows and therefore removes the need for generate-import-lib. [5] If you meant something specific about “release Windows raw-dylib linking 2026” (e.g., a particular date of the v0.29.0 tag or exact changelog entry), tell me whether you want the expected release date/ETA from planning sources or whether you only care about the Windows linking behavior; I can then narrow the search accordingly.

Citations:


Pin git dependencies to specific commits for reproducible builds.

Both pyo3 and pyo3-ffi are using unpinned git dependencies pointing to the default branch HEAD. This breaks build reproducibility because different build times will pull different versions of the code, and upstream changes could introduce breaking changes or instability.

The project already follows this best practice elsewhere (e.g., lines 172-175 show Ruff dependencies pinned to commit hashes "to ensure reproducible builds").

The Windows fix you need (raw-dylib linking for Windows support) is present in the current PyO3 main branch at commit 901b032bfb8ec8eb05b39be3b98ef880427cf15a. Pin both dependencies to this commit:

Recommended fix
 [patch.crates-io]
 parking_lot_core = { git = "https://github.com/youknowone/parking_lot", branch = "rustpython" }
-pyo3-ffi = { git = "https://github.com/PyO3/pyo3" }
+pyo3-ffi = { git = "https://github.com/PyO3/pyo3", rev = "901b032bfb8ec8eb05b39be3b98ef880427cf15a" }
 pymath = { version = "0.2.0", features = ["mul_add", "malachite-bigint", "complex"] }
-pyo3 = { git = "https://github.com/PyO3/pyo3" }
+pyo3 = { git = "https://github.com/PyO3/pyo3", rev = "901b032bfb8ec8eb05b39be3b98ef880427cf15a" }

Also applies to: 260-260

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Cargo.toml` at line 106, The Cargo.toml currently references pyo3 and
pyo3-ffi as unpinned git dependencies which breaks reproducible builds; update
both pyo3 and pyo3-ffi entries in Cargo.toml to pin them to the known-good
commit 901b032bfb8ec8eb05b39be3b98ef880427cf15a by adding the git URL and rev
(or tag) fields for each dependency so Cargo always fetches that commit (ensure
you update both the pyo3 and pyo3-ffi dependency entries).

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.

2 participants