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..1f70e37b65 --- /dev/null +++ b/crates/capi/src/listobject.rs @@ -0,0 +1,186 @@ +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| { + 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)] +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)?; + 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}"))) + }) +} + +#[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 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 { + ..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(index_error()), + } + }) +} + +#[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(); + let index = if index < 0 { + index + vec.len() as isize + } else { + index + } + .clamp(0, vec.len() as isize) as usize; + vec.insert(index, 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); + list.insert(2, 3).unwrap(); + assert_eq!(list.get_item(1).unwrap().extract::().unwrap(), 3); + }) + } + + #[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); + }) + } +}