Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
101 changes: 54 additions & 47 deletions crates/capi/src/listobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ use crate::object::define_py_check;
use crate::pystate::with_vm;
use crate::util::FfiPtrExt;
use core::ffi::c_int;
use rustpython_vm::AsObject;
use rustpython_vm::PyObjectRef;
use rustpython_vm::builtins::PyList;
use rustpython_vm::sliceable::{SaturatedSlice, SliceableSequenceMutOp, SliceableSequenceOp};

define_py_check!(fn PyList_Check, types.list_type);
define_py_check!(exact fn PyList_CheckExact, types.list_type);
Expand All @@ -25,19 +22,16 @@ pub extern "C" fn PyList_New(size: isize) -> *mut PyObject {
pub unsafe extern "C" fn PyList_Size(obj: *mut PyObject) -> isize {
with_vm(|vm| {
let list = unsafe { obj.assume_borrowed_and_cast::<PyList>(vm) }?;
Ok(list.__len__())
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.assume_borrowed_and_cast::<PyList>(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}")))
// A negative index wraps past the end and is out of range.
list.get_item(index as usize, vm)
})
}

Expand All @@ -50,25 +44,17 @@ pub unsafe extern "C" fn PyList_SetItem(
with_vm(|vm| {
let list = unsafe { list.assume_borrowed_and_cast::<PyList>(vm) }?;
let item = unsafe { item.assume_owned() };
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(())
}
// A negative index wraps past the end and is out of range.
let index = index as usize;
{
let mut list_mut = list.borrow_vec_mut();
// 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 => {
if index == list_mut.len() && list_mut.capacity() > index {
list_mut.push(item);
Ok(())
return Ok(());
}
0.. => Err(index_error()),
}
list.set_item(index, item, vm)
})
}

Expand All @@ -77,7 +63,7 @@ pub unsafe extern "C" fn PyList_Append(list: *mut PyObject, item: *mut PyObject)
with_vm(|vm| {
let list = unsafe { list.assume_borrowed_and_cast::<PyList>(vm) }?;
let item = unsafe { item.assume_borrowed() }.to_owned();
list.borrow_vec_mut().push(item);
list.append(item);
Ok(())
})
}
Expand All @@ -91,14 +77,7 @@ pub unsafe extern "C" fn PyList_Insert(
with_vm(|vm| {
let list = unsafe { list.assume_borrowed_and_cast::<PyList>(vm) }?;
let item = unsafe { item.assume_borrowed() }.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);
list.insert(index, item);
Ok(())
})
}
Expand All @@ -107,7 +86,7 @@ pub unsafe extern "C" fn PyList_Insert(
pub unsafe extern "C" fn PyList_Reverse(list: *mut PyObject) -> c_int {
with_vm(|vm| {
let list = unsafe { list.assume_borrowed_and_cast::<PyList>(vm) }?;
list.borrow_vec_mut().reverse();
list.reverse();
Ok(())
})
}
Expand All @@ -116,7 +95,7 @@ pub unsafe extern "C" fn PyList_Reverse(list: *mut PyObject) -> c_int {
pub unsafe extern "C" fn PyList_AsTuple(list: *mut PyObject) -> *mut PyObject {
with_vm(|vm| {
let list = unsafe { list.assume_borrowed_and_cast::<PyList>(vm) }?;
Ok(vm.ctx.new_tuple(list.borrow_vec().to_vec()))
Ok(list.to_tuple(vm))
})
}

Expand All @@ -128,9 +107,7 @@ pub unsafe extern "C" fn PyList_GetSlice(
) -> *mut PyObject {
with_vm(|vm| {
let list = unsafe { list.assume_borrowed_and_cast::<PyList>(vm) }?;
let vec = list.borrow_vec();
let sliced = vec.getitem_by_slice(vm, SaturatedSlice::from_parts(low, high, 1))?;
Ok(vm.ctx.new_list(sliced))
Ok(list.get_slice(low, high, vm))
})
}

Expand All @@ -143,31 +120,26 @@ pub unsafe extern "C" fn PyList_SetSlice(
) -> c_int {
with_vm(|vm| {
let list = unsafe { list.assume_borrowed_and_cast::<PyList>(vm) }?;
let slice = SaturatedSlice::from_parts(low, high, 1);
let mut vec = list.borrow_vec_mut();

let Some(itemlist) = (unsafe { itemlist.assume_borrowed_or_opt() }) else {
vec.delitem_by_slice(vm, slice)?;
list.del_slice(low, high);
return Ok(());
};

let items: Vec<PyObjectRef> = itemlist.try_to_value(vm)?;
vec.setitem_by_slice(vm, slice, &items)
list.set_slice(low, high, itemlist, vm)
})
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn PyList_Sort(list: *mut PyObject) -> c_int {
with_vm(|vm| {
let list = unsafe { list.assume_borrowed_and_cast::<PyList>(vm) }?;
vm.call_method(list.as_object(), "sort", ())?;
Ok(())
list.sort(vm)
})
}

#[cfg(test)]
mod tests {
use pyo3::exceptions::PyIndexError;
use pyo3::ffi;
use pyo3::prelude::*;
use pyo3::types::{PyList, PyListMethods};

Expand Down Expand Up @@ -288,4 +260,39 @@ mod tests {
assert_eq!(list.get_item(2).unwrap().extract::<u32>().unwrap(), 3);
})
}

#[test]
fn list_del_slice() {
Python::attach(|py| {
let list = PyList::new(py, [1, 2, 3, 4]).unwrap();
list.del_slice(1, 3).unwrap();
assert_eq!(list.extract::<Vec<u32>>().unwrap(), [1, 4]);
})
}

#[test]
fn list_negative_indices() {
Python::attach(|py| unsafe {
let list = PyList::new(py, [1, 2, 3, 4]).unwrap();

assert!(ffi::PyList_GetItemRef(list.as_ptr(), -1).is_null());
assert!(PyErr::take(py).unwrap().is_instance_of::<PyIndexError>(py));

let slice = Bound::from_owned_ptr(py, ffi::PyList_GetSlice(list.as_ptr(), -1, 2));
assert_eq!(slice.extract::<Vec<u32>>().unwrap(), [1, 2]);

let repl = PyList::new(py, [9]).unwrap();
assert_eq!(ffi::PyList_SetSlice(list.as_ptr(), -1, 1, repl.as_ptr()), 0);
assert_eq!(list.extract::<Vec<u32>>().unwrap(), [9, 2, 3, 4]);
})
}

#[test]
fn list_set_slice_self() {
Python::attach(|py| {
let list = PyList::new(py, [1, 2]).unwrap();
list.set_slice(2, 2, list.as_any()).unwrap();
assert_eq!(list.extract::<Vec<u32>>().unwrap(), [1, 2, 1, 2]);
})
}
}
6 changes: 3 additions & 3 deletions crates/derive-impl/src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ pub(crate) fn impl_pyclass(attr: PunctuatedNestedMeta, item: Item) -> Result<Tok
//
// For clear (tp_clear):
// 1. no `clear`: HAS_CLEAR = HAS_TRAVERSE (default: same as traverse)
// 2. `clear` or `clear = true`: HAS_CLEAR = true, try_clear calls Traverse::clear
// 2. `clear` or `clear = true`: HAS_CLEAR = true, try_clear_refs calls Traverse::clear_refs
// 3. `clear = false`: HAS_CLEAR = false (rare: traverse without clear)
let has_traverse = class_meta.inner().contains_key("traverse");
let has_clear = if class_meta.inner().contains_key("clear") {
Expand Down Expand Up @@ -790,7 +790,7 @@ pub(crate) fn impl_pyclass(attr: PunctuatedNestedMeta, item: Item) -> Result<Tok

let try_clear_body = if has_clear {
quote! {
::rustpython_vm::object::Traverse::clear(self, out);
::rustpython_vm::object::Traverse::clear_refs(self, out);
}
} else {
quote! {
Expand All @@ -807,7 +807,7 @@ pub(crate) fn impl_pyclass(attr: PunctuatedNestedMeta, item: Item) -> Result<Tok
#try_traverse_body
}

fn try_clear(&mut self, out: &mut ::std::vec::Vec<::rustpython_vm::PyObjectRef>) {
fn try_clear_refs(&mut self, out: &mut ::std::vec::Vec<::rustpython_vm::PyObjectRef>) {
#try_clear_body
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/derive-impl/src/pystructseq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,8 +596,8 @@ pub(crate) fn impl_pystruct_sequence(
self.0.try_traverse(traverse_fn)
}

fn try_clear(&mut self, out: &mut ::std::vec::Vec<::rustpython_vm::PyObjectRef>) {
self.0.try_clear(out)
fn try_clear_refs(&mut self, out: &mut ::std::vec::Vec<::rustpython_vm::PyObjectRef>) {
self.0.try_clear_refs(out)
}
}

Expand Down
22 changes: 11 additions & 11 deletions crates/stdlib/src/_heapq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ mod _heapq {
mut pos: usize,
vm: &VirtualMachine,
) -> PyResult<()> {
let size = heap.__len__();
let size = heap.borrow_vec().len();

let newitem = match heap.borrow_vec().get(pos) {
Some(v) => v.clone(),
Expand All @@ -35,7 +35,7 @@ mod _heapq {

let cmp = newitem.rich_compare_bool(&parent, PyComparisonOp::Lt, vm)?;

if size != heap.__len__() {
if size != heap.borrow_vec().len() {
return Err(vm.new_runtime_error("list changed size during iteration"));
}

Expand All @@ -56,7 +56,7 @@ mod _heapq {

/// [CPython's siftup](https://github.com/python/cpython/blob/v3.14.5/Modules/_heapqmodule.c#L70-L118)
fn siftup(heap: &Py<PyList>, mut pos: usize, vm: &VirtualMachine) -> PyResult<()> {
let endpos = heap.__len__();
let endpos = heap.borrow_vec().len();
let startpos = pos;

if pos >= endpos {
Expand All @@ -78,7 +78,7 @@ mod _heapq {

let cmp = a.rich_compare_bool(&b, PyComparisonOp::Lt, vm)?;

if endpos != heap.__len__() {
if endpos != heap.borrow_vec().len() {
return Err(vm.new_runtime_error("list changed size during iteration"));
}

Expand Down Expand Up @@ -119,7 +119,7 @@ mod _heapq {
vec.push(item);
}

let size = heap.__len__();
let size = heap.borrow_vec().len();

siftdown_func(heap, 0, size - 1, vm)
}
Expand Down Expand Up @@ -251,7 +251,7 @@ mod _heapq {
where
F: Fn(&Py<PyList>, usize, &VirtualMachine) -> PyResult<()>,
{
let m = heap.__len__() >> 1; // index of first childless node
let m = heap.borrow_vec().len() >> 1; // index of first childless node
let leftmost = keep_top_bit(m + 1) - 1; // leftmost node in row of m
let mhalf = m >> 1; // parent of first childless node

Expand Down Expand Up @@ -291,7 +291,7 @@ mod _heapq {
where
F: Fn(&Py<PyList>, usize, &VirtualMachine) -> PyResult<()>,
{
let n = heap.__len__();
let n = heap.borrow_vec().len();

if n > 2500 {
return cache_friendly_heapify(heap, siftup_func, vm);
Expand All @@ -316,7 +316,7 @@ mod _heapq {
mut pos: usize,
vm: &VirtualMachine,
) -> PyResult<()> {
let size = heap.__len__();
let size = heap.borrow_vec().len();

let newitem = match heap.borrow_vec().get(pos) {
Some(v) => v.clone(),
Expand All @@ -334,7 +334,7 @@ mod _heapq {

let cmp = parent.rich_compare_bool(&newitem, PyComparisonOp::Lt, vm)?;

if size != heap.__len__() {
if size != heap.borrow_vec().len() {
return Err(vm.new_runtime_error("list changed size during iteration"));
}

Expand All @@ -355,7 +355,7 @@ mod _heapq {

/// [CPython's siftup_max](https://github.com/python/cpython/blob/v3.14.5/Modules/_heapqmodule.c#L451-L499)
fn siftup_max(heap: &Py<PyList>, mut pos: usize, vm: &VirtualMachine) -> PyResult<()> {
let endpos = heap.__len__();
let endpos = heap.borrow_vec().len();
let startpos = pos;

if pos >= endpos {
Expand All @@ -377,7 +377,7 @@ mod _heapq {

let cmp = a.rich_compare_bool(&b, PyComparisonOp::Lt, vm)?;

if endpos != heap.__len__() {
if endpos != heap.borrow_vec().len() {
return Err(vm.new_runtime_error("list changed size during iteration"));
}

Expand Down
6 changes: 3 additions & 3 deletions crates/stdlib/src/elementtree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ pub(crate) mod _elementtree {
inner.children.traverse(traverse_fn);
}

fn clear(&mut self, out: &mut Vec<PyObjectRef>) {
fn clear_refs(&mut self, out: &mut Vec<PyObjectRef>) {
// Only reached for an object the collector has already proven
// unreachable, so grabbing the lock cannot race with user code.
let Some(mut inner) = self.inner.try_write() else {
Expand Down Expand Up @@ -1331,7 +1331,7 @@ pub(crate) mod _elementtree {
st.pi_event.traverse(traverse_fn);
}

fn clear(&mut self, out: &mut Vec<PyObjectRef>) {
fn clear_refs(&mut self, out: &mut Vec<PyObjectRef>) {
let Some(mut st) = self.state.try_write() else {
return;
};
Expand Down Expand Up @@ -1880,7 +1880,7 @@ pub(crate) mod _elementtree {
st.handle_doctype.traverse(traverse_fn);
}

fn clear(&mut self, out: &mut Vec<PyObjectRef>) {
fn clear_refs(&mut self, out: &mut Vec<PyObjectRef>) {
let Some(mut st) = self.state.try_write() else {
return;
};
Expand Down
2 changes: 1 addition & 1 deletion crates/vm/src/builtins/dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ unsafe impl Traverse for PyDict {
self.entries.traverse(traverse_fn);
}

fn clear(&mut self, out: &mut Vec<PyObjectRef>) {
fn clear_refs(&mut self, out: &mut Vec<PyObjectRef>) {
// Pop all entries and collect both keys and values
for (key, value) in self.entries.drain_entries() {
out.push(key);
Expand Down
2 changes: 1 addition & 1 deletion crates/vm/src/builtins/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ unsafe impl Traverse for PyFunction {
self.doc.traverse(tracer_fn);
}

fn clear(&mut self, out: &mut Vec<crate::PyObjectRef>) {
fn clear_refs(&mut self, out: &mut Vec<crate::PyObjectRef>) {
// Pop closure if present (equivalent to Py_CLEAR(func_closure))
if let Some(closure) = self.closure.take() {
out.push(closure.into());
Expand Down
5 changes: 3 additions & 2 deletions crates/vm/src/builtins/genericalias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::{
convert::ToPyObject,
function::{FuncArgs, PyComparisonValue},
protocol::{PyMappingMethods, PyNumberMethods},
sequence::MutObjectSequenceOp,
types::{
AsMapping, AsNumber, Callable, Comparable, Constructor, GetAttr, Hashable, IterNext,
Iterable, PyComparisonOp, Representable,
Expand Down Expand Up @@ -244,8 +245,8 @@ impl PyGenericAlias {
let dir = vm.dir(Some(self.origin.clone()))?;
for exc in &ATTR_EXCEPTIONS {
let exc_obj = (*exc).to_pyobject(vm);
if !dir.__contains__(&exc_obj, vm)? {
dir.append((*exc).to_pyobject(vm));
if !dir.mut_contains(vm, &exc_obj)? {
dir.borrow_vec_mut().push((*exc).to_pyobject(vm));
}
}
Ok(dir)
Expand Down
Loading
Loading