pyo3/impl_/
pyclass.rs

1use crate::{
2    exceptions::{PyAttributeError, PyNotImplementedError, PyRuntimeError},
3    ffi,
4    impl_::{
5        freelist::PyObjectFreeList,
6        pycell::{GetBorrowChecker, PyClassMutability, PyClassObjectLayout},
7        pyclass_init::PyObjectInit,
8        pymethods::{PyGetterDef, PyMethodDefType},
9    },
10    pycell::PyBorrowError,
11    types::{any::PyAnyMethods, PyBool},
12    Borrowed, BoundObject, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyClass, PyClassGuard, PyErr,
13    PyResult, PyTypeInfo, Python,
14};
15use std::{
16    ffi::CStr,
17    marker::PhantomData,
18    os::raw::{c_int, c_void},
19    ptr,
20    ptr::NonNull,
21    sync::Mutex,
22    thread,
23};
24
25mod assertions;
26pub mod doc;
27mod lazy_type_object;
28#[macro_use]
29mod probes;
30
31pub use assertions::*;
32pub use lazy_type_object::{type_object_init_failed, LazyTypeObject};
33pub use probes::*;
34
35/// Gets the offset of the dictionary from the start of the object in bytes.
36#[inline]
37pub fn dict_offset<T: PyClass>() -> ffi::Py_ssize_t {
38    PyClassObject::<T>::dict_offset()
39}
40
41/// Gets the offset of the weakref list from the start of the object in bytes.
42#[inline]
43pub fn weaklist_offset<T: PyClass>() -> ffi::Py_ssize_t {
44    PyClassObject::<T>::weaklist_offset()
45}
46
47mod sealed {
48    pub trait Sealed {}
49
50    impl Sealed for super::PyClassDummySlot {}
51    impl Sealed for super::PyClassDictSlot {}
52    impl Sealed for super::PyClassWeakRefSlot {}
53    impl Sealed for super::ThreadCheckerImpl {}
54    impl<T: Send> Sealed for super::SendablePyClass<T> {}
55}
56
57/// Represents the `__dict__` field for `#[pyclass]`.
58pub trait PyClassDict: sealed::Sealed {
59    /// Initial form of a [PyObject](crate::ffi::PyObject) `__dict__` reference.
60    const INIT: Self;
61    /// Empties the dictionary of its key-value pairs.
62    #[inline]
63    fn clear_dict(&mut self, _py: Python<'_>) {}
64}
65
66/// Represents the `__weakref__` field for `#[pyclass]`.
67pub trait PyClassWeakRef: sealed::Sealed {
68    /// Initializes a `weakref` instance.
69    const INIT: Self;
70    /// Clears the weak references to the given object.
71    ///
72    /// # Safety
73    /// - `_obj` must be a pointer to the pyclass instance which contains `self`.
74    /// - The GIL must be held.
75    #[inline]
76    unsafe fn clear_weakrefs(&mut self, _obj: *mut ffi::PyObject, _py: Python<'_>) {}
77}
78
79/// Zero-sized dummy field.
80pub struct PyClassDummySlot;
81
82impl PyClassDict for PyClassDummySlot {
83    const INIT: Self = PyClassDummySlot;
84}
85
86impl PyClassWeakRef for PyClassDummySlot {
87    const INIT: Self = PyClassDummySlot;
88}
89
90/// Actual dict field, which holds the pointer to `__dict__`.
91///
92/// `#[pyclass(dict)]` automatically adds this.
93#[repr(transparent)]
94#[allow(dead_code)] // These are constructed in INIT and used by the macro code
95pub struct PyClassDictSlot(*mut ffi::PyObject);
96
97impl PyClassDict for PyClassDictSlot {
98    const INIT: Self = Self(std::ptr::null_mut());
99    #[inline]
100    fn clear_dict(&mut self, _py: Python<'_>) {
101        if !self.0.is_null() {
102            unsafe { ffi::PyDict_Clear(self.0) }
103        }
104    }
105}
106
107/// Actual weakref field, which holds the pointer to `__weakref__`.
108///
109/// `#[pyclass(weakref)]` automatically adds this.
110#[repr(transparent)]
111#[allow(dead_code)] // These are constructed in INIT and used by the macro code
112pub struct PyClassWeakRefSlot(*mut ffi::PyObject);
113
114impl PyClassWeakRef for PyClassWeakRefSlot {
115    const INIT: Self = Self(std::ptr::null_mut());
116    #[inline]
117    unsafe fn clear_weakrefs(&mut self, obj: *mut ffi::PyObject, _py: Python<'_>) {
118        if !self.0.is_null() {
119            unsafe { ffi::PyObject_ClearWeakRefs(obj) }
120        }
121    }
122}
123
124/// This type is used as a "dummy" type on which dtolnay specializations are
125/// applied to apply implementations from `#[pymethods]`
126pub struct PyClassImplCollector<T>(PhantomData<T>);
127
128impl<T> PyClassImplCollector<T> {
129    pub fn new() -> Self {
130        Self(PhantomData)
131    }
132}
133
134impl<T> Default for PyClassImplCollector<T> {
135    fn default() -> Self {
136        Self::new()
137    }
138}
139
140impl<T> Clone for PyClassImplCollector<T> {
141    fn clone(&self) -> Self {
142        *self
143    }
144}
145
146impl<T> Copy for PyClassImplCollector<T> {}
147
148pub enum MaybeRuntimePyMethodDef {
149    /// Used in cases where const functionality is not sufficient to define the method
150    /// purely at compile time.
151    Runtime(fn() -> PyMethodDefType),
152    Static(PyMethodDefType),
153}
154
155pub struct PyClassItems {
156    pub methods: &'static [MaybeRuntimePyMethodDef],
157    pub slots: &'static [ffi::PyType_Slot],
158}
159
160// Allow PyClassItems in statics
161unsafe impl Sync for PyClassItems {}
162
163/// Implements the underlying functionality of `#[pyclass]`, assembled by various proc macros.
164///
165/// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail
166/// and may be changed at any time.
167pub trait PyClassImpl: Sized + 'static {
168    /// #[pyclass(subclass)]
169    const IS_BASETYPE: bool = false;
170
171    /// #[pyclass(extends=...)]
172    const IS_SUBCLASS: bool = false;
173
174    /// #[pyclass(mapping)]
175    const IS_MAPPING: bool = false;
176
177    /// #[pyclass(sequence)]
178    const IS_SEQUENCE: bool = false;
179
180    /// #[pyclass(immutable_type)]
181    const IS_IMMUTABLE_TYPE: bool = false;
182
183    /// Base class
184    type BaseType: PyTypeInfo + PyClassBaseType;
185
186    /// Immutable or mutable
187    type PyClassMutability: PyClassMutability + GetBorrowChecker<Self>;
188
189    /// Specify this class has `#[pyclass(dict)]` or not.
190    type Dict: PyClassDict;
191
192    /// Specify this class has `#[pyclass(weakref)]` or not.
193    type WeakRef: PyClassWeakRef;
194
195    /// The closest native ancestor. This is `PyAny` by default, and when you declare
196    /// `#[pyclass(extends=PyDict)]`, it's `PyDict`.
197    type BaseNativeType: PyTypeInfo;
198
199    /// This handles following two situations:
200    /// 1. In case `T` is `Send`, stub `ThreadChecker` is used and does nothing.
201    ///    This implementation is used by default. Compile fails if `T: !Send`.
202    /// 2. In case `T` is `!Send`, `ThreadChecker` panics when `T` is accessed by another thread.
203    ///    This implementation is used when `#[pyclass(unsendable)]` is given.
204    ///    Panicking makes it safe to expose `T: !Send` to the Python interpreter, where all objects
205    ///    can be accessed by multiple threads by `threading` module.
206    type ThreadChecker: PyClassThreadChecker<Self>;
207
208    #[cfg(feature = "multiple-pymethods")]
209    type Inventory: PyClassInventory;
210
211    /// Docstring for the class provided on the struct or enum.
212    ///
213    /// This is exposed for `PyClassDocGenerator` to use as a docstring piece.
214    const RAW_DOC: &'static CStr;
215
216    /// Fully rendered class doc, including the `text_signature` if a constructor is defined.
217    ///
218    /// This is constructed at compile-time with const specialization via the proc macros with help
219    /// from the PyClassDocGenerator` type.
220    const DOC: &'static CStr;
221
222    #[cfg(feature = "experimental-inspect")]
223    const TYPE_NAME: &'static str;
224
225    fn items_iter() -> PyClassItemsIter;
226
227    #[inline]
228    fn dict_offset() -> Option<ffi::Py_ssize_t> {
229        None
230    }
231
232    #[inline]
233    fn weaklist_offset() -> Option<ffi::Py_ssize_t> {
234        None
235    }
236
237    fn lazy_type_object() -> &'static LazyTypeObject<Self>;
238}
239
240/// Iterator used to process all class items during type instantiation.
241pub struct PyClassItemsIter {
242    /// Iteration state
243    idx: usize,
244    /// Items from the `#[pyclass]` macro
245    pyclass_items: &'static PyClassItems,
246    /// Items from the `#[pymethods]` macro
247    #[cfg(not(feature = "multiple-pymethods"))]
248    pymethods_items: &'static PyClassItems,
249    /// Items from the `#[pymethods]` macro with inventory
250    #[cfg(feature = "multiple-pymethods")]
251    pymethods_items: Box<dyn Iterator<Item = &'static PyClassItems>>,
252}
253
254impl PyClassItemsIter {
255    pub fn new(
256        pyclass_items: &'static PyClassItems,
257        #[cfg(not(feature = "multiple-pymethods"))] pymethods_items: &'static PyClassItems,
258        #[cfg(feature = "multiple-pymethods")] pymethods_items: Box<
259            dyn Iterator<Item = &'static PyClassItems>,
260        >,
261    ) -> Self {
262        Self {
263            idx: 0,
264            pyclass_items,
265            pymethods_items,
266        }
267    }
268}
269
270impl Iterator for PyClassItemsIter {
271    type Item = &'static PyClassItems;
272
273    #[cfg(not(feature = "multiple-pymethods"))]
274    fn next(&mut self) -> Option<Self::Item> {
275        match self.idx {
276            0 => {
277                self.idx += 1;
278                Some(self.pyclass_items)
279            }
280            1 => {
281                self.idx += 1;
282                Some(self.pymethods_items)
283            }
284            // Termination clause
285            _ => None,
286        }
287    }
288
289    #[cfg(feature = "multiple-pymethods")]
290    fn next(&mut self) -> Option<Self::Item> {
291        match self.idx {
292            0 => {
293                self.idx += 1;
294                Some(self.pyclass_items)
295            }
296            // Termination clause
297            _ => self.pymethods_items.next(),
298        }
299    }
300}
301
302// Traits describing known special methods.
303
304macro_rules! slot_fragment_trait {
305    ($trait_name:ident, $($default_method:tt)*) => {
306        #[allow(non_camel_case_types)]
307        pub trait $trait_name<T>: Sized {
308            $($default_method)*
309        }
310
311        impl<T> $trait_name<T> for &'_ PyClassImplCollector<T> {}
312    }
313}
314
315slot_fragment_trait! {
316    PyClass__getattribute__SlotFragment,
317
318    /// # Safety: _slf and _attr must be valid non-null Python objects
319    #[inline]
320    unsafe fn __getattribute__(
321        self,
322        py: Python<'_>,
323        slf: *mut ffi::PyObject,
324        attr: *mut ffi::PyObject,
325    ) -> PyResult<*mut ffi::PyObject> {
326        let res = unsafe { ffi::PyObject_GenericGetAttr(slf, attr) };
327        if res.is_null() {
328            Err(PyErr::fetch(py))
329        } else {
330            Ok(res)
331        }
332    }
333}
334
335slot_fragment_trait! {
336    PyClass__getattr__SlotFragment,
337
338    /// # Safety: _slf and _attr must be valid non-null Python objects
339    #[inline]
340    unsafe fn __getattr__(
341        self,
342        py: Python<'_>,
343        _slf: *mut ffi::PyObject,
344        attr: *mut ffi::PyObject,
345    ) -> PyResult<*mut ffi::PyObject> {
346        Err(PyErr::new::<PyAttributeError, _>(
347            (unsafe {Py::<PyAny>::from_borrowed_ptr(py, attr)},)
348        ))
349    }
350}
351
352#[doc(hidden)]
353#[macro_export]
354macro_rules! generate_pyclass_getattro_slot {
355    ($cls:ty) => {{
356        unsafe extern "C" fn __wrap(
357            _slf: *mut $crate::ffi::PyObject,
358            attr: *mut $crate::ffi::PyObject,
359        ) -> *mut $crate::ffi::PyObject {
360            unsafe {
361                $crate::impl_::trampoline::getattrofunc(_slf, attr, |py, _slf, attr| {
362                    use ::std::result::Result::*;
363                    use $crate::impl_::pyclass::*;
364                    let collector = PyClassImplCollector::<$cls>::new();
365
366                    // Strategy:
367                    // - Try __getattribute__ first. Its default is PyObject_GenericGetAttr.
368                    // - If it returns a result, use it.
369                    // - If it fails with AttributeError, try __getattr__.
370                    // - If it fails otherwise, reraise.
371                    match collector.__getattribute__(py, _slf, attr) {
372                        Ok(obj) => Ok(obj),
373                        Err(e) if e.is_instance_of::<$crate::exceptions::PyAttributeError>(py) => {
374                            collector.__getattr__(py, _slf, attr)
375                        }
376                        Err(e) => Err(e),
377                    }
378                })
379            }
380        }
381        $crate::ffi::PyType_Slot {
382            slot: $crate::ffi::Py_tp_getattro,
383            pfunc: __wrap as $crate::ffi::getattrofunc as _,
384        }
385    }};
386}
387
388pub use generate_pyclass_getattro_slot;
389
390/// Macro which expands to three items
391/// - Trait for a __setitem__ dunder
392/// - Trait for the corresponding __delitem__ dunder
393/// - A macro which will use dtolnay specialisation to generate the shared slot for the two dunders
394macro_rules! define_pyclass_setattr_slot {
395    (
396        $set_trait:ident,
397        $del_trait:ident,
398        $set:ident,
399        $del:ident,
400        $set_error:expr,
401        $del_error:expr,
402        $generate_macro:ident,
403        $slot:ident,
404        $func_ty:ident,
405    ) => {
406        slot_fragment_trait! {
407            $set_trait,
408
409            /// # Safety: _slf and _attr must be valid non-null Python objects
410            #[inline]
411            unsafe fn $set(
412                self,
413                _py: Python<'_>,
414                _slf: *mut ffi::PyObject,
415                _attr: *mut ffi::PyObject,
416                _value: NonNull<ffi::PyObject>,
417            ) -> PyResult<()> {
418                $set_error
419            }
420        }
421
422        slot_fragment_trait! {
423            $del_trait,
424
425            /// # Safety: _slf and _attr must be valid non-null Python objects
426            #[inline]
427            unsafe fn $del(
428                self,
429                _py: Python<'_>,
430                _slf: *mut ffi::PyObject,
431                _attr: *mut ffi::PyObject,
432            ) -> PyResult<()> {
433                $del_error
434            }
435        }
436
437        #[doc(hidden)]
438        #[macro_export]
439        macro_rules! $generate_macro {
440            ($cls:ty) => {{
441                unsafe extern "C" fn __wrap(
442                    _slf: *mut $crate::ffi::PyObject,
443                    attr: *mut $crate::ffi::PyObject,
444                    value: *mut $crate::ffi::PyObject,
445                ) -> ::std::ffi::c_int {
446                    unsafe {
447                        $crate::impl_::trampoline::setattrofunc(
448                            _slf,
449                            attr,
450                            value,
451                            |py, _slf, attr, value| {
452                                use ::std::option::Option::*;
453                                use $crate::impl_::callback::IntoPyCallbackOutput;
454                                use $crate::impl_::pyclass::*;
455                                let collector = PyClassImplCollector::<$cls>::new();
456                                if let Some(value) = ::std::ptr::NonNull::new(value) {
457                                    collector.$set(py, _slf, attr, value).convert(py)
458                                } else {
459                                    collector.$del(py, _slf, attr).convert(py)
460                                }
461                            },
462                        )
463                    }
464                }
465                $crate::ffi::PyType_Slot {
466                    slot: $crate::ffi::$slot,
467                    pfunc: __wrap as $crate::ffi::$func_ty as _,
468                }
469            }};
470        }
471        pub use $generate_macro;
472    };
473}
474
475define_pyclass_setattr_slot! {
476    PyClass__setattr__SlotFragment,
477    PyClass__delattr__SlotFragment,
478    __setattr__,
479    __delattr__,
480    Err(PyAttributeError::new_err("can't set attribute")),
481    Err(PyAttributeError::new_err("can't delete attribute")),
482    generate_pyclass_setattr_slot,
483    Py_tp_setattro,
484    setattrofunc,
485}
486
487define_pyclass_setattr_slot! {
488    PyClass__set__SlotFragment,
489    PyClass__delete__SlotFragment,
490    __set__,
491    __delete__,
492    Err(PyNotImplementedError::new_err("can't set descriptor")),
493    Err(PyNotImplementedError::new_err("can't delete descriptor")),
494    generate_pyclass_setdescr_slot,
495    Py_tp_descr_set,
496    descrsetfunc,
497}
498
499define_pyclass_setattr_slot! {
500    PyClass__setitem__SlotFragment,
501    PyClass__delitem__SlotFragment,
502    __setitem__,
503    __delitem__,
504    Err(PyNotImplementedError::new_err("can't set item")),
505    Err(PyNotImplementedError::new_err("can't delete item")),
506    generate_pyclass_setitem_slot,
507    Py_mp_ass_subscript,
508    objobjargproc,
509}
510
511/// Macro which expands to three items
512/// - Trait for a lhs dunder e.g. __add__
513/// - Trait for the corresponding rhs e.g. __radd__
514/// - A macro which will use dtolnay specialisation to generate the shared slot for the two dunders
515macro_rules! define_pyclass_binary_operator_slot {
516    (
517        $lhs_trait:ident,
518        $rhs_trait:ident,
519        $lhs:ident,
520        $rhs:ident,
521        $generate_macro:ident,
522        $slot:ident,
523        $func_ty:ident,
524    ) => {
525        slot_fragment_trait! {
526            $lhs_trait,
527
528            /// # Safety: _slf and _other must be valid non-null Python objects
529            #[inline]
530            unsafe fn $lhs(
531                self,
532                py: Python<'_>,
533                _slf: *mut ffi::PyObject,
534                _other: *mut ffi::PyObject,
535            ) -> PyResult<*mut ffi::PyObject> {
536                Ok(py.NotImplemented().into_ptr())
537            }
538        }
539
540        slot_fragment_trait! {
541            $rhs_trait,
542
543            /// # Safety: _slf and _other must be valid non-null Python objects
544            #[inline]
545            unsafe fn $rhs(
546                self,
547                py: Python<'_>,
548                _slf: *mut ffi::PyObject,
549                _other: *mut ffi::PyObject,
550            ) -> PyResult<*mut ffi::PyObject> {
551                Ok(py.NotImplemented().into_ptr())
552            }
553        }
554
555        #[doc(hidden)]
556        #[macro_export]
557        macro_rules! $generate_macro {
558            ($cls:ty) => {{
559                unsafe extern "C" fn __wrap(
560                    _slf: *mut $crate::ffi::PyObject,
561                    _other: *mut $crate::ffi::PyObject,
562                ) -> *mut $crate::ffi::PyObject {
563                    unsafe {
564                        $crate::impl_::trampoline::binaryfunc(_slf, _other, |py, _slf, _other| {
565                            use $crate::impl_::pyclass::*;
566                            let collector = PyClassImplCollector::<$cls>::new();
567                            let lhs_result = collector.$lhs(py, _slf, _other)?;
568                            if lhs_result == $crate::ffi::Py_NotImplemented() {
569                                $crate::ffi::Py_DECREF(lhs_result);
570                                collector.$rhs(py, _other, _slf)
571                            } else {
572                                ::std::result::Result::Ok(lhs_result)
573                            }
574                        })
575                    }
576                }
577                $crate::ffi::PyType_Slot {
578                    slot: $crate::ffi::$slot,
579                    pfunc: __wrap as $crate::ffi::$func_ty as _,
580                }
581            }};
582        }
583        pub use $generate_macro;
584    };
585}
586
587define_pyclass_binary_operator_slot! {
588    PyClass__add__SlotFragment,
589    PyClass__radd__SlotFragment,
590    __add__,
591    __radd__,
592    generate_pyclass_add_slot,
593    Py_nb_add,
594    binaryfunc,
595}
596
597define_pyclass_binary_operator_slot! {
598    PyClass__sub__SlotFragment,
599    PyClass__rsub__SlotFragment,
600    __sub__,
601    __rsub__,
602    generate_pyclass_sub_slot,
603    Py_nb_subtract,
604    binaryfunc,
605}
606
607define_pyclass_binary_operator_slot! {
608    PyClass__mul__SlotFragment,
609    PyClass__rmul__SlotFragment,
610    __mul__,
611    __rmul__,
612    generate_pyclass_mul_slot,
613    Py_nb_multiply,
614    binaryfunc,
615}
616
617define_pyclass_binary_operator_slot! {
618    PyClass__mod__SlotFragment,
619    PyClass__rmod__SlotFragment,
620    __mod__,
621    __rmod__,
622    generate_pyclass_mod_slot,
623    Py_nb_remainder,
624    binaryfunc,
625}
626
627define_pyclass_binary_operator_slot! {
628    PyClass__divmod__SlotFragment,
629    PyClass__rdivmod__SlotFragment,
630    __divmod__,
631    __rdivmod__,
632    generate_pyclass_divmod_slot,
633    Py_nb_divmod,
634    binaryfunc,
635}
636
637define_pyclass_binary_operator_slot! {
638    PyClass__lshift__SlotFragment,
639    PyClass__rlshift__SlotFragment,
640    __lshift__,
641    __rlshift__,
642    generate_pyclass_lshift_slot,
643    Py_nb_lshift,
644    binaryfunc,
645}
646
647define_pyclass_binary_operator_slot! {
648    PyClass__rshift__SlotFragment,
649    PyClass__rrshift__SlotFragment,
650    __rshift__,
651    __rrshift__,
652    generate_pyclass_rshift_slot,
653    Py_nb_rshift,
654    binaryfunc,
655}
656
657define_pyclass_binary_operator_slot! {
658    PyClass__and__SlotFragment,
659    PyClass__rand__SlotFragment,
660    __and__,
661    __rand__,
662    generate_pyclass_and_slot,
663    Py_nb_and,
664    binaryfunc,
665}
666
667define_pyclass_binary_operator_slot! {
668    PyClass__or__SlotFragment,
669    PyClass__ror__SlotFragment,
670    __or__,
671    __ror__,
672    generate_pyclass_or_slot,
673    Py_nb_or,
674    binaryfunc,
675}
676
677define_pyclass_binary_operator_slot! {
678    PyClass__xor__SlotFragment,
679    PyClass__rxor__SlotFragment,
680    __xor__,
681    __rxor__,
682    generate_pyclass_xor_slot,
683    Py_nb_xor,
684    binaryfunc,
685}
686
687define_pyclass_binary_operator_slot! {
688    PyClass__matmul__SlotFragment,
689    PyClass__rmatmul__SlotFragment,
690    __matmul__,
691    __rmatmul__,
692    generate_pyclass_matmul_slot,
693    Py_nb_matrix_multiply,
694    binaryfunc,
695}
696
697define_pyclass_binary_operator_slot! {
698    PyClass__truediv__SlotFragment,
699    PyClass__rtruediv__SlotFragment,
700    __truediv__,
701    __rtruediv__,
702    generate_pyclass_truediv_slot,
703    Py_nb_true_divide,
704    binaryfunc,
705}
706
707define_pyclass_binary_operator_slot! {
708    PyClass__floordiv__SlotFragment,
709    PyClass__rfloordiv__SlotFragment,
710    __floordiv__,
711    __rfloordiv__,
712    generate_pyclass_floordiv_slot,
713    Py_nb_floor_divide,
714    binaryfunc,
715}
716
717slot_fragment_trait! {
718    PyClass__pow__SlotFragment,
719
720    /// # Safety: _slf and _other must be valid non-null Python objects
721    #[inline]
722    unsafe fn __pow__(
723        self,
724        py: Python<'_>,
725        _slf: *mut ffi::PyObject,
726        _other: *mut ffi::PyObject,
727        _mod: *mut ffi::PyObject,
728    ) -> PyResult<*mut ffi::PyObject> {
729        Ok(py.NotImplemented().into_ptr())
730    }
731}
732
733slot_fragment_trait! {
734    PyClass__rpow__SlotFragment,
735
736    /// # Safety: _slf and _other must be valid non-null Python objects
737    #[inline]
738    unsafe fn __rpow__(
739        self,
740        py: Python<'_>,
741        _slf: *mut ffi::PyObject,
742        _other: *mut ffi::PyObject,
743        _mod: *mut ffi::PyObject,
744    ) -> PyResult<*mut ffi::PyObject> {
745        Ok(py.NotImplemented().into_ptr())
746    }
747}
748
749#[doc(hidden)]
750#[macro_export]
751macro_rules! generate_pyclass_pow_slot {
752    ($cls:ty) => {{
753        unsafe extern "C" fn __wrap(
754            _slf: *mut $crate::ffi::PyObject,
755            _other: *mut $crate::ffi::PyObject,
756            _mod: *mut $crate::ffi::PyObject,
757        ) -> *mut $crate::ffi::PyObject {
758            unsafe {
759                $crate::impl_::trampoline::ternaryfunc(
760                    _slf,
761                    _other,
762                    _mod,
763                    |py, _slf, _other, _mod| {
764                        use $crate::impl_::pyclass::*;
765                        let collector = PyClassImplCollector::<$cls>::new();
766                        let lhs_result = collector.__pow__(py, _slf, _other, _mod)?;
767                        if lhs_result == $crate::ffi::Py_NotImplemented() {
768                            $crate::ffi::Py_DECREF(lhs_result);
769                            collector.__rpow__(py, _other, _slf, _mod)
770                        } else {
771                            ::std::result::Result::Ok(lhs_result)
772                        }
773                    },
774                )
775            }
776        }
777        $crate::ffi::PyType_Slot {
778            slot: $crate::ffi::Py_nb_power,
779            pfunc: __wrap as $crate::ffi::ternaryfunc as _,
780        }
781    }};
782}
783pub use generate_pyclass_pow_slot;
784
785slot_fragment_trait! {
786    PyClass__lt__SlotFragment,
787
788    /// # Safety: _slf and _other must be valid non-null Python objects
789    #[inline]
790    unsafe fn __lt__(
791        self,
792        py: Python<'_>,
793        _slf: *mut ffi::PyObject,
794        _other: *mut ffi::PyObject,
795    ) -> PyResult<*mut ffi::PyObject> {
796        Ok(py.NotImplemented().into_ptr())
797    }
798}
799
800slot_fragment_trait! {
801    PyClass__le__SlotFragment,
802
803    /// # Safety: _slf and _other must be valid non-null Python objects
804    #[inline]
805    unsafe fn __le__(
806        self,
807        py: Python<'_>,
808        _slf: *mut ffi::PyObject,
809        _other: *mut ffi::PyObject,
810    ) -> PyResult<*mut ffi::PyObject> {
811        Ok(py.NotImplemented().into_ptr())
812    }
813}
814
815slot_fragment_trait! {
816    PyClass__eq__SlotFragment,
817
818    /// # Safety: _slf and _other must be valid non-null Python objects
819    #[inline]
820    unsafe fn __eq__(
821        self,
822        py: Python<'_>,
823        _slf: *mut ffi::PyObject,
824        _other: *mut ffi::PyObject,
825    ) -> PyResult<*mut ffi::PyObject> {
826        Ok(py.NotImplemented().into_ptr())
827    }
828}
829
830slot_fragment_trait! {
831    PyClass__ne__SlotFragment,
832
833    /// # Safety: _slf and _other must be valid non-null Python objects
834    #[inline]
835    unsafe fn __ne__(
836        self,
837        py: Python<'_>,
838        slf: *mut ffi::PyObject,
839        other: *mut ffi::PyObject,
840    ) -> PyResult<*mut ffi::PyObject> {
841        // By default `__ne__` will try `__eq__` and invert the result
842        let slf = unsafe { Borrowed::from_ptr(py, slf)};
843        let other = unsafe { Borrowed::from_ptr(py, other)};
844        slf.eq(other).map(|is_eq| PyBool::new(py, !is_eq).to_owned().into_ptr())
845    }
846}
847
848slot_fragment_trait! {
849    PyClass__gt__SlotFragment,
850
851    /// # Safety: _slf and _other must be valid non-null Python objects
852    #[inline]
853    unsafe fn __gt__(
854        self,
855        py: Python<'_>,
856        _slf: *mut ffi::PyObject,
857        _other: *mut ffi::PyObject,
858    ) -> PyResult<*mut ffi::PyObject> {
859        Ok(py.NotImplemented().into_ptr())
860    }
861}
862
863slot_fragment_trait! {
864    PyClass__ge__SlotFragment,
865
866    /// # Safety: _slf and _other must be valid non-null Python objects
867    #[inline]
868    unsafe fn __ge__(
869        self,
870        py: Python<'_>,
871        _slf: *mut ffi::PyObject,
872        _other: *mut ffi::PyObject,
873    ) -> PyResult<*mut ffi::PyObject> {
874        Ok(py.NotImplemented().into_ptr())
875    }
876}
877
878#[doc(hidden)]
879#[macro_export]
880macro_rules! generate_pyclass_richcompare_slot {
881    ($cls:ty) => {{
882        #[allow(unknown_lints, non_local_definitions)]
883        impl $cls {
884            #[allow(non_snake_case)]
885            unsafe extern "C" fn __pymethod___richcmp____(
886                slf: *mut $crate::ffi::PyObject,
887                other: *mut $crate::ffi::PyObject,
888                op: ::std::ffi::c_int,
889            ) -> *mut $crate::ffi::PyObject {
890                unsafe {
891                    $crate::impl_::trampoline::richcmpfunc(slf, other, op, |py, slf, other, op| {
892                        use $crate::class::basic::CompareOp;
893                        use $crate::impl_::pyclass::*;
894                        let collector = PyClassImplCollector::<$cls>::new();
895                        match CompareOp::from_raw(op).expect("invalid compareop") {
896                            CompareOp::Lt => collector.__lt__(py, slf, other),
897                            CompareOp::Le => collector.__le__(py, slf, other),
898                            CompareOp::Eq => collector.__eq__(py, slf, other),
899                            CompareOp::Ne => collector.__ne__(py, slf, other),
900                            CompareOp::Gt => collector.__gt__(py, slf, other),
901                            CompareOp::Ge => collector.__ge__(py, slf, other),
902                        }
903                    })
904                }
905            }
906        }
907        $crate::ffi::PyType_Slot {
908            slot: $crate::ffi::Py_tp_richcompare,
909            pfunc: <$cls>::__pymethod___richcmp____ as $crate::ffi::richcmpfunc as _,
910        }
911    }};
912}
913pub use generate_pyclass_richcompare_slot;
914
915use super::pycell::PyClassObject;
916
917/// Implements a freelist.
918///
919/// Do not implement this trait manually. Instead, use `#[pyclass(freelist = N)]`
920/// on a Rust struct to implement it.
921pub trait PyClassWithFreeList: PyClass {
922    fn get_free_list(py: Python<'_>) -> &'static Mutex<PyObjectFreeList>;
923}
924
925/// Implementation of tp_alloc for `freelist` classes.
926///
927/// # Safety
928/// - `subtype` must be a valid pointer to the type object of T or a subclass.
929/// - The calling thread must be attached to the interpreter
930pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
931    subtype: *mut ffi::PyTypeObject,
932    nitems: ffi::Py_ssize_t,
933) -> *mut ffi::PyObject {
934    let py = unsafe { Python::assume_attached() };
935
936    #[cfg(not(Py_3_8))]
937    unsafe {
938        bpo_35810_workaround(py, subtype)
939    };
940
941    let self_type = T::type_object_raw(py);
942    // If this type is a variable type or the subtype is not equal to this type, we cannot use the
943    // freelist
944    if nitems == 0 && ptr::eq(subtype, self_type) {
945        let mut free_list = T::get_free_list(py).lock().unwrap();
946        if let Some(obj) = free_list.pop() {
947            drop(free_list);
948            unsafe { ffi::PyObject_Init(obj, subtype) };
949            unsafe { ffi::PyObject_Init(obj, subtype) };
950            return obj as _;
951        }
952    }
953
954    unsafe { ffi::PyType_GenericAlloc(subtype, nitems) }
955}
956
957/// Implementation of tp_free for `freelist` classes.
958///
959/// # Safety
960/// - `obj` must be a valid pointer to an instance of T (not a subclass).
961/// - The calling thread must be attached to the interpreter
962pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
963    let obj = obj as *mut ffi::PyObject;
964    unsafe {
965        debug_assert_eq!(
966            T::type_object_raw(Python::assume_attached()),
967            ffi::Py_TYPE(obj)
968        );
969        let mut free_list = T::get_free_list(Python::assume_attached()).lock().unwrap();
970        if let Some(obj) = free_list.insert(obj) {
971            drop(free_list);
972            let ty = ffi::Py_TYPE(obj);
973
974            // Deduce appropriate inverse of PyType_GenericAlloc
975            let free = if ffi::PyType_IS_GC(ty) != 0 {
976                ffi::PyObject_GC_Del
977            } else {
978                ffi::PyObject_Free
979            };
980            free(obj as *mut c_void);
981
982            #[cfg(Py_3_8)]
983            if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
984                ffi::Py_DECREF(ty as *mut ffi::PyObject);
985            }
986        }
987    }
988}
989
990/// Workaround for Python issue 35810; no longer necessary in Python 3.8
991#[inline]
992#[cfg(not(Py_3_8))]
993unsafe fn bpo_35810_workaround(py: Python<'_>, ty: *mut ffi::PyTypeObject) {
994    #[cfg(Py_LIMITED_API)]
995    {
996        // Must check version at runtime for abi3 wheels - they could run against a higher version
997        // than the build config suggests.
998        use crate::sync::PyOnceLock;
999        static IS_PYTHON_3_8: PyOnceLock<bool> = PyOnceLock::new();
1000
1001        if *IS_PYTHON_3_8.get_or_init(py, || py.version_info() >= (3, 8)) {
1002            // No fix needed - the wheel is running on a sufficiently new interpreter.
1003            return;
1004        }
1005    }
1006    #[cfg(not(Py_LIMITED_API))]
1007    {
1008        // suppress unused variable warning
1009        let _ = py;
1010    }
1011
1012    unsafe { ffi::Py_INCREF(ty as *mut ffi::PyObject) };
1013}
1014
1015/// Method storage for `#[pyclass]`.
1016///
1017/// Implementation detail. Only to be used through our proc macro code.
1018/// Allows arbitrary `#[pymethod]` blocks to submit their methods,
1019/// which are eventually collected by `#[pyclass]`.
1020#[cfg(feature = "multiple-pymethods")]
1021pub trait PyClassInventory: inventory::Collect {
1022    /// Returns the items for a single `#[pymethods] impl` block
1023    fn items(&'static self) -> &'static PyClassItems;
1024}
1025
1026// Items from #[pymethods] if not using inventory.
1027#[cfg(not(feature = "multiple-pymethods"))]
1028pub trait PyMethods<T> {
1029    fn py_methods(self) -> &'static PyClassItems;
1030}
1031
1032#[cfg(not(feature = "multiple-pymethods"))]
1033impl<T> PyMethods<T> for &'_ PyClassImplCollector<T> {
1034    fn py_methods(self) -> &'static PyClassItems {
1035        &PyClassItems {
1036            methods: &[],
1037            slots: &[],
1038        }
1039    }
1040}
1041
1042// Thread checkers
1043
1044#[doc(hidden)]
1045pub trait PyClassThreadChecker<T>: Sized + sealed::Sealed {
1046    fn ensure(&self);
1047    fn check(&self) -> bool;
1048    fn can_drop(&self, py: Python<'_>) -> bool;
1049    fn new() -> Self;
1050}
1051
1052/// Default thread checker for `#[pyclass]`.
1053///
1054/// Keeping the T: Send bound here slightly improves the compile
1055/// error message to hint to users to figure out what's wrong
1056/// when `#[pyclass]` types do not implement `Send`.
1057#[doc(hidden)]
1058pub struct SendablePyClass<T: Send>(PhantomData<T>);
1059
1060impl<T: Send> PyClassThreadChecker<T> for SendablePyClass<T> {
1061    fn ensure(&self) {}
1062    fn check(&self) -> bool {
1063        true
1064    }
1065    fn can_drop(&self, _py: Python<'_>) -> bool {
1066        true
1067    }
1068    #[inline]
1069    fn new() -> Self {
1070        SendablePyClass(PhantomData)
1071    }
1072}
1073
1074/// Thread checker for `#[pyclass(unsendable)]` types.
1075/// Panics when the value is accessed by another thread.
1076#[doc(hidden)]
1077pub struct ThreadCheckerImpl(thread::ThreadId);
1078
1079impl ThreadCheckerImpl {
1080    fn ensure(&self, type_name: &'static str) {
1081        assert_eq!(
1082            thread::current().id(),
1083            self.0,
1084            "{type_name} is unsendable, but sent to another thread"
1085        );
1086    }
1087
1088    fn check(&self) -> bool {
1089        thread::current().id() == self.0
1090    }
1091
1092    fn can_drop(&self, py: Python<'_>, type_name: &'static str) -> bool {
1093        if thread::current().id() != self.0 {
1094            PyRuntimeError::new_err(format!(
1095                "{type_name} is unsendable, but is being dropped on another thread"
1096            ))
1097            .write_unraisable(py, None);
1098            return false;
1099        }
1100
1101        true
1102    }
1103}
1104
1105impl<T> PyClassThreadChecker<T> for ThreadCheckerImpl {
1106    fn ensure(&self) {
1107        self.ensure(std::any::type_name::<T>());
1108    }
1109    fn check(&self) -> bool {
1110        self.check()
1111    }
1112    fn can_drop(&self, py: Python<'_>) -> bool {
1113        self.can_drop(py, std::any::type_name::<T>())
1114    }
1115    fn new() -> Self {
1116        ThreadCheckerImpl(thread::current().id())
1117    }
1118}
1119
1120/// Trait denoting that this class is suitable to be used as a base type for PyClass.
1121#[cfg_attr(
1122    all(diagnostic_namespace, Py_LIMITED_API),
1123    diagnostic::on_unimplemented(
1124        message = "pyclass `{Self}` cannot be subclassed",
1125        label = "required for `#[pyclass(extends={Self})]`",
1126        note = "`{Self}` must have `#[pyclass(subclass)]` to be eligible for subclassing",
1127        note = "with the `abi3` feature enabled, PyO3 does not support subclassing native types",
1128    )
1129)]
1130#[cfg_attr(
1131    all(diagnostic_namespace, not(Py_LIMITED_API)),
1132    diagnostic::on_unimplemented(
1133        message = "pyclass `{Self}` cannot be subclassed",
1134        label = "required for `#[pyclass(extends={Self})]`",
1135        note = "`{Self}` must have `#[pyclass(subclass)]` to be eligible for subclassing",
1136    )
1137)]
1138pub trait PyClassBaseType: Sized {
1139    type LayoutAsBase: PyClassObjectLayout<Self>;
1140    type BaseNativeType;
1141    type Initializer: PyObjectInit<Self>;
1142    type PyClassMutability: PyClassMutability;
1143}
1144
1145/// Implementation of tp_dealloc for pyclasses without gc
1146pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
1147    unsafe { crate::impl_::trampoline::dealloc(obj, PyClassObject::<T>::tp_dealloc) }
1148}
1149
1150/// Implementation of tp_dealloc for pyclasses with gc
1151pub(crate) unsafe extern "C" fn tp_dealloc_with_gc<T: PyClass>(obj: *mut ffi::PyObject) {
1152    #[cfg(not(PyPy))]
1153    unsafe {
1154        ffi::PyObject_GC_UnTrack(obj.cast());
1155    }
1156    unsafe { crate::impl_::trampoline::dealloc(obj, PyClassObject::<T>::tp_dealloc) }
1157}
1158
1159pub(crate) unsafe extern "C" fn get_sequence_item_from_mapping(
1160    obj: *mut ffi::PyObject,
1161    index: ffi::Py_ssize_t,
1162) -> *mut ffi::PyObject {
1163    let index = unsafe { ffi::PyLong_FromSsize_t(index) };
1164    if index.is_null() {
1165        return std::ptr::null_mut();
1166    }
1167    let result = unsafe { ffi::PyObject_GetItem(obj, index) };
1168    unsafe { ffi::Py_DECREF(index) };
1169    result
1170}
1171
1172pub(crate) unsafe extern "C" fn assign_sequence_item_from_mapping(
1173    obj: *mut ffi::PyObject,
1174    index: ffi::Py_ssize_t,
1175    value: *mut ffi::PyObject,
1176) -> c_int {
1177    unsafe {
1178        let index = ffi::PyLong_FromSsize_t(index);
1179        if index.is_null() {
1180            return -1;
1181        }
1182        let result = if value.is_null() {
1183            ffi::PyObject_DelItem(obj, index)
1184        } else {
1185            ffi::PyObject_SetItem(obj, index, value)
1186        };
1187        ffi::Py_DECREF(index);
1188        result
1189    }
1190}
1191
1192/// Helper trait to locate field within a `#[pyclass]` for a `#[pyo3(get)]`.
1193///
1194/// Below MSRV 1.77 we can't use `std::mem::offset_of!`, and the replacement in
1195/// `memoffset::offset_of` doesn't work in const contexts for types containing `UnsafeCell`.
1196///
1197/// # Safety
1198///
1199/// The trait is unsafe to implement because producing an incorrect offset will lead to UB.
1200pub unsafe trait OffsetCalculator<T: PyClass, U> {
1201    /// Offset to the field within a `PyClassObject<T>`, in bytes.
1202    fn offset() -> usize;
1203}
1204
1205// Used in generated implementations of OffsetCalculator
1206pub fn class_offset<T: PyClass>() -> usize {
1207    offset_of!(PyClassObject<T>, contents)
1208}
1209
1210// Used in generated implementations of OffsetCalculator
1211pub use memoffset::offset_of;
1212
1213/// Type which uses specialization on impl blocks to determine how to read a field from a Rust pyclass
1214/// as part of a `#[pyo3(get)]` annotation.
1215pub struct PyClassGetterGenerator<
1216    // structural information about the field: class type, field type, where the field is within the
1217    // class struct
1218    ClassT: PyClass,
1219    FieldT,
1220    Offset: OffsetCalculator<ClassT, FieldT>, // on Rust 1.77+ this could be a const OFFSET: usize
1221    // additional metadata about the field which is used to switch between different implementations
1222    // at compile time
1223    const IS_PY_T: bool,
1224    const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1225    const IMPLEMENTS_INTOPYOBJECT: bool,
1226>(PhantomData<(ClassT, FieldT, Offset)>);
1227
1228impl<
1229        ClassT: PyClass,
1230        FieldT,
1231        Offset: OffsetCalculator<ClassT, FieldT>,
1232        const IS_PY_T: bool,
1233        const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1234        const IMPLEMENTS_INTOPYOBJECT: bool,
1235    >
1236    PyClassGetterGenerator<
1237        ClassT,
1238        FieldT,
1239        Offset,
1240        IS_PY_T,
1241        IMPLEMENTS_INTOPYOBJECT_REF,
1242        IMPLEMENTS_INTOPYOBJECT,
1243    >
1244{
1245    /// Safety: constructing this type requires that there exists a value of type FieldT
1246    /// at the calculated offset within the type ClassT.
1247    pub const unsafe fn new() -> Self {
1248        Self(PhantomData)
1249    }
1250}
1251
1252impl<
1253        ClassT: PyClass,
1254        U,
1255        Offset: OffsetCalculator<ClassT, Py<U>>,
1256        const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1257        const IMPLEMENTS_INTOPYOBJECT: bool,
1258    >
1259    PyClassGetterGenerator<
1260        ClassT,
1261        Py<U>,
1262        Offset,
1263        true,
1264        IMPLEMENTS_INTOPYOBJECT_REF,
1265        IMPLEMENTS_INTOPYOBJECT,
1266    >
1267{
1268    /// `Py<T>` fields have a potential optimization to use Python's "struct members" to read
1269    /// the field directly from the struct, rather than using a getter function.
1270    ///
1271    /// This is the most efficient operation the Python interpreter could possibly do to
1272    /// read a field, but it's only possible for us to allow this for frozen classes.
1273    pub fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1274        use crate::pyclass::boolean_struct::private::Boolean;
1275        if ClassT::Frozen::VALUE {
1276            PyMethodDefType::StructMember(ffi::PyMemberDef {
1277                name: name.as_ptr(),
1278                type_code: ffi::Py_T_OBJECT_EX,
1279                offset: Offset::offset() as ffi::Py_ssize_t,
1280                flags: ffi::Py_READONLY,
1281                doc: doc.as_ptr(),
1282            })
1283        } else {
1284            PyMethodDefType::Getter(PyGetterDef {
1285                name,
1286                meth: pyo3_get_value_into_pyobject_ref::<ClassT, Py<U>, Offset>,
1287                doc,
1288            })
1289        }
1290    }
1291}
1292
1293/// Field is not `Py<T>`; try to use `IntoPyObject` for `&T` (prefered over `ToPyObject`) to avoid
1294/// potentially expensive clones of containers like `Vec`
1295impl<ClassT, FieldT, Offset, const IMPLEMENTS_INTOPYOBJECT: bool>
1296    PyClassGetterGenerator<ClassT, FieldT, Offset, false, true, IMPLEMENTS_INTOPYOBJECT>
1297where
1298    ClassT: PyClass,
1299    for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1300    Offset: OffsetCalculator<ClassT, FieldT>,
1301{
1302    pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1303        PyMethodDefType::Getter(PyGetterDef {
1304            name,
1305            meth: pyo3_get_value_into_pyobject_ref::<ClassT, FieldT, Offset>,
1306            doc,
1307        })
1308    }
1309}
1310
1311#[cfg_attr(
1312    diagnostic_namespace,
1313    diagnostic::on_unimplemented(
1314        message = "`{Self}` cannot be converted to a Python object",
1315        label = "required by `#[pyo3(get)]` to create a readable property from a field of type `{Self}`",
1316        note = "implement `IntoPyObject` for `&{Self}` or `IntoPyObject + Clone` for `{Self}` to define the conversion"
1317    )
1318)]
1319pub trait PyO3GetField<'py>: IntoPyObject<'py> + Clone {}
1320impl<'py, T> PyO3GetField<'py> for T where T: IntoPyObject<'py> + Clone {}
1321
1322/// Base case attempts to use IntoPyObject + Clone
1323impl<
1324        ClassT: PyClass,
1325        FieldT,
1326        Offset: OffsetCalculator<ClassT, FieldT>,
1327        const IMPLEMENTS_INTOPYOBJECT: bool,
1328    > PyClassGetterGenerator<ClassT, FieldT, Offset, false, false, IMPLEMENTS_INTOPYOBJECT>
1329{
1330    pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType
1331    // The bound goes here rather than on the block so that this impl is always available
1332    // if no specialization is used instead
1333    where
1334        for<'py> FieldT: PyO3GetField<'py>,
1335    {
1336        PyMethodDefType::Getter(PyGetterDef {
1337            name,
1338            meth: pyo3_get_value_into_pyobject::<ClassT, FieldT, Offset>,
1339            doc,
1340        })
1341    }
1342}
1343
1344/// ensures `obj` is not mutably aliased
1345#[inline]
1346unsafe fn ensure_no_mutable_alias<'a, ClassT: PyClass>(
1347    _py: Python<'_>,
1348    obj: &'a *mut ffi::PyObject,
1349) -> Result<PyClassGuard<'a, ClassT>, PyBorrowError> {
1350    unsafe { PyClassGuard::try_borrow(NonNull::from(obj).cast::<Py<ClassT>>().as_ref()) }
1351}
1352
1353/// calculates the field pointer from an PyObject pointer
1354#[inline]
1355fn field_from_object<ClassT, FieldT, Offset>(obj: *mut ffi::PyObject) -> *mut FieldT
1356where
1357    ClassT: PyClass,
1358    Offset: OffsetCalculator<ClassT, FieldT>,
1359{
1360    unsafe { obj.cast::<u8>().add(Offset::offset()).cast::<FieldT>() }
1361}
1362
1363fn pyo3_get_value_into_pyobject_ref<ClassT, FieldT, Offset>(
1364    py: Python<'_>,
1365    obj: *mut ffi::PyObject,
1366) -> PyResult<*mut ffi::PyObject>
1367where
1368    ClassT: PyClass,
1369    for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1370    Offset: OffsetCalculator<ClassT, FieldT>,
1371{
1372    let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1373    let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1374
1375    // SAFETY: Offset is known to describe the location of the value, and
1376    // _holder is preventing mutable aliasing
1377    Ok((unsafe { &*value })
1378        .into_pyobject(py)
1379        .map_err(Into::into)?
1380        .into_ptr())
1381}
1382
1383fn pyo3_get_value_into_pyobject<ClassT, FieldT, Offset>(
1384    py: Python<'_>,
1385    obj: *mut ffi::PyObject,
1386) -> PyResult<*mut ffi::PyObject>
1387where
1388    ClassT: PyClass,
1389    for<'py> FieldT: IntoPyObject<'py> + Clone,
1390    Offset: OffsetCalculator<ClassT, FieldT>,
1391{
1392    let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1393    let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1394
1395    // SAFETY: Offset is known to describe the location of the value, and
1396    // _holder is preventing mutable aliasing
1397    Ok((unsafe { &*value })
1398        .clone()
1399        .into_pyobject(py)
1400        .map_err(Into::into)?
1401        .into_ptr())
1402}
1403
1404pub struct ConvertField<
1405    const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1406    const IMPLEMENTS_INTOPYOBJECT: bool,
1407>;
1408
1409impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<true, IMPLEMENTS_INTOPYOBJECT> {
1410    #[inline]
1411    pub fn convert_field<'a, 'py, T>(obj: &'a T, py: Python<'py>) -> PyResult<Py<PyAny>>
1412    where
1413        &'a T: IntoPyObject<'py>,
1414    {
1415        obj.into_py_any(py)
1416    }
1417}
1418
1419impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<false, IMPLEMENTS_INTOPYOBJECT> {
1420    #[inline]
1421    pub fn convert_field<'py, T>(obj: &T, py: Python<'py>) -> PyResult<Py<PyAny>>
1422    where
1423        T: PyO3GetField<'py>,
1424    {
1425        obj.clone().into_py_any(py)
1426    }
1427}
1428
1429#[cfg(test)]
1430#[cfg(feature = "macros")]
1431mod tests {
1432    use super::*;
1433
1434    #[test]
1435    fn get_py_for_frozen_class() {
1436        #[crate::pyclass(crate = "crate", frozen)]
1437        struct FrozenClass {
1438            #[pyo3(get)]
1439            value: Py<PyAny>,
1440        }
1441
1442        let mut methods = Vec::new();
1443        let mut slots = Vec::new();
1444
1445        for items in FrozenClass::items_iter() {
1446            methods.extend(items.methods.iter().map(|m| match m {
1447                MaybeRuntimePyMethodDef::Static(m) => m.clone(),
1448                MaybeRuntimePyMethodDef::Runtime(r) => r(),
1449            }));
1450            slots.extend_from_slice(items.slots);
1451        }
1452
1453        assert_eq!(methods.len(), 1);
1454        assert!(slots.is_empty());
1455
1456        match methods.first() {
1457            Some(PyMethodDefType::StructMember(member)) => {
1458                assert_eq!(unsafe { CStr::from_ptr(member.name) }, ffi::c_str!("value"));
1459                assert_eq!(member.type_code, ffi::Py_T_OBJECT_EX);
1460                assert_eq!(
1461                    member.offset,
1462                    (memoffset::offset_of!(PyClassObject<FrozenClass>, contents)
1463                        + memoffset::offset_of!(FrozenClass, value))
1464                        as ffi::Py_ssize_t
1465                );
1466                assert_eq!(member.flags, ffi::Py_READONLY);
1467            }
1468            _ => panic!("Expected a StructMember"),
1469        }
1470    }
1471
1472    #[test]
1473    fn get_py_for_non_frozen_class() {
1474        #[crate::pyclass(crate = "crate")]
1475        struct FrozenClass {
1476            #[pyo3(get)]
1477            value: Py<PyAny>,
1478        }
1479
1480        let mut methods = Vec::new();
1481        let mut slots = Vec::new();
1482
1483        for items in FrozenClass::items_iter() {
1484            methods.extend(items.methods.iter().map(|m| match m {
1485                MaybeRuntimePyMethodDef::Static(m) => m.clone(),
1486                MaybeRuntimePyMethodDef::Runtime(r) => r(),
1487            }));
1488            slots.extend_from_slice(items.slots);
1489        }
1490
1491        assert_eq!(methods.len(), 1);
1492        assert!(slots.is_empty());
1493
1494        match methods.first() {
1495            Some(PyMethodDefType::Getter(getter)) => {
1496                assert_eq!(getter.name, ffi::c_str!("value"));
1497                assert_eq!(getter.doc, ffi::c_str!(""));
1498                // tests for the function pointer are in test_getter_setter.py
1499            }
1500            _ => panic!("Expected a StructMember"),
1501        }
1502    }
1503}