PyO3 / pyo3

Rust bindings for the Python interpreter
https://pyo3.rs
Apache License 2.0
12.32k stars 760 forks source link

create_exception: the trait bound `MyError: pyo3::PyNativeType` is not satisfied #4562

Closed Wicpar closed 1 month ago

Wicpar commented 1 month ago

Bug Description

while migrating to pyo3 0.22.3 the declared exceptions were no longer compiling due to a missing trait bound

Steps to Reproduce

  1. define a create_exception!(my_module, MyError, PyException, "Some description.");
  2. it doen't compile

Backtrace

error[E0277]: the trait bound `MyError: pyo3::PyNativeType` is not satisfied
  --> \bindings\python\src\exceptions.rs:4:1
   |
4  | create_exception!(my_module, MyError, PyException, "Some description.");
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `pyo3::PyNativeType` is not implemented for `MyError`
   |
   = help: the following other types implement trait `pyo3::PyNativeType`:
             PyArray<T, D>
             PyArrayDescr
             PyUntypedArray
             PyCell<T>
             PyAny
             PyBool
             PyByteArray
             PyBytes
           and 115 others
   = note: required for `MyError` to implement `HasPyGilRef`
note: required by a bound in `PyTypeInfo`
  --> .cargo\registry\src\index.crates.io-6f17d22bba15001f\pyo3-0.22.3\src\type_object.rs:63:38
   |
63 | pub unsafe trait PyTypeInfo: Sized + HasPyGilRef {
   |                                      ^^^^^^^^^^^ required by this bound in `PyTypeInfo`
   = note: this error originates in the macro `$crate::create_exception_type_object` which comes from the expansion of the macro `create_exception` (in Nightly builds, run with -Z macro-backtrace for more info)

Your operating system and version

Windows 10

Your Python version (python --version)

3.12

Your Rust version (rustc --version)

1.75

Your PyO3 version

0.22

How did you install python? Did you use a virtualenv?

python is not required to reproduce the issue

Additional Info

No response

davidhewitt commented 1 month ago

Thanks for the report. Can you please provide a more complete minimal reproduction? I cannot reproduce this locally.

Wicpar commented 1 month ago

Before i got there i found the issue:

The macro expands to: ```rust #[repr(transparent)] #[allow(non_camel_case_types)] #[doc = "Some description."] pub struct MyError(::pyo3::PyAny); #[allow(unknown_lints, non_local_definitions)] #[cfg(feature = "gil-refs")] impl ::std::convert::From<&MyError> for ::pyo3::PyErr { #[inline] fn from(err: &MyError) -> ::pyo3::PyErr { #[allow(deprecated)] ::pyo3::PyErr::from_value(err) } } impl MyError { /// Creates a new [ `PyErr` ] of this type. /// /// [`PyErr`] : https://docs.rs/pyo3/latest/pyo3/struct.PyErr.html "PyErr in pyo3" #[inline] #[allow(dead_code)] pub fn new_err(args: A) -> ::pyo3::PyErr where A: ::pyo3::PyErrArguments + ::std::marker::Send + ::std::marker::Sync + 'static, { ::pyo3::PyErr::new::(args) } } #[cfg(feature = "gil-refs")] impl ::std::error::Error for MyError { fn source(&self) -> ::std::option::Option<&( dyn ::std::error::Error + 'static )> { #[allow(unsafe_code)] unsafe { #[allow(deprecated)] let cause: &::pyo3::exceptions::PyBaseException = self .py() .from_owned_ptr_or_opt(::pyo3::ffi::PyException_GetCause(self.as_ptr()))?; ::std::option::Option::Some(cause) } } } impl ::pyo3::ToPyErr for MyError {} #[cfg(feature = "gil-refs")] #[allow(unsafe_code)] unsafe impl<> ::pyo3::PyNativeType for MyError { type AsRefSource = Self; } #[cfg(feature = "gil-refs")] impl<> ::std::fmt::Debug for MyError { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::result::Result<(), ::std::fmt::Error> { use ::pyo3::{PyNativeType, types::{PyAnyMethods, PyStringMethods}}; let s = self.as_borrowed().repr().or(::std::result::Result::Err(::std::fmt::Error))?; f.write_str(&s.to_string_lossy()) } } #[cfg(feature = "gil-refs")] impl<> ::std::fmt::Display for MyError { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::result::Result<(), ::std::fmt::Error> { use ::pyo3::{PyNativeType, types::{PyAnyMethods, PyStringMethods, PyTypeMethods}}; match self.as_borrowed().str() { ::std::result::Result::Ok(s) => return f.write_str(&s.to_string_lossy()), ::std::result::Result::Err(err) => err.write_unraisable_bound(self.py(), ::std::option::Option::Some(&self.as_borrowed())), } match self.as_borrowed().get_type().name() { ::std::result::Result::Ok(name) => ::std::write!(f, "", name), ::std::result::Result::Err(_err) => f.write_str(""), } } } #[cfg(feature = "gil-refs")] impl<> ::pyo3::ToPyObject for MyError { #[inline] fn to_object(&self, py: ::pyo3::Python<'_>) -> ::pyo3::PyObject { #[allow(unsafe_code)] unsafe { ::pyo3::PyObject::from_borrowed_ptr(py, self.as_ptr()) } } } impl<> ::std::convert::AsRef<::pyo3::PyAny> for MyError { #[inline] fn as_ref(&self) -> &::pyo3::PyAny { &self.0 } } impl<> ::std::ops::Deref for MyError { type Target = ::pyo3::PyAny; #[inline] fn deref(&self) -> &::pyo3::PyAny { &self.0 } } #[allow(unsafe_code)] unsafe impl<> ::pyo3::AsPyPointer for MyError { /// Gets the underlying FFI pointer, returns a borrowed pointer. #[inline] fn as_ptr(&self) -> *mut ::pyo3::ffi::PyObject { self.0.as_ptr() } } #[allow(unknown_lints, non_local_definitions)] #[cfg(feature = "gil-refs")] impl<> ::pyo3::IntoPy<::pyo3::Py> for &'_ MyError { #[inline] fn into_py(self, py: ::pyo3::Python<'_>) -> ::pyo3::Py { #[allow(unsafe_code)] unsafe { ::pyo3::Py::from_borrowed_ptr(py, self.as_ptr()) } } } #[allow(unknown_lints, non_local_definitions)] #[cfg(feature = "gil-refs")] impl<> ::std::convert::From<&'_ MyError> for ::pyo3::Py { #[inline] fn from(other: &MyError) -> Self { use ::pyo3::PyNativeType; #[allow(unsafe_code)] unsafe { ::pyo3::Py::from_borrowed_ptr(other.py(), other.as_ptr()) } } } #[allow(unknown_lints, non_local_definitions)] #[cfg(feature = "gil-refs")] impl<'a, > ::std::convert::From<&'a MyError> for &'a ::pyo3::PyAny { fn from(ob: &'a MyError) -> Self { #[allow(unsafe_code)] unsafe { &*(ob as *const MyError as *const ::pyo3::PyAny) } } } impl ::pyo3::types::DerefToPyAny for MyError {} #[allow(unsafe_code)] unsafe impl<> ::pyo3::type_object::PyTypeInfo for MyError { const NAME: &'static str = "MyError"; const MODULE: ::std::option::Option<&'static str> = (::std::option::Option::Some("my_module") ); #[inline] #[allow(clippy::redundant_closure_call)] fn type_object_raw(py: ::pyo3::Python<'_>) -> *mut ::pyo3::ffi::PyTypeObject { MyError::type_object_raw(py) } } impl MyError { #[doc(hidden)] pub const _PYO3_DEF: ::pyo3::impl_::pymodule::AddTypeToModule = ::pyo3::impl_::pymodule::AddTypeToModule::new(); } #[allow(unknown_lints, non_local_definitions)] #[cfg(feature = "gil-refs")] impl<'py, > ::pyo3::FromPyObject<'py> for &'py MyError { #[inline] fn extract_bound(obj: &::pyo3::Bound<'py, ::pyo3::PyAny>) -> ::pyo3::PyResult { ::std::clone::Clone::clone(obj).into_gil_ref().downcast().map_err(::std::convert::Into::into) } } impl MyError { fn type_object_raw(py: ::pyo3::Python<'_>) -> *mut ::pyo3::ffi::PyTypeObject { use ::pyo3::sync::GILOnceCell; static TYPE_OBJECT: GILOnceCell<::pyo3::Py<::pyo3::types::PyType>> = GILOnceCell::new(); TYPE_OBJECT .get_or_init(py, || ::pyo3::PyErr::new_type_bound( py, "my_module.MyError", (::std::option::Option::Some("Some description.") ), ::std::option::Option::Some(&py.get_type_bound::()), ::std::option::Option::None, ).expect("Failed to initialize new exception type."), ).as_ptr() as *mut ::pyo3::ffi::PyTypeObject } } ```

#[cfg(feature = "gil-refs")] is a feature requried on the current crate, not on the dependency. However more importantly it is applied to the PyNativeType but not to PyTypeInfo which requires PyNativeType.

davidhewitt commented 1 month ago

Thanks for debugging, that makes a lot of sense. I think the fix is that we have to rework to have two copies of the macros, which have the cfg applied to them rather than within their expansion.

Note also that you should probably aim to stop using the gil-refs feature ASAP, as that will be removed entirely in PyO3 0.23.

davidhewitt commented 1 month ago

Fixed in #4589 / to be released in 0.22.4