pyo3/
type_object.rs

1//! Python type object information
2
3use crate::ffi_ptr_ext::FfiPtrExt;
4use crate::types::any::PyAnyMethods;
5use crate::types::{PyAny, PyType};
6#[cfg(feature = "gil-refs")]
7use crate::PyNativeType;
8use crate::{ffi, Bound, Python};
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/// Specifies that this type has a "GIL-bound Reference" form.
28///
29/// This is expected to be deprecated in the near future, see <https://github.com/PyO3/pyo3/issues/3382>
30///
31/// # Safety
32///
33/// - `Py<Self>::as_ref` will hand out references to `Self::AsRefTarget`.
34/// - `Self::AsRefTarget` must have the same layout as `UnsafeCell<ffi::PyAny>`.
35#[cfg(feature = "gil-refs")]
36pub unsafe trait HasPyGilRef {
37    /// Utility type to make Py::as_ref work.
38    type AsRefTarget: PyNativeType;
39}
40
41#[cfg(feature = "gil-refs")]
42unsafe impl<T> HasPyGilRef for T
43where
44    T: PyNativeType,
45{
46    type AsRefTarget = Self;
47}
48
49/// Python type information.
50/// All Python native types (e.g., `PyDict`) and `#[pyclass]` structs implement this trait.
51///
52/// This trait is marked unsafe because:
53///  - specifying the incorrect layout can lead to memory errors
54///  - the return value of type_object must always point to the same PyTypeObject instance
55///
56/// It is safely implemented by the `pyclass` macro.
57///
58/// # Safety
59///
60/// Implementations must provide an implementation for `type_object_raw` which infallibly produces a
61/// non-null pointer to the corresponding Python type object.
62#[cfg(feature = "gil-refs")]
63pub unsafe trait PyTypeInfo: Sized + HasPyGilRef {
64    /// Class name.
65    const NAME: &'static str;
66
67    /// Module name, if any.
68    const MODULE: Option<&'static str>;
69
70    /// Returns the PyTypeObject instance for this type.
71    fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject;
72
73    /// Returns the safe abstraction over the type object.
74    #[inline]
75    #[cfg(feature = "gil-refs")]
76    #[deprecated(
77        since = "0.21.0",
78        note = "`PyTypeInfo::type_object` will be replaced by `PyTypeInfo::type_object_bound` in a future PyO3 version"
79    )]
80    fn type_object(py: Python<'_>) -> &PyType {
81        // This isn't implemented in terms of `type_object_bound` because this just borrowed the
82        // object, for legacy reasons.
83        #[allow(deprecated)]
84        unsafe {
85            py.from_borrowed_ptr(Self::type_object_raw(py) as _)
86        }
87    }
88
89    /// Returns the safe abstraction over the type object.
90    #[inline]
91    fn type_object_bound(py: Python<'_>) -> Bound<'_, PyType> {
92        // Making the borrowed object `Bound` is necessary for soundness reasons. It's an extreme
93        // edge case, but arbitrary Python code _could_ change the __class__ of an object and cause
94        // the type object to be freed.
95        //
96        // By making `Bound` we assume ownership which is then safe against races.
97        unsafe {
98            Self::type_object_raw(py)
99                .cast::<ffi::PyObject>()
100                .assume_borrowed_unchecked(py)
101                .to_owned()
102                .downcast_into_unchecked()
103        }
104    }
105
106    /// Checks if `object` is an instance of this type or a subclass of this type.
107    #[inline]
108    #[cfg(feature = "gil-refs")]
109    #[deprecated(
110        since = "0.21.0",
111        note = "`PyTypeInfo::is_type_of` will be replaced by `PyTypeInfo::is_type_of_bound` in a future PyO3 version"
112    )]
113    fn is_type_of(object: &PyAny) -> bool {
114        Self::is_type_of_bound(&object.as_borrowed())
115    }
116
117    /// Checks if `object` is an instance of this type or a subclass of this type.
118    #[inline]
119    fn is_type_of_bound(object: &Bound<'_, PyAny>) -> bool {
120        unsafe { ffi::PyObject_TypeCheck(object.as_ptr(), Self::type_object_raw(object.py())) != 0 }
121    }
122
123    /// Checks if `object` is an instance of this type.
124    #[inline]
125    #[cfg(feature = "gil-refs")]
126    #[deprecated(
127        since = "0.21.0",
128        note = "`PyTypeInfo::is_exact_type_of` will be replaced by `PyTypeInfo::is_exact_type_of_bound` in a future PyO3 version"
129    )]
130    fn is_exact_type_of(object: &PyAny) -> bool {
131        Self::is_exact_type_of_bound(&object.as_borrowed())
132    }
133
134    /// Checks if `object` is an instance of this type.
135    #[inline]
136    fn is_exact_type_of_bound(object: &Bound<'_, PyAny>) -> bool {
137        unsafe { ffi::Py_TYPE(object.as_ptr()) == Self::type_object_raw(object.py()) }
138    }
139}
140
141/// Python type information.
142/// All Python native types (e.g., `PyDict`) and `#[pyclass]` structs implement this trait.
143///
144/// This trait is marked unsafe because:
145///  - specifying the incorrect layout can lead to memory errors
146///  - the return value of type_object must always point to the same PyTypeObject instance
147///
148/// It is safely implemented by the `pyclass` macro.
149///
150/// # Safety
151///
152/// Implementations must provide an implementation for `type_object_raw` which infallibly produces a
153/// non-null pointer to the corresponding Python type object.
154#[cfg(not(feature = "gil-refs"))]
155pub unsafe trait PyTypeInfo: Sized {
156    /// Class name.
157    const NAME: &'static str;
158
159    /// Module name, if any.
160    const MODULE: Option<&'static str>;
161
162    /// Returns the PyTypeObject instance for this type.
163    fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject;
164
165    /// Returns the safe abstraction over the type object.
166    #[inline]
167    fn type_object_bound(py: Python<'_>) -> Bound<'_, PyType> {
168        // Making the borrowed object `Bound` is necessary for soundness reasons. It's an extreme
169        // edge case, but arbitrary Python code _could_ change the __class__ of an object and cause
170        // the type object to be freed.
171        //
172        // By making `Bound` we assume ownership which is then safe against races.
173        unsafe {
174            Self::type_object_raw(py)
175                .cast::<ffi::PyObject>()
176                .assume_borrowed_unchecked(py)
177                .to_owned()
178                .downcast_into_unchecked()
179        }
180    }
181
182    /// Checks if `object` is an instance of this type or a subclass of this type.
183    #[inline]
184    fn is_type_of_bound(object: &Bound<'_, PyAny>) -> bool {
185        unsafe { ffi::PyObject_TypeCheck(object.as_ptr(), Self::type_object_raw(object.py())) != 0 }
186    }
187
188    /// Checks if `object` is an instance of this type.
189    #[inline]
190    fn is_exact_type_of_bound(object: &Bound<'_, PyAny>) -> bool {
191        unsafe { ffi::Py_TYPE(object.as_ptr()) == Self::type_object_raw(object.py()) }
192    }
193}
194
195/// Implemented by types which can be used as a concrete Python type inside `Py<T>` smart pointers.
196#[cfg(feature = "gil-refs")]
197pub trait PyTypeCheck: HasPyGilRef {
198    /// Name of self. This is used in error messages, for example.
199    const NAME: &'static str;
200
201    /// Checks if `object` is an instance of `Self`, which may include a subtype.
202    ///
203    /// This should be equivalent to the Python expression `isinstance(object, Self)`.
204    fn type_check(object: &Bound<'_, PyAny>) -> bool;
205}
206
207/// Implemented by types which can be used as a concrete Python type inside `Py<T>` smart pointers.
208#[cfg(not(feature = "gil-refs"))]
209pub trait PyTypeCheck {
210    /// Name of self. This is used in error messages, for example.
211    const NAME: &'static str;
212
213    /// Checks if `object` is an instance of `Self`, which may include a subtype.
214    ///
215    /// This should be equivalent to the Python expression `isinstance(object, Self)`.
216    fn type_check(object: &Bound<'_, PyAny>) -> bool;
217}
218
219impl<T> PyTypeCheck for T
220where
221    T: PyTypeInfo,
222{
223    const NAME: &'static str = <T as PyTypeInfo>::NAME;
224
225    #[inline]
226    fn type_check(object: &Bound<'_, PyAny>) -> bool {
227        T::is_type_of_bound(object)
228    }
229}