Skip to content

Commit

Permalink
Implement From<Bound<'py, T>> for PyErr (#3881)
Browse files Browse the repository at this point in the history
* Implement `From<Bound<'py, T>>` for PyErr

* Replace PyErr::from_value_bound calls with .into

* Fix From<MyError> expected error message

* Add a trait bound to From<Bound<'py, T>> for PyErr
  • Loading branch information
LilyFoote authored Feb 26, 2024
1 parent cd1c0db commit 5c41ea0
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 26 deletions.
18 changes: 18 additions & 0 deletions src/err/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,24 @@ impl PyErrArguments for PyDowncastErrorArguments {
}
}

/// Python exceptions that can be converted to [`PyErr`].
///
/// This is used to implement [`From<Bound<'_, T>> for PyErr`].
///
/// Users should not need to implement this trait directly. It is implemented automatically in the
/// [`crate::import_exception!`] and [`crate::create_exception!`] macros.
pub trait ToPyErr {}

impl<'py, T> std::convert::From<Bound<'py, T>> for PyErr
where
T: ToPyErr,
{
#[inline]
fn from(err: Bound<'py, T>) -> PyErr {
PyErr::from_value_bound(err.into_any())
}
}

/// Convert `PyDowncastError` to Python `TypeError`.
impl<'a> std::convert::From<PyDowncastError<'a>> for PyErr {
fn from(err: PyDowncastError<'_>) -> PyErr {
Expand Down
4 changes: 3 additions & 1 deletion src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ macro_rules! impl_exception_boilerplate {
}
}
}

impl $crate::ToPyErr for $name {}
};
}

Expand Down Expand Up @@ -1074,7 +1076,7 @@ mod tests {
);

// Restoring should preserve the same error
let e = PyErr::from_value_bound(decode_err.into_any());
let e: PyErr = decode_err.into();
e.restore(py);

assert_eq!(
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ pub use crate::conversion::{AsPyPointer, FromPyObject, FromPyPointer, IntoPy, To
#[allow(deprecated)]
pub use crate::conversion::{PyTryFrom, PyTryInto};
pub use crate::err::{
DowncastError, DowncastIntoError, PyDowncastError, PyErr, PyErrArguments, PyResult,
DowncastError, DowncastIntoError, PyDowncastError, PyErr, PyErrArguments, PyResult, ToPyErr,
};
pub use crate::gil::GILPool;
#[cfg(not(PyPy))]
Expand Down
40 changes: 17 additions & 23 deletions src/types/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,40 +73,34 @@ impl<'a> PyStringData<'a> {
match self {
Self::Ucs1(data) => match str::from_utf8(data) {
Ok(s) => Ok(Cow::Borrowed(s)),
Err(e) => Err(crate::PyErr::from_value_bound(
PyUnicodeDecodeError::new_utf8_bound(py, data, e)?.into_any(),
)),
Err(e) => Err(PyUnicodeDecodeError::new_utf8_bound(py, data, e)?.into()),
},
Self::Ucs2(data) => match String::from_utf16(data) {
Ok(s) => Ok(Cow::Owned(s)),
Err(e) => {
let mut message = e.to_string().as_bytes().to_vec();
message.push(0);

Err(crate::PyErr::from_value_bound(
PyUnicodeDecodeError::new_bound(
py,
CStr::from_bytes_with_nul(b"utf-16\0").unwrap(),
self.as_bytes(),
0..self.as_bytes().len(),
CStr::from_bytes_with_nul(&message).unwrap(),
)?
.into_any(),
))
}
},
Self::Ucs4(data) => match data.iter().map(|&c| std::char::from_u32(c)).collect() {
Some(s) => Ok(Cow::Owned(s)),
None => Err(crate::PyErr::from_value_bound(
PyUnicodeDecodeError::new_bound(
Err(PyUnicodeDecodeError::new_bound(
py,
CStr::from_bytes_with_nul(b"utf-32\0").unwrap(),
CStr::from_bytes_with_nul(b"utf-16\0").unwrap(),
self.as_bytes(),
0..self.as_bytes().len(),
CStr::from_bytes_with_nul(b"error converting utf-32\0").unwrap(),
CStr::from_bytes_with_nul(&message).unwrap(),
)?
.into_any(),
)),
.into())
}
},
Self::Ucs4(data) => match data.iter().map(|&c| std::char::from_u32(c)).collect() {
Some(s) => Ok(Cow::Owned(s)),
None => Err(PyUnicodeDecodeError::new_bound(
py,
CStr::from_bytes_with_nul(b"utf-32\0").unwrap(),
self.as_bytes(),
0..self.as_bytes().len(),
CStr::from_bytes_with_nul(b"error converting utf-32\0").unwrap(),
)?
.into()),
},
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/invalid_result_conversion.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ error[E0277]: the trait bound `PyErr: From<MyError>` is not satisfied
| ^^^^^^^^^^^^^ the trait `From<MyError>` is not implemented for `PyErr`
|
= help: the following other types implement trait `From<T>`:
<PyErr as From<pyo3::Bound<'py, T>>>
<PyErr as From<std::io::Error>>
<PyErr as From<PyBorrowError>>
<PyErr as From<PyBorrowMutError>>
<PyErr as From<PyDowncastError<'a>>>
<PyErr as From<DowncastError<'_, '_>>>
<PyErr as From<DowncastIntoError<'_>>>
<PyErr as From<NulError>>
<PyErr as From<IntoStringError>>
and $N others
= note: required for `MyError` to implement `Into<PyErr>`
= note: this error originates in the attribute macro `pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)

0 comments on commit 5c41ea0

Please sign in to comment.