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