Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add list support to c-api
  • Loading branch information
bschoenmaeckers committed May 26, 2026
commit 0bc0e20f069ab7fe291cc74d9b9934f30ebea82e
1 change: 1 addition & 0 deletions crates/capi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mod complexobject;
pub mod dictobject;
pub mod floatobject;
pub mod import;
pub mod listobject;
pub mod longobject;
pub mod object;
pub mod pyerrors;
Expand Down
173 changes: 173 additions & 0 deletions crates/capi/src/listobject.rs
Original file line number Diff line number Diff line change
@@ -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)))
}
Comment thread
bschoenmaeckers marked this conversation as resolved.

#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyList_Size(obj: *mut PyObject) -> isize {
with_vm(|vm| {
let list = unsafe { &*obj }.try_downcast_ref::<PyList>(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::<PyList>(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::<PyList>(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;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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::<PyList>(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::<PyList>(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::<PyList>(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::<u32>().unwrap(), 1);
assert_eq!(list.get_item(1).unwrap().extract::<u32>().unwrap(), 2);
assert_eq!(list.get_item(2).unwrap().extract::<u32>().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::<u32>().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::<PyIndexError>(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::<u32>().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::<u32>().unwrap(), 3);
assert_eq!(list.get_item(2).unwrap().extract::<u32>().unwrap(), 1);
})
}
}