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