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