diff --git a/newsfragments/5661.added.md b/newsfragments/5661.added.md new file mode 100644 index 00000000000..fb4e9eaed6d --- /dev/null +++ b/newsfragments/5661.added.md @@ -0,0 +1 @@ +Add FFI definition `PyIter_NextItem` on Python 3.14 and up, and `compat::PyIter_NextItem` for older versions. diff --git a/newsfragments/5661.changed.md b/newsfragments/5661.changed.md new file mode 100644 index 00000000000..2706c47e7a4 --- /dev/null +++ b/newsfragments/5661.changed.md @@ -0,0 +1 @@ +Use `PyIter_NextItem` in `PyIterator::next` implementation. diff --git a/pyo3-ffi/src/abstract_.rs b/pyo3-ffi/src/abstract_.rs index 712a0739b43..8f2b4831231 100644 --- a/pyo3-ffi/src/abstract_.rs +++ b/pyo3-ffi/src/abstract_.rs @@ -145,6 +145,9 @@ extern "C" { #[cfg_attr(PyPy, link_name = "PyPyIter_Check")] pub fn PyIter_Check(obj: *mut PyObject) -> c_int; + #[cfg(Py_3_14)] + #[cfg_attr(PyPy, link_name = "PyPyIter_NextItem")] + pub fn PyIter_NextItem(iter: *mut PyObject, item: *mut *mut PyObject) -> c_int; #[cfg_attr(PyPy, link_name = "PyPyIter_Next")] pub fn PyIter_Next(arg1: *mut PyObject) -> *mut PyObject; #[cfg(all(not(PyPy), Py_3_10))] diff --git a/pyo3-ffi/src/compat/py_3_14.rs b/pyo3-ffi/src/compat/py_3_14.rs index 859d26ea777..28cef7975c9 100644 --- a/pyo3-ffi/src/compat/py_3_14.rs +++ b/pyo3-ffi/src/compat/py_3_14.rs @@ -24,3 +24,22 @@ compat_function!( } } ); + +compat_function!( + originally_defined_for(Py_3_14); + + #[inline] + pub unsafe fn PyIter_NextItem( + iter: *mut crate::PyObject, + item: *mut *mut crate::PyObject, + ) -> std::ffi::c_int { + *item = crate::PyIter_Next(iter); + if !(*item).is_null() { + 1 + } else if crate::PyErr_Occurred().is_null() { + 0 + } else { + -1 + } + } +); diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 3e18969e420..817c1ac0eb7 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -1,5 +1,4 @@ use crate::ffi_ptr_ext::FfiPtrExt; -use crate::instance::Borrowed; use crate::py_result_ext::PyResultExt; use crate::sync::PyOnceLock; #[cfg(Py_LIMITED_API)] @@ -105,9 +104,17 @@ impl<'py> Iterator for Bound<'py, PyIterator> { /// If an exception occurs, returns `Some(Err(..))`. /// Further `next()` calls after an exception occurs are likely /// to repeatedly result in the same exception. - #[inline] fn next(&mut self) -> Option { - Borrowed::from(&*self).next() + let py = self.py(); + let mut item = std::ptr::null_mut(); + + // SAFETY: `self` is a valid iterator object, `item` is a valid pointer to receive the next item + match unsafe { ffi::compat::PyIter_NextItem(self.as_ptr(), &mut item) } { + std::ffi::c_int::MIN..=-1 => Some(Err(PyErr::fetch(py))), + 0 => None, + // SAFETY: `item` is guaranteed to be a non-null strong reference + 1..=std::ffi::c_int::MAX => Some(Ok(unsafe { item.assume_owned_unchecked(py) })), + } } fn size_hint(&self) -> (usize, Option) { @@ -141,19 +148,6 @@ fn length_hint(iter: &Bound<'_, PyIterator>) -> PyResult { length_hint.call1((iter, 0))?.extract() } -impl<'py> Borrowed<'_, 'py, PyIterator> { - // TODO: this method is on Borrowed so that &'py PyIterator can use this; once that - // implementation is deleted this method should be moved to the `Bound<'py, PyIterator> impl - fn next(self) -> Option>> { - let py = self.py(); - - match unsafe { ffi::PyIter_Next(self.as_ptr()).assume_owned_or_opt(py) } { - Some(obj) => Some(Ok(obj)), - None => PyErr::take(py).map(Err), - } - } -} - impl<'py> IntoIterator for &Bound<'py, PyIterator> { type Item = PyResult>; type IntoIter = Bound<'py, PyIterator>;