Skip to main content

pyo3/
type_object.rs

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