Skip to main content

pyo3/impl_/
pyclass.rs

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