Skip to content

Commit 5c41ea0

Browse files
authored
Implement From<Bound<'py, T>> for PyErr (#3881)
* 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
1 parent cd1c0db commit 5c41ea0

File tree

5 files changed

+40
-26
lines changed

5 files changed

+40
-26
lines changed

src/err/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,24 @@ impl PyErrArguments for PyDowncastErrorArguments {
982982
}
983983
}
984984

985+
/// Python exceptions that can be converted to [`PyErr`].
986+
///
987+
/// This is used to implement [`From<Bound<'_, T>> for PyErr`].
988+
///
989+
/// Users should not need to implement this trait directly. It is implemented automatically in the
990+
/// [`crate::import_exception!`] and [`crate::create_exception!`] macros.
991+
pub trait ToPyErr {}
992+
993+
impl<'py, T> std::convert::From<Bound<'py, T>> for PyErr
994+
where
995+
T: ToPyErr,
996+
{
997+
#[inline]
998+
fn from(err: Bound<'py, T>) -> PyErr {
999+
PyErr::from_value_bound(err.into_any())
1000+
}
1001+
}
1002+
9851003
/// Convert `PyDowncastError` to Python `TypeError`.
9861004
impl<'a> std::convert::From<PyDowncastError<'a>> for PyErr {
9871005
fn from(err: PyDowncastError<'_>) -> PyErr {

src/exceptions.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ macro_rules! impl_exception_boilerplate {
5454
}
5555
}
5656
}
57+
58+
impl $crate::ToPyErr for $name {}
5759
};
5860
}
5961

@@ -1074,7 +1076,7 @@ mod tests {
10741076
);
10751077

10761078
// Restoring should preserve the same error
1077-
let e = PyErr::from_value_bound(decode_err.into_any());
1079+
let e: PyErr = decode_err.into();
10781080
e.restore(py);
10791081

10801082
assert_eq!(

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ pub use crate::conversion::{AsPyPointer, FromPyObject, FromPyPointer, IntoPy, To
308308
#[allow(deprecated)]
309309
pub use crate::conversion::{PyTryFrom, PyTryInto};
310310
pub use crate::err::{
311-
DowncastError, DowncastIntoError, PyDowncastError, PyErr, PyErrArguments, PyResult,
311+
DowncastError, DowncastIntoError, PyDowncastError, PyErr, PyErrArguments, PyResult, ToPyErr,
312312
};
313313
pub use crate::gil::GILPool;
314314
#[cfg(not(PyPy))]

src/types/string.rs

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -73,40 +73,34 @@ impl<'a> PyStringData<'a> {
7373
match self {
7474
Self::Ucs1(data) => match str::from_utf8(data) {
7575
Ok(s) => Ok(Cow::Borrowed(s)),
76-
Err(e) => Err(crate::PyErr::from_value_bound(
77-
PyUnicodeDecodeError::new_utf8_bound(py, data, e)?.into_any(),
78-
)),
76+
Err(e) => Err(PyUnicodeDecodeError::new_utf8_bound(py, data, e)?.into()),
7977
},
8078
Self::Ucs2(data) => match String::from_utf16(data) {
8179
Ok(s) => Ok(Cow::Owned(s)),
8280
Err(e) => {
8381
let mut message = e.to_string().as_bytes().to_vec();
8482
message.push(0);
8583

86-
Err(crate::PyErr::from_value_bound(
87-
PyUnicodeDecodeError::new_bound(
88-
py,
89-
CStr::from_bytes_with_nul(b"utf-16\0").unwrap(),
90-
self.as_bytes(),
91-
0..self.as_bytes().len(),
92-
CStr::from_bytes_with_nul(&message).unwrap(),
93-
)?
94-
.into_any(),
95-
))
96-
}
97-
},
98-
Self::Ucs4(data) => match data.iter().map(|&c| std::char::from_u32(c)).collect() {
99-
Some(s) => Ok(Cow::Owned(s)),
100-
None => Err(crate::PyErr::from_value_bound(
101-
PyUnicodeDecodeError::new_bound(
84+
Err(PyUnicodeDecodeError::new_bound(
10285
py,
103-
CStr::from_bytes_with_nul(b"utf-32\0").unwrap(),
86+
CStr::from_bytes_with_nul(b"utf-16\0").unwrap(),
10487
self.as_bytes(),
10588
0..self.as_bytes().len(),
106-
CStr::from_bytes_with_nul(b"error converting utf-32\0").unwrap(),
89+
CStr::from_bytes_with_nul(&message).unwrap(),
10790
)?
108-
.into_any(),
109-
)),
91+
.into())
92+
}
93+
},
94+
Self::Ucs4(data) => match data.iter().map(|&c| std::char::from_u32(c)).collect() {
95+
Some(s) => Ok(Cow::Owned(s)),
96+
None => Err(PyUnicodeDecodeError::new_bound(
97+
py,
98+
CStr::from_bytes_with_nul(b"utf-32\0").unwrap(),
99+
self.as_bytes(),
100+
0..self.as_bytes().len(),
101+
CStr::from_bytes_with_nul(b"error converting utf-32\0").unwrap(),
102+
)?
103+
.into()),
110104
},
111105
}
112106
}

tests/ui/invalid_result_conversion.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ error[E0277]: the trait bound `PyErr: From<MyError>` is not satisfied
55
| ^^^^^^^^^^^^^ the trait `From<MyError>` is not implemented for `PyErr`
66
|
77
= help: the following other types implement trait `From<T>`:
8+
<PyErr as From<pyo3::Bound<'py, T>>>
89
<PyErr as From<std::io::Error>>
910
<PyErr as From<PyBorrowError>>
1011
<PyErr as From<PyBorrowMutError>>
1112
<PyErr as From<PyDowncastError<'a>>>
1213
<PyErr as From<DowncastError<'_, '_>>>
1314
<PyErr as From<DowncastIntoError<'_>>>
1415
<PyErr as From<NulError>>
15-
<PyErr as From<IntoStringError>>
1616
and $N others
1717
= note: required for `MyError` to implement `Into<PyErr>`
1818
= note: this error originates in the attribute macro `pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)

0 commit comments

Comments
 (0)