From 2562f9dd748e54fd5d467c300d9adb69fec06391 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Tue, 5 May 2026 12:42:11 +0200 Subject: [PATCH 01/16] Add basic capi error support --- crates/capi/src/lib.rs | 16 +- crates/capi/src/pyerrors.rs | 269 +++++++++++++++++++++++++++++++++ crates/capi/src/pylifecycle.rs | 5 +- crates/capi/src/pystate.rs | 9 +- crates/capi/src/util.rs | 137 +++++++++++++++++ crates/vm/src/builtins/type.rs | 2 +- crates/vm/src/vm/mod.rs | 17 ++- src/lib.rs | 2 +- 8 files changed, 449 insertions(+), 8 deletions(-) create mode 100644 crates/capi/src/pyerrors.rs create mode 100644 crates/capi/src/util.rs diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs index c6a250da724..3207149e316 100644 --- a/crates/capi/src/lib.rs +++ b/crates/capi/src/lib.rs @@ -1,15 +1,17 @@ #![allow(clippy::missing_safety_doc)] +use crate::pyerrors::init_exception_statics; use crate::pylifecycle::MAIN_INTERP; -use rustpython_vm::Interpreter; pub use rustpython_vm::PyObject; +use rustpython_vm::{Context, Interpreter}; use std::sync::MutexGuard; extern crate alloc; - +pub mod pyerrors; pub mod pylifecycle; pub mod pystate; pub mod refcount; +mod util; /// Get main interpreter of this process. Will be None if it has not been initialized yet. pub fn get_main_interpreter() -> MutexGuard<'static, Option> { @@ -17,3 +19,13 @@ pub fn get_main_interpreter() -> MutexGuard<'static, Option> { .lock() .expect("Failed to lock interpreter mutex") } + +/// Set the main interpreter of this process. This method will panic when there is already an +/// interpreter set. +pub fn set_main_interpreter(interpreter: Interpreter) { + let mut interp = get_main_interpreter(); + assert!(interp.is_none(), "Main interpreter is already set"); + // Safety: Interpreter was not initialized before, so we can safely assume the statics are not used + unsafe { init_exception_statics(&Context::genesis().exceptions) }; + *interp = Some(interpreter); +} diff --git a/crates/capi/src/pyerrors.rs b/crates/capi/src/pyerrors.rs new file mode 100644 index 00000000000..ec5578e48fa --- /dev/null +++ b/crates/capi/src/pyerrors.rs @@ -0,0 +1,269 @@ +use crate::PyObject; +use crate::pystate::with_vm; +use core::convert::Infallible; +use core::ffi::{CStr, c_char, c_int}; +use core::ptr::NonNull; +use rustpython_vm::builtins::{PyBaseException, PyTuple, PyType}; +use rustpython_vm::convert::IntoObject; +use rustpython_vm::exceptions::ExceptionZoo; +use rustpython_vm::{AsObject, PyObjectRef, PyResult}; + +macro_rules! define_exception_statics { + ($( $(#[$meta:meta])* $export:ident => $exc:ident ),* $(,)?) => { + $( + $(#[$meta])* + #[unsafe(no_mangle)] + pub static mut $export: *mut PyObject = core::ptr::null_mut(); + )* + + #[allow(static_mut_refs)] + pub(crate) unsafe fn init_exception_statics(zoo: &'static ExceptionZoo) { + unsafe { + $( + $export = zoo.$exc.as_object().as_raw().cast_mut(); + )* + } + } + }; +} + +define_exception_statics! { + PyExc_BaseException => base_exception_type, + PyExc_BaseExceptionGroup => base_exception_group, + PyExc_SystemExit => system_exit, + PyExc_KeyboardInterrupt => keyboard_interrupt, + PyExc_GeneratorExit => generator_exit, + PyExc_Exception => exception_type, + PyExc_StopIteration => stop_iteration, + PyExc_StopAsyncIteration => stop_async_iteration, + PyExc_ArithmeticError => arithmetic_error, + PyExc_FloatingPointError => floating_point_error, + PyExc_SystemError => system_error, + PyExc_TypeError => type_error, + PyExc_OverflowError => overflow_error, + PyExc_ZeroDivisionError => zero_division_error, + PyExc_AssertionError => assertion_error, + PyExc_IndexError => index_error, + PyExc_KeyError => key_error, + PyExc_LookupError => lookup_error, + PyExc_AttributeError => attribute_error, + PyExc_BufferError => buffer_error, + PyExc_EOFError => eof_error, + PyExc_ImportError => import_error, + PyExc_ModuleNotFoundError => module_not_found_error, + PyExc_MemoryError => memory_error, + PyExc_NameError => name_error, + PyExc_UnboundLocalError => unbound_local_error, + PyExc_OSError => os_error, + PyExc_BlockingIOError => blocking_io_error, + PyExc_ChildProcessError => child_process_error, + PyExc_ConnectionError => connection_error, + PyExc_BrokenPipeError => broken_pipe_error, + PyExc_ConnectionAbortedError => connection_aborted_error, + PyExc_ConnectionRefusedError => connection_refused_error, + PyExc_ConnectionResetError => connection_reset_error, + PyExc_FileExistsError => file_exists_error, + PyExc_FileNotFoundError => file_not_found_error, + PyExc_InterruptedError => interrupted_error, + PyExc_IsADirectoryError => is_a_directory_error, + PyExc_NotADirectoryError => not_a_directory_error, + PyExc_PermissionError => permission_error, + PyExc_ProcessLookupError => process_lookup_error, + PyExc_TimeoutError => timeout_error, + PyExc_ReferenceError => reference_error, + PyExc_RuntimeError => runtime_error, + PyExc_NotImplementedError => not_implemented_error, + PyExc_RecursionError => recursion_error, + PyExc_SyntaxError => syntax_error, + PyExc_IndentationError => indentation_error, + PyExc_TabError => tab_error, + PyExc_ValueError => value_error, + PyExc_UnicodeError => unicode_error, + PyExc_UnicodeDecodeError => unicode_decode_error, + PyExc_UnicodeEncodeError => unicode_encode_error, + PyExc_UnicodeTranslateError => unicode_translate_error, + PyExc_Warning => warning, + PyExc_DeprecationWarning => deprecation_warning, + PyExc_PendingDeprecationWarning => pending_deprecation_warning, + PyExc_RuntimeWarning => runtime_warning, + PyExc_SyntaxWarning => syntax_warning, + PyExc_UserWarning => user_warning, + PyExc_FutureWarning => future_warning, + PyExc_ImportWarning => import_warning, + PyExc_UnicodeWarning => unicode_warning, + PyExc_BytesWarning => bytes_warning, + PyExc_ResourceWarning => resource_warning, + PyExc_EncodingWarning => encoding_warning, +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_Occurred() -> *mut PyObject { + with_vm(|vm| { + vm.current_exception() + .map(|exc| exc.class().as_object().as_raw()) + .unwrap_or_default() + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_GetRaisedException() -> *mut PyObject { + with_vm(|vm| { + vm.take_raised_exception() + .map(|exc| exc.into_object().into_raw().as_ptr()) + .unwrap_or_default() + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyErr_SetRaisedException(exc: *mut PyObject) { + with_vm(|vm| { + if let Some(exc) = NonNull::new(exc) { + let exception = unsafe { PyObjectRef::from_raw(exc).downcast_unchecked() }; + vm.set_exception(Some(exception)); + } else { + vm.set_exception(None); + } + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyErr_SetObject(exception: *mut PyObject, value: *mut PyObject) { + with_vm::, _>(|vm| { + let exc_type = unsafe { (&*exception).to_owned() }; + let exc_val = unsafe { (&*value).to_owned() }; + + let normalized = vm.normalize_exception(exc_type, exc_val, vm.ctx.none())?; + Err(normalized) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyErr_SetString(exception: *mut PyObject, message: *const c_char) { + with_vm::, _>(|vm| { + let exc_type = unsafe { &*exception }.try_downcast_ref::(vm)?; + + let Ok(message) = unsafe { CStr::from_ptr(message) }.to_str() else { + return Err(vm.new_type_error("Exception message is not valid UTF-8")); + }; + + let exc = vm.invoke_exception( + exc_type.to_owned(), + vec![vm.ctx.new_str(message).into_object()], + )?; + + Err(exc) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyErr_PrintEx(_set_sys_last_vars: c_int) { + with_vm(|vm| { + let exception = vm + .take_raised_exception() + .expect("No exception set in PyErr_PrintEx"); + + vm.print_exception(exception); + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyErr_DisplayException(exc: *mut PyObject) { + with_vm(|vm| { + let exception = unsafe { &*exc } + .downcast_ref::() + .expect("PyErr_DisplayException exc must be an exception instance") + .to_owned(); + + vm.print_exception(exception); + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyErr_WriteUnraisable(obj: *mut PyObject) { + with_vm(|vm| { + let exception = vm + .take_raised_exception() + .expect("No exception set in PyErr_WriteUnraisable"); + + let object = unsafe { vm.unwrap_or_none(obj.as_ref().map(|obj| obj.to_owned())) }; + + vm.run_unraisable(exception, None, object) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyErr_NewException( + name: *const c_char, + base: *mut PyObject, + dict: *mut PyObject, +) -> *mut PyObject { + with_vm(|vm| { + let (module, name) = unsafe { + CStr::from_ptr(name) + .to_str() + .expect("Exception name is not valid UTF-8") + .rsplit_once('.') + .expect("Exception name must be of the form 'module.ExceptionName'") + }; + + let bases = unsafe { base.as_ref() }.map(|bases| { + if let Some(ty) = bases.downcast_ref::() { + vec![ty.to_owned()] + } else if let Some(tuple) = bases.downcast_ref::() { + tuple + .iter() + .map(|item| item.to_owned().downcast()) + .collect::, _>>() + .expect("PyErr_NewException base tuple must contain only types") + } else { + panic!("PyErr_NewException base must be a type or a tuple of types"); + } + }); + + assert!( + dict.is_null(), + "PyErr_NewException with non-null dict is not supported yet" + ); + + vm.ctx.new_exception_type(module, name, bases) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyErr_NewExceptionWithDoc( + name: *const c_char, + _doc: *const c_char, + base: *mut PyObject, + dict: *mut PyObject, +) -> *mut PyObject { + unsafe { PyErr_NewException(name, base, dict) } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyErr_GivenExceptionMatches( + given: *mut PyObject, + exc: *mut PyObject, +) -> c_int { + with_vm(|vm| { + let given = unsafe { &*given }; + let exc = unsafe { &*exc }; + + given.is_subclass(exc, vm) + }) +} + +#[cfg(test)] +mod tests { + use pyo3::exceptions::PyTypeError; + use pyo3::prelude::*; + + #[test] + fn test_raised_exception() { + Python::attach(|py| { + PyTypeError::new_err(py.None()).restore(py); + assert!(PyErr::occurred(py)); + assert!(unsafe { !pyo3::ffi::PyErr_GetRaisedException().is_null() }); + assert!(!PyErr::occurred(py)); + }) + } +} diff --git a/crates/capi/src/pylifecycle.rs b/crates/capi/src/pylifecycle.rs index 6e986c98f4e..6760b2822a3 100644 --- a/crates/capi/src/pylifecycle.rs +++ b/crates/capi/src/pylifecycle.rs @@ -1,8 +1,9 @@ use crate::get_main_interpreter; +use crate::pyerrors::init_exception_statics; use crate::pystate::ensure_thread_has_vm_attached; use core::ffi::c_int; -use rustpython_vm::Interpreter; use rustpython_vm::vm::thread::ThreadedVirtualMachine; +use rustpython_vm::{Context, Interpreter}; use std::sync::Mutex; pub(crate) static MAIN_INTERP: Mutex> = Mutex::new(None); @@ -29,6 +30,8 @@ pub extern "C" fn Py_Initialize() { pub extern "C" fn Py_InitializeEx(_initsigs: c_int) { let mut interp = get_main_interpreter(); if interp.is_none() { + // Safety: Interpreter was not initialized before, so we can safely assume the statics are not used + unsafe { init_exception_statics(&Context::genesis().exceptions) }; *interp = Interpreter::with_init(Default::default(), |_vm| {}).into(); drop(interp); ensure_thread_has_vm_attached(); diff --git a/crates/capi/src/pystate.rs b/crates/capi/src/pystate.rs index 107750be89e..ecbff21713d 100644 --- a/crates/capi/src/pystate.rs +++ b/crates/capi/src/pystate.rs @@ -1,10 +1,17 @@ use crate::pylifecycle::request_vm_from_interpreter; +use crate::util::FfiResult; use core::ffi::c_int; use core::ptr; +use rustpython_vm::VirtualMachine; use rustpython_vm::vm::thread::{ - CurrentVmAttachState, attach_current_thread, release_current_thread, + CurrentVmAttachState, attach_current_thread, release_current_thread, with_current_vm, }; +#[allow(dead_code)] +pub(crate) fn with_vm, O>(f: impl FnOnce(&VirtualMachine) -> R) -> O { + with_current_vm(|vm| f(vm).into_output(vm)) +} + #[allow(non_camel_case_types)] type PyGILState_STATE = c_int; const PYGILSTATE_LOCKED: PyGILState_STATE = 0; diff --git a/crates/capi/src/util.rs b/crates/capi/src/util.rs new file mode 100644 index 00000000000..95e11ff576e --- /dev/null +++ b/crates/capi/src/util.rs @@ -0,0 +1,137 @@ +use crate::PyObject; +use core::convert::Infallible; +use core::ffi::{c_char, c_double, c_int, c_long, c_void}; +use rustpython_vm::{PyObjectRef, PyRef, PyResult, VirtualMachine}; + +pub(crate) trait FfiResult { + const ERR_VALUE: Output; + + fn into_output(self, vm: &VirtualMachine) -> Output; +} + +impl FfiResult for () { + const ERR_VALUE: () = (); + + fn into_output(self, _vm: &VirtualMachine) { + self + } +} + +impl FfiResult for () { + const ERR_VALUE: c_int = -1; + + fn into_output(self, _vm: &VirtualMachine) -> c_int { + 0 + } +} + +impl FfiResult<*mut PyObject> for PyRef +where + Self: Into, +{ + const ERR_VALUE: *mut PyObject = core::ptr::null_mut(); + + fn into_output(self, _vm: &VirtualMachine) -> *mut PyObject { + self.into().into_raw().as_ptr() + } +} + +impl FfiResult<*mut PyObject> for PyObjectRef { + const ERR_VALUE: *mut PyObject = core::ptr::null_mut(); + + fn into_output(self, _vm: &VirtualMachine) -> *mut PyObject { + self.into_raw().as_ptr() + } +} + +impl FfiResult for *mut PyObject { + const ERR_VALUE: *mut PyObject = core::ptr::null_mut(); + + fn into_output(self, _vm: &VirtualMachine) -> *mut PyObject { + self + } +} + +impl FfiResult<*mut PyObject> for *const PyObject { + const ERR_VALUE: *mut PyObject = core::ptr::null_mut(); + + fn into_output(self, _vm: &VirtualMachine) -> *mut PyObject { + self.cast_mut() + } +} + +impl FfiResult for *mut c_void { + const ERR_VALUE: *mut c_void = core::ptr::null_mut(); + + fn into_output(self, _vm: &VirtualMachine) -> *mut c_void { + self + } +} + +impl FfiResult<*mut c_char> for *const u8 { + const ERR_VALUE: *mut c_char = core::ptr::null_mut(); + + fn into_output(self, _vm: &VirtualMachine) -> *mut c_char { + self.cast_mut().cast() + } +} + +impl FfiResult 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") + } +} + +impl FfiResult for c_long { + const ERR_VALUE: c_long = -1; + + fn into_output(self, _vm: &VirtualMachine) -> c_long { + self + } +} + +impl FfiResult for c_double { + const ERR_VALUE: c_double = -1.0; + + fn into_output(self, _vm: &VirtualMachine) -> c_double { + self + } +} + +impl FfiResult for bool { + const ERR_VALUE: c_int = -1; + + fn into_output(self, _vm: &VirtualMachine) -> c_int { + self as c_int + } +} + +impl FfiResult<()> for PyResult { + const ERR_VALUE: () = (); + + fn into_output(self, vm: &VirtualMachine) { + match self { + Err(err) => vm.set_exception(Some(err)), + } + } +} + +impl FfiResult for PyResult +where + T: FfiResult, +{ + const ERR_VALUE: Output = T::ERR_VALUE; + + fn into_output(self, vm: &VirtualMachine) -> Output { + self.map_or_else( + |err| { + vm.set_exception(Some(err)); + T::ERR_VALUE + }, + |obj| obj.into_output(vm), + ) + } +} diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 6208bc5ebfe..d535618982f 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1263,7 +1263,7 @@ impl PyType { } impl Py { - pub(crate) fn is_subtype(&self, other: &Self) -> bool { + pub fn is_subtype(&self, other: &Self) -> bool { is_subtype_with_mro(&self.mro.read(), self, other) } diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 3cfe4642907..e55a68fb805 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -2060,12 +2060,12 @@ impl VirtualMachine { exc } - pub(crate) fn current_exception(&self) -> Option { + pub fn current_exception(&self) -> Option { self.exceptions.borrow().stack.last().cloned().flatten() } /// Set the current exc_info slot value (PUSH_EXC_INFO / POP_EXCEPT). - pub(crate) fn set_exception(&self, exc: Option) { + pub fn set_exception(&self, exc: Option) { // don't be holding the RefCell guard while __del__ is called let mut excs = self.exceptions.borrow_mut(); debug_assert!( @@ -2084,6 +2084,19 @@ impl VirtualMachine { thread::update_thread_exception(self.topmost_exception()); } + pub fn take_raised_exception(&self) -> Option { + let mut excs = self.exceptions.borrow_mut(); + if let Some(top) = excs.stack.last_mut() { + let exc = top.take(); + drop(excs); + #[cfg(feature = "threading")] + thread::update_thread_exception(self.topmost_exception()); + exc + } else { + None + } + } + pub(crate) fn contextualize_exception(&self, exception: &Py) { if let Some(context_exc) = self.topmost_exception() && !context_exc.is(exception) diff --git a/src/lib.rs b/src/lib.rs index a8fd5034d83..d04b63d6572 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,7 +121,7 @@ pub fn run(mut builder: InterpreterBuilder) -> ExitCode { let exitcode = cfg_select! { feature = "capi" => {{ let local_vm = interp.enter(|vm| vm.new_thread()); - *(rustpython_capi::get_main_interpreter()) = Some(interp); + rustpython_capi::set_main_interpreter(interp); let result = local_vm.run(|vm| run_rustpython(vm, run_mode)); rustpython_capi::get_main_interpreter().take().unwrap().finalize(result.err()) }}, From 40d97ea83cf8bfca8c2b50197f775c3354f5e895 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Tue, 5 May 2026 20:57:54 +0200 Subject: [PATCH 02/16] Add missing symbols to make tests compile again --- crates/capi/src/lib.rs | 2 + crates/capi/src/object.rs | 83 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 crates/capi/src/object.rs diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs index 3207149e316..c1bac97cd6b 100644 --- a/crates/capi/src/lib.rs +++ b/crates/capi/src/lib.rs @@ -7,6 +7,8 @@ use rustpython_vm::{Context, Interpreter}; use std::sync::MutexGuard; extern crate alloc; + +pub mod object; pub mod pyerrors; pub mod pylifecycle; pub mod pystate; diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs new file mode 100644 index 00000000000..859b9b95cba --- /dev/null +++ b/crates/capi/src/object.rs @@ -0,0 +1,83 @@ +use crate::PyObject; +use crate::pystate::with_vm; +use core::ffi::{c_int, c_uint, c_ulong}; +use rustpython_vm::builtins::PyType; +use rustpython_vm::{AsObject, Context, Py}; + +const PY_TPFLAGS_LONG_SUBCLASS: c_ulong = 1 << 24; +const PY_TPFLAGS_LIST_SUBCLASS: c_ulong = 1 << 25; +const PY_TPFLAGS_TUPLE_SUBCLASS: c_ulong = 1 << 26; +const PY_TPFLAGS_BYTES_SUBCLASS: c_ulong = 1 << 27; +const PY_TPFLAGS_UNICODE_SUBCLASS: c_ulong = 1 << 28; +const PY_TPFLAGS_DICT_SUBCLASS: c_ulong = 1 << 29; +const PY_TPFLAGS_BASE_EXC_SUBCLASS: c_ulong = 1 << 30; +const PY_TPFLAGS_TYPE_SUBCLASS: c_ulong = 1 << 31; + +pub type PyTypeObject = Py; + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn Py_TYPE(op: *mut PyObject) -> *const PyTypeObject { + unsafe { (*op).class() } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn Py_IS_TYPE(op: *mut PyObject, ty: *mut PyTypeObject) -> c_int { + with_vm(|_vm| { + let obj = unsafe { &*op }; + let ty = unsafe { &*ty }; + obj.class().is(ty) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyType_GetFlags(ptr: *const PyTypeObject) -> c_ulong { + let ctx = Context::genesis(); + let zoo = &ctx.types; + let exp_zoo = &ctx.exceptions; + + let ty = unsafe { &*ptr }; + let mut flags = ty.slots.flags.bits(); + + if ty.is_subtype(zoo.int_type) { + flags |= PY_TPFLAGS_LONG_SUBCLASS; + } + if ty.is_subtype(zoo.list_type) { + flags |= PY_TPFLAGS_LIST_SUBCLASS + } + if ty.is_subtype(zoo.tuple_type) { + flags |= PY_TPFLAGS_TUPLE_SUBCLASS; + } + if ty.is_subtype(zoo.bytes_type) { + flags |= PY_TPFLAGS_BYTES_SUBCLASS; + } + if ty.is_subtype(zoo.str_type) { + flags |= PY_TPFLAGS_UNICODE_SUBCLASS; + } + if ty.is_subtype(zoo.dict_type) { + flags |= PY_TPFLAGS_DICT_SUBCLASS; + } + if ty.is_subtype(exp_zoo.base_exception_type) { + flags |= PY_TPFLAGS_BASE_EXC_SUBCLASS; + } + if ty.is_subtype(zoo.type_type) { + flags |= PY_TPFLAGS_TYPE_SUBCLASS; + } + + flags +} + +#[unsafe(no_mangle)] +pub extern "C" fn Py_GetConstantBorrowed(constant_id: c_uint) -> *mut PyObject { + with_vm(|vm| { + let ctx = &vm.ctx; + match constant_id { + 0 => ctx.none.as_object(), + 1 => ctx.false_value.as_object(), + 2 => ctx.true_value.as_object(), + 3 => ctx.ellipsis.as_object(), + 4 => ctx.not_implemented.as_object(), + _ => panic!("Invalid constant_id passed to Py_GetConstantBorrowed"), + } + .as_raw() + }) +} From 6b79cb5e06e56a5b4a5b6901f67fdbda60ee2e19 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Tue, 5 May 2026 21:02:48 +0200 Subject: [PATCH 03/16] Add `pyerrors` to dictionary --- .cspell.dict/cpython.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index 3bbe7426c74..1fb1c56e029 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -157,6 +157,7 @@ pybuilddir pycore pyinner pydecimal +pyerrors Pyfunc pylifecycle pymain From 71438c698f5c25303982c4a81c50bf3e3816fa51 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Tue, 5 May 2026 21:10:40 +0200 Subject: [PATCH 04/16] Return exception in `Py_GetConstantBorrowed` --- crates/capi/src/object.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs index 859b9b95cba..043eb16e468 100644 --- a/crates/capi/src/object.rs +++ b/crates/capi/src/object.rs @@ -70,14 +70,19 @@ pub unsafe extern "C" fn PyType_GetFlags(ptr: *const PyTypeObject) -> c_ulong { pub extern "C" fn Py_GetConstantBorrowed(constant_id: c_uint) -> *mut PyObject { with_vm(|vm| { let ctx = &vm.ctx; - match constant_id { + let constant = match constant_id { 0 => ctx.none.as_object(), 1 => ctx.false_value.as_object(), 2 => ctx.true_value.as_object(), 3 => ctx.ellipsis.as_object(), 4 => ctx.not_implemented.as_object(), - _ => panic!("Invalid constant_id passed to Py_GetConstantBorrowed"), + _ => { + return Err( + vm.new_system_error("Invalid constant ID passed to Py_GetConstantBorrowed") + ); + } } - .as_raw() + .as_raw(); + Ok(constant) }) } From d708bfe52f35524610de622eb6ffd401311df399 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Tue, 5 May 2026 21:17:05 +0200 Subject: [PATCH 05/16] Remove `allow(dead_code)` --- crates/capi/src/pystate.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/capi/src/pystate.rs b/crates/capi/src/pystate.rs index ecbff21713d..00ad5c41e4a 100644 --- a/crates/capi/src/pystate.rs +++ b/crates/capi/src/pystate.rs @@ -7,7 +7,6 @@ use rustpython_vm::vm::thread::{ CurrentVmAttachState, attach_current_thread, release_current_thread, with_current_vm, }; -#[allow(dead_code)] pub(crate) fn with_vm, O>(f: impl FnOnce(&VirtualMachine) -> R) -> O { with_current_vm(|vm| f(vm).into_output(vm)) } From bbf365658b18722d8f5fca7235c51c6188b74508 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Tue, 5 May 2026 21:43:52 +0200 Subject: [PATCH 06/16] Fix windows --- crates/capi/src/object.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs index 043eb16e468..4b4fa70840d 100644 --- a/crates/capi/src/object.rs +++ b/crates/capi/src/object.rs @@ -4,14 +4,14 @@ use core::ffi::{c_int, c_uint, c_ulong}; use rustpython_vm::builtins::PyType; use rustpython_vm::{AsObject, Context, Py}; -const PY_TPFLAGS_LONG_SUBCLASS: c_ulong = 1 << 24; -const PY_TPFLAGS_LIST_SUBCLASS: c_ulong = 1 << 25; -const PY_TPFLAGS_TUPLE_SUBCLASS: c_ulong = 1 << 26; -const PY_TPFLAGS_BYTES_SUBCLASS: c_ulong = 1 << 27; -const PY_TPFLAGS_UNICODE_SUBCLASS: c_ulong = 1 << 28; -const PY_TPFLAGS_DICT_SUBCLASS: c_ulong = 1 << 29; -const PY_TPFLAGS_BASE_EXC_SUBCLASS: c_ulong = 1 << 30; -const PY_TPFLAGS_TYPE_SUBCLASS: c_ulong = 1 << 31; +const PY_TPFLAGS_LONG_SUBCLASS: u64 = 1 << 24; +const PY_TPFLAGS_LIST_SUBCLASS: u64 = 1 << 25; +const PY_TPFLAGS_TUPLE_SUBCLASS: u64 = 1 << 26; +const PY_TPFLAGS_BYTES_SUBCLASS: u64 = 1 << 27; +const PY_TPFLAGS_UNICODE_SUBCLASS: u64 = 1 << 28; +const PY_TPFLAGS_DICT_SUBCLASS: u64 = 1 << 29; +const PY_TPFLAGS_BASE_EXC_SUBCLASS: u64 = 1 << 30; +const PY_TPFLAGS_TYPE_SUBCLASS: u64 = 1 << 31; pub type PyTypeObject = Py; @@ -63,7 +63,7 @@ pub unsafe extern "C" fn PyType_GetFlags(ptr: *const PyTypeObject) -> c_ulong { flags |= PY_TPFLAGS_TYPE_SUBCLASS; } - flags + flags as c_ulong } #[unsafe(no_mangle)] From bccd38e9816d4e4a8181b1bfc2d29fc90861044a Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Tue, 5 May 2026 22:21:18 +0200 Subject: [PATCH 07/16] Load stdlib when calling `Py_InitializeEx` --- Cargo.lock | 1 + crates/capi/Cargo.toml | 5 +---- crates/capi/src/pylifecycle.rs | 22 ++++++++++++++++++++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b1e7a3204d4..8fd9cc02fde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3157,6 +3157,7 @@ name = "rustpython-capi" version = "0.5.0" dependencies = [ "pyo3", + "rustpython-pylib", "rustpython-stdlib", "rustpython-vm", ] diff --git a/crates/capi/Cargo.toml b/crates/capi/Cargo.toml index a090aaedaf3..c6c08ccbc05 100644 --- a/crates/capi/Cargo.toml +++ b/crates/capi/Cargo.toml @@ -17,10 +17,7 @@ rustpython-stdlib = {workspace = true, features = ["threading"] } [dev-dependencies] pyo3 = { version = "0.28", features = ["auto-initialize", "abi3"] } +rustpython-pylib = { workspace = true, features = ["freeze-stdlib"] } [lints] workspace = true - -[package.metadata.cargo-shear] -# Not a direct dependency (yet), but we need to enable threading support in the stdlib. -ignored = ["rustpython-stdlib"] \ No newline at end of file diff --git a/crates/capi/src/pylifecycle.rs b/crates/capi/src/pylifecycle.rs index 6760b2822a3..10c0df5fce9 100644 --- a/crates/capi/src/pylifecycle.rs +++ b/crates/capi/src/pylifecycle.rs @@ -3,7 +3,7 @@ use crate::pyerrors::init_exception_statics; use crate::pystate::ensure_thread_has_vm_attached; use core::ffi::c_int; use rustpython_vm::vm::thread::ThreadedVirtualMachine; -use rustpython_vm::{Context, Interpreter}; +use rustpython_vm::{Context, Interpreter, Settings}; use std::sync::Mutex; pub(crate) static MAIN_INTERP: Mutex> = Mutex::new(None); @@ -32,7 +32,25 @@ pub extern "C" fn Py_InitializeEx(_initsigs: c_int) { if interp.is_none() { // Safety: Interpreter was not initialized before, so we can safely assume the statics are not used unsafe { init_exception_statics(&Context::genesis().exceptions) }; - *interp = Interpreter::with_init(Default::default(), |_vm| {}).into(); + + let settings = Settings::default(); + let mut builder = Interpreter::builder(settings); + + let defs = rustpython_stdlib::stdlib_module_defs(&builder.ctx); + builder = builder.add_native_modules(&defs); + + #[cfg(test)] + { + use rustpython_vm::common::rc::PyRc; + builder = builder + .add_frozen_modules(rustpython_pylib::FROZEN_STDLIB) + .init_hook(|vm| { + let state = PyRc::get_mut(&mut vm.state).unwrap(); + state.config.paths.stdlib_dir = Some(rustpython_pylib::LIB_PATH.to_owned()); + }); + } + + *interp = Some(builder.build()); drop(interp); ensure_thread_has_vm_attached(); } From 9e20cf7be626f4bbc6b7c9478dc0e43aa8c65c2f Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Tue, 5 May 2026 22:49:11 +0200 Subject: [PATCH 08/16] Debug tests --- .github/workflows/ci.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2db52bb04e3..f0ed29f9d35 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -112,10 +112,14 @@ jobs: uses: ./.github/actions/install-macos-deps - name: run rust tests - run: cargo test --workspace ${{ env.WORKSPACE_EXCLUDES }} --features threading ${{ env.CARGO_ARGS }} + run: cargo test --workspace --exclude rustpython-capi ${{ env.WORKSPACE_EXCLUDES }} --features threading ${{ env.CARGO_ARGS }} env: INSTA_WORKSPACE_ROOT: ${{ github.workspace }} + - name: run c-api tests + working-directory: crates/capi + run: cargo test + - run: cargo doc --locked if: runner.os == 'Linux' From 268856b7fab6ae804aa03c7d35802029a6cdc981 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 6 May 2026 09:08:56 +0200 Subject: [PATCH 09/16] Revert "Load stdlib when calling `Py_InitializeEx`" This reverts commit bccd38e9816d4e4a8181b1bfc2d29fc90861044a. --- Cargo.lock | 1 - crates/capi/Cargo.toml | 5 ++++- crates/capi/src/pylifecycle.rs | 22 ++-------------------- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8fd9cc02fde..b1e7a3204d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3157,7 +3157,6 @@ name = "rustpython-capi" version = "0.5.0" dependencies = [ "pyo3", - "rustpython-pylib", "rustpython-stdlib", "rustpython-vm", ] diff --git a/crates/capi/Cargo.toml b/crates/capi/Cargo.toml index c6c08ccbc05..a090aaedaf3 100644 --- a/crates/capi/Cargo.toml +++ b/crates/capi/Cargo.toml @@ -17,7 +17,10 @@ rustpython-stdlib = {workspace = true, features = ["threading"] } [dev-dependencies] pyo3 = { version = "0.28", features = ["auto-initialize", "abi3"] } -rustpython-pylib = { workspace = true, features = ["freeze-stdlib"] } [lints] workspace = true + +[package.metadata.cargo-shear] +# Not a direct dependency (yet), but we need to enable threading support in the stdlib. +ignored = ["rustpython-stdlib"] \ No newline at end of file diff --git a/crates/capi/src/pylifecycle.rs b/crates/capi/src/pylifecycle.rs index 10c0df5fce9..6760b2822a3 100644 --- a/crates/capi/src/pylifecycle.rs +++ b/crates/capi/src/pylifecycle.rs @@ -3,7 +3,7 @@ use crate::pyerrors::init_exception_statics; use crate::pystate::ensure_thread_has_vm_attached; use core::ffi::c_int; use rustpython_vm::vm::thread::ThreadedVirtualMachine; -use rustpython_vm::{Context, Interpreter, Settings}; +use rustpython_vm::{Context, Interpreter}; use std::sync::Mutex; pub(crate) static MAIN_INTERP: Mutex> = Mutex::new(None); @@ -32,25 +32,7 @@ pub extern "C" fn Py_InitializeEx(_initsigs: c_int) { if interp.is_none() { // Safety: Interpreter was not initialized before, so we can safely assume the statics are not used unsafe { init_exception_statics(&Context::genesis().exceptions) }; - - let settings = Settings::default(); - let mut builder = Interpreter::builder(settings); - - let defs = rustpython_stdlib::stdlib_module_defs(&builder.ctx); - builder = builder.add_native_modules(&defs); - - #[cfg(test)] - { - use rustpython_vm::common::rc::PyRc; - builder = builder - .add_frozen_modules(rustpython_pylib::FROZEN_STDLIB) - .init_hook(|vm| { - let state = PyRc::get_mut(&mut vm.state).unwrap(); - state.config.paths.stdlib_dir = Some(rustpython_pylib::LIB_PATH.to_owned()); - }); - } - - *interp = Some(builder.build()); + *interp = Interpreter::with_init(Default::default(), |_vm| {}).into(); drop(interp); ensure_thread_has_vm_attached(); } From 8432c1783af996b75974303f9f916c76945d51a5 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 6 May 2026 09:30:47 +0200 Subject: [PATCH 10/16] Disable tests on windows for now --- .github/workflows/ci.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f0ed29f9d35..24a1a4a86a7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -119,6 +119,7 @@ jobs: - name: run c-api tests working-directory: crates/capi run: cargo test + if: runner.os != 'Windows' # Requires pyo3 0.29+ on Windows - run: cargo doc --locked if: runner.os == 'Linux' From 130321e36fb27c867929bd23685f7e3c53e619db Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 6 May 2026 11:44:31 +0200 Subject: [PATCH 11/16] Truncate `PyType_GetFlags` to be always 32 bits --- crates/capi/src/object.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs index 4b4fa70840d..878b31bdba6 100644 --- a/crates/capi/src/object.rs +++ b/crates/capi/src/object.rs @@ -4,14 +4,14 @@ use core::ffi::{c_int, c_uint, c_ulong}; use rustpython_vm::builtins::PyType; use rustpython_vm::{AsObject, Context, Py}; -const PY_TPFLAGS_LONG_SUBCLASS: u64 = 1 << 24; -const PY_TPFLAGS_LIST_SUBCLASS: u64 = 1 << 25; -const PY_TPFLAGS_TUPLE_SUBCLASS: u64 = 1 << 26; -const PY_TPFLAGS_BYTES_SUBCLASS: u64 = 1 << 27; -const PY_TPFLAGS_UNICODE_SUBCLASS: u64 = 1 << 28; -const PY_TPFLAGS_DICT_SUBCLASS: u64 = 1 << 29; -const PY_TPFLAGS_BASE_EXC_SUBCLASS: u64 = 1 << 30; -const PY_TPFLAGS_TYPE_SUBCLASS: u64 = 1 << 31; +const PY_TPFLAGS_LONG_SUBCLASS: u32 = 1 << 24; +const PY_TPFLAGS_LIST_SUBCLASS: u32 = 1 << 25; +const PY_TPFLAGS_TUPLE_SUBCLASS: u32 = 1 << 26; +const PY_TPFLAGS_BYTES_SUBCLASS: u32 = 1 << 27; +const PY_TPFLAGS_UNICODE_SUBCLASS: u32 = 1 << 28; +const PY_TPFLAGS_DICT_SUBCLASS: u32 = 1 << 29; +const PY_TPFLAGS_BASE_EXC_SUBCLASS: u32 = 1 << 30; +const PY_TPFLAGS_TYPE_SUBCLASS: u32 = 1 << 31; pub type PyTypeObject = Py; @@ -36,7 +36,7 @@ pub unsafe extern "C" fn PyType_GetFlags(ptr: *const PyTypeObject) -> c_ulong { let exp_zoo = &ctx.exceptions; let ty = unsafe { &*ptr }; - let mut flags = ty.slots.flags.bits(); + let mut flags = ty.slots.flags.bits() as u32; if ty.is_subtype(zoo.int_type) { flags |= PY_TPFLAGS_LONG_SUBCLASS; From 30ac0610b1ffe627c9d8cbf2480d2ab4246ee539 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 6 May 2026 12:02:58 +0200 Subject: [PATCH 12/16] Add test for exception type checking --- crates/capi/src/pyerrors.rs | 8 ++++++++ crates/capi/src/pystate.rs | 3 +++ 2 files changed, 11 insertions(+) diff --git a/crates/capi/src/pyerrors.rs b/crates/capi/src/pyerrors.rs index ec5578e48fa..4554757c66e 100644 --- a/crates/capi/src/pyerrors.rs +++ b/crates/capi/src/pyerrors.rs @@ -266,4 +266,12 @@ mod tests { assert!(!PyErr::occurred(py)); }) } + + #[test] + fn test_error_is_instance() { + Python::attach(|py| { + let err = PyTypeError::new_err(py.None()); + assert!(err.is_instance_of::(py)); + }) + } } diff --git a/crates/capi/src/pystate.rs b/crates/capi/src/pystate.rs index 00ad5c41e4a..97b29bcebe1 100644 --- a/crates/capi/src/pystate.rs +++ b/crates/capi/src/pystate.rs @@ -47,6 +47,9 @@ pub extern "C" fn PyEval_SaveThread() -> *mut PyThreadState { ptr::null_mut() } +#[unsafe(no_mangle)] +pub extern "C" fn PyEval_RestoreThread(_state: *mut PyThreadState) {} + #[cfg(test)] mod tests { use crate::get_main_interpreter; From 4e4f7df6bd267bad857cc81e40d82173ec80573b Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 6 May 2026 16:35:21 +0200 Subject: [PATCH 13/16] Remove subclass type flags --- crates/capi/src/object.rs | 44 ++------------------------------------- 1 file changed, 2 insertions(+), 42 deletions(-) diff --git a/crates/capi/src/object.rs b/crates/capi/src/object.rs index 878b31bdba6..fb8fc3de54a 100644 --- a/crates/capi/src/object.rs +++ b/crates/capi/src/object.rs @@ -2,16 +2,7 @@ use crate::PyObject; use crate::pystate::with_vm; use core::ffi::{c_int, c_uint, c_ulong}; use rustpython_vm::builtins::PyType; -use rustpython_vm::{AsObject, Context, Py}; - -const PY_TPFLAGS_LONG_SUBCLASS: u32 = 1 << 24; -const PY_TPFLAGS_LIST_SUBCLASS: u32 = 1 << 25; -const PY_TPFLAGS_TUPLE_SUBCLASS: u32 = 1 << 26; -const PY_TPFLAGS_BYTES_SUBCLASS: u32 = 1 << 27; -const PY_TPFLAGS_UNICODE_SUBCLASS: u32 = 1 << 28; -const PY_TPFLAGS_DICT_SUBCLASS: u32 = 1 << 29; -const PY_TPFLAGS_BASE_EXC_SUBCLASS: u32 = 1 << 30; -const PY_TPFLAGS_TYPE_SUBCLASS: u32 = 1 << 31; +use rustpython_vm::{AsObject, Py}; pub type PyTypeObject = Py; @@ -31,39 +22,8 @@ pub unsafe extern "C" fn Py_IS_TYPE(op: *mut PyObject, ty: *mut PyTypeObject) -> #[unsafe(no_mangle)] pub unsafe extern "C" fn PyType_GetFlags(ptr: *const PyTypeObject) -> c_ulong { - let ctx = Context::genesis(); - let zoo = &ctx.types; - let exp_zoo = &ctx.exceptions; - let ty = unsafe { &*ptr }; - let mut flags = ty.slots.flags.bits() as u32; - - if ty.is_subtype(zoo.int_type) { - flags |= PY_TPFLAGS_LONG_SUBCLASS; - } - if ty.is_subtype(zoo.list_type) { - flags |= PY_TPFLAGS_LIST_SUBCLASS - } - if ty.is_subtype(zoo.tuple_type) { - flags |= PY_TPFLAGS_TUPLE_SUBCLASS; - } - if ty.is_subtype(zoo.bytes_type) { - flags |= PY_TPFLAGS_BYTES_SUBCLASS; - } - if ty.is_subtype(zoo.str_type) { - flags |= PY_TPFLAGS_UNICODE_SUBCLASS; - } - if ty.is_subtype(zoo.dict_type) { - flags |= PY_TPFLAGS_DICT_SUBCLASS; - } - if ty.is_subtype(exp_zoo.base_exception_type) { - flags |= PY_TPFLAGS_BASE_EXC_SUBCLASS; - } - if ty.is_subtype(zoo.type_type) { - flags |= PY_TPFLAGS_TYPE_SUBCLASS; - } - - flags as c_ulong + ty.slots.flags.bits() as u32 as c_ulong } #[unsafe(no_mangle)] From b2c2f6913fc1c2dbacc607c1f1b2e5f37a4768eb Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Sat, 9 May 2026 19:59:33 +0200 Subject: [PATCH 14/16] Use latest pyo3 to make test work on windows --- .github/workflows/ci.yaml | 1 - Cargo.lock | 16 +++++----------- Cargo.toml | 3 ++- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 61e81a8e61d..8ff92f4d12b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -121,7 +121,6 @@ jobs: - name: run c-api tests working-directory: crates/capi run: cargo test - if: runner.os != 'Windows' # Requires pyo3 0.29+ on Windows - run: cargo doc --locked if: runner.os == 'Linux' diff --git a/Cargo.lock b/Cargo.lock index 508dc691457..fb4c24a60d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2682,8 +2682,7 @@ dependencies = [ [[package]] name = "pyo3" version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" +source = "git+https://github.com/PyO3/pyo3#901b032bfb8ec8eb05b39be3b98ef880427cf15a" dependencies = [ "libc", "once_cell", @@ -2696,8 +2695,7 @@ dependencies = [ [[package]] name = "pyo3-build-config" version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" +source = "git+https://github.com/PyO3/pyo3#901b032bfb8ec8eb05b39be3b98ef880427cf15a" dependencies = [ "target-lexicon", ] @@ -2705,8 +2703,7 @@ dependencies = [ [[package]] name = "pyo3-ffi" version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" +source = "git+https://github.com/PyO3/pyo3#901b032bfb8ec8eb05b39be3b98ef880427cf15a" dependencies = [ "libc", "pyo3-build-config", @@ -2715,8 +2712,7 @@ dependencies = [ [[package]] name = "pyo3-macros" version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" +source = "git+https://github.com/PyO3/pyo3#901b032bfb8ec8eb05b39be3b98ef880427cf15a" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -2727,12 +2723,10 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" +source = "git+https://github.com/PyO3/pyo3#901b032bfb8ec8eb05b39be3b98ef880427cf15a" dependencies = [ "heck", "proc-macro2", - "pyo3-build-config", "quote", "syn", ] diff --git a/Cargo.toml b/Cargo.toml index d926b5f5e2e..40c7efacbe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,6 +103,7 @@ lto = "thin" [patch.crates-io] parking_lot_core = { git = "https://github.com/youknowone/parking_lot", branch = "rustpython" } +pyo3-ffi = { git = "https://github.com/PyO3/pyo3" } # REDOX START, Uncomment when you want to compile/check with redoxer # REDOX END @@ -256,7 +257,7 @@ pkcs8 = "0.10" proc-macro2 = "1.0.105" psm = "0.1" pymath = { version = "0.2.0", features = ["mul_add", "malachite-bigint", "complex"] } -pyo3 = "0.28" +pyo3 = { git = "https://github.com/PyO3/pyo3" } quote = "1.0.45" radium = "1.1.1" rand = "0.9" From d18d259aa62c77714f8e2504e6d9931e36fe5744 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Sun, 10 May 2026 12:52:11 +0200 Subject: [PATCH 15/16] Revert "Use latest pyo3 to make test work on windows" This reverts commit b2c2f6913fc1c2dbacc607c1f1b2e5f37a4768eb. --- .github/workflows/ci.yaml | 1 + Cargo.lock | 16 +++++++++++----- Cargo.toml | 3 +-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8ff92f4d12b..61e81a8e61d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -121,6 +121,7 @@ jobs: - name: run c-api tests working-directory: crates/capi run: cargo test + if: runner.os != 'Windows' # Requires pyo3 0.29+ on Windows - run: cargo doc --locked if: runner.os == 'Linux' diff --git a/Cargo.lock b/Cargo.lock index fb4c24a60d1..508dc691457 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2682,7 +2682,8 @@ dependencies = [ [[package]] name = "pyo3" version = "0.28.3" -source = "git+https://github.com/PyO3/pyo3#901b032bfb8ec8eb05b39be3b98ef880427cf15a" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" dependencies = [ "libc", "once_cell", @@ -2695,7 +2696,8 @@ dependencies = [ [[package]] name = "pyo3-build-config" version = "0.28.3" -source = "git+https://github.com/PyO3/pyo3#901b032bfb8ec8eb05b39be3b98ef880427cf15a" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" dependencies = [ "target-lexicon", ] @@ -2703,7 +2705,8 @@ dependencies = [ [[package]] name = "pyo3-ffi" version = "0.28.3" -source = "git+https://github.com/PyO3/pyo3#901b032bfb8ec8eb05b39be3b98ef880427cf15a" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" dependencies = [ "libc", "pyo3-build-config", @@ -2712,7 +2715,8 @@ dependencies = [ [[package]] name = "pyo3-macros" version = "0.28.3" -source = "git+https://github.com/PyO3/pyo3#901b032bfb8ec8eb05b39be3b98ef880427cf15a" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -2723,10 +2727,12 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" version = "0.28.3" -source = "git+https://github.com/PyO3/pyo3#901b032bfb8ec8eb05b39be3b98ef880427cf15a" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" dependencies = [ "heck", "proc-macro2", + "pyo3-build-config", "quote", "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 40c7efacbe9..d926b5f5e2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,7 +103,6 @@ lto = "thin" [patch.crates-io] parking_lot_core = { git = "https://github.com/youknowone/parking_lot", branch = "rustpython" } -pyo3-ffi = { git = "https://github.com/PyO3/pyo3" } # REDOX START, Uncomment when you want to compile/check with redoxer # REDOX END @@ -257,7 +256,7 @@ pkcs8 = "0.10" proc-macro2 = "1.0.105" psm = "0.1" pymath = { version = "0.2.0", features = ["mul_add", "malachite-bigint", "complex"] } -pyo3 = { git = "https://github.com/PyO3/pyo3" } +pyo3 = "0.28" quote = "1.0.45" radium = "1.1.1" rand = "0.9" From 4e7bcbfced3e995be15494aa75de11d048e27948 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 13 May 2026 13:07:19 +0200 Subject: [PATCH 16/16] `set_main_interpreter` -> `init_main_interpreter` --- crates/capi/src/lib.rs | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs index c1bac97cd6b..4dc17536a8a 100644 --- a/crates/capi/src/lib.rs +++ b/crates/capi/src/lib.rs @@ -24,7 +24,7 @@ pub fn get_main_interpreter() -> MutexGuard<'static, Option> { /// Set the main interpreter of this process. This method will panic when there is already an /// interpreter set. -pub fn set_main_interpreter(interpreter: Interpreter) { +pub fn init_main_interpreter(interpreter: Interpreter) { let mut interp = get_main_interpreter(); assert!(interp.is_none(), "Main interpreter is already set"); // Safety: Interpreter was not initialized before, so we can safely assume the statics are not used diff --git a/src/lib.rs b/src/lib.rs index d04b63d6572..a8384244cfa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,7 +121,7 @@ pub fn run(mut builder: InterpreterBuilder) -> ExitCode { let exitcode = cfg_select! { feature = "capi" => {{ let local_vm = interp.enter(|vm| vm.new_thread()); - rustpython_capi::set_main_interpreter(interp); + rustpython_capi::init_main_interpreter(interp); let result = local_vm.run(|vm| run_rustpython(vm, run_mode)); rustpython_capi::get_main_interpreter().take().unwrap().finalize(result.err()) }},