From dfe08a8aa5849a6cc1e45066daf6c55258a9e59f Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Thu, 21 May 2026 08:38:51 +0200 Subject: [PATCH 1/3] Add list support to c-api --- crates/capi/src/lib.rs | 1 + crates/capi/src/listobject.rs | 173 ++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 crates/capi/src/listobject.rs diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs index baa4ddc6b7..90e471e1b5 100644 --- a/crates/capi/src/lib.rs +++ b/crates/capi/src/lib.rs @@ -14,6 +14,7 @@ pub mod bytesobject; pub mod ceval; pub mod dictobject; pub mod import; +pub mod listobject; pub mod longobject; pub mod object; pub mod pyerrors; diff --git a/crates/capi/src/listobject.rs b/crates/capi/src/listobject.rs new file mode 100644 index 0000000000..a5a5858ff6 --- /dev/null +++ b/crates/capi/src/listobject.rs @@ -0,0 +1,173 @@ +use crate::PyObject; +use crate::object::define_py_check; +use crate::pystate::with_vm; +use core::ffi::c_int; +use core::ptr::NonNull; +use rustpython_vm::PyObjectRef; +use rustpython_vm::builtins::PyList; + +define_py_check!(fn PyList_Check, types.list_type); +define_py_check!(exact fn PyList_CheckExact, types.list_type); + +#[unsafe(no_mangle)] +pub extern "C" fn PyList_New(size: isize) -> *mut PyObject { + with_vm(|vm| vm.ctx.new_list(Vec::with_capacity(size as usize))) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyList_Size(obj: *mut PyObject) -> isize { + with_vm(|vm| { + let list = unsafe { &*obj }.try_downcast_ref::(vm)?; + Ok(list.__len__()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyList_GetItemRef(obj: *mut PyObject, index: isize) -> *mut PyObject { + with_vm(|vm| { + let list = unsafe { &*obj }.try_downcast_ref::(vm)?; + + list.borrow_vec() + .get(index as usize) + .ok_or_else(|| vm.new_index_error(format!("list index out of range: {index}"))) + .map(ToOwned::to_owned) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyList_SetItem( + list: *mut PyObject, + index: isize, + item: *mut PyObject, +) -> c_int { + with_vm(|vm| { + let list = unsafe { &*list }.try_downcast_ref::(vm)?; + let item = unsafe { PyObjectRef::from_raw(NonNull::new_unchecked(item)) }; + + let mut list_mut = list.borrow_vec_mut(); + match index - list_mut.len() as isize { + ..0 => { + list_mut[index as usize] = item; + Ok(()) + } + // This is somewhat a hack, we assume that we are populating a list right after PyList_New + 0 if list_mut.capacity() > index as usize => { + list_mut.push(item); + Ok(()) + } + 0.. => Err(vm.new_index_error(format!("list assignment index out of range: {index}"))), + } + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyList_Append(list: *mut PyObject, item: *mut PyObject) -> c_int { + with_vm(|vm| { + let list = unsafe { &*list }.try_downcast_ref::(vm)?; + let item = unsafe { &*item }.to_owned(); + list.borrow_vec_mut().push(item); + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyList_Insert( + list: *mut PyObject, + index: isize, + item: *mut PyObject, +) -> c_int { + with_vm(|vm| { + let list = unsafe { &*list }.try_downcast_ref::(vm)?; + let item = unsafe { &*item }.to_owned(); + let mut vec = list.borrow_vec_mut(); + if index as usize > vec.len() { + Err(vm.new_index_error(format!("list index out of range: {index}"))) + } else { + vec.insert(index as _, item); + Ok(()) + } + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyList_Reverse(list: *mut PyObject) -> c_int { + with_vm(|vm| { + let list = unsafe { &*list }.try_downcast_ref::(vm)?; + list.borrow_vec_mut().reverse(); + Ok(()) + }) +} + +#[cfg(false)] +mod tests { + use pyo3::exceptions::PyIndexError; + use pyo3::prelude::*; + use pyo3::types::PyList; + + #[test] + fn test_create_list() { + Python::attach(|py| { + let list = PyList::new(py, &[1, 2, 3]).unwrap(); + assert_eq!(list.len(), 3); + assert_eq!(list.get_item(0).unwrap().extract::().unwrap(), 1); + assert_eq!(list.get_item(1).unwrap().extract::().unwrap(), 2); + assert_eq!(list.get_item(2).unwrap().extract::().unwrap(), 3); + assert!(list.get_item(3).is_err()); + }) + } + + #[test] + fn test_replace_item_in_list() { + Python::attach(|py| { + let list = PyList::new(py, &[1]).unwrap(); + assert_eq!(list.len(), 1); + list.set_item(0, 2).unwrap(); + assert_eq!(list.len(), 1); + assert_eq!(list.get_item(0).unwrap().extract::().unwrap(), 2); + }) + } + + #[test] + fn test_set_item_out_of_range() { + Python::attach(|py| { + let list = PyList::empty(py); + assert!( + list.set_item(0, 1) + .unwrap_err() + .is_instance_of::(py) + ); + }) + } + + #[test] + fn test_list_append() { + Python::attach(|py| { + let list = PyList::empty(py); + assert_eq!(list.len(), 0); + list.append(1).unwrap(); + assert_eq!(list.len(), 1); + assert_eq!(list.get_item(0).unwrap().extract::().unwrap(), 1); + }) + } + + #[test] + fn test_list_insert() { + Python::attach(|py| { + let list = PyList::empty(py); + assert_eq!(list.len(), 0); + list.insert(0, 1).unwrap(); + assert_eq!(list.len(), 1); + assert!(list.insert(2, 3).is_err()); + }) + } + + #[test] + fn test_list_reverse() { + Python::attach(|py| { + let list = PyList::new(py, &[1, 2, 3]).unwrap(); + list.reverse().unwrap(); + assert_eq!(list.get_item(0).unwrap().extract::().unwrap(), 3); + assert_eq!(list.get_item(2).unwrap().extract::().unwrap(), 1); + }) + } +} From 0f3361b31a01aaf8d3f330dcf8ded4dd4ae0d848 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Thu, 21 May 2026 18:16:53 +0200 Subject: [PATCH 2/3] Review --- crates/capi/src/listobject.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/crates/capi/src/listobject.rs b/crates/capi/src/listobject.rs index a5a5858ff6..bac5d8d260 100644 --- a/crates/capi/src/listobject.rs +++ b/crates/capi/src/listobject.rs @@ -11,7 +11,12 @@ define_py_check!(exact fn PyList_CheckExact, types.list_type); #[unsafe(no_mangle)] pub extern "C" fn PyList_New(size: isize) -> *mut PyObject { - with_vm(|vm| vm.ctx.new_list(Vec::with_capacity(size as usize))) + with_vm(|vm| { + let capacity = size + .try_into() + .map_err(|_| vm.new_system_error("Negative size passed to PyList_New"))?; + Ok(vm.ctx.new_list(Vec::with_capacity(capacity))) + }) } #[unsafe(no_mangle)] @@ -26,11 +31,11 @@ pub unsafe extern "C" fn PyList_Size(obj: *mut PyObject) -> isize { pub unsafe extern "C" fn PyList_GetItemRef(obj: *mut PyObject, index: isize) -> *mut PyObject { with_vm(|vm| { let list = unsafe { &*obj }.try_downcast_ref::(vm)?; - - list.borrow_vec() - .get(index as usize) + index + .try_into() + .ok() + .and_then(|index: usize| list.borrow_vec().get(index).map(ToOwned::to_owned)) .ok_or_else(|| vm.new_index_error(format!("list index out of range: {index}"))) - .map(ToOwned::to_owned) }) } @@ -43,6 +48,11 @@ pub unsafe extern "C" fn PyList_SetItem( with_vm(|vm| { let list = unsafe { &*list }.try_downcast_ref::(vm)?; let item = unsafe { PyObjectRef::from_raw(NonNull::new_unchecked(item)) }; + let index_error = + || vm.new_index_error(format!("list assignment index out of range: {index}")); + if index < 0 { + return Err(index_error()); + } let mut list_mut = list.borrow_vec_mut(); match index - list_mut.len() as isize { @@ -55,7 +65,7 @@ pub unsafe extern "C" fn PyList_SetItem( list_mut.push(item); Ok(()) } - 0.. => Err(vm.new_index_error(format!("list assignment index out of range: {index}"))), + 0.. => Err(index_error()), } }) } From fe2571d2236c9efe4444d8b287c690ed93951d26 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Fri, 22 May 2026 13:57:03 +0200 Subject: [PATCH 3/3] Fix Insert clamping --- crates/capi/src/listobject.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/capi/src/listobject.rs b/crates/capi/src/listobject.rs index bac5d8d260..1f70e37b65 100644 --- a/crates/capi/src/listobject.rs +++ b/crates/capi/src/listobject.rs @@ -90,12 +90,14 @@ pub unsafe extern "C" fn PyList_Insert( let list = unsafe { &*list }.try_downcast_ref::(vm)?; let item = unsafe { &*item }.to_owned(); let mut vec = list.borrow_vec_mut(); - if index as usize > vec.len() { - Err(vm.new_index_error(format!("list index out of range: {index}"))) + let index = if index < 0 { + index + vec.len() as isize } else { - vec.insert(index as _, item); - Ok(()) + index } + .clamp(0, vec.len() as isize) as usize; + vec.insert(index, item); + Ok(()) }) } @@ -167,7 +169,8 @@ mod tests { assert_eq!(list.len(), 0); list.insert(0, 1).unwrap(); assert_eq!(list.len(), 1); - assert!(list.insert(2, 3).is_err()); + list.insert(2, 3).unwrap(); + assert_eq!(list.get_item(1).unwrap().extract::().unwrap(), 3); }) }