Skip to content

Commit 196ed31

Browse files
committed
Introduce PyTypeCheck::classinfo_object and make use of it
1 parent 2bb66c9 commit 196ed31

File tree

12 files changed

+190
-49
lines changed

12 files changed

+190
-49
lines changed

newsfragments/5387.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `PyTypeCheck::classinfo_object` that returns an object that can be used as parameter in `isinstance` or `issubclass`

newsfragments/5387.changed.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Fetch type name dynamically on cast errors instead of using `PyTypeInfo::NAME`
1+
Fetch type name dynamically on cast errors instead of using `PyTypeCheck::NAME`

newsfragments/5387.removed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
removed `PyTypeCheck::NAME`

src/conversions/smallvec.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ where
102102
if ffi::PySequence_Check(obj.as_ptr()) != 0 {
103103
obj.cast_unchecked::<PySequence>()
104104
} else {
105-
return Err(DowncastError::new_from_borrowed(obj, "Sequence").into());
105+
return Err(
106+
DowncastError::new_from_type(obj, PySequence::type_object(obj.py())).into(),
107+
);
106108
}
107109
};
108110

src/conversions/std/array.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::conversion::{FromPyObjectOwned, IntoPyObject};
22
use crate::types::any::PyAnyMethods;
33
use crate::types::PySequence;
4-
use crate::{err::DowncastError, ffi, FromPyObject, PyAny, PyResult, Python};
4+
use crate::{err::DowncastError, ffi, FromPyObject, PyAny, PyResult, PyTypeInfo, Python};
55
use crate::{exceptions, Borrowed, Bound, PyErr};
66

77
impl<'py, T, const N: usize> IntoPyObject<'py> for [T; N]
@@ -57,7 +57,11 @@ where
5757
if ffi::PySequence_Check(obj.as_ptr()) != 0 {
5858
obj.cast_unchecked::<PySequence>()
5959
} else {
60-
return Err(DowncastError::new_from_borrowed(obj, "Sequence").into());
60+
return Err(DowncastError::new_from_type(
61+
obj,
62+
PySequence::type_object(obj.py()).into_any(),
63+
)
64+
.into());
6165
}
6266
};
6367
let seq_len = seq.len()?;

src/err/mod.rs

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::type_object::PyTypeInfo;
44
use crate::types::any::PyAnyMethods;
55
use crate::types::{
66
string::PyStringMethods, traceback::PyTracebackMethods, typeobject::PyTypeMethods, PyTraceback,
7-
PyType,
7+
PyTuple, PyTupleMethods, PyType,
88
};
99
use crate::{
1010
exceptions::{self, PyBaseException},
@@ -59,17 +59,7 @@ impl<'a, 'py> DowncastError<'a, 'py> {
5959
}
6060
}
6161

62-
pub(crate) fn new_from_borrowed(
63-
from: Borrowed<'a, 'py, PyAny>,
64-
to: impl Into<Cow<'static, str>>,
65-
) -> Self {
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 {
62+
pub(crate) fn new_from_type(from: Borrowed<'a, 'py, PyAny>, to: Bound<'py, PyAny>) -> Self {
7363
Self {
7464
from,
7565
to: TypeNameOrValue::Value(to),
@@ -94,7 +84,7 @@ impl<'py> DowncastIntoError<'py> {
9484
}
9585
}
9686

97-
pub(crate) fn new_from_type(from: Bound<'py, PyAny>, to: Bound<'py, PyType>) -> Self {
87+
pub(crate) fn new_from_type(from: Bound<'py, PyAny>, to: Bound<'py, PyAny>) -> Self {
9888
Self {
9989
from,
10090
to: TypeNameOrValue::Value(to),
@@ -114,7 +104,7 @@ impl<'py> DowncastIntoError<'py> {
114104
#[derive(Debug)]
115105
enum TypeNameOrValue<'py> {
116106
Name(Cow<'static, str>),
117-
Value(Bound<'py, PyType>),
107+
Value(Bound<'py, PyAny>),
118108
}
119109
/// Helper conversion trait that allows to use custom arguments for lazy exception construction.
120110
pub trait PyErrArguments: Send + Sync {
@@ -766,7 +756,7 @@ impl PyErrArguments for PyDowncastErrorArguments {
766756

767757
enum OwnedTypeNameOrValue {
768758
Name(Cow<'static, str>),
769-
Value(Py<PyType>),
759+
Value(Py<PyAny>),
770760
}
771761

772762
/// Python exceptions that can be converted to [`PyErr`].
@@ -850,11 +840,24 @@ impl std::fmt::Display for TypeNameOrValue<'_> {
850840
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
851841
match self {
852842
Self::Name(name) => name.fmt(f),
853-
Self::Value(t) => t
854-
.qualname()
855-
.map_err(|_| std::fmt::Error)?
856-
.to_string_lossy()
857-
.fmt(f),
843+
Self::Value(t) => {
844+
if let Ok(t) = t.downcast::<PyType>() {
845+
t.qualname()
846+
.map_err(|_| std::fmt::Error)?
847+
.to_string_lossy()
848+
.fmt(f)
849+
} else if let Ok(t) = t.downcast::<PyTuple>() {
850+
for (i, t) in t.iter().enumerate() {
851+
if i > 0 {
852+
f.write_str(" | ")?;
853+
}
854+
TypeNameOrValue::Value(t).fmt(f)?;
855+
}
856+
Ok(())
857+
} else {
858+
t.fmt(f)
859+
}
860+
}
858861
}
859862
}
860863
}

src/instance.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,10 @@ impl<'py, T> Bound<'py, T> {
167167
// Safety: type_check is responsible for ensuring that the type is correct
168168
Ok(unsafe { any.cast_unchecked() })
169169
} else {
170-
Err(DowncastError::new(any, U::NAME))
170+
Err(DowncastError::new_from_type(
171+
any.as_borrowed(),
172+
U::classinfo_object(any.py()),
173+
))
171174
}
172175
}
173176

@@ -211,7 +214,8 @@ impl<'py, T> Bound<'py, T> {
211214
// Safety: type_check is responsible for ensuring that the type is correct
212215
Ok(unsafe { any.cast_into_unchecked() })
213216
} else {
214-
Err(DowncastIntoError::new(any, U::NAME))
217+
let to = U::classinfo_object(any.py());
218+
Err(DowncastIntoError::new_from_type(any, to))
215219
}
216220
}
217221

@@ -266,7 +270,7 @@ impl<'py, T> Bound<'py, T> {
266270
} else {
267271
Err(DowncastError::new_from_type(
268272
any.as_borrowed(),
269-
U::type_object(any.py()),
273+
U::type_object(any.py()).into_any(),
270274
))
271275
}
272276
}
@@ -289,7 +293,7 @@ impl<'py, T> Bound<'py, T> {
289293
// Safety: is_exact_instance_of is responsible for ensuring that the type is correct
290294
Ok(unsafe { any.cast_into_unchecked() })
291295
} else {
292-
let to = U::type_object(any.py());
296+
let to = U::type_object(any.py()).into_any();
293297
Err(DowncastIntoError::new_from_type(any, to))
294298
}
295299
}
@@ -1048,7 +1052,10 @@ impl<'a, 'py> Borrowed<'a, 'py, PyAny> {
10481052
// Safety: type_check is responsible for ensuring that the type is correct
10491053
Ok(unsafe { self.cast_unchecked() })
10501054
} else {
1051-
Err(DowncastError::new_from_borrowed(self, T::NAME))
1055+
Err(DowncastError::new_from_type(
1056+
self,
1057+
T::classinfo_object(self.py()),
1058+
))
10521059
}
10531060
}
10541061

src/pybacked.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ use std::{convert::Infallible, ops::Deref, ptr::NonNull, sync::Arc};
55
use crate::{
66
types::{
77
bytearray::PyByteArrayMethods, bytes::PyBytesMethods, string::PyStringMethods, PyByteArray,
8-
PyBytes, PyString,
8+
PyBytes, PyString, PyTuple,
99
},
10-
Borrowed, Bound, DowncastError, FromPyObject, IntoPyObject, Py, PyAny, PyErr, Python,
10+
Borrowed, Bound, DowncastError, FromPyObject, IntoPyObject, Py, PyAny, PyErr, PyTypeInfo,
11+
Python,
1112
};
1213

1314
/// A wrapper around `str` where the storage is owned by a Python `bytes` or `str` object.
@@ -212,9 +213,17 @@ impl<'a, 'py> FromPyObject<'a, 'py> for PyBackedBytes {
212213
} else if let Ok(bytearray) = obj.cast::<PyByteArray>() {
213214
Ok(Self::from(bytearray.to_owned()))
214215
} else {
215-
Err(DowncastError::new_from_borrowed(
216+
Err(DowncastError::new_from_type(
216217
obj,
217-
"`bytes` or `bytearray`",
218+
PyTuple::new(
219+
obj.py(),
220+
[
221+
PyBytes::type_object(obj.py()),
222+
PyByteArray::type_object(obj.py()),
223+
],
224+
)
225+
.unwrap()
226+
.into_any(),
218227
))
219228
}
220229
}

src/type_object.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,6 @@ pub unsafe trait PyTypeInfo: Sized {
8686

8787
/// Implemented by types which can be used as a concrete Python type inside `Py<T>` smart pointers.
8888
pub trait PyTypeCheck {
89-
/// Name of self. This is used in error messages, for example.
90-
const NAME: &'static str;
91-
9289
/// Provides the full python type of the allowed values.
9390
#[cfg(feature = "experimental-inspect")]
9491
const PYTHON_TYPE: &'static str;
@@ -97,19 +94,27 @@ pub trait PyTypeCheck {
9794
///
9895
/// This should be equivalent to the Python expression `isinstance(object, Self)`.
9996
fn type_check(object: &Bound<'_, PyAny>) -> bool;
97+
98+
/// Returns the expected type as a possible argument for the `isinstance` and `issubclass` function.
99+
///
100+
/// It may be a single type or a tuple of types.
101+
fn classinfo_object(py: Python<'_>) -> Bound<'_, PyAny>;
100102
}
101103

102104
impl<T> PyTypeCheck for T
103105
where
104106
T: PyTypeInfo,
105107
{
106-
const NAME: &'static str = <T as PyTypeInfo>::NAME;
107-
108108
#[cfg(feature = "experimental-inspect")]
109109
const PYTHON_TYPE: &'static str = <T as PyTypeInfo>::PYTHON_TYPE;
110110

111111
#[inline]
112112
fn type_check(object: &Bound<'_, PyAny>) -> bool {
113113
T::is_type_of(object)
114114
}
115+
116+
#[inline]
117+
fn classinfo_object(py: Python<'_>) -> Bound<'_, PyAny> {
118+
T::type_object(py).into_any()
119+
}
115120
}

src/types/sequence.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,11 @@ where
387387
if ffi::PySequence_Check(obj.as_ptr()) != 0 {
388388
obj.cast_unchecked::<PySequence>()
389389
} else {
390-
return Err(DowncastError::new_from_borrowed(obj, "Sequence").into());
390+
return Err(DowncastError::new_from_type(
391+
obj,
392+
PySequence::type_object(obj.py()).into_any(),
393+
)
394+
.into());
391395
}
392396
};
393397

0 commit comments

Comments
 (0)