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(py)?,
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            // Safety: This is the correct slot type for Py_tp_new
441            unsafe { self.push_slot(ffi::Py_tp_new, no_constructor_defined as *mut c_void) }
442        }
443
444        let base_is_gc = unsafe { ffi::PyType_IS_GC(self.tp_base) == 1 };
445        let tp_dealloc = if self.has_traverse || base_is_gc {
446            self.tp_dealloc_with_gc
447        } else {
448            self.tp_dealloc
449        };
450        unsafe { self.push_slot(ffi::Py_tp_dealloc, tp_dealloc as *mut c_void) }
451
452        if self.has_clear && !self.has_traverse {
453            return Err(PyTypeError::new_err(format!(
454                "`#[pyclass]` {} implements __clear__ without __traverse__",
455                name
456            )));
457        }
458
459        // If this type is a GC type, and the base also is, we may need to add
460        // `tp_traverse` / `tp_clear` implementations to call the base, if this type didn't
461        // define `__traverse__` or `__clear__`.
462        //
463        // This is because when Py_TPFLAGS_HAVE_GC is set, then `tp_traverse` and
464        // `tp_clear` are not inherited.
465        if ((self.class_flags & ffi::Py_TPFLAGS_HAVE_GC) != 0) && base_is_gc {
466            // If this assertion breaks, need to consider doing the same for __traverse__.
467            assert!(self.has_traverse); // Py_TPFLAGS_HAVE_GC is set when a `__traverse__` method is found
468
469            if !self.has_clear {
470                // Safety: This is the correct slot type for Py_tp_clear
471                unsafe { self.push_slot(ffi::Py_tp_clear, call_super_clear as *mut c_void) }
472            }
473        }
474
475        // For sequences, implement sq_length instead of mp_length
476        if self.is_sequence {
477            for slot in &mut self.slots {
478                if slot.slot == ffi::Py_mp_length {
479                    slot.slot = ffi::Py_sq_length;
480                }
481            }
482        }
483
484        // Add empty sentinel at the end
485        // Safety: python expects this empty slot
486        unsafe { self.push_slot(0, ptr::null_mut::<c_void>()) }
487
488        let class_name = py_class_qualified_name(module_name, name)?;
489        let mut spec = ffi::PyType_Spec {
490            name: class_name.as_ptr() as _,
491            basicsize: basicsize as c_int,
492            itemsize: 0,
493
494            flags: (ffi::Py_TPFLAGS_DEFAULT | self.class_flags)
495                .try_into()
496                .unwrap(),
497            slots: self.slots.as_mut_ptr(),
498        };
499
500        // Safety: We've correctly setup the PyType_Spec at this point
501        let type_object: Py<PyType> =
502            unsafe { Py::from_owned_ptr_or_err(py, ffi::PyType_FromSpec(&mut spec))? };
503
504        #[cfg(not(Py_3_11))]
505        bpo_45315_workaround(py, class_name);
506
507        for cleanup in std::mem::take(&mut self.cleanup) {
508            cleanup(&self, type_object.bind(py).as_type_ptr());
509        }
510
511        Ok(PyClassTypeObject {
512            type_object,
513            is_immutable_type: self.is_immutable_type,
514            getset_destructors,
515        })
516    }
517}
518
519fn py_class_qualified_name(module_name: Option<&str>, class_name: &str) -> PyResult<CString> {
520    Ok(CString::new(format!(
521        "{}.{}",
522        module_name.unwrap_or("builtins"),
523        class_name
524    ))?)
525}
526
527/// Workaround for Python issue 45315; no longer necessary in Python 3.11
528#[inline]
529#[cfg(not(Py_3_11))]
530fn bpo_45315_workaround(py: Python<'_>, class_name: CString) {
531    #[cfg(Py_LIMITED_API)]
532    {
533        // Must check version at runtime for abi3 wheels - they could run against a higher version
534        // than the build config suggests.
535        use crate::sync::GILOnceCell;
536        static IS_PYTHON_3_11: GILOnceCell<bool> = GILOnceCell::new();
537
538        if *IS_PYTHON_3_11.get_or_init(py, || py.version_info() >= (3, 11)) {
539            // No fix needed - the wheel is running on a sufficiently new interpreter.
540            return;
541        }
542    }
543    #[cfg(not(Py_LIMITED_API))]
544    {
545        // suppress unused variable warning
546        let _ = py;
547    }
548
549    std::mem::forget(class_name);
550}
551
552/// Default new implementation
553unsafe extern "C" fn no_constructor_defined(
554    subtype: *mut ffi::PyTypeObject,
555    _args: *mut ffi::PyObject,
556    _kwds: *mut ffi::PyObject,
557) -> *mut ffi::PyObject {
558    unsafe {
559        trampoline(|py| {
560            let tpobj = PyType::from_borrowed_type_ptr(py, subtype);
561            let name = tpobj
562                .name()
563                .map_or_else(|_| "<unknown>".into(), |name| name.to_string());
564            Err(crate::exceptions::PyTypeError::new_err(format!(
565                "No constructor defined for {}",
566                name
567            )))
568        })
569    }
570}
571
572unsafe extern "C" fn call_super_clear(slf: *mut ffi::PyObject) -> c_int {
573    unsafe { _call_clear(slf, |_, _| Ok(()), call_super_clear) }
574}
575
576#[derive(Default)]
577struct GetSetDefBuilder {
578    doc: Option<&'static CStr>,
579    getter: Option<Getter>,
580    setter: Option<Setter>,
581}
582
583impl GetSetDefBuilder {
584    fn add_getter(&mut self, getter: &PyGetterDef) {
585        // TODO: be smarter about merging getter and setter docs
586        if self.doc.is_none() {
587            self.doc = Some(getter.doc);
588        }
589        // TODO: return an error if getter already defined?
590        self.getter = Some(getter.meth)
591    }
592
593    fn add_setter(&mut self, setter: &PySetterDef) {
594        // TODO: be smarter about merging getter and setter docs
595        if self.doc.is_none() {
596            self.doc = Some(setter.doc);
597        }
598        // TODO: return an error if setter already defined?
599        self.setter = Some(setter.meth)
600    }
601
602    fn as_get_set_def(&self, name: &'static CStr) -> (ffi::PyGetSetDef, GetSetDefDestructor) {
603        let getset_type = match (self.getter, self.setter) {
604            (Some(getter), None) => GetSetDefType::Getter(getter),
605            (None, Some(setter)) => GetSetDefType::Setter(setter),
606            (Some(getter), Some(setter)) => {
607                GetSetDefType::GetterAndSetter(Box::new(GetterAndSetter { getter, setter }))
608            }
609            (None, None) => {
610                unreachable!("GetSetDefBuilder expected to always have either getter or setter")
611            }
612        };
613
614        let getset_def = getset_type.create_py_get_set_def(name, self.doc);
615        let destructor = GetSetDefDestructor {
616            closure: getset_type,
617        };
618        (getset_def, destructor)
619    }
620}
621
622#[allow(dead_code)] // a stack of fields which are purely to cache until dropped
623struct GetSetDefDestructor {
624    closure: GetSetDefType,
625}
626
627/// Possible forms of property - either a getter, setter, or both
628enum GetSetDefType {
629    Getter(Getter),
630    Setter(Setter),
631    // The box is here so that the `GetterAndSetter` has a stable
632    // memory address even if the `GetSetDefType` enum is moved
633    GetterAndSetter(Box<GetterAndSetter>),
634}
635
636pub(crate) struct GetterAndSetter {
637    getter: Getter,
638    setter: Setter,
639}
640
641impl GetSetDefType {
642    /// Fills a PyGetSetDef structure
643    /// It is only valid for as long as this GetSetDefType remains alive,
644    /// as well as name and doc members
645    pub(crate) fn create_py_get_set_def(
646        &self,
647        name: &CStr,
648        doc: Option<&CStr>,
649    ) -> ffi::PyGetSetDef {
650        let (get, set, closure): (Option<ffi::getter>, Option<ffi::setter>, *mut c_void) =
651            match self {
652                &Self::Getter(closure) => {
653                    unsafe extern "C" fn getter(
654                        slf: *mut ffi::PyObject,
655                        closure: *mut c_void,
656                    ) -> *mut ffi::PyObject {
657                        // Safety: PyO3 sets the closure when constructing the ffi getter so this cast should always be valid
658                        let getter: Getter = unsafe { std::mem::transmute(closure) };
659                        unsafe { trampoline(|py| getter(py, slf)) }
660                    }
661                    (Some(getter), None, closure as Getter as _)
662                }
663                &Self::Setter(closure) => {
664                    unsafe extern "C" fn setter(
665                        slf: *mut ffi::PyObject,
666                        value: *mut ffi::PyObject,
667                        closure: *mut c_void,
668                    ) -> c_int {
669                        // Safety: PyO3 sets the closure when constructing the ffi setter so this cast should always be valid
670                        let setter: Setter = unsafe { std::mem::transmute(closure) };
671                        unsafe { trampoline(|py| setter(py, slf, value)) }
672                    }
673                    (None, Some(setter), closure as Setter as _)
674                }
675                Self::GetterAndSetter(closure) => {
676                    unsafe extern "C" fn getset_getter(
677                        slf: *mut ffi::PyObject,
678                        closure: *mut c_void,
679                    ) -> *mut ffi::PyObject {
680                        let getset: &GetterAndSetter = unsafe { &*closure.cast() };
681                        unsafe { trampoline(|py| (getset.getter)(py, slf)) }
682                    }
683
684                    unsafe extern "C" fn getset_setter(
685                        slf: *mut ffi::PyObject,
686                        value: *mut ffi::PyObject,
687                        closure: *mut c_void,
688                    ) -> c_int {
689                        let getset: &GetterAndSetter = unsafe { &*closure.cast() };
690                        unsafe { trampoline(|py| (getset.setter)(py, slf, value)) }
691                    }
692                    (
693                        Some(getset_getter),
694                        Some(getset_setter),
695                        ptr_from_ref::<GetterAndSetter>(closure) as *mut _,
696                    )
697                }
698            };
699        ffi::PyGetSetDef {
700            name: name.as_ptr(),
701            doc: doc.map_or(ptr::null(), CStr::as_ptr),
702            get,
703            set,
704            closure,
705        }
706    }
707}