diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index fd136c83747..68304753b05 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -181,7 +181,7 @@ struct RustyTuple(String, String); # use pyo3::types::PyTuple; # fn main() -> PyResult<()> { # Python::with_gil(|py| -> PyResult<()> { -# let tuple = PyTuple::new(py, vec!["test", "test2"]); +# let tuple = PyTuple::new_bound(py, vec!["test", "test2"]); # # let rustytuple: RustyTuple = tuple.extract()?; # assert_eq!(rustytuple.0, "test"); @@ -204,7 +204,7 @@ struct RustyTuple((String,)); # use pyo3::types::PyTuple; # fn main() -> PyResult<()> { # Python::with_gil(|py| -> PyResult<()> { -# let tuple = PyTuple::new(py, vec!["test"]); +# let tuple = PyTuple::new_bound(py, vec!["test"]); # # let rustytuple: RustyTuple = tuple.extract()?; # assert_eq!((rustytuple.0).0, "test"); @@ -482,7 +482,7 @@ If the input is neither a string nor an integer, the error message will be: - retrieve the field from a mapping, possibly with the custom key specified as an argument. - can be any literal that implements `ToBorrowedObject` - `pyo3(from_py_with = "...")` - - apply a custom function to convert the field from Python the desired Rust type. + - apply a custom function to convert the field from Python the desired Rust type. - the argument must be the name of the function as a string. - the function signature must be `fn(&PyAny) -> PyResult` where `T` is the Rust type of the argument. diff --git a/guide/src/python_from_rust.md b/guide/src/python_from_rust.md index ed51b772d2d..6d4034822d7 100644 --- a/guide/src/python_from_rust.md +++ b/guide/src/python_from_rust.md @@ -51,7 +51,7 @@ fn main() -> PyResult<()> { fun.call0(py)?; // call object with PyTuple - let args = PyTuple::new(py, &[arg1, arg2, arg3]); + let args = PyTuple::new_bound(py, &[arg1, arg2, arg3]); fun.call1(py, args)?; // pass arguments as rust tuple diff --git a/pytests/src/datetime.rs b/pytests/src/datetime.rs index f5a15b4f682..1407da3fe76 100644 --- a/pytests/src/datetime.rs +++ b/pytests/src/datetime.rs @@ -12,8 +12,8 @@ fn make_date(py: Python<'_>, year: i32, month: u8, day: u8) -> PyResult<&PyDate> } #[pyfunction] -fn get_date_tuple<'p>(py: Python<'p>, d: &PyDate) -> &'p PyTuple { - PyTuple::new(py, [d.get_year(), d.get_month() as i32, d.get_day() as i32]) +fn get_date_tuple<'p>(py: Python<'p>, d: &PyDate) -> Bound<'p, PyTuple> { + PyTuple::new_bound(py, [d.get_year(), d.get_month() as i32, d.get_day() as i32]) } #[pyfunction] @@ -48,8 +48,8 @@ fn time_with_fold<'p>( } #[pyfunction] -fn get_time_tuple<'p>(py: Python<'p>, dt: &PyTime) -> &'p PyTuple { - PyTuple::new( +fn get_time_tuple<'p>(py: Python<'p>, dt: &PyTime) -> Bound<'p, PyTuple> { + PyTuple::new_bound( py, [ dt.get_hour() as u32, @@ -61,8 +61,8 @@ fn get_time_tuple<'p>(py: Python<'p>, dt: &PyTime) -> &'p PyTuple { } #[pyfunction] -fn get_time_tuple_fold<'p>(py: Python<'p>, dt: &PyTime) -> &'p PyTuple { - PyTuple::new( +fn get_time_tuple_fold<'p>(py: Python<'p>, dt: &PyTime) -> Bound<'p, PyTuple> { + PyTuple::new_bound( py, [ dt.get_hour() as u32, @@ -80,8 +80,8 @@ fn make_delta(py: Python<'_>, days: i32, seconds: i32, microseconds: i32) -> PyR } #[pyfunction] -fn get_delta_tuple<'p>(py: Python<'p>, delta: &PyDelta) -> &'p PyTuple { - PyTuple::new( +fn get_delta_tuple<'p>(py: Python<'p>, delta: &PyDelta) -> Bound<'p, PyTuple> { + PyTuple::new_bound( py, [ delta.get_days(), @@ -118,8 +118,8 @@ fn make_datetime<'p>( } #[pyfunction] -fn get_datetime_tuple<'p>(py: Python<'p>, dt: &PyDateTime) -> &'p PyTuple { - PyTuple::new( +fn get_datetime_tuple<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyTuple> { + PyTuple::new_bound( py, [ dt.get_year(), @@ -134,8 +134,8 @@ fn get_datetime_tuple<'p>(py: Python<'p>, dt: &PyDateTime) -> &'p PyTuple { } #[pyfunction] -fn get_datetime_tuple_fold<'p>(py: Python<'p>, dt: &PyDateTime) -> &'p PyTuple { - PyTuple::new( +fn get_datetime_tuple_fold<'p>(py: Python<'p>, dt: &PyDateTime) -> Bound<'p, PyTuple> { + PyTuple::new_bound( py, [ dt.get_year(), diff --git a/src/conversion.rs b/src/conversion.rs index 0a842f9a419..429ca9e8779 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -465,7 +465,7 @@ mod implementations { /// Converts `()` to an empty Python tuple. impl IntoPy> for () { fn into_py(self, py: Python<'_>) -> Py { - PyTuple::empty(py).into() + PyTuple::empty_bound(py).unbind() } } diff --git a/src/impl_/extract_argument.rs b/src/impl_/extract_argument.rs index 437ece483ce..a18c8d4bc30 100644 --- a/src/impl_/extract_argument.rs +++ b/src/impl_/extract_argument.rs @@ -615,7 +615,7 @@ impl<'py> VarargsHandler<'py> for TupleVarargs { varargs: &[Option<&PyAny>], _function_description: &FunctionDescription, ) -> PyResult { - Ok(PyTuple::new(py, varargs)) + Ok(PyTuple::new_bound(py, varargs).into_gil_ref()) } #[inline] @@ -697,7 +697,7 @@ fn push_parameter_list(msg: &mut String, parameter_names: &[&str]) { mod tests { use crate::{ types::{IntoPyDict, PyTuple}, - PyAny, Python, ToPyObject, + PyAny, Python, }; use super::{push_parameter_list, FunctionDescription, NoVarargs, NoVarkeywords}; @@ -714,8 +714,8 @@ mod tests { }; Python::with_gil(|py| { - let args = PyTuple::new(py, Vec::<&PyAny>::new()); - let kwargs = [("foo".to_object(py).into_ref(py), 0u8)].into_py_dict(py); + let args = PyTuple::new_bound(py, Vec::<&PyAny>::new()); + let kwargs = [("foo", 0u8)].into_py_dict(py); let err = unsafe { function_description .extract_arguments_tuple_dict::( @@ -745,8 +745,8 @@ mod tests { }; Python::with_gil(|py| { - let args = PyTuple::new(py, Vec::<&PyAny>::new()); - let kwargs = [(1u8.to_object(py).into_ref(py), 1u8)].into_py_dict(py); + let args = PyTuple::new_bound(py, Vec::<&PyAny>::new()); + let kwargs = [(1u8, 1u8)].into_py_dict(py); let err = unsafe { function_description .extract_arguments_tuple_dict::( @@ -776,7 +776,7 @@ mod tests { }; Python::with_gil(|py| { - let args = PyTuple::new(py, Vec::<&PyAny>::new()); + let args = PyTuple::new_bound(py, Vec::<&PyAny>::new()); let mut output = [None, None]; let err = unsafe { function_description.extract_arguments_tuple_dict::( diff --git a/src/impl_/pymodule.rs b/src/impl_/pymodule.rs index 7c5243fcf1c..0fe5c3846c2 100644 --- a/src/impl_/pymodule.rs +++ b/src/impl_/pymodule.rs @@ -72,7 +72,7 @@ impl ModuleDef { .import("sys")? .getattr("implementation")? .getattr("version")?; - if version.lt(crate::types::PyTuple::new(py, PYPY_GOOD_VERSION))? { + if version.lt(crate::types::PyTuple::new_bound(py, PYPY_GOOD_VERSION))? { let warn = py.import("warnings")?.getattr("warn")?; warn.call1(( "PyPy 3.7 versions older than 7.3.8 are known to have binary \ diff --git a/src/instance.rs b/src/instance.rs index e3f3743ebf7..11936d6c94f 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -325,10 +325,10 @@ impl<'py, T> Borrowed<'py, 'py, T> where T: HasPyGilRef, { - // pub(crate) fn into_gil_ref(self) -> &'py T::AsRefTarget { - // // Safety: self is a borrow over `'py`. - // unsafe { self.py().from_borrowed_ptr(self.0.as_ptr()) } - // } + pub(crate) fn into_gil_ref(self) -> &'py T::AsRefTarget { + // Safety: self is a borrow over `'py`. + unsafe { self.py().from_borrowed_ptr(self.0.as_ptr()) } + } } impl std::fmt::Debug for Borrowed<'_, '_, T> { diff --git a/src/prelude.rs b/src/prelude.rs index 60ce03063b8..6be820ae92a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -38,3 +38,4 @@ pub use crate::types::module::PyModuleMethods; pub use crate::types::sequence::PySequenceMethods; pub use crate::types::set::PySetMethods; pub use crate::types::string::PyStringMethods; +pub use crate::types::tuple::PyTupleMethods; diff --git a/src/types/datetime.rs b/src/types/datetime.rs index 3d66cee4d95..354414b8b4c 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -212,7 +212,7 @@ impl PyDate { /// /// This is equivalent to `datetime.date.fromtimestamp` pub fn from_timestamp(py: Python<'_>, timestamp: i64) -> PyResult<&PyDate> { - let time_tuple = PyTuple::new(py, [timestamp]); + let time_tuple = PyTuple::new_bound(py, [timestamp]); // safety ensure that the API is loaded let _api = ensure_datetime_api(py); diff --git a/src/types/list.rs b/src/types/list.rs index 2ba4b10cd76..f8db0e62ee6 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -1231,7 +1231,7 @@ mod tests { Python::with_gil(|py| { let list = PyList::new(py, vec![1, 2, 3]); let tuple = list.to_tuple(); - let tuple_expected = PyTuple::new(py, vec![1, 2, 3]); + let tuple_expected = PyTuple::new_bound(py, vec![1, 2, 3]); assert!(tuple.eq(tuple_expected).unwrap()); }) } diff --git a/src/types/mod.rs b/src/types/mod.rs index c2885f4630c..00fe81cf37e 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -80,7 +80,7 @@ pub mod iter { pub use super::frozenset::{BoundFrozenSetIterator, PyFrozenSetIterator}; pub use super::list::{BoundListIterator, PyListIterator}; pub use super::set::{BoundSetIterator, PySetIterator}; - pub use super::tuple::PyTupleIterator; + pub use super::tuple::{BorrowedTupleIterator, BoundTupleIterator, PyTupleIterator}; } // Implementations core to all native types @@ -305,5 +305,5 @@ pub(crate) mod set; mod slice; pub(crate) mod string; mod traceback; -mod tuple; +pub(crate) mod tuple; mod typeobject; diff --git a/src/types/sequence.rs b/src/types/sequence.rs index f4eae4d05a9..fa15fe06858 100644 --- a/src/types/sequence.rs +++ b/src/types/sequence.rs @@ -1009,7 +1009,7 @@ mod tests { assert!(seq .to_tuple() .unwrap() - .eq(PyTuple::new(py, ["foo", "bar"])) + .eq(PyTuple::new_bound(py, ["foo", "bar"])) .unwrap()); }); } @@ -1020,7 +1020,11 @@ mod tests { let v = vec!["foo", "bar"]; let ob = v.to_object(py); let seq = ob.downcast::(py).unwrap(); - assert!(seq.to_tuple().unwrap().eq(PyTuple::new(py, &v)).unwrap()); + assert!(seq + .to_tuple() + .unwrap() + .eq(PyTuple::new_bound(py, &v)) + .unwrap()); }); } diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 0165061a7a1..4860b8de30e 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -2,21 +2,23 @@ use std::convert::TryInto; use std::iter::FusedIterator; use crate::ffi::{self, Py_ssize_t}; +use crate::ffi_ptr_ext::FfiPtrExt; #[cfg(feature = "experimental-inspect")] use crate::inspect::types::TypeInfo; +use crate::instance::Borrowed; use crate::internal_tricks::get_ssize_index; -use crate::types::PyList; -use crate::types::PySequence; +use crate::types::{any::PyAnyMethods, sequence::PySequenceMethods, PyList, PySequence}; use crate::{ - exceptions, FromPyObject, IntoPy, Py, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject, + exceptions, Bound, FromPyObject, IntoPy, Py, PyAny, PyErr, PyNativeType, PyObject, PyResult, + Python, ToPyObject, }; #[inline] #[track_caller] -fn new_from_iter( - py: Python<'_>, +fn new_from_iter<'py>( + py: Python<'py>, elements: &mut dyn ExactSizeIterator, -) -> Py { +) -> Bound<'py, PyTuple> { unsafe { // PyTuple_New checks for overflow but has a bad error message, so we check ourselves let len: Py_ssize_t = elements @@ -28,7 +30,7 @@ fn new_from_iter( // - Panics if the ptr is null // - Cleans up the tuple if `convert` or the asserts panic - let tup: Py = Py::from_owned_ptr(py, ptr); + let tup = ptr.assume_owned(py).downcast_into_unchecked(); let mut counter: Py_ssize_t = 0; @@ -56,6 +58,26 @@ pub struct PyTuple(PyAny); pyobject_native_type_core!(PyTuple, pyobject_native_static_type_object!(ffi::PyTuple_Type), #checkfunction=ffi::PyTuple_Check); impl PyTuple { + /// Deprecated form of `PyTuple::new_bound`. + #[track_caller] + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyTuple::new` will be replaced by `PyTuple::new_bound` in a future PyO3 version" + ) + )] + pub fn new( + py: Python<'_>, + elements: impl IntoIterator, + ) -> &PyTuple + where + T: ToPyObject, + U: ExactSizeIterator, + { + Self::new_bound(py, elements).into_gil_ref() + } + /// Constructs a new tuple with the given elements. /// /// If you want to create a [`PyTuple`] with elements of different or unknown types, or from an @@ -71,7 +93,7 @@ impl PyTuple { /// # fn main() { /// Python::with_gil(|py| { /// let elements: Vec = vec![0, 1, 2, 3, 4, 5]; - /// let tuple: &PyTuple = PyTuple::new(py, elements); + /// let tuple = PyTuple::new_bound(py, elements); /// assert_eq!(format!("{:?}", tuple), "(0, 1, 2, 3, 4, 5)"); /// }); /// # } @@ -83,39 +105,47 @@ impl PyTuple { /// All standard library structures implement this trait correctly, if they do, so calling this /// function using [`Vec`]`` or `&[T]` will always succeed. #[track_caller] - pub fn new( + pub fn new_bound( py: Python<'_>, elements: impl IntoIterator, - ) -> &PyTuple + ) -> Bound<'_, PyTuple> where T: ToPyObject, U: ExactSizeIterator, { let mut elements = elements.into_iter().map(|e| e.to_object(py)); - let tup = new_from_iter(py, &mut elements); - tup.into_ref(py) + new_from_iter(py, &mut elements) } - /// Constructs an empty tuple (on the Python side, a singleton object). + /// Deprecated form of `PyTuple::empty_bound`. + #[cfg_attr( + not(feature = "gil-refs"), + deprecated( + since = "0.21.0", + note = "`PyTuple::empty` will be replaced by `PyTuple::empty_bound` in a future PyO3 version" + ) + )] pub fn empty(py: Python<'_>) -> &PyTuple { - unsafe { py.from_owned_ptr(ffi::PyTuple_New(0)) } + Self::empty_bound(py).into_gil_ref() } - /// Gets the length of the tuple. - pub fn len(&self) -> usize { + /// Constructs an empty tuple (on the Python side, a singleton object). + pub fn empty_bound(py: Python<'_>) -> Bound<'_, PyTuple> { unsafe { - #[cfg(not(any(Py_LIMITED_API, PyPy)))] - let size = ffi::PyTuple_GET_SIZE(self.as_ptr()); - #[cfg(any(Py_LIMITED_API, PyPy))] - let size = ffi::PyTuple_Size(self.as_ptr()); - // non-negative Py_ssize_t should always fit into Rust uint - size as usize + ffi::PyTuple_New(0) + .assume_owned(py) + .downcast_into_unchecked() } } + /// Gets the length of the tuple. + pub fn len(&self) -> usize { + self.as_borrowed().len() + } + /// Checks if the tuple is empty. pub fn is_empty(&self) -> bool { - self.len() == 0 + self.as_borrowed().is_empty() } /// Returns `self` cast as a `PySequence`. @@ -128,13 +158,7 @@ impl PyTuple { /// Indices must be nonnegative, and out-of-range indices are clipped to /// `self.len()`. pub fn get_slice(&self, low: usize, high: usize) -> &PyTuple { - unsafe { - self.py().from_owned_ptr(ffi::PyTuple_GetSlice( - self.as_ptr(), - get_ssize_index(low), - get_ssize_index(high), - )) - } + self.as_borrowed().get_slice(low, high).into_gil_ref() } /// Gets the tuple item at the specified index. @@ -153,10 +177,9 @@ impl PyTuple { /// # } /// ``` pub fn get_item(&self, index: usize) -> PyResult<&PyAny> { - unsafe { - let item = ffi::PyTuple_GetItem(self.as_ptr(), index as Py_ssize_t); - self.py().from_borrowed_ptr_or_err(item) - } + self.as_borrowed() + .get_borrowed_item(index) + .map(Borrowed::into_gil_ref) } /// Gets the tuple item at the specified index. Undefined behavior on bad index. Use with caution. @@ -166,8 +189,9 @@ impl PyTuple { /// Caller must verify that the index is within the bounds of the tuple. #[cfg(not(any(Py_LIMITED_API, PyPy)))] pub unsafe fn get_item_unchecked(&self, index: usize) -> &PyAny { - let item = ffi::PyTuple_GET_ITEM(self.as_ptr(), index as Py_ssize_t); - self.py().from_borrowed_ptr(item) + self.as_borrowed() + .get_borrowed_item_unchecked(index) + .into_gil_ref() } /// Returns `self` as a slice of objects. @@ -190,7 +214,7 @@ impl PyTuple { where V: ToPyObject, { - self.as_sequence().contains(value) + self.as_borrowed().contains(value) } /// Returns the first index `i` for which `self[i] == value`. @@ -201,54 +225,368 @@ impl PyTuple { where V: ToPyObject, { - self.as_sequence().index(value) + self.as_borrowed().index(value) } /// Returns an iterator over the tuple items. pub fn iter(&self) -> PyTupleIterator<'_> { - PyTupleIterator { - tuple: self, - index: 0, - length: self.len(), - } + PyTupleIterator(BorrowedTupleIterator::new(self.as_borrowed())) } /// Return a new list containing the contents of this tuple; equivalent to the Python expression `list(tuple)`. /// /// This method is equivalent to `self.as_sequence().to_list()` and faster than `PyList::new(py, self)`. pub fn to_list(&self) -> &PyList { + self.as_borrowed().to_list().into_gil_ref() + } +} + +index_impls!(PyTuple, "tuple", PyTuple::len, PyTuple::get_slice); + +/// Implementation of functionality for [`PyTuple`]. +/// +/// These methods are defined for the `Bound<'py, PyTuple>` smart pointer, so to use method call +/// syntax these methods are separated into a trait, because stable Rust does not yet support +/// `arbitrary_self_types`. +#[doc(alias = "PyTuple")] +pub trait PyTupleMethods<'py> { + /// Gets the length of the tuple. + fn len(&self) -> usize; + + /// Checks if the tuple is empty. + fn is_empty(&self) -> bool; + + /// Returns `self` cast as a `PySequence`. + fn as_sequence(&self) -> &Bound<'py, PySequence>; + + /// 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()`. + fn get_slice(&self, low: usize, high: usize) -> Bound<'py, PyTuple>; + + /// Gets the tuple item at the specified index. + /// # Example + /// ``` + /// use pyo3::{prelude::*, types::PyTuple}; + /// + /// # fn main() -> PyResult<()> { + /// Python::with_gil(|py| -> PyResult<()> { + /// let ob = (1, 2, 3).to_object(py); + /// let tuple: &PyTuple = ob.downcast(py).unwrap(); + /// let obj = tuple.get_item(0); + /// assert_eq!(obj.unwrap().extract::().unwrap(), 1); + /// Ok(()) + /// }) + /// # } + /// ``` + fn get_item(&self, index: usize) -> PyResult>; + + /// Like [`get_item`][PyTupleMethods::get_item], but returns a borrowed object, which is a slight performance optimization + /// by avoiding a reference count change. + fn get_borrowed_item<'a>(&'a self, index: usize) -> PyResult>; + + /// Gets the tuple item at the specified index. Undefined behavior on bad index. Use with caution. + /// + /// # Safety + /// + /// Caller must verify that the index is within the bounds of the tuple. + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + unsafe fn get_item_unchecked(&self, index: usize) -> Bound<'py, PyAny>; + + /// Like [`get_item_unchecked`][PyTupleMethods::get_item_unchecked], but returns a borrowed object, + /// which is a slight performance optimization by avoiding a reference count change. + /// + /// # Safety + /// + /// Caller must verify that the index is within the bounds of the tuple. + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + unsafe fn get_borrowed_item_unchecked<'a>(&'a self, index: usize) -> Borrowed<'a, 'py, PyAny>; + + /// Returns `self` as a slice of objects. + #[cfg(not(Py_LIMITED_API))] + fn as_slice(&self) -> &[Bound<'py, PyAny>]; + + /// Determines if self contains `value`. + /// + /// This is equivalent to the Python expression `value in self`. + fn contains(&self, value: V) -> PyResult + where + V: ToPyObject; + + /// Returns the first index `i` for which `self[i] == value`. + /// + /// This is equivalent to the Python expression `self.index(value)`. + fn index(&self, value: V) -> PyResult + where + V: ToPyObject; + + /// Returns an iterator over the tuple items. + fn iter(&self) -> BoundTupleIterator<'py>; + + /// Like [`iter`][PyTupleMethods::iter], but produces an iterator which returns borrowed objects, + /// which is a slight performance optimization by avoiding a reference count change. + fn iter_borrowed<'a>(&'a self) -> BorrowedTupleIterator<'a, 'py>; + + /// Return a new list containing the contents of this tuple; equivalent to the Python expression `list(tuple)`. + /// + /// This method is equivalent to `self.as_sequence().to_list()` and faster than `PyList::new(py, self)`. + fn to_list(&self) -> Bound<'py, PyList>; +} + +impl<'py> PyTupleMethods<'py> for Bound<'py, PyTuple> { + fn len(&self) -> usize { + unsafe { + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + let size = ffi::PyTuple_GET_SIZE(self.as_ptr()); + #[cfg(any(Py_LIMITED_API, PyPy))] + let size = ffi::PyTuple_Size(self.as_ptr()); + // non-negative Py_ssize_t should always fit into Rust uint + size as usize + } + } + + fn is_empty(&self) -> bool { + self.len() == 0 + } + + fn as_sequence(&self) -> &Bound<'py, PySequence> { + unsafe { self.downcast_unchecked() } + } + + fn get_slice(&self, low: usize, high: usize) -> Bound<'py, PyTuple> { + unsafe { + ffi::PyTuple_GetSlice(self.as_ptr(), get_ssize_index(low), get_ssize_index(high)) + .assume_owned(self.py()) + .downcast_into_unchecked() + } + } + + fn get_item(&self, index: usize) -> PyResult> { + self.get_borrowed_item(index).map(Borrowed::to_owned) + } + + fn get_borrowed_item<'a>(&'a self, index: usize) -> PyResult> { + self.as_borrowed().get_borrowed_item(index) + } + + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + unsafe fn get_item_unchecked(&self, index: usize) -> Bound<'py, PyAny> { + self.get_borrowed_item_unchecked(index).to_owned() + } + + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + unsafe fn get_borrowed_item_unchecked<'a>(&'a self, index: usize) -> Borrowed<'a, 'py, PyAny> { + self.as_borrowed().get_borrowed_item_unchecked(index) + } + + #[cfg(not(Py_LIMITED_API))] + fn as_slice(&self) -> &[Bound<'py, PyAny>] { + // This is safe because Bound<'py, PyAny> has the same memory layout as *mut ffi::PyObject, + // and because tuples are immutable. + unsafe { + let ptr = self.as_ptr() as *mut ffi::PyTupleObject; + let slice = std::slice::from_raw_parts((*ptr).ob_item.as_ptr(), self.len()); + &*(slice as *const [*mut ffi::PyObject] as *const [Bound<'py, PyAny>]) + } + } + + #[inline] + fn contains(&self, value: V) -> PyResult + where + V: ToPyObject, + { + self.as_sequence().contains(value) + } + + #[inline] + fn index(&self, value: V) -> PyResult + where + V: ToPyObject, + { + self.as_sequence().index(value) + } + + fn iter(&self) -> BoundTupleIterator<'py> { + BoundTupleIterator::new(self.clone()) + } + + fn iter_borrowed<'a>(&'a self) -> BorrowedTupleIterator<'a, 'py> { + BorrowedTupleIterator::new(self.as_borrowed()) + } + + fn to_list(&self) -> Bound<'py, PyList> { self.as_sequence() .to_list() .expect("failed to convert tuple to list") } } -index_impls!(PyTuple, "tuple", PyTuple::len, PyTuple::get_slice); +impl<'a, 'py> Borrowed<'a, 'py, PyTuple> { + fn get_borrowed_item(self, index: usize) -> PyResult> { + unsafe { + ffi::PyTuple_GetItem(self.as_ptr(), index as Py_ssize_t) + .assume_borrowed_or_err(self.py()) + } + } + + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + unsafe fn get_borrowed_item_unchecked(self, index: usize) -> Borrowed<'a, 'py, PyAny> { + ffi::PyTuple_GET_ITEM(self.as_ptr(), index as Py_ssize_t).assume_borrowed(self.py()) + } +} /// Used by `PyTuple::iter()`. -pub struct PyTupleIterator<'a> { - tuple: &'a PyTuple, +pub struct PyTupleIterator<'a>(BorrowedTupleIterator<'a, 'a>); + +impl<'a> Iterator for PyTupleIterator<'a> { + type Item = &'a PyAny; + + #[inline] + fn next(&mut self) -> Option { + self.0.next().map(Borrowed::into_gil_ref) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl<'a> DoubleEndedIterator for PyTupleIterator<'a> { + #[inline] + fn next_back(&mut self) -> Option { + self.0.next_back().map(Borrowed::into_gil_ref) + } +} + +impl<'a> ExactSizeIterator for PyTupleIterator<'a> { + fn len(&self) -> usize { + self.0.len() + } +} + +impl FusedIterator for PyTupleIterator<'_> {} + +impl<'a> IntoIterator for &'a PyTuple { + type Item = &'a PyAny; + type IntoIter = PyTupleIterator<'a>; + + fn into_iter(self) -> Self::IntoIter { + PyTupleIterator(BorrowedTupleIterator::new(self.as_borrowed())) + } +} + +/// Used by `PyTuple::into_iter()`. +pub struct BoundTupleIterator<'py> { + tuple: Bound<'py, PyTuple>, + index: usize, + length: usize, +} + +impl<'py> BoundTupleIterator<'py> { + fn new(tuple: Bound<'py, PyTuple>) -> Self { + let length = tuple.len(); + BoundTupleIterator { + tuple, + index: 0, + length, + } + } +} + +impl<'py> Iterator for BoundTupleIterator<'py> { + type Item = Bound<'py, PyAny>; + + #[inline] + fn next(&mut self) -> Option { + if self.index < self.length { + let item = unsafe { + BorrowedTupleIterator::get_item(self.tuple.as_borrowed(), self.index).to_owned() + }; + self.index += 1; + Some(item) + } else { + None + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let len = self.len(); + (len, Some(len)) + } +} + +impl<'py> DoubleEndedIterator for BoundTupleIterator<'py> { + #[inline] + fn next_back(&mut self) -> Option { + if self.index < self.length { + let item = unsafe { + BorrowedTupleIterator::get_item(self.tuple.as_borrowed(), self.length - 1) + .to_owned() + }; + self.length -= 1; + Some(item) + } else { + None + } + } +} + +impl<'py> ExactSizeIterator for BoundTupleIterator<'py> { + fn len(&self) -> usize { + self.length.saturating_sub(self.index) + } +} + +impl FusedIterator for BoundTupleIterator<'_> {} + +impl<'py> IntoIterator for Bound<'py, PyTuple> { + type Item = Bound<'py, PyAny>; + type IntoIter = BoundTupleIterator<'py>; + + fn into_iter(self) -> Self::IntoIter { + BoundTupleIterator::new(self) + } +} + +/// Used by `PyTuple::iter_borrowed()`. +pub struct BorrowedTupleIterator<'a, 'py> { + tuple: Borrowed<'a, 'py, PyTuple>, index: usize, length: usize, } -impl<'a> PyTupleIterator<'a> { - unsafe fn get_item(&self, index: usize) -> &'a PyAny { +impl<'a, 'py> BorrowedTupleIterator<'a, 'py> { + fn new(tuple: Borrowed<'a, 'py, PyTuple>) -> Self { + let length = tuple.len(); + BorrowedTupleIterator { + tuple, + index: 0, + length, + } + } + + unsafe fn get_item( + tuple: Borrowed<'a, 'py, PyTuple>, + index: usize, + ) -> Borrowed<'a, 'py, PyAny> { #[cfg(any(Py_LIMITED_API, PyPy))] - let item = self.tuple.get_item(index).expect("tuple.get failed"); + let item = tuple.get_borrowed_item(index).expect("tuple.get failed"); #[cfg(not(any(Py_LIMITED_API, PyPy)))] - let item = self.tuple.get_item_unchecked(index); + let item = tuple.get_borrowed_item_unchecked(index); item } } -impl<'a> Iterator for PyTupleIterator<'a> { - type Item = &'a PyAny; +impl<'a, 'py> Iterator for BorrowedTupleIterator<'a, 'py> { + type Item = Borrowed<'a, 'py, PyAny>; #[inline] fn next(&mut self) -> Option { if self.index < self.length { - let item = unsafe { self.get_item(self.index) }; + let item = unsafe { Self::get_item(self.tuple, self.index) }; self.index += 1; Some(item) } else { @@ -263,11 +601,11 @@ impl<'a> Iterator for PyTupleIterator<'a> { } } -impl<'a> DoubleEndedIterator for PyTupleIterator<'a> { +impl<'a, 'py> DoubleEndedIterator for BorrowedTupleIterator<'a, 'py> { #[inline] fn next_back(&mut self) -> Option { if self.index < self.length { - let item = unsafe { self.get_item(self.length - 1) }; + let item = unsafe { Self::get_item(self.tuple, self.length - 1) }; self.length -= 1; Some(item) } else { @@ -276,20 +614,17 @@ impl<'a> DoubleEndedIterator for PyTupleIterator<'a> { } } -impl<'a> ExactSizeIterator for PyTupleIterator<'a> { +impl<'a, 'py> ExactSizeIterator for BorrowedTupleIterator<'a, 'py> { fn len(&self) -> usize { self.length.saturating_sub(self.index) } } -impl FusedIterator for PyTupleIterator<'_> {} - -impl<'a> IntoIterator for &'a PyTuple { - type Item = &'a PyAny; - type IntoIter = PyTupleIterator<'a>; +impl FusedIterator for BorrowedTupleIterator<'_, '_> {} - fn into_iter(self) -> Self::IntoIter { - self.iter() +impl IntoPy> for Bound<'_, PyTuple> { + fn into_py(self, _: Python<'_>) -> Py { + self.unbind() } } @@ -334,7 +669,7 @@ fn type_output() -> TypeInfo { impl<'s, $($T: FromPyObject<'s>),+> FromPyObject<'s> for ($($T,)+) { fn extract(obj: &'s PyAny) -> PyResult { - let t: &PyTuple = obj.downcast()?; + let t = obj.downcast::()?; if t.len() == $length { #[cfg(any(Py_LIMITED_API, PyPy))] return Ok(($(t.get_item($n)?.extract::<$T>()?,)+)); @@ -472,8 +807,9 @@ tuple_conversion!( ); #[cfg(test)] +#[allow(deprecated)] // TODO: remove allow when GIL Pool is removed mod tests { - use crate::types::{PyAny, PyList, PyTuple}; + use crate::types::{any::PyAnyMethods, tuple::PyTupleMethods, PyAny, PyList, PyTuple}; use crate::{Python, ToPyObject}; use std::collections::HashSet; @@ -498,11 +834,21 @@ mod tests { let ob = (1, 2, 3).to_object(py); let tuple: &PyTuple = ob.downcast(py).unwrap(); assert_eq!(3, tuple.len()); + assert!(!tuple.is_empty()); let ob: &PyAny = tuple.into(); assert_eq!((1, 2, 3), ob.extract().unwrap()); }); } + #[test] + fn test_empty() { + Python::with_gil(|py| { + let tuple = PyTuple::empty(py); + assert!(tuple.is_empty()); + assert_eq!(0, tuple.len()); + }); + } + #[test] fn test_slice() { Python::with_gil(|py| { @@ -562,6 +908,52 @@ mod tests { }); } + #[test] + fn test_bound_iter() { + Python::with_gil(|py| { + let tuple = PyTuple::new_bound(py, [1, 2, 3]); + assert_eq!(3, tuple.len()); + let mut iter = tuple.iter(); + + assert_eq!(iter.size_hint(), (3, Some(3))); + + assert_eq!(1_i32, iter.next().unwrap().extract::<'_, i32>().unwrap()); + assert_eq!(iter.size_hint(), (2, Some(2))); + + assert_eq!(2_i32, iter.next().unwrap().extract::<'_, i32>().unwrap()); + assert_eq!(iter.size_hint(), (1, Some(1))); + + assert_eq!(3_i32, iter.next().unwrap().extract::<'_, i32>().unwrap()); + assert_eq!(iter.size_hint(), (0, Some(0))); + + assert!(iter.next().is_none()); + assert!(iter.next().is_none()); + }); + } + + #[test] + fn test_bound_iter_rev() { + Python::with_gil(|py| { + let tuple = PyTuple::new_bound(py, [1, 2, 3]); + assert_eq!(3, tuple.len()); + let mut iter = tuple.iter().rev(); + + assert_eq!(iter.size_hint(), (3, Some(3))); + + assert_eq!(3_i32, iter.next().unwrap().extract::<'_, i32>().unwrap()); + assert_eq!(iter.size_hint(), (2, Some(2))); + + assert_eq!(2_i32, iter.next().unwrap().extract::<'_, i32>().unwrap()); + assert_eq!(iter.size_hint(), (1, Some(1))); + + assert_eq!(1_i32, iter.next().unwrap().extract::<'_, i32>().unwrap()); + assert_eq!(iter.size_hint(), (0, Some(0))); + + assert!(iter.next().is_none()); + assert!(iter.next().is_none()); + }); + } + #[test] fn test_into_iter() { Python::with_gil(|py| { @@ -984,4 +1376,50 @@ mod tests { assert!(list.eq(list_expected).unwrap()); }) } + + #[test] + fn test_tuple_as_sequence() { + Python::with_gil(|py| { + let tuple = PyTuple::new(py, vec![1, 2, 3]); + let sequence = tuple.as_sequence(); + assert!(tuple.get_item(0).unwrap().eq(1).unwrap()); + assert!(sequence.get_item(0).unwrap().eq(1).unwrap()); + + assert_eq!(tuple.len(), 3); + assert_eq!(sequence.len().unwrap(), 3); + }) + } + + #[test] + fn test_bound_tuple_get_item() { + Python::with_gil(|py| { + let tuple = PyTuple::new_bound(py, vec![1, 2, 3, 4]); + + assert_eq!(tuple.len(), 4); + assert_eq!(tuple.get_item(0).unwrap().extract::().unwrap(), 1); + assert_eq!( + tuple + .get_borrowed_item(1) + .unwrap() + .extract::() + .unwrap(), + 2 + ); + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + { + assert_eq!( + unsafe { tuple.get_item_unchecked(2) } + .extract::() + .unwrap(), + 3 + ); + assert_eq!( + unsafe { tuple.get_borrowed_item_unchecked(3) } + .extract::() + .unwrap(), + 4 + ); + } + }) + } } diff --git a/tests/test_frompyobject.rs b/tests/test_frompyobject.rs index 47e5ec53e92..30edf6f7836 100644 --- a/tests/test_frompyobject.rs +++ b/tests/test_frompyobject.rs @@ -162,11 +162,11 @@ pub struct Tuple(String, usize); #[test] fn test_tuple_struct() { Python::with_gil(|py| { - let tup = PyTuple::new(py, &[1.into_py(py), "test".into_py(py)]); - let tup = Tuple::extract(tup.as_ref()); + let tup = PyTuple::new_bound(py, &[1.into_py(py), "test".into_py(py)]); + let tup = Tuple::extract(tup.as_gil_ref()); assert!(tup.is_err()); - let tup = PyTuple::new(py, &["test".into_py(py), 1.into_py(py)]); - let tup = Tuple::extract(tup.as_ref()).expect("Failed to extract Tuple from PyTuple"); + let tup = PyTuple::new_bound(py, &["test".into_py(py), 1.into_py(py)]); + let tup = Tuple::extract(tup.as_gil_ref()).expect("Failed to extract Tuple from PyTuple"); assert_eq!(tup.0, "test"); assert_eq!(tup.1, 1); }); @@ -324,8 +324,8 @@ pub struct PyBool { #[test] fn test_enum() { Python::with_gil(|py| { - let tup = PyTuple::new(py, &[1.into_py(py), "test".into_py(py)]); - let f = Foo::extract(tup.as_ref()).expect("Failed to extract Foo from tuple"); + let tup = PyTuple::new_bound(py, &[1.into_py(py), "test".into_py(py)]); + let f = Foo::extract(tup.as_gil_ref()).expect("Failed to extract Foo from tuple"); match f { Foo::TupleVar(test, test2) => { assert_eq!(test, 1); @@ -401,8 +401,8 @@ TypeError: failed to extract enum Foo ('TupleVar | StructVar | TransparentTuple - variant StructWithGetItemArg (StructWithGetItemArg): KeyError: 'foo'" ); - let tup = PyTuple::empty(py); - let err = Foo::extract(tup.as_ref()).unwrap_err(); + let tup = PyTuple::empty_bound(py); + let err = Foo::extract(tup.as_gil_ref()).unwrap_err(); assert_eq!( err.to_string(), "\ diff --git a/tests/test_various.rs b/tests/test_various.rs index 076d2ba2cb5..6560610f35f 100644 --- a/tests/test_various.rs +++ b/tests/test_various.rs @@ -91,7 +91,7 @@ fn intopytuple_pyclass() { #[test] fn pytuple_primitive_iter() { Python::with_gil(|py| { - let tup = PyTuple::new(py, [1u32, 2, 3].iter()); + let tup = PyTuple::new_bound(py, [1u32, 2, 3].iter()); py_assert!(py, tup, "tup == (1, 2, 3)"); }); } @@ -99,7 +99,7 @@ fn pytuple_primitive_iter() { #[test] fn pytuple_pyclass_iter() { Python::with_gil(|py| { - let tup = PyTuple::new( + let tup = PyTuple::new_bound( py, [ PyCell::new(py, SimplePyClass {}).unwrap(), @@ -126,10 +126,10 @@ impl PickleSupport { pub fn __reduce__<'py>( slf: &'py PyCell, py: Python<'py>, - ) -> PyResult<(PyObject, &'py PyTuple, PyObject)> { + ) -> PyResult<(PyObject, Bound<'py, PyTuple>, PyObject)> { let cls = slf.to_object(py).getattr(py, "__class__")?; let dict = slf.to_object(py).getattr(py, "__dict__")?; - Ok((cls, PyTuple::empty(py), dict)) + Ok((cls, PyTuple::empty_bound(py), dict)) } }