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