pyo3/internal/
get_slot.rs

1use crate::{
2    ffi,
3    types::{PyType, PyTypeMethods},
4    Borrowed, Bound,
5};
6use std::os::raw::c_int;
7
8impl Bound<'_, PyType> {
9    #[inline]
10    pub(crate) fn get_slot<const S: c_int>(&self, slot: Slot<S>) -> <Slot<S> as GetSlotImpl>::Type
11    where
12        Slot<S>: GetSlotImpl,
13    {
14        // SAFETY: `self` is a valid type object.
15        unsafe {
16            slot.get_slot(
17                self.as_type_ptr(),
18                #[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
19                is_runtime_3_10(self.py()),
20            )
21        }
22    }
23}
24
25impl Borrowed<'_, '_, PyType> {
26    #[inline]
27    pub(crate) fn get_slot<const S: c_int>(self, slot: Slot<S>) -> <Slot<S> as GetSlotImpl>::Type
28    where
29        Slot<S>: GetSlotImpl,
30    {
31        // SAFETY: `self` is a valid type object.
32        unsafe {
33            slot.get_slot(
34                self.as_type_ptr(),
35                #[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
36                is_runtime_3_10(self.py()),
37            )
38        }
39    }
40}
41
42/// Gets a slot from a raw FFI pointer.
43///
44/// Safety:
45///   - `ty` must be a valid non-null pointer to a `PyTypeObject`.
46///   - The Python runtime must be initialized
47pub(crate) unsafe fn get_slot<const S: c_int>(
48    ty: *mut ffi::PyTypeObject,
49    slot: Slot<S>,
50) -> <Slot<S> as GetSlotImpl>::Type
51where
52    Slot<S>: GetSlotImpl,
53{
54    slot.get_slot(
55        ty,
56        // SAFETY: the Python runtime is initialized
57        #[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
58        is_runtime_3_10(crate::Python::assume_gil_acquired()),
59    )
60}
61
62pub(crate) trait GetSlotImpl {
63    type Type;
64
65    /// Gets the requested slot from a type object.
66    ///
67    /// Safety:
68    ///  - `ty` must be a valid non-null pointer to a `PyTypeObject`.
69    ///  - `is_runtime_3_10` must be `false` if the runtime is not Python 3.10 or later.
70    unsafe fn get_slot(
71        self,
72        ty: *mut ffi::PyTypeObject,
73        #[cfg(all(Py_LIMITED_API, not(Py_3_10)))] is_runtime_3_10: bool,
74    ) -> Self::Type;
75}
76
77#[derive(Copy, Clone)]
78pub(crate) struct Slot<const S: c_int>;
79
80macro_rules! impl_slots {
81    ($($name:ident: ($slot:ident, $field:ident) -> $tp:ty),+ $(,)?) => {
82        $(
83            pub (crate) const $name: Slot<{ ffi::$slot }> = Slot;
84
85            impl GetSlotImpl for Slot<{ ffi::$slot }> {
86                type Type = $tp;
87
88                #[inline]
89                unsafe fn get_slot(
90                    self,
91                    ty: *mut ffi::PyTypeObject,
92                    #[cfg(all(Py_LIMITED_API, not(Py_3_10)))] is_runtime_3_10: bool
93                ) -> Self::Type {
94                    #[cfg(not(Py_LIMITED_API))]
95                    {
96                        (*ty).$field
97                    }
98
99                    #[cfg(Py_LIMITED_API)]
100                    {
101                        #[cfg(not(Py_3_10))]
102                        {
103                            // Calling PyType_GetSlot on static types is not valid before Python 3.10
104                            // ... so the workaround is to first do a runtime check for these versions
105                            // (3.7, 3.8, 3.9) and then look in the type object anyway. This is only ok
106                            // because we know that the interpreter is not going to change the size
107                            // of the type objects for these historical versions.
108                            if !is_runtime_3_10 && ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) == 0
109                            {
110                                return (*ty.cast::<PyTypeObject39Snapshot>()).$field;
111                            }
112                        }
113
114                        // SAFETY: slot type is set carefully to be valid
115                        std::mem::transmute(ffi::PyType_GetSlot(ty, ffi::$slot))
116                    }
117                }
118            }
119        )*
120    };
121}
122
123// Slots are implemented on-demand as needed.)
124impl_slots! {
125    TP_ALLOC: (Py_tp_alloc, tp_alloc) -> Option<ffi::allocfunc>,
126    TP_BASE: (Py_tp_base, tp_base) -> *mut ffi::PyTypeObject,
127    TP_CLEAR: (Py_tp_clear, tp_clear) -> Option<ffi::inquiry>,
128    TP_DESCR_GET: (Py_tp_descr_get, tp_descr_get) -> Option<ffi::descrgetfunc>,
129    TP_FREE: (Py_tp_free, tp_free) -> Option<ffi::freefunc>,
130    TP_TRAVERSE: (Py_tp_traverse, tp_traverse) -> Option<ffi::traverseproc>,
131}
132
133#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
134fn is_runtime_3_10(py: crate::Python<'_>) -> bool {
135    use crate::sync::GILOnceCell;
136
137    static IS_RUNTIME_3_10: GILOnceCell<bool> = GILOnceCell::new();
138    *IS_RUNTIME_3_10.get_or_init(py, || py.version_info() >= (3, 10))
139}
140
141#[repr(C)]
142#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
143pub struct PyNumberMethods39Snapshot {
144    pub nb_add: Option<ffi::binaryfunc>,
145    pub nb_subtract: Option<ffi::binaryfunc>,
146    pub nb_multiply: Option<ffi::binaryfunc>,
147    pub nb_remainder: Option<ffi::binaryfunc>,
148    pub nb_divmod: Option<ffi::binaryfunc>,
149    pub nb_power: Option<ffi::ternaryfunc>,
150    pub nb_negative: Option<ffi::unaryfunc>,
151    pub nb_positive: Option<ffi::unaryfunc>,
152    pub nb_absolute: Option<ffi::unaryfunc>,
153    pub nb_bool: Option<ffi::inquiry>,
154    pub nb_invert: Option<ffi::unaryfunc>,
155    pub nb_lshift: Option<ffi::binaryfunc>,
156    pub nb_rshift: Option<ffi::binaryfunc>,
157    pub nb_and: Option<ffi::binaryfunc>,
158    pub nb_xor: Option<ffi::binaryfunc>,
159    pub nb_or: Option<ffi::binaryfunc>,
160    pub nb_int: Option<ffi::unaryfunc>,
161    pub nb_reserved: *mut std::os::raw::c_void,
162    pub nb_float: Option<ffi::unaryfunc>,
163    pub nb_inplace_add: Option<ffi::binaryfunc>,
164    pub nb_inplace_subtract: Option<ffi::binaryfunc>,
165    pub nb_inplace_multiply: Option<ffi::binaryfunc>,
166    pub nb_inplace_remainder: Option<ffi::binaryfunc>,
167    pub nb_inplace_power: Option<ffi::ternaryfunc>,
168    pub nb_inplace_lshift: Option<ffi::binaryfunc>,
169    pub nb_inplace_rshift: Option<ffi::binaryfunc>,
170    pub nb_inplace_and: Option<ffi::binaryfunc>,
171    pub nb_inplace_xor: Option<ffi::binaryfunc>,
172    pub nb_inplace_or: Option<ffi::binaryfunc>,
173    pub nb_floor_divide: Option<ffi::binaryfunc>,
174    pub nb_true_divide: Option<ffi::binaryfunc>,
175    pub nb_inplace_floor_divide: Option<ffi::binaryfunc>,
176    pub nb_inplace_true_divide: Option<ffi::binaryfunc>,
177    pub nb_index: Option<ffi::unaryfunc>,
178    pub nb_matrix_multiply: Option<ffi::binaryfunc>,
179    pub nb_inplace_matrix_multiply: Option<ffi::binaryfunc>,
180}
181
182#[repr(C)]
183#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
184pub struct PySequenceMethods39Snapshot {
185    pub sq_length: Option<ffi::lenfunc>,
186    pub sq_concat: Option<ffi::binaryfunc>,
187    pub sq_repeat: Option<ffi::ssizeargfunc>,
188    pub sq_item: Option<ffi::ssizeargfunc>,
189    pub was_sq_slice: *mut std::os::raw::c_void,
190    pub sq_ass_item: Option<ffi::ssizeobjargproc>,
191    pub was_sq_ass_slice: *mut std::os::raw::c_void,
192    pub sq_contains: Option<ffi::objobjproc>,
193    pub sq_inplace_concat: Option<ffi::binaryfunc>,
194    pub sq_inplace_repeat: Option<ffi::ssizeargfunc>,
195}
196
197#[repr(C)]
198#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
199pub struct PyMappingMethods39Snapshot {
200    pub mp_length: Option<ffi::lenfunc>,
201    pub mp_subscript: Option<ffi::binaryfunc>,
202    pub mp_ass_subscript: Option<ffi::objobjargproc>,
203}
204
205#[repr(C)]
206#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
207pub struct PyAsyncMethods39Snapshot {
208    pub am_await: Option<ffi::unaryfunc>,
209    pub am_aiter: Option<ffi::unaryfunc>,
210    pub am_anext: Option<ffi::unaryfunc>,
211}
212
213#[repr(C)]
214#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
215pub struct PyBufferProcs39Snapshot {
216    // not available in limited api, but structure needs to have the right size
217    pub bf_getbuffer: *mut std::os::raw::c_void,
218    pub bf_releasebuffer: *mut std::os::raw::c_void,
219}
220
221/// Snapshot of the structure of PyTypeObject for Python 3.7 through 3.9.
222///
223/// This is used as a fallback for static types in abi3 when the Python version is less than 3.10;
224/// this is a bit of a hack but there's no better option and the structure of the type object is
225/// not going to change for those historical versions.
226#[repr(C)]
227#[cfg(all(Py_LIMITED_API, not(Py_3_10)))]
228struct PyTypeObject39Snapshot {
229    pub ob_base: ffi::PyVarObject,
230    pub tp_name: *const std::os::raw::c_char,
231    pub tp_basicsize: ffi::Py_ssize_t,
232    pub tp_itemsize: ffi::Py_ssize_t,
233    pub tp_dealloc: Option<ffi::destructor>,
234    #[cfg(not(Py_3_8))]
235    pub tp_print: *mut std::os::raw::c_void, // stubbed out, not available in limited API
236    #[cfg(Py_3_8)]
237    pub tp_vectorcall_offset: ffi::Py_ssize_t,
238    pub tp_getattr: Option<ffi::getattrfunc>,
239    pub tp_setattr: Option<ffi::setattrfunc>,
240    pub tp_as_async: *mut PyAsyncMethods39Snapshot,
241    pub tp_repr: Option<ffi::reprfunc>,
242    pub tp_as_number: *mut PyNumberMethods39Snapshot,
243    pub tp_as_sequence: *mut PySequenceMethods39Snapshot,
244    pub tp_as_mapping: *mut PyMappingMethods39Snapshot,
245    pub tp_hash: Option<ffi::hashfunc>,
246    pub tp_call: Option<ffi::ternaryfunc>,
247    pub tp_str: Option<ffi::reprfunc>,
248    pub tp_getattro: Option<ffi::getattrofunc>,
249    pub tp_setattro: Option<ffi::setattrofunc>,
250    pub tp_as_buffer: *mut PyBufferProcs39Snapshot,
251    pub tp_flags: std::os::raw::c_ulong,
252    pub tp_doc: *const std::os::raw::c_char,
253    pub tp_traverse: Option<ffi::traverseproc>,
254    pub tp_clear: Option<ffi::inquiry>,
255    pub tp_richcompare: Option<ffi::richcmpfunc>,
256    pub tp_weaklistoffset: ffi::Py_ssize_t,
257    pub tp_iter: Option<ffi::getiterfunc>,
258    pub tp_iternext: Option<ffi::iternextfunc>,
259    pub tp_methods: *mut ffi::PyMethodDef,
260    pub tp_members: *mut ffi::PyMemberDef,
261    pub tp_getset: *mut ffi::PyGetSetDef,
262    pub tp_base: *mut ffi::PyTypeObject,
263    pub tp_dict: *mut ffi::PyObject,
264    pub tp_descr_get: Option<ffi::descrgetfunc>,
265    pub tp_descr_set: Option<ffi::descrsetfunc>,
266    pub tp_dictoffset: ffi::Py_ssize_t,
267    pub tp_init: Option<ffi::initproc>,
268    pub tp_alloc: Option<ffi::allocfunc>,
269    pub tp_new: Option<ffi::newfunc>,
270    pub tp_free: Option<ffi::freefunc>,
271    pub tp_is_gc: Option<ffi::inquiry>,
272    pub tp_bases: *mut ffi::PyObject,
273    pub tp_mro: *mut ffi::PyObject,
274    pub tp_cache: *mut ffi::PyObject,
275    pub tp_subclasses: *mut ffi::PyObject,
276    pub tp_weaklist: *mut ffi::PyObject,
277    pub tp_del: Option<ffi::destructor>,
278    pub tp_version_tag: std::os::raw::c_uint,
279    pub tp_finalize: Option<ffi::destructor>,
280    #[cfg(Py_3_8)]
281    pub tp_vectorcall: Option<ffi::vectorcallfunc>,
282}