diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index be6e1757bd1..0bffef174d6 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -787,9 +787,11 @@ pub const __RICHCMP__: SlotDef = SlotDef::new("Py_tp_richcompare", "richcmpfunc" const __GET__: SlotDef = SlotDef::new("Py_tp_descr_get", "descrgetfunc") .arguments(&[Ty::MaybeNullObject, Ty::MaybeNullObject]); const __ITER__: SlotDef = SlotDef::new("Py_tp_iter", "getiterfunc"); -const __NEXT__: SlotDef = SlotDef::new("Py_tp_iternext", "iternextfunc").return_conversion( - TokenGenerator(|| quote! { _pyo3::class::iter::IterNextOutput::<_, _> }), -); +const __NEXT__: SlotDef = SlotDef::new("Py_tp_iternext", "iternextfunc") + .return_specialized_conversion( + TokenGenerator(|| quote! { IterBaseKind, IterOptionKind, IterResultOptionKind }), + TokenGenerator(|| quote! { iter_tag }), + ); const __AWAIT__: SlotDef = SlotDef::new("Py_am_await", "unaryfunc"); const __AITER__: SlotDef = SlotDef::new("Py_am_aiter", "unaryfunc"); const __ANEXT__: SlotDef = SlotDef::new("Py_am_anext", "unaryfunc").return_conversion( @@ -987,17 +989,23 @@ fn extract_object( enum ReturnMode { ReturnSelf, Conversion(TokenGenerator), + SpecializedConversion(TokenGenerator, TokenGenerator), } impl ReturnMode { fn return_call_output(&self, call: TokenStream) -> TokenStream { match self { ReturnMode::Conversion(conversion) => quote! { - let _result: _pyo3::PyResult<#conversion> = #call; + let _result: _pyo3::PyResult<#conversion> = _pyo3::callback::convert(py, #call); _pyo3::callback::convert(py, _result) }, + ReturnMode::SpecializedConversion(traits, tag) => quote! { + let _result = #call; + use _pyo3::callback::{#traits}; + (&_result).#tag().convert(py, _result) + }, ReturnMode::ReturnSelf => quote! { - let _result: _pyo3::PyResult<()> = #call; + let _result: _pyo3::PyResult<()> = _pyo3::callback::convert(py, #call); _result?; _pyo3::ffi::Py_XINCREF(_raw_slf); ::std::result::Result::Ok(_raw_slf) @@ -1046,6 +1054,15 @@ impl SlotDef { self } + const fn return_specialized_conversion( + mut self, + traits: TokenGenerator, + tag: TokenGenerator, + ) -> Self { + self.return_mode = Some(ReturnMode::SpecializedConversion(traits, tag)); + self + } + const fn extract_error_mode(mut self, extract_error_mode: ExtractErrorMode) -> Self { self.extract_error_mode = extract_error_mode; self @@ -1142,11 +1159,11 @@ fn generate_method_body( let self_arg = spec.tp.self_arg(Some(cls), extract_error_mode); let rust_name = spec.name; let args = extract_proto_arguments(spec, arguments, extract_error_mode)?; - let call = quote! { _pyo3::callback::convert(py, #cls::#rust_name(#self_arg #(#args),*)) }; + let call = quote! { #cls::#rust_name(#self_arg #(#args),*) }; Ok(if let Some(return_mode) = return_mode { return_mode.return_call_output(call) } else { - call + quote! { _pyo3::callback::convert(py, #call) } }) } diff --git a/pytests/src/awaitable.rs b/pytests/src/awaitable.rs index 1f798aa4ec1..0cc173334b9 100644 --- a/pytests/src/awaitable.rs +++ b/pytests/src/awaitable.rs @@ -5,7 +5,8 @@ //! when awaited, see guide examples related to pyo3-asyncio for ways //! to suspend tasks and await results. -use pyo3::{prelude::*, pyclass::IterNextOutput}; +use pyo3::exceptions::PyStopIteration; +use pyo3::prelude::*; #[pyclass] #[derive(Debug)] @@ -30,13 +31,13 @@ impl IterAwaitable { pyself } - fn __next__(&mut self, py: Python<'_>) -> PyResult> { + fn __next__(&mut self, py: Python<'_>) -> PyResult { match self.result.take() { Some(res) => match res { - Ok(v) => Ok(IterNextOutput::Return(v)), + Ok(v) => Err(PyStopIteration::new_err(v)), Err(err) => Err(err), }, - _ => Ok(IterNextOutput::Yield(py.None().into())), + _ => Ok(py.None().into()), } } } @@ -66,15 +67,13 @@ impl FutureAwaitable { pyself } - fn __next__( - mut pyself: PyRefMut<'_, Self>, - ) -> PyResult, PyObject>> { + fn __next__(mut pyself: PyRefMut<'_, Self>) -> PyResult> { match pyself.result { Some(_) => match pyself.result.take().unwrap() { - Ok(v) => Ok(IterNextOutput::Return(v)), + Ok(v) => Err(PyStopIteration::new_err(v)), Err(err) => Err(err), }, - _ => Ok(IterNextOutput::Yield(pyself)), + _ => Ok(pyself), } } } diff --git a/pytests/src/pyclasses.rs b/pytests/src/pyclasses.rs index 46c8523c2dd..326893d123d 100644 --- a/pytests/src/pyclasses.rs +++ b/pytests/src/pyclasses.rs @@ -1,5 +1,4 @@ -use pyo3::exceptions::PyValueError; -use pyo3::iter::IterNextOutput; +use pyo3::exceptions::{PyStopIteration, PyValueError}; use pyo3::prelude::*; use pyo3::types::PyType; @@ -28,12 +27,12 @@ impl PyClassIter { Default::default() } - fn __next__(&mut self) -> IterNextOutput { + fn __next__(&mut self) -> PyResult { if self.count < 5 { self.count += 1; - IterNextOutput::Yield(self.count) + Ok(self.count) } else { - IterNextOutput::Return("Ended") + Err(PyStopIteration::new_err("Ended")) } } } diff --git a/src/callback.rs b/src/callback.rs index a56b268aa1e..d08f032aa4d 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -6,6 +6,7 @@ use crate::ffi::{self, Py_hash_t}; use crate::{IntoPy, PyObject, Python}; use std::isize; use std::os::raw::c_int; +use std::ptr::null_mut; /// A type which can be the return type of a python C-API callback pub trait PyCallbackOutput: Copy { @@ -176,3 +177,86 @@ where { value.convert(py) } + +// Autoref-based specialization to allow deprecation of __next__ returning `Option` + +#[doc(hidden)] +pub struct IterBaseTag; + +impl IterBaseTag { + #[inline] + pub fn convert(self, py: Python<'_>, value: Value) -> PyResult + where + Value: IntoPyCallbackOutput, + { + value.convert(py) + } +} + +#[doc(hidden)] +pub trait IterBaseKind { + fn iter_tag(&self) -> IterBaseTag { + IterBaseTag + } +} + +impl IterBaseKind for &Value {} + +#[doc(hidden)] +pub struct IterOptionTag; + +impl IterOptionTag { + #[inline] + pub fn convert( + self, + py: Python<'_>, + value: Option, + ) -> PyResult<*mut ffi::PyObject> + where + Value: IntoPyCallbackOutput<*mut ffi::PyObject>, + { + match value { + Some(value) => value.convert(py), + None => Ok(null_mut()), + } + } +} + +#[doc(hidden)] +pub trait IterOptionKind { + fn iter_tag(&self) -> IterOptionTag { + IterOptionTag + } +} + +impl IterOptionKind for Option {} + +#[doc(hidden)] +pub struct IterResultOptionTag; + +impl IterResultOptionTag { + #[inline] + pub fn convert( + self, + py: Python<'_>, + value: PyResult>, + ) -> PyResult<*mut ffi::PyObject> + where + Value: IntoPyCallbackOutput<*mut ffi::PyObject>, + { + match value { + Ok(Some(value)) => value.convert(py), + Ok(None) => Ok(null_mut()), + Err(err) => Err(err), + } + } +} + +#[doc(hidden)] +pub trait IterResultOptionKind { + fn iter_tag(&self) -> IterResultOptionTag { + IterResultOptionTag + } +} + +impl IterResultOptionKind for PyResult> {} diff --git a/src/coroutine.rs b/src/coroutine.rs index 6380b4e0a1f..7dd73cbba10 100644 --- a/src/coroutine.rs +++ b/src/coroutine.rs @@ -14,7 +14,6 @@ use crate::{ coroutine::{cancel::ThrowCallback, waker::AsyncioWaker}, exceptions::{PyAttributeError, PyRuntimeError, PyStopIteration}, panic::PanicException, - pyclass::IterNextOutput, types::{PyIterator, PyString}, IntoPy, Py, PyAny, PyErr, PyObject, PyResult, Python, }; @@ -68,11 +67,7 @@ impl Coroutine { } } - fn poll( - &mut self, - py: Python<'_>, - throw: Option, - ) -> PyResult> { + fn poll(&mut self, py: Python<'_>, throw: Option) -> PyResult { // raise if the coroutine has already been run to completion let future_rs = match self.future { Some(ref mut fut) => fut, @@ -100,7 +95,7 @@ impl Coroutine { match panic::catch_unwind(panic::AssertUnwindSafe(poll)) { Ok(Poll::Ready(res)) => { self.close(); - return Ok(IterNextOutput::Return(res?)); + return Err(PyStopIteration::new_err(res?)); } Err(err) => { self.close(); @@ -115,19 +110,12 @@ impl Coroutine { if let Some(future) = PyIterator::from_object(future).unwrap().next() { // future has not been leaked into Python for now, and Rust code can only call // `set_result(None)` in `Wake` implementation, so it's safe to unwrap - return Ok(IterNextOutput::Yield(future.unwrap().into())); + return Ok(future.unwrap().into()); } } // if waker has been waken during future polling, this is roughly equivalent to // `await asyncio.sleep(0)`, so just yield `None`. - Ok(IterNextOutput::Yield(py.None().into())) - } -} - -pub(crate) fn iter_result(result: IterNextOutput) -> PyResult { - match result { - IterNextOutput::Yield(ob) => Ok(ob), - IterNextOutput::Return(ob) => Err(PyStopIteration::new_err(ob)), + Ok(py.None().into()) } } @@ -153,11 +141,11 @@ impl Coroutine { } fn send(&mut self, py: Python<'_>, _value: &PyAny) -> PyResult { - iter_result(self.poll(py, None)?) + self.poll(py, None) } fn throw(&mut self, py: Python<'_>, exc: PyObject) -> PyResult { - iter_result(self.poll(py, Some(exc))?) + self.poll(py, Some(exc)) } fn close(&mut self) { @@ -170,7 +158,7 @@ impl Coroutine { self_ } - fn __next__(&mut self, py: Python<'_>) -> PyResult> { + fn __next__(&mut self, py: Python<'_>) -> PyResult { self.poll(py, None) } } diff --git a/src/lib.rs b/src/lib.rs index 985ec0aa4ff..802def0e1a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -354,17 +354,6 @@ pub mod class { pub use crate::pyclass::{IterANextOutput, PyIterANextOutput}; } - /// Old module which contained some implementation details of the `#[pyproto]` module. - /// - /// Prefer using the same content from `pyo3::pyclass`, e.g. `use pyo3::pyclass::IterNextOutput` instead - /// of `use pyo3::class::pyasync::IterNextOutput`. - /// - /// For compatibility reasons this has not yet been removed, however will be done so - /// once is resolved. - pub mod iter { - pub use crate::pyclass::{IterNextOutput, PyIterNextOutput}; - } - /// Old module which contained some implementation details of the `#[pyproto]` module. /// /// Prefer using the same content from `pyo3::pyclass`, e.g. `use pyo3::pyclass::PyTraverseError` instead diff --git a/src/pyclass.rs b/src/pyclass.rs index 23affb4bf51..df11cf9a7e0 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -85,87 +85,6 @@ impl CompareOp { } } -/// Output of `__next__` which can either `yield` the next value in the iteration, or -/// `return` a value to raise `StopIteration` in Python. -/// -/// Usage example: -/// -/// ```rust -/// use pyo3::prelude::*; -/// use pyo3::iter::IterNextOutput; -/// -/// #[pyclass] -/// struct PyClassIter { -/// count: usize, -/// } -/// -/// #[pymethods] -/// impl PyClassIter { -/// #[new] -/// pub fn new() -> Self { -/// PyClassIter { count: 0 } -/// } -/// -/// fn __next__(&mut self) -> IterNextOutput { -/// if self.count < 5 { -/// self.count += 1; -/// // Given an instance `counter`, First five `next(counter)` calls yield 1, 2, 3, 4, 5. -/// IterNextOutput::Yield(self.count) -/// } else { -/// // At the sixth time, we get a `StopIteration` with `'Ended'`. -/// // try: -/// // next(counter) -/// // except StopIteration as e: -/// // assert e.value == 'Ended' -/// IterNextOutput::Return("Ended") -/// } -/// } -/// } -/// ``` -pub enum IterNextOutput { - /// The value yielded by the iterator. - Yield(T), - /// The `StopIteration` object. - Return(U), -} - -/// Alias of `IterNextOutput` with `PyObject` yield & return values. -pub type PyIterNextOutput = IterNextOutput; - -impl IntoPyCallbackOutput<*mut ffi::PyObject> for PyIterNextOutput { - fn convert(self, _py: Python<'_>) -> PyResult<*mut ffi::PyObject> { - match self { - IterNextOutput::Yield(o) => Ok(o.into_ptr()), - IterNextOutput::Return(opt) => Err(crate::exceptions::PyStopIteration::new_err((opt,))), - } - } -} - -impl IntoPyCallbackOutput for IterNextOutput -where - T: IntoPy, - U: IntoPy, -{ - fn convert(self, py: Python<'_>) -> PyResult { - match self { - IterNextOutput::Yield(o) => Ok(IterNextOutput::Yield(o.into_py(py))), - IterNextOutput::Return(o) => Ok(IterNextOutput::Return(o.into_py(py))), - } - } -} - -impl IntoPyCallbackOutput for Option -where - T: IntoPy, -{ - fn convert(self, py: Python<'_>) -> PyResult { - match self { - Some(o) => Ok(PyIterNextOutput::Yield(o.into_py(py))), - None => Ok(PyIterNextOutput::Return(py.None().into())), - } - } -} - /// Output of `__anext__`. /// ///