From 96a9396c1609bc980837c2ebc0bf0609ec62882a Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Fri, 8 May 2026 17:47:17 +0200 Subject: [PATCH 1/2] Add tuple support to c-api --- crates/capi/src/lib.rs | 1 + crates/capi/src/tupleobject.rs | 114 +++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 crates/capi/src/tupleobject.rs diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs index 70c2226e67f..35158e394de 100644 --- a/crates/capi/src/lib.rs +++ b/crates/capi/src/lib.rs @@ -17,6 +17,7 @@ pub mod pyerrors; pub mod pylifecycle; pub mod pystate; pub mod refcount; +pub mod tupleobject; pub mod unicodeobject; mod util; diff --git a/crates/capi/src/tupleobject.rs b/crates/capi/src/tupleobject.rs new file mode 100644 index 00000000000..de253acc8e0 --- /dev/null +++ b/crates/capi/src/tupleobject.rs @@ -0,0 +1,114 @@ +use crate::PyObject; +use crate::object::define_py_check; +use crate::pystate::with_vm; +use core::slice; +use rustpython_vm::PyResult; +use rustpython_vm::builtins::PyTuple; +use rustpython_vm::sliceable::SliceableSequenceOp; + +define_py_check!(fn PyTuple_Check, types.int_type); +define_py_check!(exact fn PyTuple_CheckExact, types.int_type); + +#[unsafe(no_mangle)] +pub extern "C" fn PyTuple_New(len: isize) -> *mut PyObject { + with_vm(|vm| { + if len == 0 { + return Ok(vm.ctx.empty_tuple.to_owned()); + } + + Err(vm.new_not_implemented_error( + "PyTuple_New for non zero sized tuples is not supported, use PyTuple_FromArray instead", + )) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyTuple_FromArray( + array: *const *mut PyObject, + size: isize, +) -> *mut PyObject { + with_vm(|vm| { + let slice = unsafe { slice::from_raw_parts(array, size as usize) }; + let elements = slice + .iter() + .map(|ptr| unsafe { &**ptr }.to_owned()) + .collect::>(); + vm.new_tuple(elements) + }) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyTuple_SetItem( + _tuple: *mut PyObject, + _pos: isize, + _value: *mut PyObject, +) -> *mut PyObject { + with_vm::(|vm| Err(vm.new_not_implemented_error("Tuple objects are immutable"))) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyTuple_Size(tuple: *mut PyObject) -> isize { + with_vm(|vm| { + let tuple = unsafe { &*tuple }.try_downcast_ref::(vm)?; + Ok(tuple.__len__()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyTuple_GetItem(tuple: *mut PyObject, pos: isize) -> *mut PyObject { + with_vm(|vm| { + let tuple = unsafe { &*tuple }.try_downcast_ref::(vm)?; + let result: &PyObject = pos + .try_into() + .ok() + .and_then(|index: usize| tuple.get(index)) + .ok_or_else(|| vm.new_index_error("tuple index out of range"))?; + + Ok(result.as_raw()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyTuple_GetSlice( + tuple: *mut PyObject, + low: isize, + high: isize, +) -> *mut PyObject { + with_vm(|vm| { + let slice = unsafe { &*tuple } + .try_downcast_ref::(vm)? + .do_slice(low as usize..high as usize); + Ok(vm.ctx.new_tuple(slice)) + }) +} + +#[cfg(false)] +mod tests { + use pyo3::prelude::*; + use pyo3::types::PyTuple; + + #[test] + fn test_empty_tuple() { + Python::attach(|py| { + let tuple = PyTuple::empty(py); + assert_eq!(tuple.len(), 0); + }) + } + + #[test] + fn test_tuple_into_python() { + Python::attach(|py| { + let tuple = (1, 2, 3).into_pyobject(py).unwrap(); + assert_eq!(tuple.len(), 3); + }) + } + + #[test] + fn test_tuple_get_slice() { + Python::attach(|py| { + let tuple = (1, 2, 3).into_pyobject(py).unwrap(); + let slice = tuple.get_slice(1, 2); + assert_eq!(slice.extract::<(u32,)>().unwrap(), (2,)); + }) + } +} From 8aebd8a2781701ff964fd00054e58de77d20cb13 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Mon, 18 May 2026 11:21:04 +0200 Subject: [PATCH 2/2] Review --- crates/capi/src/tupleobject.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/crates/capi/src/tupleobject.rs b/crates/capi/src/tupleobject.rs index de253acc8e0..985141f6d4c 100644 --- a/crates/capi/src/tupleobject.rs +++ b/crates/capi/src/tupleobject.rs @@ -1,13 +1,14 @@ use crate::PyObject; use crate::object::define_py_check; use crate::pystate::with_vm; +use core::ffi::c_int; use core::slice; use rustpython_vm::PyResult; use rustpython_vm::builtins::PyTuple; use rustpython_vm::sliceable::SliceableSequenceOp; -define_py_check!(fn PyTuple_Check, types.int_type); -define_py_check!(exact fn PyTuple_CheckExact, types.int_type); +define_py_check!(fn PyTuple_Check, types.tuple_type); +define_py_check!(exact fn PyTuple_CheckExact, types.tuple_type); #[unsafe(no_mangle)] pub extern "C" fn PyTuple_New(len: isize) -> *mut PyObject { @@ -28,12 +29,15 @@ pub unsafe extern "C" fn PyTuple_FromArray( size: isize, ) -> *mut PyObject { with_vm(|vm| { - let slice = unsafe { slice::from_raw_parts(array, size as usize) }; + let size = size + .try_into() + .map_err(|_| vm.new_system_error("negative size passed to Tuple_FromArray"))?; + let slice = unsafe { slice::from_raw_parts(array, size) }; let elements = slice .iter() .map(|ptr| unsafe { &**ptr }.to_owned()) .collect::>(); - vm.new_tuple(elements) + Ok(vm.new_tuple(elements)) }) } @@ -42,8 +46,10 @@ pub extern "C" fn PyTuple_SetItem( _tuple: *mut PyObject, _pos: isize, _value: *mut PyObject, -) -> *mut PyObject { - with_vm::(|vm| Err(vm.new_not_implemented_error("Tuple objects are immutable"))) +) -> c_int { + with_vm::, _>( + |vm| Err(vm.new_not_implemented_error("Tuple objects are immutable")), + ) } #[unsafe(no_mangle)] @@ -75,9 +81,11 @@ pub unsafe extern "C" fn PyTuple_GetSlice( high: isize, ) -> *mut PyObject { with_vm(|vm| { - let slice = unsafe { &*tuple } - .try_downcast_ref::(vm)? - .do_slice(low as usize..high as usize); + let tuple = unsafe { &*tuple }.try_downcast_ref::(vm)?; + let len = tuple.__len__() as isize; + let low = low.clamp(0, len); + let high = high.clamp(low, len); + let slice = tuple.do_slice(low as usize..high as usize); Ok(vm.ctx.new_tuple(slice)) }) }