Skip to content

Commit a47baf5

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

File tree

7 files changed

+125
-133
lines changed

7 files changed

+125
-133
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, IterResultOptionKind }),
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

pytests/src/awaitable.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
//! when awaited, see guide examples related to pyo3-asyncio for ways
66
//! to suspend tasks and await results.
77
8-
use pyo3::{prelude::*, pyclass::IterNextOutput};
8+
use pyo3::exceptions::PyStopIteration;
9+
use pyo3::prelude::*;
910

1011
#[pyclass]
1112
#[derive(Debug)]
@@ -30,13 +31,13 @@ impl IterAwaitable {
3031
pyself
3132
}
3233

33-
fn __next__(&mut self, py: Python<'_>) -> PyResult<IterNextOutput<PyObject, PyObject>> {
34+
fn __next__(&mut self, py: Python<'_>) -> PyResult<PyObject> {
3435
match self.result.take() {
3536
Some(res) => match res {
36-
Ok(v) => Ok(IterNextOutput::Return(v)),
37+
Ok(v) => Err(PyStopIteration::new_err(v)),
3738
Err(err) => Err(err),
3839
},
39-
_ => Ok(IterNextOutput::Yield(py.None().into())),
40+
_ => Ok(py.None().into()),
4041
}
4142
}
4243
}
@@ -66,15 +67,13 @@ impl FutureAwaitable {
6667
pyself
6768
}
6869

69-
fn __next__(
70-
mut pyself: PyRefMut<'_, Self>,
71-
) -> PyResult<IterNextOutput<PyRefMut<'_, Self>, PyObject>> {
70+
fn __next__(mut pyself: PyRefMut<'_, Self>) -> PyResult<PyRefMut<'_, Self>> {
7271
match pyself.result {
7372
Some(_) => match pyself.result.take().unwrap() {
74-
Ok(v) => Ok(IterNextOutput::Return(v)),
73+
Ok(v) => Err(PyStopIteration::new_err(v)),
7574
Err(err) => Err(err),
7675
},
77-
_ => Ok(IterNextOutput::Yield(pyself)),
76+
_ => Ok(pyself),
7877
}
7978
}
8079
}

pytests/src/pyclasses.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
use pyo3::exceptions::PyValueError;
2-
use pyo3::iter::IterNextOutput;
1+
use pyo3::exceptions::{PyStopIteration, PyValueError};
32
use pyo3::prelude::*;
43
use pyo3::types::PyType;
54

@@ -28,12 +27,12 @@ impl PyClassIter {
2827
Default::default()
2928
}
3029

31-
fn __next__(&mut self) -> IterNextOutput<usize, &'static str> {
30+
fn __next__(&mut self) -> PyResult<usize> {
3231
if self.count < 5 {
3332
self.count += 1;
34-
IterNextOutput::Yield(self.count)
33+
Ok(self.count)
3534
} else {
36-
IterNextOutput::Return("Ended")
35+
Err(PyStopIteration::new_err("Ended"))
3736
}
3837
}
3938
}

src/callback.rs

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Utilities for a Python callable object that invokes a Rust function.
22
33
use crate::err::{PyErr, PyResult};
4-
use crate::exceptions::PyOverflowError;
4+
use crate::exceptions::{PyOverflowError, PyStopIteration};
55
use crate::ffi::{self, Py_hash_t};
66
use crate::{IntoPy, PyObject, Python};
77
use std::isize;
@@ -176,3 +176,84 @@ where
176176
{
177177
value.convert(py)
178178
}
179+
180+
// Autoref-based specialization to allow deprecation of __next__ returning `Option`
181+
182+
#[doc(hidden)]
183+
pub struct IterBaseTag;
184+
185+
impl IterBaseTag {
186+
#[inline]
187+
pub fn convert<Value, Target>(self, py: Python<'_>, value: Value) -> PyResult<Target>
188+
where
189+
Value: IntoPyCallbackOutput<Target>,
190+
{
191+
value.convert(py)
192+
}
193+
}
194+
195+
#[doc(hidden)]
196+
pub trait IterBaseKind {
197+
fn iter_tag(&self) -> IterBaseTag {
198+
IterBaseTag
199+
}
200+
}
201+
202+
impl<Value> IterBaseKind for &Value {}
203+
204+
#[doc(hidden)]
205+
pub struct IterOptionTag;
206+
207+
impl IterOptionTag {
208+
// TODO: #[deprecated(since = "0.21.0", note = "TODO")]
209+
#[inline]
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 => Err(PyStopIteration::new_err(())),
217+
}
218+
}
219+
}
220+
221+
#[doc(hidden)]
222+
pub trait IterOptionKind {
223+
fn iter_tag(&self) -> IterOptionTag {
224+
IterOptionTag
225+
}
226+
}
227+
228+
impl<Value> IterOptionKind for Option<Value> {}
229+
230+
#[doc(hidden)]
231+
pub struct IterResultOptionTag;
232+
233+
impl IterResultOptionTag {
234+
// TODO: #[deprecated(since = "0.21.0", note = "TODO")]
235+
#[inline]
236+
pub fn convert<Value, Target>(
237+
self,
238+
py: Python<'_>,
239+
value: PyResult<Option<Value>>,
240+
) -> PyResult<Target>
241+
where
242+
Value: IntoPyCallbackOutput<Target>,
243+
{
244+
match value {
245+
Ok(Some(value)) => value.convert(py),
246+
Ok(None) => Err(PyStopIteration::new_err(())),
247+
Err(err) => Err(err),
248+
}
249+
}
250+
}
251+
252+
#[doc(hidden)]
253+
pub trait IterResultOptionKind {
254+
fn iter_tag(&self) -> IterResultOptionTag {
255+
IterResultOptionTag
256+
}
257+
}
258+
259+
impl<Value> IterResultOptionKind for PyResult<Option<Value>> {}

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

0 commit comments

Comments
 (0)