Skip to content

Commit dc1eb9f

Browse files
committed
Lazily fetch type name
1 parent 7b921c9 commit dc1eb9f

File tree

2 files changed

+76
-28
lines changed

2 files changed

+76
-28
lines changed

src/err/mod.rs

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,33 @@ pub type PyResult<T> = Result<T, PyErr>;
4646
#[derive(Debug)]
4747
pub struct DowncastError<'a, 'py> {
4848
from: Borrowed<'a, 'py, PyAny>,
49-
to: Cow<'static, str>,
49+
to: TypeNameOrValue<'py>,
5050
}
5151

5252
impl<'a, 'py> DowncastError<'a, 'py> {
5353
/// Create a new `PyDowncastError` representing a failure to convert the object
5454
/// `from` into the type named in `to`.
5555
pub fn new(from: &'a Bound<'py, PyAny>, to: impl Into<Cow<'static, str>>) -> Self {
56-
DowncastError {
56+
Self {
5757
from: from.as_borrowed(),
58-
to: to.into(),
58+
to: TypeNameOrValue::Name(to.into()),
5959
}
6060
}
61+
6162
pub(crate) fn new_from_borrowed(
6263
from: Borrowed<'a, 'py, PyAny>,
6364
to: impl Into<Cow<'static, str>>,
6465
) -> Self {
65-
DowncastError {
66+
Self {
67+
from,
68+
to: TypeNameOrValue::Name(to.into()),
69+
}
70+
}
71+
72+
pub(crate) fn new_from_type(from: Borrowed<'a, 'py, PyAny>, to: Bound<'py, PyType>) -> Self {
73+
Self {
6674
from,
67-
to: to.into(),
75+
to: TypeNameOrValue::Value(to),
6876
}
6977
}
7078
}
@@ -73,16 +81,23 @@ impl<'a, 'py> DowncastError<'a, 'py> {
7381
#[derive(Debug)]
7482
pub struct DowncastIntoError<'py> {
7583
from: Bound<'py, PyAny>,
76-
to: Cow<'static, str>,
84+
to: TypeNameOrValue<'py>,
7785
}
7886

7987
impl<'py> DowncastIntoError<'py> {
8088
/// Create a new `DowncastIntoError` representing a failure to convert the object
8189
/// `from` into the type named in `to`.
8290
pub fn new(from: Bound<'py, PyAny>, to: impl Into<Cow<'static, str>>) -> Self {
83-
DowncastIntoError {
91+
Self {
8492
from,
85-
to: to.into(),
93+
to: TypeNameOrValue::Name(to.into()),
94+
}
95+
}
96+
97+
pub(crate) fn new_from_type(from: Bound<'py, PyAny>, to: Bound<'py, PyType>) -> Self {
98+
Self {
99+
from,
100+
to: TypeNameOrValue::Value(to),
86101
}
87102
}
88103

@@ -95,6 +110,11 @@ impl<'py> DowncastIntoError<'py> {
95110
}
96111
}
97112

113+
// Helper to store either a concrete type or a type name
114+
enum TypeNameOrValue<'py> {
115+
Name(Cow<'static, str>),
116+
Value(Bound<'py, PyType>),
117+
}
98118
/// Helper conversion trait that allows to use custom arguments for lazy exception construction.
99119
pub trait PyErrArguments: Send + Sync {
100120
/// Arguments for exception
@@ -721,25 +741,33 @@ impl<'py> IntoPyObject<'py> for &PyErr {
721741

722742
struct PyDowncastErrorArguments {
723743
from: Py<PyType>,
724-
to: Cow<'static, str>,
744+
to: OwnedTypeNameOrValue,
725745
}
726746

727747
impl PyErrArguments for PyDowncastErrorArguments {
728748
fn arguments(self, py: Python<'_>) -> Py<PyAny> {
729-
const FAILED_TO_EXTRACT: Cow<'_, str> = Cow::Borrowed("<failed to extract type name>");
730749
let from = self.from.bind(py).qualname();
731-
let from = match &from {
732-
Ok(qn) => qn.to_cow().unwrap_or(FAILED_TO_EXTRACT),
733-
Err(_) => FAILED_TO_EXTRACT,
750+
let from = from
751+
.as_ref()
752+
.map(|name| name.to_string_lossy())
753+
.unwrap_or(Cow::Borrowed("<failed to extract type name>"));
754+
let to = match self.to {
755+
OwnedTypeNameOrValue::Name(name) => TypeNameOrValue::Name(name),
756+
OwnedTypeNameOrValue::Value(t) => TypeNameOrValue::Value(t.into_bound(py)),
734757
};
735-
format!("'{}' object cannot be converted to '{}'", from, self.to)
758+
format!("'{}' object cannot be converted to '{}'", from, to)
736759
.into_pyobject(py)
737760
.unwrap()
738761
.into_any()
739762
.unbind()
740763
}
741764
}
742765

766+
enum OwnedTypeNameOrValue {
767+
Name(Cow<'static, str>),
768+
Value(Py<PyType>),
769+
}
770+
743771
/// Python exceptions that can be converted to [`PyErr`].
744772
///
745773
/// This is used to implement [`From<Bound<'_, T>> for PyErr`].
@@ -763,7 +791,10 @@ impl std::convert::From<DowncastError<'_, '_>> for PyErr {
763791
fn from(err: DowncastError<'_, '_>) -> PyErr {
764792
let args = PyDowncastErrorArguments {
765793
from: err.from.get_type().into(),
766-
to: err.to,
794+
to: match err.to {
795+
TypeNameOrValue::Name(name) => OwnedTypeNameOrValue::Name(name),
796+
TypeNameOrValue::Value(t) => OwnedTypeNameOrValue::Value(t.into()),
797+
},
767798
};
768799

769800
exceptions::PyTypeError::new_err(args)
@@ -783,7 +814,10 @@ impl std::convert::From<DowncastIntoError<'_>> for PyErr {
783814
fn from(err: DowncastIntoError<'_>) -> PyErr {
784815
let args = PyDowncastErrorArguments {
785816
from: err.from.get_type().into(),
786-
to: err.to,
817+
to: match err.to {
818+
TypeNameOrValue::Name(name) => OwnedTypeNameOrValue::Name(name),
819+
TypeNameOrValue::Value(t) => OwnedTypeNameOrValue::Value(t.into()),
820+
},
787821
};
788822

789823
exceptions::PyTypeError::new_err(args)
@@ -801,7 +835,7 @@ impl std::fmt::Display for DowncastIntoError<'_> {
801835
fn display_downcast_error(
802836
f: &mut std::fmt::Formatter<'_>,
803837
from: &Bound<'_, PyAny>,
804-
to: &str,
838+
to: &TypeNameOrValue<'_>,
805839
) -> std::fmt::Result {
806840
write!(
807841
f,
@@ -811,6 +845,25 @@ fn display_downcast_error(
811845
)
812846
}
813847

848+
impl std::fmt::Debug for TypeNameOrValue<'_> {
849+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
850+
std::fmt::Display::fmt(self, f)
851+
}
852+
}
853+
854+
impl std::fmt::Display for TypeNameOrValue<'_> {
855+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
856+
match self {
857+
Self::Name(name) => name.fmt(f),
858+
Self::Value(t) => t
859+
.qualname()
860+
.map_err(|_| std::fmt::Error)?
861+
.to_string_lossy()
862+
.fmt(f),
863+
}
864+
}
865+
}
866+
814867
#[track_caller]
815868
pub fn panic_after_error(_py: Python<'_>) -> ! {
816869
unsafe {

src/instance.rs

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use crate::{
1212
PyRefMut, PyTypeInfo, Python,
1313
};
1414
use crate::{internal::state, PyTypeCheck};
15-
use std::borrow::Cow;
1615
use std::marker::PhantomData;
1716
use std::mem::ManuallyDrop;
1817
use std::ops::Deref;
@@ -265,7 +264,10 @@ impl<'py, T> Bound<'py, T> {
265264
// Safety: is_exact_instance_of is responsible for ensuring that the type is correct
266265
Ok(unsafe { any.cast_unchecked() })
267266
} else {
268-
Err(DowncastError::new(any, type_name::<U>(any.py())))
267+
Err(DowncastError::new_from_type(
268+
any.as_borrowed(),
269+
U::type_object(any.py()),
270+
))
269271
}
270272
}
271273

@@ -287,8 +289,8 @@ impl<'py, T> Bound<'py, T> {
287289
// Safety: is_exact_instance_of is responsible for ensuring that the type is correct
288290
Ok(unsafe { any.cast_into_unchecked() })
289291
} else {
290-
let to = type_name::<U>(any.py());
291-
Err(DowncastIntoError::new(any, to))
292+
let to = U::type_object(any.py());
293+
Err(DowncastIntoError::new_from_type(any, to))
292294
}
293295
}
294296

@@ -2219,13 +2221,6 @@ impl<T> Py<T> {
22192221
}
22202222
}
22212223

2222-
fn type_name<T: PyTypeInfo>(py: Python<'_>) -> Cow<'static, str> {
2223-
T::type_object(py)
2224-
.name()
2225-
.map(|name| Cow::Owned(name.to_string_lossy().into_owned()))
2226-
.unwrap_or(Cow::Borrowed("unknown type"))
2227-
}
2228-
22292224
#[cfg(test)]
22302225
mod tests {
22312226
use super::{Bound, IntoPyObject, Py};

0 commit comments

Comments
 (0)