@@ -2,6 +2,8 @@ use crate::ffi_ptr_ext::FfiPtrExt;
22use crate :: instance:: Borrowed ;
33use crate :: py_result_ext:: PyResultExt ;
44use crate :: sync:: PyOnceLock ;
5+ #[ cfg( Py_LIMITED_API ) ]
6+ use crate :: types:: PyAnyMethods ;
57use crate :: types:: { PyType , PyTypeMethods } ;
68use 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+
125144impl < ' 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