Skip to content

Commit b467428

Browse files
authored
fall back to operator.length_hint on abi3 (#5727)
* fall back to `operator.length_hint` on abi3 * newsfragment
1 parent 2078731 commit b467428

File tree

2 files changed

+31
-12
lines changed

2 files changed

+31
-12
lines changed

newsfragments/5727.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implement `PyIterator::size_hint` on abi3 builds (previously was only on unlimited API builds).

src/types/iterator.rs

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ use crate::ffi_ptr_ext::FfiPtrExt;
22
use crate::instance::Borrowed;
33
use crate::py_result_ext::PyResultExt;
44
use crate::sync::PyOnceLock;
5+
#[cfg(Py_LIMITED_API)]
6+
use crate::types::PyAnyMethods;
57
use crate::types::{PyType, PyTypeMethods};
68
use crate::{ffi, Bound, Py, PyAny, PyErr, PyResult};
79

@@ -108,20 +110,37 @@ impl<'py> Iterator for Bound<'py, PyIterator> {
108110
Borrowed::from(&*self).next()
109111
}
110112

111-
#[cfg(not(Py_LIMITED_API))]
112113
fn size_hint(&self) -> (usize, Option<usize>) {
113-
// SAFETY: `self` is a valid iterator object
114-
let hint = unsafe { ffi::PyObject_LengthHint(self.as_ptr(), 0) };
115-
if hint < 0 {
116-
let py = self.py();
117-
PyErr::fetch(py).write_unraisable(py, Some(self));
118-
(0, None)
119-
} else {
120-
(hint as usize, None)
114+
match length_hint(self) {
115+
Ok(hint) => (hint, None),
116+
Err(e) => {
117+
e.write_unraisable(self.py(), Some(self));
118+
(0, None)
119+
}
121120
}
122121
}
123122
}
124123

124+
#[cfg(not(Py_LIMITED_API))]
125+
fn length_hint(iter: &Bound<'_, PyIterator>) -> PyResult<usize> {
126+
// SAFETY: `iter` is a valid iterator object
127+
let hint = unsafe { ffi::PyObject_LengthHint(iter.as_ptr(), 0) };
128+
if hint < 0 {
129+
Err(PyErr::fetch(iter.py()))
130+
} else {
131+
Ok(hint as usize)
132+
}
133+
}
134+
135+
/// On the limited API, we cannot use `PyObject_LengthHint`, so we fall back to calling
136+
/// `operator.length_hint()`, which is documented equivalent to calling `PyObject_LengthHint`.
137+
#[cfg(Py_LIMITED_API)]
138+
fn length_hint(iter: &Bound<'_, PyIterator>) -> PyResult<usize> {
139+
static LENGTH_HINT: PyOnceLock<Py<PyAny>> = PyOnceLock::new();
140+
let length_hint = LENGTH_HINT.import(iter.py(), "operator", "length_hint")?;
141+
length_hint.call1((iter, 0))?.extract()
142+
}
143+
125144
impl<'py> Borrowed<'_, 'py, PyIterator> {
126145
// TODO: this method is on Borrowed so that &'py PyIterator can use this; once that
127146
// implementation is deleted this method should be moved to the `Bound<'py, PyIterator> impl
@@ -153,7 +172,7 @@ mod tests {
153172
#[cfg(all(not(PyPy), Py_3_10))]
154173
use crate::types::PyNone;
155174
use crate::types::{PyAnyMethods, PyDict, PyList, PyListMethods};
156-
#[cfg(all(feature = "macros", Py_3_8, not(Py_LIMITED_API)))]
175+
#[cfg(all(feature = "macros", Py_3_8))]
157176
use crate::PyErr;
158177
use crate::{IntoPyObject, PyTypeInfo, Python};
159178

@@ -393,7 +412,6 @@ def fibonacci(target):
393412
}
394413

395414
#[test]
396-
#[cfg(not(Py_LIMITED_API))]
397415
fn length_hint_becomes_size_hint_lower_bound() {
398416
Python::attach(|py| {
399417
let list = py.eval(c"[1, 2, 3]", None, None).unwrap();
@@ -404,7 +422,7 @@ def fibonacci(target):
404422
}
405423

406424
#[test]
407-
#[cfg(all(feature = "macros", Py_3_8, not(Py_LIMITED_API)))]
425+
#[cfg(all(feature = "macros", Py_3_8))]
408426
fn length_hint_error() {
409427
#[crate::pyfunction(crate = "crate")]
410428
fn test_size_hint(obj: &crate::Bound<'_, crate::PyAny>, should_error: bool) {

0 commit comments

Comments
 (0)