1#![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#[inline]
40pub const fn dict_offset<T: PyClass>() -> PyObjectOffset {
41 <T as PyClassImpl>::Layout::DICT_OFFSET
42}
43
44#[inline]
46pub const fn weaklist_offset<T: PyClass>() -> PyObjectOffset {
47 <T as PyClassImpl>::Layout::WEAKLIST_OFFSET
48}
49
50#[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
70pub trait PyClassDict: sealed::Sealed {
72 const INIT: Self;
74 #[inline]
76 fn clear_dict(&self, _py: Python<'_>) {}
77 #[inline]
79 fn release_dict(&mut self, _py: Python<'_>) {}
80 #[inline]
86 unsafe fn traverse_dict(&self, _visit: ffi::visitproc, _arg: *mut c_void) -> c_int {
87 0
88 }
89}
90
91pub trait PyClassWeakRef: sealed::Sealed {
93 const INIT: Self;
95 #[inline]
101 unsafe fn clear_weakrefs(&mut self, _obj: *mut ffi::PyObject, _py: Python<'_>) {}
102}
103
104pub 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#[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#[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
159pub 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
188unsafe impl Sync for PyClassItems {}
190
191pub trait PyClassImpl: Sized + 'static {
196 const MODULE: Option<&'static str>;
201
202 const IS_BASETYPE: bool = false;
204
205 const IS_SUBCLASS: bool = false;
207
208 const IS_MAPPING: bool = false;
210
211 const IS_SEQUENCE: bool = false;
213
214 const IS_IMMUTABLE_TYPE: bool = false;
216
217 type Layout: PyClassObjectLayout<Self>;
219
220 type BaseType: PyTypeInfo + PyClassBaseType;
222
223 type PyClassMutability: PyClassMutability + GetBorrowChecker<Self>;
225
226 type Dict: PyClassDict;
228
229 type WeakRef: PyClassWeakRef;
231
232 type BaseNativeType: PyTypeInfo;
235
236 type ThreadChecker: PyClassThreadChecker<Self>;
244
245 #[cfg(feature = "multiple-pymethods")]
246 type Inventory: PyClassInventory;
247
248 const RAW_DOC: &'static CStr;
252
253 const DOC: &'static CStr;
258
259 fn items_iter() -> PyClassItemsIter;
260
261 #[inline]
264 fn dict_offset() -> Option<PyObjectOffset> {
265 None
266 }
267
268 #[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
286pub struct PyClassItemsIter {
288 idx: usize,
290 pyclass_items: &'static PyClassItems,
292 #[cfg(not(feature = "multiple-pymethods"))]
294 pymethods_items: &'static PyClassItems,
295 #[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 _ => 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 _ => self.pymethods_items.next(),
344 }
345 }
346}
347
348macro_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 #[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 #[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 (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 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
436macro_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 #[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 #[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
553macro_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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 #[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 #[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#[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 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 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; $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
964pub trait PyClassWithFreeList: PyClass + generic_pyclass::Sealed {
969 fn get_free_list(py: Python<'_>) -> &'static Mutex<PyObjectFreeList>;
970}
971
972pub 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 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
998pub 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 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#[cfg(feature = "multiple-pymethods")]
1034pub trait PyClassInventory: inventory::Collect {
1035 fn items(&'static self) -> &'static PyClassItems;
1037}
1038
1039#[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#[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#[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#[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#[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 type Layout<T: PyClassImpl>;
1161}
1162
1163pub(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
1168pub(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#[derive(Debug, Clone, Copy)]
1212pub enum PyObjectOffset {
1213 Absolute(ffi::Py_ssize_t),
1215 #[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 #[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
1238pub struct PyClassGetterGenerator<
1241 ClassT: PyClass,
1244 FieldT,
1245 const OFFSET: usize,
1246 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 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 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
1315impl<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
1344impl<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 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#[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
1379unsafe 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 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 let value = unsafe { obj.byte_add(offset).cast::<FieldT>().as_ref() };
1407 value.into_py_any(py).map(Py::into_ptr)
1408 }
1409
1410 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 unsafe { inner::<FieldT>(py, NonNull::from(class_obj.contents()).cast(), OFFSET) }
1417}
1418
1419unsafe 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 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 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 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 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 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 }
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 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 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 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 assert_eq!(unsafe { CStr::from_ptr(def.name) }, c"my_field");
1639 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 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}