1use crate::{
2 exceptions::{PyAttributeError, PyNotImplementedError, PyRuntimeError},
3 ffi,
4 ffi_ptr_ext::FfiPtrExt,
5 impl_::{
6 freelist::PyObjectFreeList,
7 pycell::{GetBorrowChecker, PyClassMutability, PyClassObjectBaseLayout},
8 pyclass_init::PyObjectInit,
9 pymethods::{PyGetterDef, PyMethodDefType},
10 },
11 pycell::{impl_::PyClassObjectLayout, PyBorrowError},
12 types::{any::PyAnyMethods, PyBool},
13 Borrowed, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyClass, PyClassGuard, PyErr, PyResult,
14 PyTypeCheck, PyTypeInfo, Python,
15};
16use std::{
17 ffi::CStr,
18 marker::PhantomData,
19 os::raw::{c_int, c_void},
20 ptr::{self, NonNull},
21 sync::Mutex,
22 thread,
23};
24
25mod assertions;
26pub mod doc;
27mod lazy_type_object;
28#[macro_use]
29mod probes;
30
31pub use assertions::*;
32pub use lazy_type_object::{type_object_init_failed, LazyTypeObject};
33pub use probes::*;
34
35#[inline]
37pub const fn dict_offset<T: PyClass>() -> PyObjectOffset {
38 <T as PyClassImpl>::Layout::DICT_OFFSET
39}
40
41#[inline]
43pub const fn weaklist_offset<T: PyClass>() -> PyObjectOffset {
44 <T as PyClassImpl>::Layout::WEAKLIST_OFFSET
45}
46
47mod sealed {
48 pub trait Sealed {}
49
50 impl Sealed for super::PyClassDummySlot {}
51 impl Sealed for super::PyClassDictSlot {}
52 impl Sealed for super::PyClassWeakRefSlot {}
53 impl Sealed for super::ThreadCheckerImpl {}
54 impl Sealed for super::NoopThreadChecker {}
55}
56
57pub trait PyClassDict: sealed::Sealed {
59 const INIT: Self;
61 #[inline]
63 fn clear_dict(&mut self, _py: Python<'_>) {}
64}
65
66pub trait PyClassWeakRef: sealed::Sealed {
68 const INIT: Self;
70 #[inline]
76 unsafe fn clear_weakrefs(&mut self, _obj: *mut ffi::PyObject, _py: Python<'_>) {}
77}
78
79pub struct PyClassDummySlot;
81
82impl PyClassDict for PyClassDummySlot {
83 const INIT: Self = PyClassDummySlot;
84}
85
86impl PyClassWeakRef for PyClassDummySlot {
87 const INIT: Self = PyClassDummySlot;
88}
89
90#[repr(transparent)]
94pub struct PyClassDictSlot(*mut ffi::PyObject);
95
96impl PyClassDict for PyClassDictSlot {
97 const INIT: Self = Self(std::ptr::null_mut());
98 #[inline]
99 fn clear_dict(&mut self, _py: Python<'_>) {
100 if !self.0.is_null() {
101 unsafe { ffi::PyDict_Clear(self.0) }
102 }
103 }
104}
105
106#[repr(transparent)]
110pub struct PyClassWeakRefSlot(*mut ffi::PyObject);
111
112impl PyClassWeakRef for PyClassWeakRefSlot {
113 const INIT: Self = Self(std::ptr::null_mut());
114 #[inline]
115 unsafe fn clear_weakrefs(&mut self, obj: *mut ffi::PyObject, _py: Python<'_>) {
116 if !self.0.is_null() {
117 unsafe { ffi::PyObject_ClearWeakRefs(obj) }
118 }
119 }
120}
121
122pub struct PyClassImplCollector<T>(PhantomData<T>);
125
126impl<T> PyClassImplCollector<T> {
127 pub fn new() -> Self {
128 Self(PhantomData)
129 }
130}
131
132impl<T> Default for PyClassImplCollector<T> {
133 fn default() -> Self {
134 Self::new()
135 }
136}
137
138impl<T> Clone for PyClassImplCollector<T> {
139 fn clone(&self) -> Self {
140 *self
141 }
142}
143
144impl<T> Copy for PyClassImplCollector<T> {}
145
146pub struct PyClassItems {
147 pub methods: &'static [PyMethodDefType],
148 pub slots: &'static [ffi::PyType_Slot],
149}
150
151unsafe impl Sync for PyClassItems {}
153
154pub trait PyClassImpl: Sized + 'static {
159 const MODULE: Option<&'static str>;
164
165 const IS_BASETYPE: bool = false;
167
168 const IS_SUBCLASS: bool = false;
170
171 const IS_MAPPING: bool = false;
173
174 const IS_SEQUENCE: bool = false;
176
177 const IS_IMMUTABLE_TYPE: bool = false;
179
180 type Layout: PyClassObjectLayout<Self>;
182
183 type BaseType: PyTypeInfo + PyClassBaseType;
185
186 type PyClassMutability: PyClassMutability + GetBorrowChecker<Self>;
188
189 type Dict: PyClassDict;
191
192 type WeakRef: PyClassWeakRef;
194
195 type BaseNativeType: PyTypeInfo;
198
199 type ThreadChecker: PyClassThreadChecker<Self>;
207
208 #[cfg(feature = "multiple-pymethods")]
209 type Inventory: PyClassInventory;
210
211 const RAW_DOC: &'static CStr;
215
216 const DOC: &'static CStr;
221
222 fn items_iter() -> PyClassItemsIter;
223
224 #[inline]
227 fn dict_offset() -> Option<PyObjectOffset> {
228 None
229 }
230
231 #[inline]
234 fn weaklist_offset() -> Option<PyObjectOffset> {
235 None
236 }
237
238 fn lazy_type_object() -> &'static LazyTypeObject<Self>;
239}
240
241pub struct PyClassItemsIter {
243 idx: usize,
245 pyclass_items: &'static PyClassItems,
247 #[cfg(not(feature = "multiple-pymethods"))]
249 pymethods_items: &'static PyClassItems,
250 #[cfg(feature = "multiple-pymethods")]
252 pymethods_items: Box<dyn Iterator<Item = &'static PyClassItems>>,
253}
254
255impl PyClassItemsIter {
256 pub fn new(
257 pyclass_items: &'static PyClassItems,
258 #[cfg(not(feature = "multiple-pymethods"))] pymethods_items: &'static PyClassItems,
259 #[cfg(feature = "multiple-pymethods")] pymethods_items: Box<
260 dyn Iterator<Item = &'static PyClassItems>,
261 >,
262 ) -> Self {
263 Self {
264 idx: 0,
265 pyclass_items,
266 pymethods_items,
267 }
268 }
269}
270
271impl Iterator for PyClassItemsIter {
272 type Item = &'static PyClassItems;
273
274 #[cfg(not(feature = "multiple-pymethods"))]
275 fn next(&mut self) -> Option<Self::Item> {
276 match self.idx {
277 0 => {
278 self.idx += 1;
279 Some(self.pyclass_items)
280 }
281 1 => {
282 self.idx += 1;
283 Some(self.pymethods_items)
284 }
285 _ => None,
287 }
288 }
289
290 #[cfg(feature = "multiple-pymethods")]
291 fn next(&mut self) -> Option<Self::Item> {
292 match self.idx {
293 0 => {
294 self.idx += 1;
295 Some(self.pyclass_items)
296 }
297 _ => self.pymethods_items.next(),
299 }
300 }
301}
302
303macro_rules! slot_fragment_trait {
306 ($trait_name:ident, $($default_method:tt)*) => {
307 #[allow(non_camel_case_types, reason = "to match Python dunder names")]
308 pub trait $trait_name<T>: Sized {
309 $($default_method)*
310 }
311
312 impl<T> $trait_name<T> for &'_ PyClassImplCollector<T> {}
313 }
314}
315
316slot_fragment_trait! {
317 PyClass__getattribute__SlotFragment,
318
319 #[inline]
321 unsafe fn __getattribute__(
322 self,
323 py: Python<'_>,
324 slf: *mut ffi::PyObject,
325 attr: *mut ffi::PyObject,
326 ) -> PyResult<*mut ffi::PyObject> {
327 let res = unsafe { ffi::PyObject_GenericGetAttr(slf, attr) };
328 if res.is_null() {
329 Err(PyErr::fetch(py))
330 } else {
331 Ok(res)
332 }
333 }
334}
335
336slot_fragment_trait! {
337 PyClass__getattr__SlotFragment,
338
339 #[inline]
341 unsafe fn __getattr__(
342 self,
343 py: Python<'_>,
344 _slf: *mut ffi::PyObject,
345 attr: *mut ffi::PyObject,
346 ) -> PyResult<*mut ffi::PyObject> {
347 Err(PyErr::new::<PyAttributeError, _>(
348 (unsafe { attr.assume_borrowed_unchecked(py) }.to_owned().unbind(),)
350 ))
351 }
352}
353
354#[doc(hidden)]
355#[macro_export]
356macro_rules! generate_pyclass_getattro_slot {
357 ($cls:ty) => {{
358 unsafe fn slot_impl(
359 py: $crate::Python<'_>,
360 _slf: *mut $crate::ffi::PyObject,
361 attr: *mut $crate::ffi::PyObject,
362 ) -> $crate::PyResult<*mut $crate::ffi::PyObject> {
363 use ::std::result::Result::*;
364 use $crate::impl_::pyclass::*;
365 let collector = PyClassImplCollector::<$cls>::new();
366
367 match unsafe { collector.__getattribute__(py, _slf, attr) } {
373 Ok(obj) => Ok(obj),
374 Err(e) if e.is_instance_of::<$crate::exceptions::PyAttributeError>(py) => unsafe {
375 collector.__getattr__(py, _slf, attr)
376 },
377 Err(e) => Err(e),
378 }
379 }
380
381 $crate::ffi::PyType_Slot {
382 slot: $crate::ffi::Py_tp_getattro,
383 pfunc: $crate::impl_::trampoline::get_trampoline_function!(getattrofunc, slot_impl)
384 as $crate::ffi::getattrofunc as _,
385 }
386 }};
387}
388
389pub use generate_pyclass_getattro_slot;
390
391macro_rules! define_pyclass_setattr_slot {
396 (
397 $set_trait:ident,
398 $del_trait:ident,
399 $set:ident,
400 $del:ident,
401 $set_error:expr,
402 $del_error:expr,
403 $generate_macro:ident,
404 $slot:ident,
405 $func_ty:ident,
406 ) => {
407 slot_fragment_trait! {
408 $set_trait,
409
410 #[inline]
412 unsafe fn $set(
413 self,
414 _py: Python<'_>,
415 _slf: *mut ffi::PyObject,
416 _attr: *mut ffi::PyObject,
417 _value: NonNull<ffi::PyObject>,
418 ) -> PyResult<()> {
419 $set_error
420 }
421 }
422
423 slot_fragment_trait! {
424 $del_trait,
425
426 #[inline]
428 unsafe fn $del(
429 self,
430 _py: Python<'_>,
431 _slf: *mut ffi::PyObject,
432 _attr: *mut ffi::PyObject,
433 ) -> PyResult<()> {
434 $del_error
435 }
436 }
437
438 #[doc(hidden)]
439 #[macro_export]
440 macro_rules! $generate_macro {
441 ($cls:ty) => {{
442 unsafe fn slot_impl(
443 py: $crate::Python<'_>,
444 _slf: *mut $crate::ffi::PyObject,
445 attr: *mut $crate::ffi::PyObject,
446 value: *mut $crate::ffi::PyObject,
447 ) -> $crate::PyResult<::std::ffi::c_int> {
448 use ::std::option::Option::*;
449 use $crate::impl_::callback::IntoPyCallbackOutput;
450 use $crate::impl_::pyclass::*;
451 let collector = PyClassImplCollector::<$cls>::new();
452 if let Some(value) = ::std::ptr::NonNull::new(value) {
453 unsafe { collector.$set(py, _slf, attr, value).convert(py) }
454 } else {
455 unsafe { collector.$del(py, _slf, attr).convert(py) }
456 }
457 }
458
459 $crate::ffi::PyType_Slot {
460 slot: $crate::ffi::$slot,
461 pfunc: $crate::impl_::trampoline::get_trampoline_function!(
462 setattrofunc,
463 slot_impl
464 ) as $crate::ffi::$func_ty as _,
465 }
466 }};
467 }
468 pub use $generate_macro;
469 };
470}
471
472define_pyclass_setattr_slot! {
473 PyClass__setattr__SlotFragment,
474 PyClass__delattr__SlotFragment,
475 __setattr__,
476 __delattr__,
477 Err(PyAttributeError::new_err("can't set attribute")),
478 Err(PyAttributeError::new_err("can't delete attribute")),
479 generate_pyclass_setattr_slot,
480 Py_tp_setattro,
481 setattrofunc,
482}
483
484define_pyclass_setattr_slot! {
485 PyClass__set__SlotFragment,
486 PyClass__delete__SlotFragment,
487 __set__,
488 __delete__,
489 Err(PyNotImplementedError::new_err("can't set descriptor")),
490 Err(PyNotImplementedError::new_err("can't delete descriptor")),
491 generate_pyclass_setdescr_slot,
492 Py_tp_descr_set,
493 descrsetfunc,
494}
495
496define_pyclass_setattr_slot! {
497 PyClass__setitem__SlotFragment,
498 PyClass__delitem__SlotFragment,
499 __setitem__,
500 __delitem__,
501 Err(PyNotImplementedError::new_err("can't set item")),
502 Err(PyNotImplementedError::new_err("can't delete item")),
503 generate_pyclass_setitem_slot,
504 Py_mp_ass_subscript,
505 objobjargproc,
506}
507
508macro_rules! define_pyclass_binary_operator_slot {
513 (
514 $lhs_trait:ident,
515 $rhs_trait:ident,
516 $lhs:ident,
517 $rhs:ident,
518 $generate_macro:ident,
519 $slot:ident,
520 ) => {
521 slot_fragment_trait! {
522 $lhs_trait,
523
524 #[inline]
526 unsafe fn $lhs(
527 self,
528 py: Python<'_>,
529 _slf: *mut ffi::PyObject,
530 _other: *mut ffi::PyObject,
531 ) -> PyResult<*mut ffi::PyObject> {
532 Ok(py.NotImplemented().into_ptr())
533 }
534 }
535
536 slot_fragment_trait! {
537 $rhs_trait,
538
539 #[inline]
541 unsafe fn $rhs(
542 self,
543 py: Python<'_>,
544 _slf: *mut ffi::PyObject,
545 _other: *mut ffi::PyObject,
546 ) -> PyResult<*mut ffi::PyObject> {
547 Ok(py.NotImplemented().into_ptr())
548 }
549 }
550
551 #[doc(hidden)]
552 #[macro_export]
553 macro_rules! $generate_macro {
554 ($cls:ty) => {{
555 unsafe fn slot_impl(
556 py: $crate::Python<'_>,
557 _slf: *mut $crate::ffi::PyObject,
558 _other: *mut $crate::ffi::PyObject,
559 ) -> $crate::PyResult<*mut $crate::ffi::PyObject> {
560 use $crate::impl_::pyclass::*;
561 let collector = PyClassImplCollector::<$cls>::new();
562 let lhs_result = unsafe { collector.$lhs(py, _slf, _other) }?;
563 if lhs_result == unsafe { $crate::ffi::Py_NotImplemented() } {
564 unsafe { $crate::ffi::Py_DECREF(lhs_result) };
565 unsafe { collector.$rhs(py, _other, _slf) }
566 } else {
567 ::std::result::Result::Ok(lhs_result)
568 }
569 }
570
571 $crate::ffi::PyType_Slot {
572 slot: $crate::ffi::$slot,
573 pfunc: $crate::impl_::trampoline::get_trampoline_function!(
574 binaryfunc, slot_impl
575 ) as $crate::ffi::binaryfunc as _,
576 }
577 }};
578 }
579 pub use $generate_macro;
580 };
581}
582
583define_pyclass_binary_operator_slot! {
584 PyClass__add__SlotFragment,
585 PyClass__radd__SlotFragment,
586 __add__,
587 __radd__,
588 generate_pyclass_add_slot,
589 Py_nb_add,
590}
591
592define_pyclass_binary_operator_slot! {
593 PyClass__sub__SlotFragment,
594 PyClass__rsub__SlotFragment,
595 __sub__,
596 __rsub__,
597 generate_pyclass_sub_slot,
598 Py_nb_subtract,
599}
600
601define_pyclass_binary_operator_slot! {
602 PyClass__mul__SlotFragment,
603 PyClass__rmul__SlotFragment,
604 __mul__,
605 __rmul__,
606 generate_pyclass_mul_slot,
607 Py_nb_multiply,
608}
609
610define_pyclass_binary_operator_slot! {
611 PyClass__mod__SlotFragment,
612 PyClass__rmod__SlotFragment,
613 __mod__,
614 __rmod__,
615 generate_pyclass_mod_slot,
616 Py_nb_remainder,
617}
618
619define_pyclass_binary_operator_slot! {
620 PyClass__divmod__SlotFragment,
621 PyClass__rdivmod__SlotFragment,
622 __divmod__,
623 __rdivmod__,
624 generate_pyclass_divmod_slot,
625 Py_nb_divmod,
626}
627
628define_pyclass_binary_operator_slot! {
629 PyClass__lshift__SlotFragment,
630 PyClass__rlshift__SlotFragment,
631 __lshift__,
632 __rlshift__,
633 generate_pyclass_lshift_slot,
634 Py_nb_lshift,
635}
636
637define_pyclass_binary_operator_slot! {
638 PyClass__rshift__SlotFragment,
639 PyClass__rrshift__SlotFragment,
640 __rshift__,
641 __rrshift__,
642 generate_pyclass_rshift_slot,
643 Py_nb_rshift,
644}
645
646define_pyclass_binary_operator_slot! {
647 PyClass__and__SlotFragment,
648 PyClass__rand__SlotFragment,
649 __and__,
650 __rand__,
651 generate_pyclass_and_slot,
652 Py_nb_and,
653}
654
655define_pyclass_binary_operator_slot! {
656 PyClass__or__SlotFragment,
657 PyClass__ror__SlotFragment,
658 __or__,
659 __ror__,
660 generate_pyclass_or_slot,
661 Py_nb_or,
662}
663
664define_pyclass_binary_operator_slot! {
665 PyClass__xor__SlotFragment,
666 PyClass__rxor__SlotFragment,
667 __xor__,
668 __rxor__,
669 generate_pyclass_xor_slot,
670 Py_nb_xor,
671}
672
673define_pyclass_binary_operator_slot! {
674 PyClass__matmul__SlotFragment,
675 PyClass__rmatmul__SlotFragment,
676 __matmul__,
677 __rmatmul__,
678 generate_pyclass_matmul_slot,
679 Py_nb_matrix_multiply,
680}
681
682define_pyclass_binary_operator_slot! {
683 PyClass__truediv__SlotFragment,
684 PyClass__rtruediv__SlotFragment,
685 __truediv__,
686 __rtruediv__,
687 generate_pyclass_truediv_slot,
688 Py_nb_true_divide,
689}
690
691define_pyclass_binary_operator_slot! {
692 PyClass__floordiv__SlotFragment,
693 PyClass__rfloordiv__SlotFragment,
694 __floordiv__,
695 __rfloordiv__,
696 generate_pyclass_floordiv_slot,
697 Py_nb_floor_divide,
698}
699
700slot_fragment_trait! {
701 PyClass__pow__SlotFragment,
702
703 #[inline]
705 unsafe fn __pow__(
706 self,
707 py: Python<'_>,
708 _slf: *mut ffi::PyObject,
709 _other: *mut ffi::PyObject,
710 _mod: *mut ffi::PyObject,
711 ) -> PyResult<*mut ffi::PyObject> {
712 Ok(py.NotImplemented().into_ptr())
713 }
714}
715
716slot_fragment_trait! {
717 PyClass__rpow__SlotFragment,
718
719 #[inline]
721 unsafe fn __rpow__(
722 self,
723 py: Python<'_>,
724 _slf: *mut ffi::PyObject,
725 _other: *mut ffi::PyObject,
726 _mod: *mut ffi::PyObject,
727 ) -> PyResult<*mut ffi::PyObject> {
728 Ok(py.NotImplemented().into_ptr())
729 }
730}
731
732#[doc(hidden)]
733#[macro_export]
734macro_rules! generate_pyclass_pow_slot {
735 ($cls:ty) => {{
736 fn slot_impl(
737 py: $crate::Python<'_>,
738 _slf: *mut $crate::ffi::PyObject,
739 _other: *mut $crate::ffi::PyObject,
740 _mod: *mut $crate::ffi::PyObject,
741 ) -> $crate::PyResult<*mut $crate::ffi::PyObject> {
742 use $crate::impl_::pyclass::*;
743 let collector = PyClassImplCollector::<$cls>::new();
744 let lhs_result = unsafe { collector.__pow__(py, _slf, _other, _mod) }?;
745 if lhs_result == unsafe { $crate::ffi::Py_NotImplemented() } {
746 unsafe { $crate::ffi::Py_DECREF(lhs_result) };
747 unsafe { collector.__rpow__(py, _other, _slf, _mod) }
748 } else {
749 ::std::result::Result::Ok(lhs_result)
750 }
751 }
752
753 $crate::ffi::PyType_Slot {
754 slot: $crate::ffi::Py_nb_power,
755 pfunc: $crate::impl_::trampoline::get_trampoline_function!(ternaryfunc, slot_impl)
756 as $crate::ffi::ternaryfunc as _,
757 }
758 }};
759}
760pub use generate_pyclass_pow_slot;
761
762slot_fragment_trait! {
763 PyClass__lt__SlotFragment,
764
765 #[inline]
767 unsafe fn __lt__(
768 self,
769 py: Python<'_>,
770 _slf: *mut ffi::PyObject,
771 _other: *mut ffi::PyObject,
772 ) -> PyResult<*mut ffi::PyObject> {
773 Ok(py.NotImplemented().into_ptr())
774 }
775}
776
777slot_fragment_trait! {
778 PyClass__le__SlotFragment,
779
780 #[inline]
782 unsafe fn __le__(
783 self,
784 py: Python<'_>,
785 _slf: *mut ffi::PyObject,
786 _other: *mut ffi::PyObject,
787 ) -> PyResult<*mut ffi::PyObject> {
788 Ok(py.NotImplemented().into_ptr())
789 }
790}
791
792slot_fragment_trait! {
793 PyClass__eq__SlotFragment,
794
795 #[inline]
797 unsafe fn __eq__(
798 self,
799 py: Python<'_>,
800 _slf: *mut ffi::PyObject,
801 _other: *mut ffi::PyObject,
802 ) -> PyResult<*mut ffi::PyObject> {
803 Ok(py.NotImplemented().into_ptr())
804 }
805}
806
807slot_fragment_trait! {
808 PyClass__ne__SlotFragment,
809
810 #[inline]
812 unsafe fn __ne__(
813 self,
814 py: Python<'_>,
815 slf: *mut ffi::PyObject,
816 other: *mut ffi::PyObject,
817 ) -> PyResult<*mut ffi::PyObject> {
818 let slf = unsafe { Borrowed::from_ptr(py, slf)};
820 let other = unsafe { Borrowed::from_ptr(py, other)};
821 slf.eq(other).map(|is_eq| PyBool::new(py, !is_eq).to_owned().into_ptr())
822 }
823}
824
825slot_fragment_trait! {
826 PyClass__gt__SlotFragment,
827
828 #[inline]
830 unsafe fn __gt__(
831 self,
832 py: Python<'_>,
833 _slf: *mut ffi::PyObject,
834 _other: *mut ffi::PyObject,
835 ) -> PyResult<*mut ffi::PyObject> {
836 Ok(py.NotImplemented().into_ptr())
837 }
838}
839
840slot_fragment_trait! {
841 PyClass__ge__SlotFragment,
842
843 #[inline]
845 unsafe fn __ge__(
846 self,
847 py: Python<'_>,
848 _slf: *mut ffi::PyObject,
849 _other: *mut ffi::PyObject,
850 ) -> PyResult<*mut ffi::PyObject> {
851 Ok(py.NotImplemented().into_ptr())
852 }
853}
854
855#[doc(hidden)]
856#[macro_export]
857macro_rules! generate_pyclass_richcompare_slot {
858 ($cls:ty) => {{
859 #[allow(unknown_lints, non_local_definitions)]
860 impl $cls {
861 #[expect(non_snake_case)]
862 unsafe fn __pymethod___richcmp____(
863 py: $crate::Python<'_>,
864 slf: *mut $crate::ffi::PyObject,
865 other: *mut $crate::ffi::PyObject,
866 op: ::std::ffi::c_int,
867 ) -> $crate::PyResult<*mut $crate::ffi::PyObject> {
868 use $crate::class::basic::CompareOp;
869 use $crate::impl_::pyclass::*;
870 let collector = PyClassImplCollector::<$cls>::new();
871 match CompareOp::from_raw(op).expect("invalid compareop") {
872 CompareOp::Lt => unsafe { collector.__lt__(py, slf, other) },
873 CompareOp::Le => unsafe { collector.__le__(py, slf, other) },
874 CompareOp::Eq => unsafe { collector.__eq__(py, slf, other) },
875 CompareOp::Ne => unsafe { collector.__ne__(py, slf, other) },
876 CompareOp::Gt => unsafe { collector.__gt__(py, slf, other) },
877 CompareOp::Ge => unsafe { collector.__ge__(py, slf, other) },
878 }
879 }
880 }
881 $crate::ffi::PyType_Slot {
882 slot: $crate::ffi::Py_tp_richcompare,
883 pfunc: {
884 type Cls = $cls; $crate::impl_::trampoline::get_trampoline_function!(
886 richcmpfunc,
887 Cls::__pymethod___richcmp____
888 ) as $crate::ffi::richcmpfunc as _
889 },
890 }
891 }};
892}
893pub use generate_pyclass_richcompare_slot;
894
895pub trait PyClassWithFreeList: PyClass {
900 fn get_free_list(py: Python<'_>) -> &'static Mutex<PyObjectFreeList>;
901}
902
903pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
909 subtype: *mut ffi::PyTypeObject,
910 nitems: ffi::Py_ssize_t,
911) -> *mut ffi::PyObject {
912 let py = unsafe { Python::assume_attached() };
913
914 #[cfg(not(Py_3_8))]
915 unsafe {
916 bpo_35810_workaround(py, subtype)
917 };
918
919 let self_type = T::type_object_raw(py);
920 if nitems == 0 && ptr::eq(subtype, self_type) {
923 let mut free_list = T::get_free_list(py).lock().unwrap();
924 if let Some(obj) = free_list.pop() {
925 drop(free_list);
926 unsafe { ffi::PyObject_Init(obj, subtype) };
927 unsafe { ffi::PyObject_Init(obj, subtype) };
928 return obj as _;
929 }
930 }
931
932 unsafe { ffi::PyType_GenericAlloc(subtype, nitems) }
933}
934
935pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
941 let obj = obj as *mut ffi::PyObject;
942 unsafe {
943 debug_assert_eq!(
944 T::type_object_raw(Python::assume_attached()),
945 ffi::Py_TYPE(obj)
946 );
947 let mut free_list = T::get_free_list(Python::assume_attached()).lock().unwrap();
948 if let Some(obj) = free_list.insert(obj) {
949 drop(free_list);
950 let ty = ffi::Py_TYPE(obj);
951
952 let free = if ffi::PyType_IS_GC(ty) != 0 {
954 ffi::PyObject_GC_Del
955 } else {
956 ffi::PyObject_Free
957 };
958 free(obj as *mut c_void);
959
960 #[cfg(Py_3_8)]
961 if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
962 ffi::Py_DECREF(ty as *mut ffi::PyObject);
963 }
964 }
965 }
966}
967
968#[inline]
970#[cfg(not(Py_3_8))]
971unsafe fn bpo_35810_workaround(py: Python<'_>, ty: *mut ffi::PyTypeObject) {
972 #[cfg(Py_LIMITED_API)]
973 {
974 use crate::sync::PyOnceLock;
977 static IS_PYTHON_3_8: PyOnceLock<bool> = PyOnceLock::new();
978
979 if *IS_PYTHON_3_8.get_or_init(py, || py.version_info() >= (3, 8)) {
980 return;
982 }
983 }
984 #[cfg(not(Py_LIMITED_API))]
985 {
986 let _ = py;
988 }
989
990 unsafe { ffi::Py_INCREF(ty as *mut ffi::PyObject) };
991}
992
993#[cfg(feature = "multiple-pymethods")]
999pub trait PyClassInventory: inventory::Collect {
1000 fn items(&'static self) -> &'static PyClassItems;
1002}
1003
1004#[cfg(not(feature = "multiple-pymethods"))]
1006pub trait PyMethods<T> {
1007 fn py_methods(self) -> &'static PyClassItems;
1008}
1009
1010#[cfg(not(feature = "multiple-pymethods"))]
1011impl<T> PyMethods<T> for &'_ PyClassImplCollector<T> {
1012 fn py_methods(self) -> &'static PyClassItems {
1013 &PyClassItems {
1014 methods: &[],
1015 slots: &[],
1016 }
1017 }
1018}
1019
1020#[doc(hidden)]
1023pub trait PyClassThreadChecker<T>: Sized + sealed::Sealed {
1024 fn ensure(&self);
1025 fn check(&self) -> bool;
1026 fn can_drop(&self, py: Python<'_>) -> bool;
1027 fn new() -> Self;
1028}
1029
1030#[doc(hidden)]
1032pub struct NoopThreadChecker;
1033
1034impl<T> PyClassThreadChecker<T> for NoopThreadChecker {
1035 fn ensure(&self) {}
1036 fn check(&self) -> bool {
1037 true
1038 }
1039 fn can_drop(&self, _py: Python<'_>) -> bool {
1040 true
1041 }
1042 #[inline]
1043 fn new() -> Self {
1044 NoopThreadChecker
1045 }
1046}
1047
1048#[doc(hidden)]
1051pub struct ThreadCheckerImpl(thread::ThreadId);
1052
1053impl ThreadCheckerImpl {
1054 fn ensure(&self, type_name: &'static str) {
1055 assert_eq!(
1056 thread::current().id(),
1057 self.0,
1058 "{type_name} is unsendable, but sent to another thread"
1059 );
1060 }
1061
1062 fn check(&self) -> bool {
1063 thread::current().id() == self.0
1064 }
1065
1066 fn can_drop(&self, py: Python<'_>, type_name: &'static str) -> bool {
1067 if thread::current().id() != self.0 {
1068 PyRuntimeError::new_err(format!(
1069 "{type_name} is unsendable, but is being dropped on another thread"
1070 ))
1071 .write_unraisable(py, None);
1072 return false;
1073 }
1074
1075 true
1076 }
1077}
1078
1079impl<T> PyClassThreadChecker<T> for ThreadCheckerImpl {
1080 fn ensure(&self) {
1081 self.ensure(std::any::type_name::<T>());
1082 }
1083 fn check(&self) -> bool {
1084 self.check()
1085 }
1086 fn can_drop(&self, py: Python<'_>) -> bool {
1087 self.can_drop(py, std::any::type_name::<T>())
1088 }
1089 fn new() -> Self {
1090 ThreadCheckerImpl(thread::current().id())
1091 }
1092}
1093
1094#[cfg_attr(
1096 Py_LIMITED_API,
1097 diagnostic::on_unimplemented(
1098 message = "pyclass `{Self}` cannot be subclassed",
1099 label = "required for `#[pyclass(extends={Self})]`",
1100 note = "`{Self}` must have `#[pyclass(subclass)]` to be eligible for subclassing",
1101 note = "with the `abi3` feature enabled, PyO3 does not support subclassing native types",
1102 )
1103)]
1104#[cfg_attr(
1105 not(Py_LIMITED_API),
1106 diagnostic::on_unimplemented(
1107 message = "pyclass `{Self}` cannot be subclassed",
1108 label = "required for `#[pyclass(extends={Self})]`",
1109 note = "`{Self}` must have `#[pyclass(subclass)]` to be eligible for subclassing",
1110 )
1111)]
1112pub trait PyClassBaseType: Sized {
1113 type LayoutAsBase: PyClassObjectBaseLayout<Self>;
1114 type BaseNativeType;
1115 type Initializer: PyObjectInit<Self>;
1116 type PyClassMutability: PyClassMutability;
1117 type Layout<T: PyClassImpl>;
1119}
1120
1121pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
1123 unsafe { crate::impl_::trampoline::dealloc(obj, <T as PyClassImpl>::Layout::tp_dealloc) }
1124}
1125
1126pub(crate) unsafe extern "C" fn tp_dealloc_with_gc<T: PyClass>(obj: *mut ffi::PyObject) {
1128 #[cfg(not(PyPy))]
1129 unsafe {
1130 ffi::PyObject_GC_UnTrack(obj.cast());
1131 }
1132 unsafe { crate::impl_::trampoline::dealloc(obj, <T as PyClassImpl>::Layout::tp_dealloc) }
1133}
1134
1135pub(crate) unsafe extern "C" fn get_sequence_item_from_mapping(
1136 obj: *mut ffi::PyObject,
1137 index: ffi::Py_ssize_t,
1138) -> *mut ffi::PyObject {
1139 let index = unsafe { ffi::PyLong_FromSsize_t(index) };
1140 if index.is_null() {
1141 return std::ptr::null_mut();
1142 }
1143 let result = unsafe { ffi::PyObject_GetItem(obj, index) };
1144 unsafe { ffi::Py_DECREF(index) };
1145 result
1146}
1147
1148pub(crate) unsafe extern "C" fn assign_sequence_item_from_mapping(
1149 obj: *mut ffi::PyObject,
1150 index: ffi::Py_ssize_t,
1151 value: *mut ffi::PyObject,
1152) -> c_int {
1153 unsafe {
1154 let index = ffi::PyLong_FromSsize_t(index);
1155 if index.is_null() {
1156 return -1;
1157 }
1158 let result = if value.is_null() {
1159 ffi::PyObject_DelItem(obj, index)
1160 } else {
1161 ffi::PyObject_SetItem(obj, index, value)
1162 };
1163 ffi::Py_DECREF(index);
1164 result
1165 }
1166}
1167
1168#[derive(Debug, Clone, Copy)]
1170pub enum PyObjectOffset {
1171 Absolute(ffi::Py_ssize_t),
1173 #[cfg(Py_3_12)]
1177 Relative(ffi::Py_ssize_t),
1178}
1179
1180impl std::ops::Add<usize> for PyObjectOffset {
1181 type Output = PyObjectOffset;
1182
1183 fn add(self, rhs: usize) -> Self::Output {
1184 #[allow(clippy::useless_conversion)]
1186 let rhs: ffi::Py_ssize_t = rhs.try_into().expect("offset should fit in Py_ssize_t");
1187
1188 match self {
1189 PyObjectOffset::Absolute(offset) => PyObjectOffset::Absolute(offset + rhs),
1190 #[cfg(Py_3_12)]
1191 PyObjectOffset::Relative(offset) => PyObjectOffset::Relative(offset + rhs),
1192 }
1193 }
1194}
1195
1196pub struct PyClassGetterGenerator<
1199 ClassT: PyClass,
1202 FieldT,
1203 const OFFSET: usize,
1204 const IS_PY_T: bool,
1207 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1208 const IMPLEMENTS_INTOPYOBJECT: bool,
1209>(PhantomData<(ClassT, FieldT)>);
1210
1211impl<
1212 ClassT: PyClass,
1213 FieldT,
1214 const OFFSET: usize,
1215 const IS_PY_T: bool,
1216 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1217 const IMPLEMENTS_INTOPYOBJECT: bool,
1218 >
1219 PyClassGetterGenerator<
1220 ClassT,
1221 FieldT,
1222 OFFSET,
1223 IS_PY_T,
1224 IMPLEMENTS_INTOPYOBJECT_REF,
1225 IMPLEMENTS_INTOPYOBJECT,
1226 >
1227{
1228 pub const unsafe fn new() -> Self {
1231 Self(PhantomData)
1232 }
1233}
1234
1235impl<
1236 ClassT: PyClass,
1237 U: PyTypeCheck,
1238 const OFFSET: usize,
1239 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1240 const IMPLEMENTS_INTOPYOBJECT: bool,
1241 >
1242 PyClassGetterGenerator<
1243 ClassT,
1244 Py<U>,
1245 OFFSET,
1246 true,
1247 IMPLEMENTS_INTOPYOBJECT_REF,
1248 IMPLEMENTS_INTOPYOBJECT,
1249 >
1250{
1251 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1257 use crate::pyclass::boolean_struct::private::Boolean;
1258 if ClassT::Frozen::VALUE {
1259 let (offset, flags) = match <ClassT as PyClassImpl>::Layout::CONTENTS_OFFSET {
1260 PyObjectOffset::Absolute(offset) => (offset, ffi::Py_READONLY),
1261 #[cfg(Py_3_12)]
1262 PyObjectOffset::Relative(offset) => {
1263 (offset, ffi::Py_READONLY | ffi::Py_RELATIVE_OFFSET)
1264 }
1265 };
1266
1267 PyMethodDefType::StructMember(ffi::PyMemberDef {
1268 name: name.as_ptr(),
1269 type_code: ffi::Py_T_OBJECT_EX,
1270 offset: offset + OFFSET as ffi::Py_ssize_t,
1271 flags,
1272 doc: doc.as_ptr(),
1273 })
1274 } else {
1275 PyMethodDefType::Getter(PyGetterDef {
1276 name,
1277 meth: pyo3_get_value_into_pyobject_ref::<ClassT, Py<U>, OFFSET>,
1278 doc,
1279 })
1280 }
1281 }
1282}
1283
1284impl<ClassT, FieldT, const OFFSET: usize, const IMPLEMENTS_INTOPYOBJECT: bool>
1287 PyClassGetterGenerator<ClassT, FieldT, OFFSET, false, true, IMPLEMENTS_INTOPYOBJECT>
1288where
1289 ClassT: PyClass,
1290 for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1291{
1292 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1293 PyMethodDefType::Getter(PyGetterDef {
1294 name,
1295 meth: pyo3_get_value_into_pyobject_ref::<ClassT, FieldT, OFFSET>,
1296 doc,
1297 })
1298 }
1299}
1300
1301#[diagnostic::on_unimplemented(
1302 message = "`{Self}` cannot be converted to a Python object",
1303 label = "required by `#[pyo3(get)]` to create a readable property from a field of type `{Self}`",
1304 note = "implement `IntoPyObject` for `&{Self}` or `IntoPyObject + Clone` for `{Self}` to define the conversion"
1305)]
1306pub trait PyO3GetField<'py>: IntoPyObject<'py> + Clone {}
1307impl<'py, T> PyO3GetField<'py> for T where T: IntoPyObject<'py> + Clone {}
1308
1309impl<ClassT: PyClass, FieldT, const OFFSET: usize, const IMPLEMENTS_INTOPYOBJECT: bool>
1311 PyClassGetterGenerator<ClassT, FieldT, OFFSET, false, false, IMPLEMENTS_INTOPYOBJECT>
1312{
1313 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType
1314 where
1317 for<'py> FieldT: PyO3GetField<'py>,
1318 {
1319 PyMethodDefType::Getter(PyGetterDef {
1320 name,
1321 meth: pyo3_get_value_into_pyobject::<ClassT, FieldT, OFFSET>,
1322 doc,
1323 })
1324 }
1325}
1326
1327#[inline]
1329unsafe fn ensure_no_mutable_alias<'a, ClassT: PyClass>(
1330 _py: Python<'_>,
1331 obj: &'a *mut ffi::PyObject,
1332) -> Result<PyClassGuard<'a, ClassT>, PyBorrowError> {
1333 unsafe { PyClassGuard::try_borrow(NonNull::from(obj).cast::<Py<ClassT>>().as_ref()) }
1334}
1335
1336unsafe fn pyo3_get_value_into_pyobject_ref<ClassT, FieldT, const OFFSET: usize>(
1342 py: Python<'_>,
1343 obj: *mut ffi::PyObject,
1344) -> PyResult<*mut ffi::PyObject>
1345where
1346 ClassT: PyClass,
1347 for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1348{
1349 unsafe fn inner<FieldT>(
1355 py: Python<'_>,
1356 obj: *const (),
1357 offset: usize,
1358 ) -> PyResult<*mut ffi::PyObject>
1359 where
1360 for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1361 {
1362 let value = unsafe { &*obj.byte_add(offset).cast::<FieldT>() };
1364 value.into_py_any(py).map(Py::into_ptr)
1365 }
1366
1367 let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1369 let class_ptr = obj.cast::<<ClassT as PyClassImpl>::Layout>();
1370 let class_obj = unsafe { &*class_ptr };
1371 let contents_ptr = ptr::from_ref(class_obj.contents());
1372
1373 unsafe { inner::<FieldT>(py, contents_ptr.cast(), OFFSET) }
1375}
1376
1377unsafe fn pyo3_get_value_into_pyobject<ClassT, FieldT, const OFFSET: usize>(
1384 py: Python<'_>,
1385 obj: *mut ffi::PyObject,
1386) -> PyResult<*mut ffi::PyObject>
1387where
1388 ClassT: PyClass,
1389 for<'py> FieldT: IntoPyObject<'py> + Clone,
1390{
1391 unsafe fn inner<FieldT>(
1397 py: Python<'_>,
1398 obj: *const (),
1399 offset: usize,
1400 ) -> PyResult<*mut ffi::PyObject>
1401 where
1402 for<'py> FieldT: IntoPyObject<'py> + Clone,
1403 {
1404 let value = unsafe { &*obj.byte_add(offset).cast::<FieldT>() };
1406 value.clone().into_py_any(py).map(Py::into_ptr)
1407 }
1408
1409 let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1411 let class_ptr = obj.cast::<<ClassT as PyClassImpl>::Layout>();
1412 let class_obj = unsafe { &*class_ptr };
1413 let contents_ptr = ptr::from_ref(class_obj.contents());
1414
1415 unsafe { inner::<FieldT>(py, contents_ptr.cast(), OFFSET) }
1417}
1418
1419pub struct ConvertField<
1420 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1421 const IMPLEMENTS_INTOPYOBJECT: bool,
1422>;
1423
1424impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<true, IMPLEMENTS_INTOPYOBJECT> {
1425 #[inline]
1426 pub fn convert_field<'a, 'py, T>(obj: &'a T, py: Python<'py>) -> PyResult<Py<PyAny>>
1427 where
1428 &'a T: IntoPyObject<'py>,
1429 {
1430 obj.into_py_any(py)
1431 }
1432}
1433
1434impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<false, IMPLEMENTS_INTOPYOBJECT> {
1435 #[inline]
1436 pub fn convert_field<'py, T>(obj: &T, py: Python<'py>) -> PyResult<Py<PyAny>>
1437 where
1438 T: PyO3GetField<'py>,
1439 {
1440 obj.clone().into_py_any(py)
1441 }
1442}
1443
1444pub trait ExtractPyClassWithClone {}
1445
1446#[cfg(test)]
1447#[cfg(feature = "macros")]
1448mod tests {
1449 use crate::pycell::impl_::PyClassObjectContents;
1450
1451 use super::*;
1452 use std::mem::offset_of;
1453
1454 #[test]
1455 fn get_py_for_frozen_class() {
1456 #[crate::pyclass(crate = "crate", frozen)]
1457 struct FrozenClass {
1458 #[pyo3(get)]
1459 value: Py<PyAny>,
1460 }
1461
1462 let mut methods = Vec::new();
1463 let mut slots = Vec::new();
1464
1465 for items in FrozenClass::items_iter() {
1466 methods.extend_from_slice(items.methods);
1467 slots.extend_from_slice(items.slots);
1468 }
1469
1470 assert_eq!(methods.len(), 1);
1471 assert!(slots.is_empty());
1472
1473 match methods.first() {
1474 Some(PyMethodDefType::StructMember(member)) => {
1475 assert_eq!(unsafe { CStr::from_ptr(member.name) }, c"value");
1476 assert_eq!(member.type_code, ffi::Py_T_OBJECT_EX);
1477 #[repr(C)]
1478 struct ExpectedLayout {
1479 ob_base: ffi::PyObject,
1480 contents: PyClassObjectContents<FrozenClass>,
1481 }
1482 assert_eq!(
1483 member.offset,
1484 (offset_of!(ExpectedLayout, contents) + offset_of!(FrozenClass, value))
1485 as ffi::Py_ssize_t
1486 );
1487 assert_eq!(member.flags, ffi::Py_READONLY);
1488 }
1489 _ => panic!("Expected a StructMember"),
1490 }
1491 }
1492
1493 #[test]
1494 fn get_py_for_non_frozen_class() {
1495 #[crate::pyclass(crate = "crate")]
1496 struct FrozenClass {
1497 #[pyo3(get)]
1498 value: Py<PyAny>,
1499 }
1500
1501 let mut methods = Vec::new();
1502 let mut slots = Vec::new();
1503
1504 for items in FrozenClass::items_iter() {
1505 methods.extend_from_slice(items.methods);
1506 slots.extend_from_slice(items.slots);
1507 }
1508
1509 assert_eq!(methods.len(), 1);
1510 assert!(slots.is_empty());
1511
1512 match methods.first() {
1513 Some(PyMethodDefType::Getter(getter)) => {
1514 assert_eq!(getter.name, c"value");
1515 assert_eq!(getter.doc, c"");
1516 }
1518 _ => panic!("Expected a StructMember"),
1519 }
1520 }
1521
1522 #[test]
1523 fn test_field_getter_generator() {
1524 #[crate::pyclass(crate = "crate")]
1525 struct MyClass {
1526 my_field: i32,
1527 }
1528
1529 const FIELD_OFFSET: usize = offset_of!(MyClass, my_field);
1530
1531 let generator = unsafe {
1534 PyClassGetterGenerator::<MyClass, i32, FIELD_OFFSET, false, true, false>::new()
1535 };
1536 let PyMethodDefType::Getter(def) = generator.generate(c"my_field", c"My field doc") else {
1537 panic!("Expected a Getter");
1538 };
1539
1540 assert_eq!(def.name, c"my_field");
1541 assert_eq!(def.doc, c"My field doc");
1542
1543 #[cfg(fn_ptr_eq)]
1544 {
1545 use crate::impl_::pymethods::Getter;
1546
1547 assert!(std::ptr::fn_addr_eq(
1548 def.meth,
1549 pyo3_get_value_into_pyobject_ref::<MyClass, i32, FIELD_OFFSET> as Getter
1550 ));
1551 }
1552
1553 let generator = unsafe {
1556 PyClassGetterGenerator::<MyClass, String, FIELD_OFFSET, false, false, true>::new()
1557 };
1558 let PyMethodDefType::Getter(def) = generator.generate(c"my_field", c"My field doc") else {
1559 panic!("Expected a Getter");
1560 };
1561 assert_eq!(def.name, c"my_field");
1562 assert_eq!(def.doc, c"My field doc");
1563
1564 #[cfg(fn_ptr_eq)]
1565 {
1566 use crate::impl_::pymethods::Getter;
1567
1568 assert!(std::ptr::fn_addr_eq(
1569 def.meth,
1570 pyo3_get_value_into_pyobject::<MyClass, String, FIELD_OFFSET> as Getter
1571 ));
1572 }
1573 }
1574
1575 #[test]
1576 fn test_field_getter_generator_py_field_frozen() {
1577 #[crate::pyclass(crate = "crate", frozen)]
1578 struct MyClass {
1579 my_field: Py<PyAny>,
1580 }
1581
1582 const FIELD_OFFSET: usize = offset_of!(MyClass, my_field);
1583 let generator = unsafe {
1585 PyClassGetterGenerator::<MyClass, Py<PyAny>, FIELD_OFFSET, true, true, true>::new()
1586 };
1587 let PyMethodDefType::StructMember(def) = generator.generate(c"my_field", c"My field doc")
1588 else {
1589 panic!("Expected a StructMember");
1590 };
1591 assert_eq!(unsafe { CStr::from_ptr(def.name) }, c"my_field");
1593 assert_eq!(unsafe { CStr::from_ptr(def.doc) }, c"My field doc");
1595 assert_eq!(def.type_code, ffi::Py_T_OBJECT_EX);
1596 #[allow(irrefutable_let_patterns)]
1597 let PyObjectOffset::Absolute(contents_offset) =
1598 <MyClass as PyClassImpl>::Layout::CONTENTS_OFFSET
1599 else {
1600 panic!()
1601 };
1602 assert_eq!(
1603 def.offset,
1604 contents_offset + FIELD_OFFSET as ffi::Py_ssize_t
1605 );
1606 assert_eq!(def.flags, ffi::Py_READONLY);
1607 }
1608
1609 #[test]
1610 fn test_field_getter_generator_py_field_non_frozen() {
1611 #[crate::pyclass(crate = "crate")]
1612 struct MyClass {
1613 my_field: Py<PyAny>,
1614 }
1615
1616 const FIELD_OFFSET: usize = offset_of!(MyClass, my_field);
1617 let generator = unsafe {
1619 PyClassGetterGenerator::<MyClass, Py<PyAny>, FIELD_OFFSET, true, true, true>::new()
1620 };
1621 let PyMethodDefType::Getter(def) = generator.generate(c"my_field", c"My field doc") else {
1622 panic!("Expected a Getter");
1623 };
1624 assert_eq!(def.name, c"my_field");
1625 assert_eq!(def.doc, c"My field doc");
1626
1627 #[cfg(fn_ptr_eq)]
1628 {
1629 use crate::impl_::pymethods::Getter;
1630
1631 assert!(std::ptr::fn_addr_eq(
1632 def.meth,
1633 pyo3_get_value_into_pyobject_ref::<MyClass, Py<PyAny>, FIELD_OFFSET> as Getter
1634 ));
1635 }
1636 }
1637}