Skip to content

Commit 4a04603

Browse files
authored
Don't use intocallback in method macros (#2664)
* Don't use intocallback in method macros Co-authored-by: mejrs <>
1 parent 125af9b commit 4a04603

File tree

10 files changed

+71
-107
lines changed

10 files changed

+71
-107
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ serde = { version = "1.0", optional = true }
4444
assert_approx_eq = "1.1.0"
4545
chrono = { version = "0.4" }
4646
criterion = "0.3.5"
47-
trybuild = "1.0.49"
47+
# Required for "and $N others" normalization
48+
trybuild = ">=1.0.70"
4849
rustversion = "1.0"
4950
# 1.0.0 requires Rust 1.50
5051
proptest = { version = "0.10.1", default-features = false, features = ["std"] }

newsfragments/2664.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PyO3's macros now emit a much nicer error message if function return values don't implement the required trait(s).

pyo3-macros-backend/src/method.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -468,17 +468,12 @@ impl<'a> FnSpec<'a> {
468468
quote!(#func_name)
469469
};
470470

471-
// The method call is necessary to generate a decent error message.
472471
let rust_call = |args: Vec<TokenStream>| {
473472
quote! {
474473
let mut ret = #rust_name(#self_arg #(#args),*);
475-
476-
if false {
477-
use _pyo3::impl_::ghost::IntoPyResult;
478-
ret.assert_into_py_result();
479-
}
480-
481-
_pyo3::callback::convert(#py, ret)
474+
let owned = _pyo3::impl_::pymethods::OkWrap::wrap(ret, #py);
475+
owned.map(|obj| _pyo3::conversion::IntoPyPointer::into_ptr(obj))
476+
.map_err(::core::convert::Into::into)
482477
}
483478
};
484479

pyo3-macros-backend/src/pymethod.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -389,11 +389,8 @@ fn impl_py_class_attribute(cls: &syn::Type, spec: &FnSpec<'_>) -> syn::Result<Me
389389
fn #wrapper_ident(py: _pyo3::Python<'_>) -> _pyo3::PyResult<_pyo3::PyObject> {
390390
#deprecations
391391
let mut ret = #fncall;
392-
if false {
393-
use _pyo3::impl_::ghost::IntoPyResult;
394-
ret.assert_into_py_result();
395-
}
396-
_pyo3::callback::convert(py, ret)
392+
let owned = _pyo3::impl_::pymethods::OkWrap::wrap(ret, py);
393+
owned.map_err(::core::convert::Into::into)
397394
}
398395
};
399396

src/impl_.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ pub mod deprecations;
1010
pub mod extract_argument;
1111
pub mod freelist;
1212
pub mod frompyobject;
13-
pub mod ghost;
1413
pub(crate) mod not_send;
1514
pub mod panic;
1615
pub mod pycell;

src/impl_/ghost.rs

Lines changed: 0 additions & 18 deletions
This file was deleted.

src/impl_/pymethods.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::internal_tricks::{extract_cstr_or_leak_cstring, NulByteInString};
2-
use crate::{ffi, PyAny, PyObject, PyResult, PyTraverseError, Python};
2+
use crate::{ffi, IntoPy, Py, PyAny, PyErr, PyObject, PyResult, PyTraverseError, Python};
33
use std::ffi::CStr;
44
use std::fmt;
55
use std::os::raw::c_int;
@@ -262,3 +262,29 @@ pub fn unwrap_traverse_result(result: Result<(), PyTraverseError>) -> c_int {
262262
Err(PyTraverseError(value)) => value,
263263
}
264264
}
265+
266+
// The macros need to Ok-wrap the output of user defined functions; i.e. if they're not a result, make them into one.
267+
pub trait OkWrap<T> {
268+
type Error;
269+
fn wrap(self, py: Python<'_>) -> Result<Py<PyAny>, Self::Error>;
270+
}
271+
272+
impl<T> OkWrap<T> for T
273+
where
274+
T: IntoPy<PyObject>,
275+
{
276+
type Error = PyErr;
277+
fn wrap(self, py: Python<'_>) -> PyResult<Py<PyAny>> {
278+
Ok(self.into_py(py))
279+
}
280+
}
281+
282+
impl<T, E> OkWrap<T> for Result<T, E>
283+
where
284+
T: IntoPy<PyObject>,
285+
{
286+
type Error = E;
287+
fn wrap(self, py: Python<'_>) -> Result<Py<PyAny>, Self::Error> {
288+
self.map(|o| o.into_py(py))
289+
}
290+
}

src/pyclass.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,10 @@ impl PyTypeBuilder {
341341
module_name: Option<&'static str>,
342342
basicsize: usize,
343343
) -> PyResult<*mut ffi::PyTypeObject> {
344+
// `c_ulong` and `c_uint` have the same size
345+
// on some platforms (like windows)
346+
#![allow(clippy::useless_conversion)]
347+
344348
self.finalize_methods_and_properties();
345349

346350
if !self.has_new {
@@ -376,9 +380,7 @@ impl PyTypeBuilder {
376380
name: py_class_qualified_name(module_name, name)?,
377381
basicsize: basicsize as c_int,
378382
itemsize: 0,
379-
// `c_ulong` and `c_uint` have the same size
380-
// on some platforms (like windows)
381-
#[allow(clippy::useless_conversion)]
383+
382384
flags: (ffi::Py_TPFLAGS_DEFAULT | self.class_flags)
383385
.try_into()
384386
.unwrap(),
Lines changed: 13 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,18 @@
1-
error[E0599]: no method named `assert_into_py_result` found for enum `Result` in the current scope
1+
error[E0277]: the trait bound `PyErr: From<MyError>` is not satisfied
22
--> tests/ui/invalid_result_conversion.rs:21:1
33
|
44
21 | #[pyfunction]
5-
| ^^^^^^^^^^^^^ method not found in `Result<(), MyError>`
5+
| ^^^^^^^^^^^^^ the trait `From<MyError>` is not implemented for `PyErr`
66
|
7-
note: the method `assert_into_py_result` exists on the type `()`
8-
--> src/impl_/ghost.rs
9-
|
10-
| fn assert_into_py_result(&mut self) {}
11-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7+
= help: the following other types implement trait `From<T>`:
8+
<PyErr as From<&CancelledError>>
9+
<PyErr as From<&IncompleteReadError>>
10+
<PyErr as From<&InvalidStateError>>
11+
<PyErr as From<&LimitOverrunError>>
12+
<PyErr as From<&PanicException>>
13+
<PyErr as From<&PyArithmeticError>>
14+
<PyErr as From<&PyAssertionError>>
15+
<PyErr as From<&PyAttributeError>>
16+
and $N others
17+
= note: required because of the requirements on the impl of `Into<PyErr>` for `MyError`
1218
= note: this error originates in the attribute macro `pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)
13-
help: use the `?` operator to extract the `()` value, propagating a `Result::Err` value to the caller
14-
|
15-
21 | #[pyfunction]?
16-
| +
17-
18-
error[E0277]: the trait bound `Result<(), MyError>: IntoPyCallbackOutput<_>` is not satisfied
19-
--> tests/ui/invalid_result_conversion.rs:21:1
20-
|
21-
21 | #[pyfunction]
22-
| ^^^^^^^^^^^^^ the trait `IntoPyCallbackOutput<_>` is not implemented for `Result<(), MyError>`
23-
|
24-
= help: the trait `IntoPyCallbackOutput<U>` is implemented for `Result<T, E>`
25-
note: required by a bound in `pyo3::callback::convert`
26-
--> src/callback.rs
27-
|
28-
| T: IntoPyCallbackOutput<U>,
29-
| ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `pyo3::callback::convert`
30-
= note: this error originates in the attribute macro `pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui/missing_intopy.stderr

Lines changed: 18 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,18 @@
1-
error[E0599]: the method `assert_into_py_result` exists for struct `Blah`, but its trait bounds were not satisfied
2-
--> tests/ui/missing_intopy.rs:3:1
3-
|
4-
1 | struct Blah;
5-
| -----------
6-
| |
7-
| method `assert_into_py_result` not found for this struct
8-
| doesn't satisfy `Blah: IntoPy<Py<PyAny>>`
9-
| doesn't satisfy `Blah: IntoPyResult<Blah>`
10-
2 |
11-
3 | #[pyo3::pyfunction]
12-
| ^^^^^^^^^^^^^^^^^^^ method cannot be called on `Blah` due to unsatisfied trait bounds
13-
|
14-
= note: the following trait bounds were not satisfied:
15-
`Blah: IntoPy<Py<PyAny>>`
16-
which is required by `Blah: IntoPyResult<Blah>`
17-
note: the following trait must be implemented
18-
--> src/conversion.rs
19-
|
20-
| pub trait IntoPy<T>: Sized {
21-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
22-
= note: this error originates in the attribute macro `pyo3::pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)
23-
24-
error[E0277]: the trait bound `Blah: IntoPyCallbackOutput<_>` is not satisfied
25-
--> tests/ui/missing_intopy.rs:3:1
26-
|
27-
3 | #[pyo3::pyfunction]
28-
| ^^^^^^^^^^^^^^^^^^^ the trait `IntoPyCallbackOutput<_>` is not implemented for `Blah`
29-
|
30-
= help: the following other types implement trait `IntoPyCallbackOutput<Target>`:
31-
<() as IntoPyCallbackOutput<()>>
32-
<() as IntoPyCallbackOutput<i32>>
33-
<*mut PyObject as IntoPyCallbackOutput<*mut PyObject>>
34-
<HashCallbackOutput as IntoPyCallbackOutput<isize>>
35-
<IterANextOutput<Py<PyAny>, Py<PyAny>> as IntoPyCallbackOutput<*mut PyObject>>
36-
<IterANextOutput<T, U> as IntoPyCallbackOutput<IterANextOutput<Py<PyAny>, Py<PyAny>>>>
37-
<IterNextOutput<Py<PyAny>, Py<PyAny>> as IntoPyCallbackOutput<*mut PyObject>>
38-
<IterNextOutput<T, U> as IntoPyCallbackOutput<IterNextOutput<Py<PyAny>, Py<PyAny>>>>
39-
and 7 others
40-
note: required by a bound in `pyo3::callback::convert`
41-
--> src/callback.rs
42-
|
43-
| T: IntoPyCallbackOutput<U>,
44-
| ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `pyo3::callback::convert`
45-
= note: this error originates in the attribute macro `pyo3::pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)
1+
error[E0277]: the trait bound `Blah: IntoPy<Py<PyAny>>` is not satisfied
2+
--> tests/ui/missing_intopy.rs:3:1
3+
|
4+
3 | #[pyo3::pyfunction]
5+
| ^^^^^^^^^^^^^^^^^^^ the trait `IntoPy<Py<PyAny>>` is not implemented for `Blah`
6+
|
7+
= help: the following other types implement trait `IntoPy<T>`:
8+
<&'a OsString as IntoPy<Py<PyAny>>>
9+
<&'a Path as IntoPy<Py<PyAny>>>
10+
<&'a PathBuf as IntoPy<Py<PyAny>>>
11+
<&'a PyErr as IntoPy<Py<PyAny>>>
12+
<&'a String as IntoPy<Py<PyAny>>>
13+
<&'a [u8] as IntoPy<Py<PyAny>>>
14+
<&'a str as IntoPy<Py<PyAny>>>
15+
<&'a str as IntoPy<Py<PyString>>>
16+
and $N others
17+
= note: required because of the requirements on the impl of `OkWrap<Blah>` for `Blah`
18+
= note: this error originates in the attribute macro `pyo3::pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)

0 commit comments

Comments
 (0)