diff --git a/src/internal_tricks.rs b/src/internal_tricks.rs index 33f9bb5c4a4..5e72a6ddee0 100644 --- a/src/internal_tricks.rs +++ b/src/internal_tricks.rs @@ -1,3 +1,4 @@ +use crate::ffi::{Py_ssize_t, PY_SSIZE_T_MAX}; use std::ffi::{CStr, CString}; use std::marker::PhantomData; use std::rc::Rc; @@ -51,3 +52,9 @@ pub(crate) fn extract_cstr_or_leak_cstring( }) .map_err(|_| NulByteInString(err_msg)) } + +/// Convert an usize index into a Py_ssize_t index, clamping overflow to +/// PY_SSIZE_T_MAX. +pub(crate) fn get_ssize_index(index: usize) -> Py_ssize_t { + index.min(PY_SSIZE_T_MAX as usize) as Py_ssize_t +} diff --git a/src/types/list.rs b/src/types/list.rs index 14e9f63b68d..73b1e18e679 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -4,6 +4,7 @@ use crate::err::{self, PyResult}; use crate::ffi::{self, Py_ssize_t}; +use crate::internal_tricks::get_ssize_index; use crate::{ AsPyPointer, IntoPy, IntoPyPointer, PyAny, PyNativeType, PyObject, Python, ToBorrowedObject, ToPyObject, @@ -83,17 +84,35 @@ impl PyList { } } + /// Takes the slice `self[low:high]` and returns it as a new list. + /// + /// Indices must be nonnegative, and out-of-range indices are clipped to + /// `self.len()`. + pub fn slice(&self, low: usize, high: usize) -> &PyList { + unsafe { + self.py().from_owned_ptr(ffi::PyList_GetSlice( + self.as_ptr(), + get_ssize_index(low), + get_ssize_index(high), + )) + } + } + /// Sets the item at the specified index. /// - /// Panics if the index is out of range. - pub fn set_item(&self, index: isize, item: I) -> PyResult<()> + /// Raises `IndexError` if the index is out of range. + pub fn set_item(&self, index: usize, item: I) -> PyResult<()> where I: ToPyObject, { unsafe { err::error_on_minusone( self.py(), - ffi::PyList_SetItem(self.as_ptr(), index, item.to_object(self.py()).into_ptr()), + ffi::PyList_SetItem( + self.as_ptr(), + get_ssize_index(index), + item.to_object(self.py()).into_ptr(), + ), ) } } @@ -110,13 +129,16 @@ impl PyList { /// Inserts an item at the specified index. /// - /// Panics if the index is out of range. - pub fn insert(&self, index: isize, item: I) -> PyResult<()> + /// If `index >= self.len()`, inserts at the end. + pub fn insert(&self, index: usize, item: I) -> PyResult<()> where I: ToBorrowedObject, { item.with_borrowed_ptr(self.py(), |item| unsafe { - err::error_on_minusone(self.py(), ffi::PyList_Insert(self.as_ptr(), index, item)) + err::error_on_minusone( + self.py(), + ffi::PyList_Insert(self.as_ptr(), get_ssize_index(index), item), + ) }) } @@ -251,14 +273,27 @@ mod tests { }); } + #[test] + fn test_slice() { + Python::with_gil(|py| { + let list = PyList::new(py, &[2, 3, 5, 7]); + let slice = list.slice(1, 3); + assert_eq!(2, slice.len()); + let slice = list.slice(1, 7); + assert_eq!(3, slice.len()); + }); + } + #[test] fn test_set_item() { Python::with_gil(|py| { let list = PyList::new(py, &[2, 3, 5, 7]); let val = 42i32.to_object(py); + let val2 = 42i32.to_object(py); assert_eq!(2, list.get_item(0).extract::().unwrap()); list.set_item(0, val).unwrap(); assert_eq!(42, list.get_item(0).extract::().unwrap()); + assert!(list.set_item(10, val2).is_err()); }); } @@ -285,12 +320,15 @@ mod tests { Python::with_gil(|py| { let list = PyList::new(py, &[2, 3, 5, 7]); let val = 42i32.to_object(py); + let val2 = 43i32.to_object(py); assert_eq!(4, list.len()); assert_eq!(2, list.get_item(0).extract::().unwrap()); list.insert(0, val).unwrap(); - assert_eq!(5, list.len()); + list.insert(1000, val2).unwrap(); + assert_eq!(6, list.len()); assert_eq!(42, list.get_item(0).extract::().unwrap()); assert_eq!(2, list.get_item(1).extract::().unwrap()); + assert_eq!(43, list.get_item(5).extract::().unwrap()); }); } diff --git a/src/types/tuple.rs b/src/types/tuple.rs index b34422b93d9..d80b64780f5 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -1,6 +1,7 @@ // Copyright (c) 2017-present PyO3 Project and Contributors use crate::ffi::{self, Py_ssize_t}; +use crate::internal_tricks::get_ssize_index; use crate::{ exceptions, AsPyPointer, FromPyObject, IntoPy, IntoPyPointer, Py, PyAny, PyErr, PyNativeType, PyObject, PyResult, PyTryFrom, Python, ToPyObject, @@ -57,18 +58,28 @@ impl PyTuple { self.len() == 0 } - /// Takes a slice of the tuple pointed from `low` to `high` and returns it as a new tuple. - pub fn slice(&self, low: isize, high: isize) -> &PyTuple { + /// Takes the slice `self[low:high]` and returns it as a new tuple. + /// + /// Indices must be nonnegative, and out-of-range indices are clipped to + /// `self.len()`. + pub fn slice(&self, low: usize, high: usize) -> &PyTuple { unsafe { - self.py() - .from_owned_ptr(ffi::PyTuple_GetSlice(self.as_ptr(), low, high)) + self.py().from_owned_ptr(ffi::PyTuple_GetSlice( + self.as_ptr(), + get_ssize_index(low), + get_ssize_index(high), + )) } } /// Takes a slice of the tuple from `low` to the end and returns it as a new tuple. - pub fn split_from(&self, low: isize) -> &PyTuple { + pub fn split_from(&self, low: usize) -> &PyTuple { unsafe { - let ptr = ffi::PyTuple_GetSlice(self.as_ptr(), low, self.len() as Py_ssize_t); + let ptr = ffi::PyTuple_GetSlice( + self.as_ptr(), + get_ssize_index(low), + self.len() as Py_ssize_t, + ); self.py().from_owned_ptr(ptr) } } @@ -346,6 +357,17 @@ mod tests { }); } + #[test] + fn test_slice() { + Python::with_gil(|py| { + let tup = PyTuple::new(py, &[2, 3, 5, 7]); + let slice = tup.slice(1, 3); + assert_eq!(2, slice.len()); + let slice = tup.slice(1, 7); + assert_eq!(3, slice.len()); + }); + } + #[test] fn test_iter() { Python::with_gil(|py| {