pyo3/impl_/
pyclass.rs

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