Skip to content

Commit 6f81bd0

Browse files
committed
WIP: Drop IterNextOutput and deprecate __next__ returing Option
1 parent ff50285 commit 6f81bd0

File tree

5 files changed

+90
-118
lines changed

5 files changed

+90
-118
lines changed

pyo3-macros-backend/src/pymethod.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -787,9 +787,11 @@ pub const __RICHCMP__: SlotDef = SlotDef::new("Py_tp_richcompare", "richcmpfunc"
787787
const __GET__: SlotDef = SlotDef::new("Py_tp_descr_get", "descrgetfunc")
788788
.arguments(&[Ty::MaybeNullObject, Ty::MaybeNullObject]);
789789
const __ITER__: SlotDef = SlotDef::new("Py_tp_iter", "getiterfunc");
790-
const __NEXT__: SlotDef = SlotDef::new("Py_tp_iternext", "iternextfunc").return_conversion(
791-
TokenGenerator(|| quote! { _pyo3::class::iter::IterNextOutput::<_, _> }),
792-
);
790+
const __NEXT__: SlotDef = SlotDef::new("Py_tp_iternext", "iternextfunc")
791+
.return_specialized_conversion(
792+
TokenGenerator(|| quote! { IterBaseKind, IterOptionKind }),
793+
TokenGenerator(|| quote! { iter_tag }),
794+
);
793795
const __AWAIT__: SlotDef = SlotDef::new("Py_am_await", "unaryfunc");
794796
const __AITER__: SlotDef = SlotDef::new("Py_am_aiter", "unaryfunc");
795797
const __ANEXT__: SlotDef = SlotDef::new("Py_am_anext", "unaryfunc").return_conversion(
@@ -987,17 +989,23 @@ fn extract_object(
987989
enum ReturnMode {
988990
ReturnSelf,
989991
Conversion(TokenGenerator),
992+
SpecializedConversion(TokenGenerator, TokenGenerator),
990993
}
991994

992995
impl ReturnMode {
993996
fn return_call_output(&self, call: TokenStream) -> TokenStream {
994997
match self {
995998
ReturnMode::Conversion(conversion) => quote! {
996-
let _result: _pyo3::PyResult<#conversion> = #call;
999+
let _result: _pyo3::PyResult<#conversion> = _pyo3::callback::convert(py, #call);
9971000
_pyo3::callback::convert(py, _result)
9981001
},
1002+
ReturnMode::SpecializedConversion(traits, tag) => quote! {
1003+
let _result = #call;
1004+
use _pyo3::callback::{#traits};
1005+
(&_result).#tag().convert(py, _result)
1006+
},
9991007
ReturnMode::ReturnSelf => quote! {
1000-
let _result: _pyo3::PyResult<()> = #call;
1008+
let _result: _pyo3::PyResult<()> = _pyo3::callback::convert(py, #call);
10011009
_result?;
10021010
_pyo3::ffi::Py_XINCREF(_raw_slf);
10031011
::std::result::Result::Ok(_raw_slf)
@@ -1046,6 +1054,15 @@ impl SlotDef {
10461054
self
10471055
}
10481056

1057+
const fn return_specialized_conversion(
1058+
mut self,
1059+
traits: TokenGenerator,
1060+
tag: TokenGenerator,
1061+
) -> Self {
1062+
self.return_mode = Some(ReturnMode::SpecializedConversion(traits, tag));
1063+
self
1064+
}
1065+
10491066
const fn extract_error_mode(mut self, extract_error_mode: ExtractErrorMode) -> Self {
10501067
self.extract_error_mode = extract_error_mode;
10511068
self
@@ -1142,11 +1159,11 @@ fn generate_method_body(
11421159
let self_arg = spec.tp.self_arg(Some(cls), extract_error_mode);
11431160
let rust_name = spec.name;
11441161
let args = extract_proto_arguments(spec, arguments, extract_error_mode)?;
1145-
let call = quote! { _pyo3::callback::convert(py, #cls::#rust_name(#self_arg #(#args),*)) };
1162+
let call = quote! { #cls::#rust_name(#self_arg #(#args),*) };
11461163
Ok(if let Some(return_mode) = return_mode {
11471164
return_mode.return_call_output(call)
11481165
} else {
1149-
call
1166+
quote! { _pyo3::callback::convert(py, #call) }
11501167
})
11511168
}
11521169

src/callback.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,62 @@ where
176176
{
177177
value.convert(py)
178178
}
179+
180+
// Autoref-based specialization to allow deprecation of __next__ returning `Option`
181+
182+
pub struct IterBaseTag;
183+
184+
impl IterBaseTag {
185+
pub fn convert<Value, Target>(self, py: Python<'_>, value: Value) -> PyResult<Target>
186+
where
187+
Value: IntoPyCallbackOutput<Target>,
188+
{
189+
value.convert(py)
190+
}
191+
}
192+
193+
pub trait IterBaseKind {
194+
fn iter_tag(&self) -> IterBaseTag;
195+
}
196+
197+
impl<Value> IterBaseKind for &Value
198+
where
199+
Value: IntoPyCallbackOutput<*mut ffi::PyObject>,
200+
{
201+
fn iter_tag(&self) -> IterBaseTag {
202+
IterBaseTag
203+
}
204+
}
205+
206+
pub struct IterOptionTag;
207+
208+
impl IterOptionTag {
209+
#[deprecated(since = "0.21.0", note = "TODO")]
210+
pub fn convert<Value, Target>(self, py: Python<'_>, value: Option<Value>) -> PyResult<Target>
211+
where
212+
Value: IntoPyCallbackOutput<Target>,
213+
{
214+
match value {
215+
Some(value) => value.convert(py),
216+
None => {
217+
let none: PyObject = py.None().into_py(py);
218+
let result: PyResult<Value> =
219+
Err(crate::exceptions::PyStopIteration::new_err((none,)));
220+
result.convert(py)
221+
}
222+
}
223+
}
224+
}
225+
226+
pub trait IterOptionKind {
227+
fn iter_tag(&self) -> IterOptionTag;
228+
}
229+
230+
impl<Value> IterOptionKind for Option<Value>
231+
where
232+
Value: IntoPyCallbackOutput<*mut ffi::PyObject>,
233+
{
234+
fn iter_tag(&self) -> IterOptionTag {
235+
IterOptionTag
236+
}
237+
}

src/coroutine.rs

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use crate::{
1414
coroutine::{cancel::ThrowCallback, waker::AsyncioWaker},
1515
exceptions::{PyAttributeError, PyRuntimeError, PyStopIteration},
1616
panic::PanicException,
17-
pyclass::IterNextOutput,
1817
types::{PyIterator, PyString},
1918
IntoPy, Py, PyAny, PyErr, PyObject, PyResult, Python,
2019
};
@@ -68,11 +67,7 @@ impl Coroutine {
6867
}
6968
}
7069

71-
fn poll(
72-
&mut self,
73-
py: Python<'_>,
74-
throw: Option<PyObject>,
75-
) -> PyResult<IterNextOutput<PyObject, PyObject>> {
70+
fn poll(&mut self, py: Python<'_>, throw: Option<PyObject>) -> PyResult<PyObject> {
7671
// raise if the coroutine has already been run to completion
7772
let future_rs = match self.future {
7873
Some(ref mut fut) => fut,
@@ -100,7 +95,7 @@ impl Coroutine {
10095
match panic::catch_unwind(panic::AssertUnwindSafe(poll)) {
10196
Ok(Poll::Ready(res)) => {
10297
self.close();
103-
return Ok(IterNextOutput::Return(res?));
98+
return Err(PyStopIteration::new_err(res?));
10499
}
105100
Err(err) => {
106101
self.close();
@@ -115,19 +110,12 @@ impl Coroutine {
115110
if let Some(future) = PyIterator::from_object(future).unwrap().next() {
116111
// future has not been leaked into Python for now, and Rust code can only call
117112
// `set_result(None)` in `Wake` implementation, so it's safe to unwrap
118-
return Ok(IterNextOutput::Yield(future.unwrap().into()));
113+
return Ok(future.unwrap().into());
119114
}
120115
}
121116
// if waker has been waken during future polling, this is roughly equivalent to
122117
// `await asyncio.sleep(0)`, so just yield `None`.
123-
Ok(IterNextOutput::Yield(py.None().into()))
124-
}
125-
}
126-
127-
pub(crate) fn iter_result(result: IterNextOutput<PyObject, PyObject>) -> PyResult<PyObject> {
128-
match result {
129-
IterNextOutput::Yield(ob) => Ok(ob),
130-
IterNextOutput::Return(ob) => Err(PyStopIteration::new_err(ob)),
118+
Ok(py.None().into())
131119
}
132120
}
133121

@@ -153,11 +141,11 @@ impl Coroutine {
153141
}
154142

155143
fn send(&mut self, py: Python<'_>, _value: &PyAny) -> PyResult<PyObject> {
156-
iter_result(self.poll(py, None)?)
144+
self.poll(py, None)
157145
}
158146

159147
fn throw(&mut self, py: Python<'_>, exc: PyObject) -> PyResult<PyObject> {
160-
iter_result(self.poll(py, Some(exc))?)
148+
self.poll(py, Some(exc))
161149
}
162150

163151
fn close(&mut self) {
@@ -170,7 +158,7 @@ impl Coroutine {
170158
self_
171159
}
172160

173-
fn __next__(&mut self, py: Python<'_>) -> PyResult<IterNextOutput<PyObject, PyObject>> {
161+
fn __next__(&mut self, py: Python<'_>) -> PyResult<PyObject> {
174162
self.poll(py, None)
175163
}
176164
}

src/lib.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -354,17 +354,6 @@ pub mod class {
354354
pub use crate::pyclass::{IterANextOutput, PyIterANextOutput};
355355
}
356356

357-
/// Old module which contained some implementation details of the `#[pyproto]` module.
358-
///
359-
/// Prefer using the same content from `pyo3::pyclass`, e.g. `use pyo3::pyclass::IterNextOutput` instead
360-
/// of `use pyo3::class::pyasync::IterNextOutput`.
361-
///
362-
/// For compatibility reasons this has not yet been removed, however will be done so
363-
/// once <https://github.com/rust-lang/rust/issues/30827> is resolved.
364-
pub mod iter {
365-
pub use crate::pyclass::{IterNextOutput, PyIterNextOutput};
366-
}
367-
368357
/// Old module which contained some implementation details of the `#[pyproto]` module.
369358
///
370359
/// Prefer using the same content from `pyo3::pyclass`, e.g. `use pyo3::pyclass::PyTraverseError` instead

src/pyclass.rs

Lines changed: 0 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -85,87 +85,6 @@ impl CompareOp {
8585
}
8686
}
8787

88-
/// Output of `__next__` which can either `yield` the next value in the iteration, or
89-
/// `return` a value to raise `StopIteration` in Python.
90-
///
91-
/// Usage example:
92-
///
93-
/// ```rust
94-
/// use pyo3::prelude::*;
95-
/// use pyo3::iter::IterNextOutput;
96-
///
97-
/// #[pyclass]
98-
/// struct PyClassIter {
99-
/// count: usize,
100-
/// }
101-
///
102-
/// #[pymethods]
103-
/// impl PyClassIter {
104-
/// #[new]
105-
/// pub fn new() -> Self {
106-
/// PyClassIter { count: 0 }
107-
/// }
108-
///
109-
/// fn __next__(&mut self) -> IterNextOutput<usize, &'static str> {
110-
/// if self.count < 5 {
111-
/// self.count += 1;
112-
/// // Given an instance `counter`, First five `next(counter)` calls yield 1, 2, 3, 4, 5.
113-
/// IterNextOutput::Yield(self.count)
114-
/// } else {
115-
/// // At the sixth time, we get a `StopIteration` with `'Ended'`.
116-
/// // try:
117-
/// // next(counter)
118-
/// // except StopIteration as e:
119-
/// // assert e.value == 'Ended'
120-
/// IterNextOutput::Return("Ended")
121-
/// }
122-
/// }
123-
/// }
124-
/// ```
125-
pub enum IterNextOutput<T, U> {
126-
/// The value yielded by the iterator.
127-
Yield(T),
128-
/// The `StopIteration` object.
129-
Return(U),
130-
}
131-
132-
/// Alias of `IterNextOutput` with `PyObject` yield & return values.
133-
pub type PyIterNextOutput = IterNextOutput<PyObject, PyObject>;
134-
135-
impl IntoPyCallbackOutput<*mut ffi::PyObject> for PyIterNextOutput {
136-
fn convert(self, _py: Python<'_>) -> PyResult<*mut ffi::PyObject> {
137-
match self {
138-
IterNextOutput::Yield(o) => Ok(o.into_ptr()),
139-
IterNextOutput::Return(opt) => Err(crate::exceptions::PyStopIteration::new_err((opt,))),
140-
}
141-
}
142-
}
143-
144-
impl<T, U> IntoPyCallbackOutput<PyIterNextOutput> for IterNextOutput<T, U>
145-
where
146-
T: IntoPy<PyObject>,
147-
U: IntoPy<PyObject>,
148-
{
149-
fn convert(self, py: Python<'_>) -> PyResult<PyIterNextOutput> {
150-
match self {
151-
IterNextOutput::Yield(o) => Ok(IterNextOutput::Yield(o.into_py(py))),
152-
IterNextOutput::Return(o) => Ok(IterNextOutput::Return(o.into_py(py))),
153-
}
154-
}
155-
}
156-
157-
impl<T> IntoPyCallbackOutput<PyIterNextOutput> for Option<T>
158-
where
159-
T: IntoPy<PyObject>,
160-
{
161-
fn convert(self, py: Python<'_>) -> PyResult<PyIterNextOutput> {
162-
match self {
163-
Some(o) => Ok(PyIterNextOutput::Yield(o.into_py(py))),
164-
None => Ok(PyIterNextOutput::Return(py.None().into())),
165-
}
166-
}
167-
}
168-
16988
/// Output of `__anext__`.
17089
///
17190
/// <https://docs.python.org/3/reference/expressions.html#agen.__anext__>

0 commit comments

Comments
 (0)