diff --git a/newsfragments/3366.added.md b/newsfragments/3366.added.md new file mode 100644 index 00000000..ffc4eff8 --- /dev/null +++ b/newsfragments/3366.added.md @@ -0,0 +1 @@ +Add implementation `DoubleEndedIterator` for `PyTupleIterator`. diff --git a/newsfragments/3366.fixed.md b/newsfragments/3366.fixed.md new file mode 100644 index 00000000..093f863d --- /dev/null +++ b/newsfragments/3366.fixed.md @@ -0,0 +1 @@ +The `PyTupleIterator` type returned by `PyTuple::iter` is now public and hence can be named by downstream crates. diff --git a/src/buffer.rs b/src/buffer.rs index eb48a756..f9dca60e 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -495,7 +495,7 @@ impl PyBuffer { err::error_on_minusone(py, unsafe { ffi::PyBuffer_ToContiguous( - target.as_ptr() as *mut raw::c_void, + target.as_mut_ptr().cast(), #[cfg(Py_3_11)] &*self.0, #[cfg(not(Py_3_11))] diff --git a/src/types/mod.rs b/src/types/mod.rs index 6cf52593..eb862655 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -75,6 +75,7 @@ pub mod iter { pub use super::dict::PyDictIterator; pub use super::frozenset::PyFrozenSetIterator; pub use super::set::PySetIterator; + pub use super::tuple::PyTupleIterator; } // Implementations core to all native types diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 687acae0..ddaec423 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -1,4 +1,5 @@ use std::convert::TryInto; +use std::iter::FusedIterator; use crate::ffi::{self, Py_ssize_t}; #[cfg(feature = "experimental-inspect")] @@ -232,16 +233,23 @@ pub struct PyTupleIterator<'a> { length: usize, } +impl<'a> PyTupleIterator<'a> { + unsafe fn get_item(&self, index: usize) -> &'a PyAny { + #[cfg(any(Py_LIMITED_API, PyPy))] + let item = self.tuple.get_item(index).expect("tuple.get failed"); + #[cfg(not(any(Py_LIMITED_API, PyPy)))] + let item = self.tuple.get_item_unchecked(index); + item + } +} + impl<'a> Iterator for PyTupleIterator<'a> { type Item = &'a PyAny; #[inline] - fn next(&mut self) -> Option<&'a PyAny> { + fn next(&mut self) -> Option { if self.index < self.length { - #[cfg(any(Py_LIMITED_API, PyPy))] - let item = self.tuple.get_item(self.index).expect("tuple.get failed"); - #[cfg(not(any(Py_LIMITED_API, PyPy)))] - let item = unsafe { self.tuple.get_item_unchecked(self.index) }; + let item = unsafe { self.get_item(self.index) }; self.index += 1; Some(item) } else { @@ -256,12 +264,27 @@ impl<'a> Iterator for PyTupleIterator<'a> { } } +impl<'a> DoubleEndedIterator for PyTupleIterator<'a> { + #[inline] + fn next_back(&mut self) -> Option { + if self.index < self.length { + let item = unsafe { self.get_item(self.length - 1) }; + self.length -= 1; + Some(item) + } else { + None + } + } +} + impl<'a> ExactSizeIterator for PyTupleIterator<'a> { 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>; @@ -510,6 +533,33 @@ mod tests { 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_iter_rev() { + Python::with_gil(|py| { + let ob = (1, 2, 3).to_object(py); + let tuple: &PyTuple = ob.downcast(py).unwrap(); + 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()); }); }