Skip to content

Commit 827a752

Browse files
Handle errors on PyIterator when calling size_hint
1 parent 5e35cc1 commit 827a752

File tree

1 file changed

+44
-2
lines changed

1 file changed

+44
-2
lines changed

src/types/iterator.rs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#[cfg(not(Py_LIMITED_API))]
2+
use crate::exceptions::PyNotImplementedError;
13
use crate::ffi_ptr_ext::FfiPtrExt;
24
use crate::instance::Borrowed;
35
use crate::py_result_ext::PyResultExt;
@@ -108,8 +110,19 @@ impl<'py> Iterator for Bound<'py, PyIterator> {
108110

109111
#[cfg(not(Py_LIMITED_API))]
110112
fn size_hint(&self) -> (usize, Option<usize>) {
113+
// SAFETY: `self` is a valid iterator object
111114
let hint = unsafe { ffi::PyObject_LengthHint(self.as_ptr(), 0) };
112-
(hint.max(0) as usize, None)
115+
if hint < 0 {
116+
let py = self.py();
117+
let err = PyErr::fetch(py);
118+
if !err.is_instance_of::<PyNotImplementedError>(py) {
119+
// Write unraisable error only if it's not NotImplementedError
120+
err.write_unraisable(py, Some(self));
121+
}
122+
(0, None)
123+
} else {
124+
(hint as usize, None)
125+
}
113126
}
114127
}
115128

@@ -144,7 +157,7 @@ mod tests {
144157
#[cfg(all(not(PyPy), Py_3_10))]
145158
use crate::types::PyNone;
146159
use crate::types::{PyAnyMethods, PyDict, PyList, PyListMethods};
147-
use crate::{IntoPyObject, PyTypeInfo, Python};
160+
use crate::{IntoPyObject, PyErr, PyTypeInfo, Python};
148161

149162
#[test]
150163
fn vec_iter() {
@@ -392,6 +405,35 @@ def fibonacci(target):
392405
});
393406
}
394407

408+
#[test]
409+
#[cfg(all(feature = "macros", not(Py_LIMITED_API)))]
410+
fn length_hint_not_implemented() {
411+
#[crate::pyfunction(crate = "crate")]
412+
fn test_size_hint(obj: &crate::Bound<'_, crate::PyAny>) {
413+
let iter = obj.cast::<PyIterator>().unwrap();
414+
assert_eq!((0, None), iter.size_hint());
415+
assert!(PyErr::take(obj.py()).is_none());
416+
}
417+
418+
Python::attach(|py| {
419+
let test_size_hint = crate::wrap_pyfunction!(test_size_hint, py).unwrap();
420+
crate::py_run!(
421+
py,
422+
test_size_hint,
423+
r#"
424+
class MyIter:
425+
def __next__(self):
426+
raise StopIteration
427+
428+
def __length_hint__(self):
429+
raise NotImplementedError
430+
431+
test_size_hint(MyIter())
432+
"#
433+
);
434+
});
435+
}
436+
395437
#[test]
396438
fn test_type_object() {
397439
Python::attach(|py| {

0 commit comments

Comments
 (0)