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}