Skip to main content

pyo3/
type_object.rs

1//! Python type object information
2
3use crate::ffi_ptr_ext::FfiPtrExt;
4#[cfg(feature = "experimental-inspect")]
5use crate::inspect::{type_hint_identifier, PyStaticExpr};
6use crate::types::{PyAny, PyType};
7use crate::{ffi, Bound, Python};
8use std::ptr;
9
10/// `T: PyLayout<U>` represents that `T` is a concrete representation of `U` in the Python heap.
11/// E.g., `PyClassObject` is a concrete representation of all `pyclass`es, and `ffi::PyObject`
12/// is of `PyAny`.
13///
14/// This trait is intended to be used internally.
15///
16/// # Safety
17///
18/// This trait must only be implemented for types which represent valid layouts of Python objects.
19pub unsafe trait PyLayout<T> {}
20
21/// `T: PySizedLayout<U>` represents that `T` is not a instance of
22/// [`PyVarObject`](https://docs.python.org/3/c-api/structures.html#c.PyVarObject).
23///
24/// In addition, that `T` is a concrete representation of `U`.
25pub trait PySizedLayout<T>: PyLayout<T> + Sized {}
26
27/// Python type information.
28/// All Python native types (e.g., `PyDict`) and `#[pyclass]` structs implement this trait.
29///
30/// This trait is marked unsafe because:
31///  - specifying the incorrect layout can lead to memory errors
32///  - the return value of type_object must always point to the same PyTypeObject instance
33///
34/// It is safely implemented by the `pyclass` macro.
35///
36/// # Safety
37///
38/// Implementations must provide an implementation for `type_object_raw` which infallibly produces a
39/// non-null pointer to the corresponding Python type object.
40///
41/// `is_type_of` must only return true for objects which can safely be treated as instances of `Self`.
42///
43/// `is_exact_type_of` must only return true for objects whose type is exactly `Self`.
44pub unsafe trait PyTypeInfo: Sized {
45    /// Class name.
46    #[deprecated(
47        since = "0.28.0",
48        note = "prefer using `::type_object(py).name()` to get the correct runtime value"
49    )]
50    const NAME: &'static str;
51
52    /// Module name, if any.
53    #[deprecated(
54        since = "0.28.0",
55        note = "prefer using `::type_object(py).module()` to get the correct runtime value"
56    )]
57    const MODULE: Option<&'static str>;
58
59    /// Provides the full python type as a type hint.
60    #[cfg(feature = "experimental-inspect")]
61    const TYPE_HINT: PyStaticExpr = type_hint_identifier!("_typeshed", "Incomplete");
62
63    /// Returns the PyTypeObject instance for this type.
64    fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject;
65
66    /// Returns the safe abstraction over the type object.
67    #[inline]
68    fn type_object(py: Python<'_>) -> Bound<'_, PyType> {
69        // Making the borrowed object `Bound` is necessary for soundness reasons. It's an extreme
70        // edge case, but arbitrary Python code _could_ change the __class__ of an object and cause
71        // the type object to be freed.
72        //
73        // By making `Bound` we assume ownership which is then safe against races.
74        unsafe {
75            Self::type_object_raw(py)
76                .cast::<ffi::PyObject>()
77                .assume_borrowed_unchecked(py)
78                .to_owned()
79                .cast_into_unchecked()
80        }
81    }
82
83    /// Checks if `object` is an instance of this type or a subclass of this type.
84    #[inline]
85    fn is_type_of(object: &Bound<'_, PyAny>) -> bool {
86        unsafe { ffi::PyObject_TypeCheck(object.as_ptr(), Self::type_object_raw(object.py())) != 0 }
87    }
88
89    /// Checks if `object` is an instance of this type.
90    #[inline]
91    fn is_exact_type_of(object: &Bound<'_, PyAny>) -> bool {
92        unsafe {
93            ptr::eq(
94                ffi::Py_TYPE(object.as_ptr()),
95                Self::type_object_raw(object.py()),
96            )
97        }
98    }
99}
100
101/// Implemented by types which can be used as a concrete Python type inside `Py<T>` smart pointers.
102///
103/// # Safety
104///
105/// This trait is used to determine whether [`Bound::cast`] and similar functions can safely cast
106/// to a concrete type. The implementor is responsible for ensuring that `type_check` only returns
107/// true for objects which can safely be treated as Python instances of `Self`.
108pub unsafe trait PyTypeCheck {
109    /// Name of self. This is used in error messages, for example.
110    #[deprecated(
111        since = "0.27.0",
112        note = "Use ::classinfo_object() instead and format the type name at runtime. Note that using built-in cast features is often better than manual PyTypeCheck usage."
113    )]
114    const NAME: &'static str;
115
116    /// Provides the full python type of the allowed values as a Python type hint.
117    #[cfg(feature = "experimental-inspect")]
118    const TYPE_HINT: PyStaticExpr;
119
120    /// Checks if `object` is an instance of `Self`, which may include a subtype.
121    ///
122    /// This should be equivalent to the Python expression `isinstance(object, Self)`.
123    fn type_check(object: &Bound<'_, PyAny>) -> bool;
124
125    /// Returns the expected type as a possible argument for the `isinstance` and `issubclass` function.
126    ///
127    /// It may be a single type or a tuple of types.
128    fn classinfo_object(py: Python<'_>) -> Bound<'_, PyAny>;
129}
130
131unsafe impl<T> PyTypeCheck for T
132where
133    T: PyTypeInfo,
134{
135    #[allow(deprecated)]
136    const NAME: &'static str = T::NAME;
137
138    #[cfg(feature = "experimental-inspect")]
139    const TYPE_HINT: PyStaticExpr = <T as PyTypeInfo>::TYPE_HINT;
140
141    #[inline]
142    fn type_check(object: &Bound<'_, PyAny>) -> bool {
143        T::is_type_of(object)
144    }
145
146    #[inline]
147    fn classinfo_object(py: Python<'_>) -> Bound<'_, PyAny> {
148        T::type_object(py).into_any()
149    }
150}