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