pyo3/pyclass/
create_type_object.rs

1use crate::{
2    exceptions::PyTypeError,
3    ffi,
4    impl_::{
5        pycell::PyClassObject,
6        pyclass::{
7            assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc,
8            tp_dealloc_with_gc, MaybeRuntimePyMethodDef, PyClassItemsIter,
9        },
10        pymethods::{Getter, PyGetterDef, PyMethodDefType, PySetterDef, Setter, _call_clear},
11        trampoline::trampoline,
12    },
13    internal_tricks::ptr_from_ref,
14    types::{typeobject::PyTypeMethods, PyType},
15    Py, PyClass, PyResult, PyTypeInfo, Python,
16};
17use std::{
18    collections::HashMap,
19    ffi::{CStr, CString},
20    os::raw::{c_char, c_int, c_ulong, c_void},
21    ptr,
22};
23
24pub(crate) struct PyClassTypeObject {
25    pub type_object: Py<PyType>,
26    pub is_immutable_type: bool,
27    #[allow(dead_code)] // This is purely a cache that must live as long as the type object
28    getset_destructors: Vec<GetSetDefDestructor>,
29}
30
31pub(crate) fn create_type_object<T>(py: Python<'_>) -> PyResult<PyClassTypeObject>
32where
33    T: PyClass,
34{
35    // Written this way to monomorphize the majority of the logic.
36    #[allow(clippy::too_many_arguments)]
37    unsafe fn inner(
38        py: Python<'_>,
39        base: *mut ffi::PyTypeObject,
40        dealloc: unsafe extern "C" fn(*mut ffi::PyObject),
41        dealloc_with_gc: unsafe extern "C" fn(*mut ffi::PyObject),
42        is_mapping: bool,
43        is_sequence: bool,
44        is_immutable_type: bool,
45        doc: &'static CStr,
46        dict_offset: Option<ffi::Py_ssize_t>,
47        weaklist_offset: Option<ffi::Py_ssize_t>,
48        is_basetype: bool,
49        items_iter: PyClassItemsIter,
50        name: &'static str,
51        module: Option<&'static str>,
52        size_of: usize,
53    ) -> PyResult<PyClassTypeObject> {
54        unsafe {
55            PyTypeBuilder {
56                slots: Vec::new(),
57                method_defs: Vec::new(),
58                member_defs: Vec::new(),
59                getset_builders: HashMap::new(),
60                cleanup: Vec::new(),
61                tp_base: base,
62                tp_dealloc: dealloc,
63                tp_dealloc_with_gc: dealloc_with_gc,
64                is_mapping,
65                is_sequence,
66                is_immutable_type,
67                has_new: false,
68                has_dealloc: false,
69                has_getitem: false,
70                has_setitem: false,
71                has_traverse: false,
72                has_clear: false,
73                dict_offset: None,
74                class_flags: 0,
75                #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
76                buffer_procs: Default::default(),
77            }
78            .type_doc(doc)
79            .offsets(dict_offset, weaklist_offset)
80            .set_is_basetype(is_basetype)
81            .class_items(items_iter)
82            .build(py, name, module, size_of)
83        }
84    }
85
86    unsafe {
87        inner(
88            py,
89            T::BaseType::type_object_raw(py),
90            tp_dealloc::<T>,
91            tp_dealloc_with_gc::<T>,
92            T::IS_MAPPING,
93            T::IS_SEQUENCE,
94            T::IS_IMMUTABLE_TYPE,
95            T::DOC,
96            T::dict_offset(),
97            T::weaklist_offset(),
98            T::IS_BASETYPE,
99            T::items_iter(),
100            T::NAME,
101            T::MODULE,
102            std::mem::size_of::<PyClassObject<T>>(),
103        )
104    }
105}
106
107type PyTypeBuilderCleanup = Box<dyn Fn(&PyTypeBuilder, *mut ffi::PyTypeObject)>;
108
109struct PyTypeBuilder {
110    slots: Vec<ffi::PyType_Slot>,
111    method_defs: Vec<ffi::PyMethodDef>,
112    member_defs: Vec<ffi::PyMemberDef>,
113    getset_builders: HashMap<&'static CStr, GetSetDefBuilder>,
114    /// Used to patch the type objects for the things there's no
115    /// PyType_FromSpec API for... there's no reason this should work,
116    /// except for that it does and we have tests.
117    cleanup: Vec<PyTypeBuilderCleanup>,
118    tp_base: *mut ffi::PyTypeObject,
119    tp_dealloc: ffi::destructor,
120    tp_dealloc_with_gc: ffi::destructor,
121    is_mapping: bool,
122    is_sequence: bool,
123    is_immutable_type: bool,
124    has_new: bool,
125    has_dealloc: bool,
126    has_getitem: bool,
127    has_setitem: bool,
128    has_traverse: bool,
129    has_clear: bool,
130    dict_offset: Option<ffi::Py_ssize_t>,
131    class_flags: c_ulong,
132    // Before Python 3.9, need to patch in buffer methods manually (they don't work in slots)
133    #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
134    buffer_procs: ffi::PyBufferProcs,
135}
136
137impl PyTypeBuilder {
138    /// # Safety
139    /// The given pointer must be of the correct type for the given slot
140    unsafe fn push_slot<T>(&mut self, slot: c_int, pfunc: *mut T) {
141        match slot {
142            ffi::Py_tp_new => self.has_new = true,
143            ffi::Py_tp_dealloc => self.has_dealloc = true,
144            ffi::Py_mp_subscript => self.has_getitem = true,
145            ffi::Py_mp_ass_subscript => self.has_setitem = true,
146            ffi::Py_tp_traverse => {
147                self.has_traverse = true;
148                self.class_flags |= ffi::Py_TPFLAGS_HAVE_GC;
149            }
150            ffi::Py_tp_clear => self.has_clear = true,
151            #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
152            ffi::Py_bf_getbuffer => {
153                // Safety: slot.pfunc is a valid function pointer
154                self.buffer_procs.bf_getbuffer =
155                    Some(unsafe { std::mem::transmute::<*mut T, ffi::getbufferproc>(pfunc) });
156            }
157            #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
158            ffi::Py_bf_releasebuffer => {
159                // Safety: slot.pfunc is a valid function pointer
160                self.buffer_procs.bf_releasebuffer =
161                    Some(unsafe { std::mem::transmute::<*mut T, ffi::releasebufferproc>(pfunc) });
162            }
163            _ => {}
164        }
165
166        self.slots.push(ffi::PyType_Slot {
167            slot,
168            pfunc: pfunc as _,
169        });
170    }
171
172    /// # Safety
173    /// It is the caller's responsibility that `data` is of the correct type for the given slot.
174    unsafe fn push_raw_vec_slot<T>(&mut self, slot: c_int, mut data: Vec<T>) {
175        if !data.is_empty() {
176            // Python expects a zeroed entry to mark the end of the defs
177            unsafe {
178                data.push(std::mem::zeroed());
179                self.push_slot(slot, Box::into_raw(data.into_boxed_slice()) as *mut c_void);
180            }
181        }
182    }
183
184    fn pymethod_def(&mut self, def: &PyMethodDefType) {
185        match def {
186            PyMethodDefType::Getter(getter) => self
187                .getset_builders
188                .entry(getter.name)
189                .or_default()
190                .add_getter(getter),
191            PyMethodDefType::Setter(setter) => self
192                .getset_builders
193                .entry(setter.name)
194                .or_default()
195                .add_setter(setter),
196            PyMethodDefType::Method(def)
197            | PyMethodDefType::Class(def)
198            | PyMethodDefType::Static(def) => self.method_defs.push(def.as_method_def()),
199            // These class attributes are added after the type gets created by LazyStaticType
200            PyMethodDefType::ClassAttribute(_) => {}
201            PyMethodDefType::StructMember(def) => self.member_defs.push(*def),
202        }
203    }
204
205    fn finalize_methods_and_properties(&mut self) -> Vec<GetSetDefDestructor> {
206        let method_defs: Vec<pyo3_ffi::PyMethodDef> = std::mem::take(&mut self.method_defs);
207        // Safety: Py_tp_methods expects a raw vec of PyMethodDef
208        unsafe { self.push_raw_vec_slot(ffi::Py_tp_methods, method_defs) };
209
210        let member_defs = std::mem::take(&mut self.member_defs);
211        // Safety: Py_tp_members expects a raw vec of PyMemberDef
212        unsafe { self.push_raw_vec_slot(ffi::Py_tp_members, member_defs) };
213
214        let mut getset_destructors = Vec::with_capacity(self.getset_builders.len());
215
216        #[allow(unused_mut)]
217        let mut property_defs: Vec<_> = self
218            .getset_builders
219            .iter()
220            .map(|(name, builder)| {
221                let (def, destructor) = builder.as_get_set_def(name);
222                getset_destructors.push(destructor);
223                def
224            })
225            .collect();
226
227        // PyPy automatically adds __dict__ getter / setter.
228        #[cfg(not(PyPy))]
229        // Supported on unlimited API for all versions, and on 3.9+ for limited API
230        #[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
231        if let Some(dict_offset) = self.dict_offset {
232            let get_dict;
233            let closure;
234            // PyObject_GenericGetDict not in the limited API until Python 3.10.
235            #[cfg(any(not(Py_LIMITED_API), Py_3_10))]
236            {
237                let _ = dict_offset;
238                get_dict = ffi::PyObject_GenericGetDict;
239                closure = ptr::null_mut();
240            }
241
242            // ... so we write a basic implementation ourselves
243            #[cfg(not(any(not(Py_LIMITED_API), Py_3_10)))]
244            {
245                extern "C" fn get_dict_impl(
246                    object: *mut ffi::PyObject,
247                    closure: *mut c_void,
248                ) -> *mut ffi::PyObject {
249                    unsafe {
250                        trampoline(|_| {
251                            let dict_offset = closure as ffi::Py_ssize_t;
252                            // we don't support negative dict_offset here; PyO3 doesn't set it negative
253                            assert!(dict_offset > 0);
254                            // TODO: use `.byte_offset` on MSRV 1.75
255                            let dict_ptr = object
256                                .cast::<u8>()
257                                .offset(dict_offset)
258                                .cast::<*mut ffi::PyObject>();
259                            if (*dict_ptr).is_null() {
260                                std::ptr::write(dict_ptr, ffi::PyDict_New());
261                            }
262                            Ok(ffi::compat::Py_XNewRef(*dict_ptr))
263                        })
264                    }
265                }
266
267                get_dict = get_dict_impl;
268                closure = dict_offset as _;
269            }
270
271            property_defs.push(ffi::PyGetSetDef {
272                name: ffi::c_str!("__dict__").as_ptr(),
273                get: Some(get_dict),
274                set: Some(ffi::PyObject_GenericSetDict),
275                doc: ptr::null(),
276                closure,
277            });
278        }
279
280        // Safety: Py_tp_getset expects a raw vec of PyGetSetDef
281        unsafe { self.push_raw_vec_slot(ffi::Py_tp_getset, property_defs) };
282
283        // If mapping methods implemented, define sequence methods get implemented too.
284        // CPython does the same for Python `class` statements.
285
286        // NB we don't implement sq_length to avoid annoying CPython behaviour of automatically adding
287        // the length to negative indices.
288
289        // Don't add these methods for "pure" mappings.
290
291        if !self.is_mapping && self.has_getitem {
292            // Safety: This is the correct slot type for Py_sq_item
293            unsafe {
294                self.push_slot(
295                    ffi::Py_sq_item,
296                    get_sequence_item_from_mapping as *mut c_void,
297                )
298            }
299        }
300
301        if !self.is_mapping && self.has_setitem {
302            // Safety: This is the correct slot type for Py_sq_ass_item
303            unsafe {
304                self.push_slot(
305                    ffi::Py_sq_ass_item,
306                    assign_sequence_item_from_mapping as *mut c_void,
307                )
308            }
309        }
310
311        getset_destructors
312    }
313
314    fn set_is_basetype(mut self, is_basetype: bool) -> Self {
315        if is_basetype {
316            self.class_flags |= ffi::Py_TPFLAGS_BASETYPE;
317        }
318        self
319    }
320
321    /// # Safety
322    /// All slots in the PyClassItemsIter should be correct
323    unsafe fn class_items(mut self, iter: PyClassItemsIter) -> Self {
324        for items in iter {
325            for slot in items.slots {
326                unsafe { self.push_slot(slot.slot, slot.pfunc) };
327            }
328            for method in items.methods {
329                let built_method;
330                let method = match method {
331                    MaybeRuntimePyMethodDef::Runtime(builder) => {
332                        built_method = builder();
333                        &built_method
334                    }
335                    MaybeRuntimePyMethodDef::Static(method) => method,
336                };
337                self.pymethod_def(method);
338            }
339        }
340        self
341    }
342
343    fn type_doc(mut self, type_doc: &'static CStr) -> Self {
344        let slice = type_doc.to_bytes();
345        if !slice.is_empty() {
346            unsafe { self.push_slot(ffi::Py_tp_doc, type_doc.as_ptr() as *mut c_char) }
347
348            // Running this causes PyPy to segfault.
349            #[cfg(all(not(PyPy), not(Py_LIMITED_API), not(Py_3_10)))]
350            {
351                // Until CPython 3.10, tp_doc was treated specially for
352                // heap-types, and it removed the text_signature value from it.
353                // We go in after the fact and replace tp_doc with something
354                // that _does_ include the text_signature value!
355                self.cleanup
356                    .push(Box::new(move |_self, type_object| unsafe {
357                        ffi::PyObject_Free((*type_object).tp_doc as _);
358                        let data = ffi::PyMem_Malloc(slice.len());
359                        data.copy_from(slice.as_ptr() as _, slice.len());
360                        (*type_object).tp_doc = data as _;
361                    }))
362            }
363        }
364        self
365    }
366
367    fn offsets(
368        mut self,
369        dict_offset: Option<ffi::Py_ssize_t>,
370        #[allow(unused_variables)] weaklist_offset: Option<ffi::Py_ssize_t>,
371    ) -> Self {
372        self.dict_offset = dict_offset;
373
374        #[cfg(Py_3_9)]
375        {
376            #[inline(always)]
377            fn offset_def(name: &'static CStr, offset: ffi::Py_ssize_t) -> ffi::PyMemberDef {
378                ffi::PyMemberDef {
379                    name: name.as_ptr().cast(),
380                    type_code: ffi::Py_T_PYSSIZET,
381                    offset,
382                    flags: ffi::Py_READONLY,
383                    doc: std::ptr::null_mut(),
384                }
385            }
386
387            // __dict__ support
388            if let Some(dict_offset) = dict_offset {
389                self.member_defs
390                    .push(offset_def(ffi::c_str!("__dictoffset__"), dict_offset));
391            }
392
393            // weakref support
394            if let Some(weaklist_offset) = weaklist_offset {
395                self.member_defs.push(offset_def(
396                    ffi::c_str!("__weaklistoffset__"),
397                    weaklist_offset,
398                ));
399            }
400        }
401
402        // Setting buffer protocols, tp_dictoffset and tp_weaklistoffset via slots doesn't work until
403        // Python 3.9, so on older versions we must manually fixup the type object.
404        #[cfg(all(not(Py_LIMITED_API), not(Py_3_9)))]
405        {
406            self.cleanup
407                .push(Box::new(move |builder, type_object| unsafe {
408                    (*(*type_object).tp_as_buffer).bf_getbuffer = builder.buffer_procs.bf_getbuffer;
409                    (*(*type_object).tp_as_buffer).bf_releasebuffer =
410                        builder.buffer_procs.bf_releasebuffer;
411
412                    if let Some(dict_offset) = dict_offset {
413                        (*type_object).tp_dictoffset = dict_offset;
414                    }
415
416                    if let Some(weaklist_offset) = weaklist_offset {
417                        (*type_object).tp_weaklistoffset = weaklist_offset;
418                    }
419                }));
420        }
421        self
422    }
423
424    fn build(
425        mut self,
426        py: Python<'_>,
427        name: &'static str,
428        module_name: Option<&'static str>,
429        basicsize: usize,
430    ) -> PyResult<PyClassTypeObject> {
431        // `c_ulong` and `c_uint` have the same size
432        // on some platforms (like windows)
433        #![allow(clippy::useless_conversion)]
434
435        let getset_destructors = self.finalize_methods_and_properties();
436
437        unsafe { self.push_slot(ffi::Py_tp_base, self.tp_base) }
438
439        if !self.has_new {
440            // Flag introduced in 3.10, only worked in PyPy on 3.11
441            #[cfg(not(any(all(Py_3_10, not(PyPy)), all(Py_3_11, PyPy))))]
442            {
443                // Safety: This is the correct slot type for Py_tp_new
444                unsafe { self.push_slot(ffi::Py_tp_new, no_constructor_defined as *mut c_void) }
445            }
446            #[cfg(any(all(Py_3_10, not(PyPy)), all(Py_3_11, PyPy)))]
447            {
448                self.class_flags |= ffi::Py_TPFLAGS_DISALLOW_INSTANTIATION;
449            }
450        }
451
452        let base_is_gc = unsafe { ffi::PyType_IS_GC(self.tp_base) == 1 };
453        let tp_dealloc = if self.has_traverse || base_is_gc {
454            self.tp_dealloc_with_gc
455        } else {
456            self.tp_dealloc
457        };
458        unsafe { self.push_slot(ffi::Py_tp_dealloc, tp_dealloc as *mut c_void) }
459
460        if self.has_clear && !self.has_traverse {
461            return Err(PyTypeError::new_err(format!(
462                "`#[pyclass]` {name} implements __clear__ without __traverse__"
463            )));
464        }
465
466        // If this type is a GC type, and the base also is, we may need to add
467        // `tp_traverse` / `tp_clear` implementations to call the base, if this type didn't
468        // define `__traverse__` or `__clear__`.
469        //
470        // This is because when Py_TPFLAGS_HAVE_GC is set, then `tp_traverse` and
471        // `tp_clear` are not inherited.
472        if ((self.class_flags & ffi::Py_TPFLAGS_HAVE_GC) != 0) && base_is_gc {
473            // If this assertion breaks, need to consider doing the same for __traverse__.
474            assert!(self.has_traverse); // Py_TPFLAGS_HAVE_GC is set when a `__traverse__` method is found
475
476            if !self.has_clear {
477                // Safety: This is the correct slot type for Py_tp_clear
478                unsafe { self.push_slot(ffi::Py_tp_clear, call_super_clear as *mut c_void) }
479            }
480        }
481
482        // For sequences, implement sq_length instead of mp_length
483        if self.is_sequence {
484            for slot in &mut self.slots {
485                if slot.slot == ffi::Py_mp_length {
486                    slot.slot = ffi::Py_sq_length;
487                }
488            }
489        }
490
491        // Add empty sentinel at the end
492        // Safety: python expects this empty slot
493        unsafe { self.push_slot(0, ptr::null_mut::<c_void>()) }
494
495        let class_name = py_class_qualified_name(module_name, name)?;
496        let mut spec = ffi::PyType_Spec {
497            name: class_name.as_ptr() as _,
498            basicsize: basicsize as c_int,
499            itemsize: 0,
500
501            flags: (ffi::Py_TPFLAGS_DEFAULT | self.class_flags)
502                .try_into()
503                .unwrap(),
504            slots: self.slots.as_mut_ptr(),
505        };
506
507        // Safety: We've correctly setup the PyType_Spec at this point
508        let type_object: Py<PyType> =
509            unsafe { Py::from_owned_ptr_or_err(py, ffi::PyType_FromSpec(&mut spec))? };
510
511        #[cfg(not(Py_3_11))]
512        bpo_45315_workaround(py, class_name);
513
514        for cleanup in std::mem::take(&mut self.cleanup) {
515            cleanup(&self, type_object.bind(py).as_type_ptr());
516        }
517
518        Ok(PyClassTypeObject {
519            type_object,
520            is_immutable_type: self.is_immutable_type,
521            getset_destructors,
522        })
523    }
524}
525
526fn py_class_qualified_name(module_name: Option<&str>, class_name: &str) -> PyResult<CString> {
527    Ok(CString::new(format!(
528        "{}.{}",
529        module_name.unwrap_or("builtins"),
530        class_name
531    ))?)
532}
533
534/// Workaround for Python issue 45315; no longer necessary in Python 3.11
535#[inline]
536#[cfg(not(Py_3_11))]
537fn bpo_45315_workaround(py: Python<'_>, class_name: CString) {
538    #[cfg(Py_LIMITED_API)]
539    {
540        // Must check version at runtime for abi3 wheels - they could run against a higher version
541        // than the build config suggests.
542        use crate::sync::PyOnceLock;
543        static IS_PYTHON_3_11: PyOnceLock<bool> = PyOnceLock::new();
544
545        if *IS_PYTHON_3_11.get_or_init(py, || py.version_info() >= (3, 11)) {
546            // No fix needed - the wheel is running on a sufficiently new interpreter.
547            return;
548        }
549    }
550    #[cfg(not(Py_LIMITED_API))]
551    {
552        // suppress unused variable warning
553        let _ = py;
554    }
555
556    std::mem::forget(class_name);
557}
558
559/// Default new implementation
560#[cfg(not(any(all(Py_3_10, not(PyPy)), all(Py_3_11, PyPy))))]
561unsafe extern "C" fn no_constructor_defined(
562    subtype: *mut ffi::PyTypeObject,
563    _args: *mut ffi::PyObject,
564    _kwds: *mut ffi::PyObject,
565) -> *mut ffi::PyObject {
566    unsafe {
567        trampoline(|py| {
568            let tpobj = PyType::from_borrowed_type_ptr(py, subtype);
569            // unlike `fully_qualified_name`, this always include the module
570            let module = tpobj
571                .module()
572                .map_or_else(|_| "<unknown>".into(), |s| s.to_string());
573            let qualname = tpobj.qualname();
574            let qualname = qualname.map_or_else(|_| "<unknown>".into(), |s| s.to_string());
575            Err(crate::exceptions::PyTypeError::new_err(format!(
576                "cannot create '{module}.{qualname}' instances"
577            )))
578        })
579    }
580}
581
582unsafe extern "C" fn call_super_clear(slf: *mut ffi::PyObject) -> c_int {
583    unsafe { _call_clear(slf, |_, _| Ok(()), call_super_clear) }
584}
585
586#[derive(Default)]
587struct GetSetDefBuilder {
588    doc: Option<&'static CStr>,
589    getter: Option<Getter>,
590    setter: Option<Setter>,
591}
592
593impl GetSetDefBuilder {
594    fn add_getter(&mut self, getter: &PyGetterDef) {
595        // TODO: be smarter about merging getter and setter docs
596        if self.doc.is_none() {
597            self.doc = Some(getter.doc);
598        }
599        // TODO: return an error if getter already defined?
600        self.getter = Some(getter.meth)
601    }
602
603    fn add_setter(&mut self, setter: &PySetterDef) {
604        // TODO: be smarter about merging getter and setter docs
605        if self.doc.is_none() {
606            self.doc = Some(setter.doc);
607        }
608        // TODO: return an error if setter already defined?
609        self.setter = Some(setter.meth)
610    }
611
612    fn as_get_set_def(&self, name: &'static CStr) -> (ffi::PyGetSetDef, GetSetDefDestructor) {
613        let getset_type = match (self.getter, self.setter) {
614            (Some(getter), None) => GetSetDefType::Getter(getter),
615            (None, Some(setter)) => GetSetDefType::Setter(setter),
616            (Some(getter), Some(setter)) => {
617                GetSetDefType::GetterAndSetter(Box::new(GetterAndSetter { getter, setter }))
618            }
619            (None, None) => {
620                unreachable!("GetSetDefBuilder expected to always have either getter or setter")
621            }
622        };
623
624        let getset_def = getset_type.create_py_get_set_def(name, self.doc);
625        let destructor = GetSetDefDestructor {
626            closure: getset_type,
627        };
628        (getset_def, destructor)
629    }
630}
631
632#[allow(dead_code)] // a stack of fields which are purely to cache until dropped
633struct GetSetDefDestructor {
634    closure: GetSetDefType,
635}
636
637/// Possible forms of property - either a getter, setter, or both
638enum GetSetDefType {
639    Getter(Getter),
640    Setter(Setter),
641    // The box is here so that the `GetterAndSetter` has a stable
642    // memory address even if the `GetSetDefType` enum is moved
643    GetterAndSetter(Box<GetterAndSetter>),
644}
645
646pub(crate) struct GetterAndSetter {
647    getter: Getter,
648    setter: Setter,
649}
650
651impl GetSetDefType {
652    /// Fills a PyGetSetDef structure
653    /// It is only valid for as long as this GetSetDefType remains alive,
654    /// as well as name and doc members
655    pub(crate) fn create_py_get_set_def(
656        &self,
657        name: &CStr,
658        doc: Option<&CStr>,
659    ) -> ffi::PyGetSetDef {
660        let (get, set, closure): (Option<ffi::getter>, Option<ffi::setter>, *mut c_void) =
661            match self {
662                &Self::Getter(closure) => {
663                    unsafe extern "C" fn getter(
664                        slf: *mut ffi::PyObject,
665                        closure: *mut c_void,
666                    ) -> *mut ffi::PyObject {
667                        // Safety: PyO3 sets the closure when constructing the ffi getter so this cast should always be valid
668                        let getter: Getter = unsafe { std::mem::transmute(closure) };
669                        unsafe { trampoline(|py| getter(py, slf)) }
670                    }
671                    (Some(getter), None, closure as Getter as _)
672                }
673                &Self::Setter(closure) => {
674                    unsafe extern "C" fn setter(
675                        slf: *mut ffi::PyObject,
676                        value: *mut ffi::PyObject,
677                        closure: *mut c_void,
678                    ) -> c_int {
679                        // Safety: PyO3 sets the closure when constructing the ffi setter so this cast should always be valid
680                        let setter: Setter = unsafe { std::mem::transmute(closure) };
681                        unsafe { trampoline(|py| setter(py, slf, value)) }
682                    }
683                    (None, Some(setter), closure as Setter as _)
684                }
685                Self::GetterAndSetter(closure) => {
686                    unsafe extern "C" fn getset_getter(
687                        slf: *mut ffi::PyObject,
688                        closure: *mut c_void,
689                    ) -> *mut ffi::PyObject {
690                        let getset: &GetterAndSetter = unsafe { &*closure.cast() };
691                        unsafe { trampoline(|py| (getset.getter)(py, slf)) }
692                    }
693
694                    unsafe extern "C" fn getset_setter(
695                        slf: *mut ffi::PyObject,
696                        value: *mut ffi::PyObject,
697                        closure: *mut c_void,
698                    ) -> c_int {
699                        let getset: &GetterAndSetter = unsafe { &*closure.cast() };
700                        unsafe { trampoline(|py| (getset.setter)(py, slf, value)) }
701                    }
702                    (
703                        Some(getset_getter),
704                        Some(getset_setter),
705                        ptr_from_ref::<GetterAndSetter>(closure) as *mut _,
706                    )
707                }
708            };
709        ffi::PyGetSetDef {
710            name: name.as_ptr(),
711            doc: doc.map_or(ptr::null(), CStr::as_ptr),
712            get,
713            set,
714            closure,
715        }
716    }
717}