Skip to main content

pyo3/impl_/
pyclass.rs

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