1use crate::{
2 exceptions::{PyAttributeError, PyNotImplementedError, PyRuntimeError, PyValueError},
3 ffi,
4 impl_::{
5 freelist::PyObjectFreeList,
6 pycell::{GetBorrowChecker, PyClassMutability, PyClassObjectLayout},
7 pyclass_init::PyObjectInit,
8 pymethods::{PyGetterDef, PyMethodDefType},
9 },
10 pycell::PyBorrowError,
11 types::{any::PyAnyMethods, PyBool},
12 Borrowed, BoundObject, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyClass, PyErr, PyRef,
13 PyResult, PyTypeInfo, Python,
14};
15#[allow(deprecated)]
16use crate::{IntoPy, ToPyObject};
17use std::{
18 borrow::Cow,
19 ffi::{CStr, CString},
20 marker::PhantomData,
21 os::raw::{c_int, c_void},
22 ptr::NonNull,
23 sync::Mutex,
24 thread,
25};
26
27mod assertions;
28mod lazy_type_object;
29mod probes;
30
31pub use assertions::*;
32pub use lazy_type_object::LazyTypeObject;
33pub use probes::*;
34
35#[inline]
37pub fn dict_offset<T: PyClass>() -> ffi::Py_ssize_t {
38 PyClassObject::<T>::dict_offset()
39}
40
41#[inline]
43pub fn weaklist_offset<T: PyClass>() -> ffi::Py_ssize_t {
44 PyClassObject::<T>::weaklist_offset()
45}
46
47mod sealed {
48 pub trait Sealed {}
49
50 impl Sealed for super::PyClassDummySlot {}
51 impl Sealed for super::PyClassDictSlot {}
52 impl Sealed for super::PyClassWeakRefSlot {}
53 impl Sealed for super::ThreadCheckerImpl {}
54 impl<T: Send> Sealed for super::SendablePyClass<T> {}
55}
56
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)]
94#[allow(dead_code)] pub struct PyClassDictSlot(*mut ffi::PyObject);
96
97impl PyClassDict for PyClassDictSlot {
98 const INIT: Self = Self(std::ptr::null_mut());
99 #[inline]
100 fn clear_dict(&mut self, _py: Python<'_>) {
101 if !self.0.is_null() {
102 unsafe { ffi::PyDict_Clear(self.0) }
103 }
104 }
105}
106
107#[repr(transparent)]
111#[allow(dead_code)] pub struct PyClassWeakRefSlot(*mut ffi::PyObject);
113
114impl PyClassWeakRef for PyClassWeakRefSlot {
115 const INIT: Self = Self(std::ptr::null_mut());
116 #[inline]
117 unsafe fn clear_weakrefs(&mut self, obj: *mut ffi::PyObject, _py: Python<'_>) {
118 if !self.0.is_null() {
119 ffi::PyObject_ClearWeakRefs(obj)
120 }
121 }
122}
123
124pub struct PyClassImplCollector<T>(PhantomData<T>);
127
128impl<T> PyClassImplCollector<T> {
129 pub fn new() -> Self {
130 Self(PhantomData)
131 }
132}
133
134impl<T> Default for PyClassImplCollector<T> {
135 fn default() -> Self {
136 Self::new()
137 }
138}
139
140impl<T> Clone for PyClassImplCollector<T> {
141 fn clone(&self) -> Self {
142 *self
143 }
144}
145
146impl<T> Copy for PyClassImplCollector<T> {}
147
148pub enum MaybeRuntimePyMethodDef {
149 Runtime(fn() -> PyMethodDefType),
152 Static(PyMethodDefType),
153}
154
155pub struct PyClassItems {
156 pub methods: &'static [MaybeRuntimePyMethodDef],
157 pub slots: &'static [ffi::PyType_Slot],
158}
159
160unsafe impl Sync for PyClassItems {}
162
163pub trait PyClassImpl: Sized + 'static {
168 const IS_BASETYPE: bool = false;
170
171 const IS_SUBCLASS: bool = false;
173
174 const IS_MAPPING: bool = false;
176
177 const IS_SEQUENCE: bool = false;
179
180 type BaseType: PyTypeInfo + PyClassBaseType;
182
183 type PyClassMutability: PyClassMutability + GetBorrowChecker<Self>;
185
186 type Dict: PyClassDict;
188
189 type WeakRef: PyClassWeakRef;
191
192 type BaseNativeType: PyTypeInfo;
195
196 type ThreadChecker: PyClassThreadChecker<Self>;
204
205 #[cfg(feature = "multiple-pymethods")]
206 type Inventory: PyClassInventory;
207
208 fn doc(py: Python<'_>) -> PyResult<&'static CStr>;
210
211 fn items_iter() -> PyClassItemsIter;
212
213 #[inline]
214 fn dict_offset() -> Option<ffi::Py_ssize_t> {
215 None
216 }
217
218 #[inline]
219 fn weaklist_offset() -> Option<ffi::Py_ssize_t> {
220 None
221 }
222
223 fn lazy_type_object() -> &'static LazyTypeObject<Self>;
224}
225
226pub fn build_pyclass_doc(
231 class_name: &'static str,
232 doc: &'static CStr,
233 text_signature: Option<&'static str>,
234) -> PyResult<Cow<'static, CStr>> {
235 if let Some(text_signature) = text_signature {
236 let doc = CString::new(format!(
237 "{}{}\n--\n\n{}",
238 class_name,
239 text_signature,
240 doc.to_str().unwrap(),
241 ))
242 .map_err(|_| PyValueError::new_err("class doc cannot contain nul bytes"))?;
243 Ok(Cow::Owned(doc))
244 } else {
245 Ok(Cow::Borrowed(doc))
246 }
247}
248
249pub struct PyClassItemsIter {
251 idx: usize,
253 pyclass_items: &'static PyClassItems,
255 #[cfg(not(feature = "multiple-pymethods"))]
257 pymethods_items: &'static PyClassItems,
258 #[cfg(feature = "multiple-pymethods")]
260 pymethods_items: Box<dyn Iterator<Item = &'static PyClassItems>>,
261}
262
263impl PyClassItemsIter {
264 pub fn new(
265 pyclass_items: &'static PyClassItems,
266 #[cfg(not(feature = "multiple-pymethods"))] pymethods_items: &'static PyClassItems,
267 #[cfg(feature = "multiple-pymethods")] pymethods_items: Box<
268 dyn Iterator<Item = &'static PyClassItems>,
269 >,
270 ) -> Self {
271 Self {
272 idx: 0,
273 pyclass_items,
274 pymethods_items,
275 }
276 }
277}
278
279impl Iterator for PyClassItemsIter {
280 type Item = &'static PyClassItems;
281
282 #[cfg(not(feature = "multiple-pymethods"))]
283 fn next(&mut self) -> Option<Self::Item> {
284 match self.idx {
285 0 => {
286 self.idx += 1;
287 Some(self.pyclass_items)
288 }
289 1 => {
290 self.idx += 1;
291 Some(self.pymethods_items)
292 }
293 _ => None,
295 }
296 }
297
298 #[cfg(feature = "multiple-pymethods")]
299 fn next(&mut self) -> Option<Self::Item> {
300 match self.idx {
301 0 => {
302 self.idx += 1;
303 Some(self.pyclass_items)
304 }
305 _ => self.pymethods_items.next(),
307 }
308 }
309}
310
311macro_rules! slot_fragment_trait {
314 ($trait_name:ident, $($default_method:tt)*) => {
315 #[allow(non_camel_case_types)]
316 pub trait $trait_name<T>: Sized {
317 $($default_method)*
318 }
319
320 impl<T> $trait_name<T> for &'_ PyClassImplCollector<T> {}
321 }
322}
323
324slot_fragment_trait! {
325 PyClass__getattribute__SlotFragment,
326
327 #[inline]
329 unsafe fn __getattribute__(
330 self,
331 py: Python<'_>,
332 slf: *mut ffi::PyObject,
333 attr: *mut ffi::PyObject,
334 ) -> PyResult<*mut ffi::PyObject> {
335 let res = ffi::PyObject_GenericGetAttr(slf, attr);
336 if res.is_null() {
337 Err(PyErr::fetch(py))
338 } else {
339 Ok(res)
340 }
341 }
342}
343
344slot_fragment_trait! {
345 PyClass__getattr__SlotFragment,
346
347 #[inline]
349 unsafe fn __getattr__(
350 self,
351 py: Python<'_>,
352 _slf: *mut ffi::PyObject,
353 attr: *mut ffi::PyObject,
354 ) -> PyResult<*mut ffi::PyObject> {
355 Err(PyErr::new::<PyAttributeError, _>(
356 (Py::<PyAny>::from_borrowed_ptr(py, attr),)
357 ))
358 }
359}
360
361#[doc(hidden)]
362#[macro_export]
363macro_rules! generate_pyclass_getattro_slot {
364 ($cls:ty) => {{
365 unsafe extern "C" fn __wrap(
366 _slf: *mut $crate::ffi::PyObject,
367 attr: *mut $crate::ffi::PyObject,
368 ) -> *mut $crate::ffi::PyObject {
369 $crate::impl_::trampoline::getattrofunc(_slf, attr, |py, _slf, attr| {
370 use ::std::result::Result::*;
371 use $crate::impl_::pyclass::*;
372 let collector = PyClassImplCollector::<$cls>::new();
373
374 match collector.__getattribute__(py, _slf, attr) {
380 Ok(obj) => Ok(obj),
381 Err(e) if e.is_instance_of::<$crate::exceptions::PyAttributeError>(py) => {
382 collector.__getattr__(py, _slf, attr)
383 }
384 Err(e) => Err(e),
385 }
386 })
387 }
388 $crate::ffi::PyType_Slot {
389 slot: $crate::ffi::Py_tp_getattro,
390 pfunc: __wrap as $crate::ffi::getattrofunc as _,
391 }
392 }};
393}
394
395pub use generate_pyclass_getattro_slot;
396
397macro_rules! define_pyclass_setattr_slot {
402 (
403 $set_trait:ident,
404 $del_trait:ident,
405 $set:ident,
406 $del:ident,
407 $set_error:expr,
408 $del_error:expr,
409 $generate_macro:ident,
410 $slot:ident,
411 $func_ty:ident,
412 ) => {
413 slot_fragment_trait! {
414 $set_trait,
415
416 #[inline]
418 unsafe fn $set(
419 self,
420 _py: Python<'_>,
421 _slf: *mut ffi::PyObject,
422 _attr: *mut ffi::PyObject,
423 _value: NonNull<ffi::PyObject>,
424 ) -> PyResult<()> {
425 $set_error
426 }
427 }
428
429 slot_fragment_trait! {
430 $del_trait,
431
432 #[inline]
434 unsafe fn $del(
435 self,
436 _py: Python<'_>,
437 _slf: *mut ffi::PyObject,
438 _attr: *mut ffi::PyObject,
439 ) -> PyResult<()> {
440 $del_error
441 }
442 }
443
444 #[doc(hidden)]
445 #[macro_export]
446 macro_rules! $generate_macro {
447 ($cls:ty) => {{
448 unsafe extern "C" fn __wrap(
449 _slf: *mut $crate::ffi::PyObject,
450 attr: *mut $crate::ffi::PyObject,
451 value: *mut $crate::ffi::PyObject,
452 ) -> ::std::os::raw::c_int {
453 $crate::impl_::trampoline::setattrofunc(
454 _slf,
455 attr,
456 value,
457 |py, _slf, attr, value| {
458 use ::std::option::Option::*;
459 use $crate::impl_::callback::IntoPyCallbackOutput;
460 use $crate::impl_::pyclass::*;
461 let collector = PyClassImplCollector::<$cls>::new();
462 if let Some(value) = ::std::ptr::NonNull::new(value) {
463 collector.$set(py, _slf, attr, value).convert(py)
464 } else {
465 collector.$del(py, _slf, attr).convert(py)
466 }
467 },
468 )
469 }
470 $crate::ffi::PyType_Slot {
471 slot: $crate::ffi::$slot,
472 pfunc: __wrap as $crate::ffi::$func_ty as _,
473 }
474 }};
475 }
476 pub use $generate_macro;
477 };
478}
479
480define_pyclass_setattr_slot! {
481 PyClass__setattr__SlotFragment,
482 PyClass__delattr__SlotFragment,
483 __setattr__,
484 __delattr__,
485 Err(PyAttributeError::new_err("can't set attribute")),
486 Err(PyAttributeError::new_err("can't delete attribute")),
487 generate_pyclass_setattr_slot,
488 Py_tp_setattro,
489 setattrofunc,
490}
491
492define_pyclass_setattr_slot! {
493 PyClass__set__SlotFragment,
494 PyClass__delete__SlotFragment,
495 __set__,
496 __delete__,
497 Err(PyNotImplementedError::new_err("can't set descriptor")),
498 Err(PyNotImplementedError::new_err("can't delete descriptor")),
499 generate_pyclass_setdescr_slot,
500 Py_tp_descr_set,
501 descrsetfunc,
502}
503
504define_pyclass_setattr_slot! {
505 PyClass__setitem__SlotFragment,
506 PyClass__delitem__SlotFragment,
507 __setitem__,
508 __delitem__,
509 Err(PyNotImplementedError::new_err("can't set item")),
510 Err(PyNotImplementedError::new_err("can't delete item")),
511 generate_pyclass_setitem_slot,
512 Py_mp_ass_subscript,
513 objobjargproc,
514}
515
516macro_rules! define_pyclass_binary_operator_slot {
521 (
522 $lhs_trait:ident,
523 $rhs_trait:ident,
524 $lhs:ident,
525 $rhs:ident,
526 $generate_macro:ident,
527 $slot:ident,
528 $func_ty:ident,
529 ) => {
530 slot_fragment_trait! {
531 $lhs_trait,
532
533 #[inline]
535 unsafe fn $lhs(
536 self,
537 py: Python<'_>,
538 _slf: *mut ffi::PyObject,
539 _other: *mut ffi::PyObject,
540 ) -> PyResult<*mut ffi::PyObject> {
541 Ok(py.NotImplemented().into_ptr())
542 }
543 }
544
545 slot_fragment_trait! {
546 $rhs_trait,
547
548 #[inline]
550 unsafe fn $rhs(
551 self,
552 py: Python<'_>,
553 _slf: *mut ffi::PyObject,
554 _other: *mut ffi::PyObject,
555 ) -> PyResult<*mut ffi::PyObject> {
556 Ok(py.NotImplemented().into_ptr())
557 }
558 }
559
560 #[doc(hidden)]
561 #[macro_export]
562 macro_rules! $generate_macro {
563 ($cls:ty) => {{
564 unsafe extern "C" fn __wrap(
565 _slf: *mut $crate::ffi::PyObject,
566 _other: *mut $crate::ffi::PyObject,
567 ) -> *mut $crate::ffi::PyObject {
568 $crate::impl_::trampoline::binaryfunc(_slf, _other, |py, _slf, _other| {
569 use $crate::impl_::pyclass::*;
570 let collector = PyClassImplCollector::<$cls>::new();
571 let lhs_result = collector.$lhs(py, _slf, _other)?;
572 if lhs_result == $crate::ffi::Py_NotImplemented() {
573 $crate::ffi::Py_DECREF(lhs_result);
574 collector.$rhs(py, _other, _slf)
575 } else {
576 ::std::result::Result::Ok(lhs_result)
577 }
578 })
579 }
580 $crate::ffi::PyType_Slot {
581 slot: $crate::ffi::$slot,
582 pfunc: __wrap as $crate::ffi::$func_ty as _,
583 }
584 }};
585 }
586 pub use $generate_macro;
587 };
588}
589
590define_pyclass_binary_operator_slot! {
591 PyClass__add__SlotFragment,
592 PyClass__radd__SlotFragment,
593 __add__,
594 __radd__,
595 generate_pyclass_add_slot,
596 Py_nb_add,
597 binaryfunc,
598}
599
600define_pyclass_binary_operator_slot! {
601 PyClass__sub__SlotFragment,
602 PyClass__rsub__SlotFragment,
603 __sub__,
604 __rsub__,
605 generate_pyclass_sub_slot,
606 Py_nb_subtract,
607 binaryfunc,
608}
609
610define_pyclass_binary_operator_slot! {
611 PyClass__mul__SlotFragment,
612 PyClass__rmul__SlotFragment,
613 __mul__,
614 __rmul__,
615 generate_pyclass_mul_slot,
616 Py_nb_multiply,
617 binaryfunc,
618}
619
620define_pyclass_binary_operator_slot! {
621 PyClass__mod__SlotFragment,
622 PyClass__rmod__SlotFragment,
623 __mod__,
624 __rmod__,
625 generate_pyclass_mod_slot,
626 Py_nb_remainder,
627 binaryfunc,
628}
629
630define_pyclass_binary_operator_slot! {
631 PyClass__divmod__SlotFragment,
632 PyClass__rdivmod__SlotFragment,
633 __divmod__,
634 __rdivmod__,
635 generate_pyclass_divmod_slot,
636 Py_nb_divmod,
637 binaryfunc,
638}
639
640define_pyclass_binary_operator_slot! {
641 PyClass__lshift__SlotFragment,
642 PyClass__rlshift__SlotFragment,
643 __lshift__,
644 __rlshift__,
645 generate_pyclass_lshift_slot,
646 Py_nb_lshift,
647 binaryfunc,
648}
649
650define_pyclass_binary_operator_slot! {
651 PyClass__rshift__SlotFragment,
652 PyClass__rrshift__SlotFragment,
653 __rshift__,
654 __rrshift__,
655 generate_pyclass_rshift_slot,
656 Py_nb_rshift,
657 binaryfunc,
658}
659
660define_pyclass_binary_operator_slot! {
661 PyClass__and__SlotFragment,
662 PyClass__rand__SlotFragment,
663 __and__,
664 __rand__,
665 generate_pyclass_and_slot,
666 Py_nb_and,
667 binaryfunc,
668}
669
670define_pyclass_binary_operator_slot! {
671 PyClass__or__SlotFragment,
672 PyClass__ror__SlotFragment,
673 __or__,
674 __ror__,
675 generate_pyclass_or_slot,
676 Py_nb_or,
677 binaryfunc,
678}
679
680define_pyclass_binary_operator_slot! {
681 PyClass__xor__SlotFragment,
682 PyClass__rxor__SlotFragment,
683 __xor__,
684 __rxor__,
685 generate_pyclass_xor_slot,
686 Py_nb_xor,
687 binaryfunc,
688}
689
690define_pyclass_binary_operator_slot! {
691 PyClass__matmul__SlotFragment,
692 PyClass__rmatmul__SlotFragment,
693 __matmul__,
694 __rmatmul__,
695 generate_pyclass_matmul_slot,
696 Py_nb_matrix_multiply,
697 binaryfunc,
698}
699
700define_pyclass_binary_operator_slot! {
701 PyClass__truediv__SlotFragment,
702 PyClass__rtruediv__SlotFragment,
703 __truediv__,
704 __rtruediv__,
705 generate_pyclass_truediv_slot,
706 Py_nb_true_divide,
707 binaryfunc,
708}
709
710define_pyclass_binary_operator_slot! {
711 PyClass__floordiv__SlotFragment,
712 PyClass__rfloordiv__SlotFragment,
713 __floordiv__,
714 __rfloordiv__,
715 generate_pyclass_floordiv_slot,
716 Py_nb_floor_divide,
717 binaryfunc,
718}
719
720slot_fragment_trait! {
721 PyClass__pow__SlotFragment,
722
723 #[inline]
725 unsafe fn __pow__(
726 self,
727 py: Python<'_>,
728 _slf: *mut ffi::PyObject,
729 _other: *mut ffi::PyObject,
730 _mod: *mut ffi::PyObject,
731 ) -> PyResult<*mut ffi::PyObject> {
732 Ok(py.NotImplemented().into_ptr())
733 }
734}
735
736slot_fragment_trait! {
737 PyClass__rpow__SlotFragment,
738
739 #[inline]
741 unsafe fn __rpow__(
742 self,
743 py: Python<'_>,
744 _slf: *mut ffi::PyObject,
745 _other: *mut ffi::PyObject,
746 _mod: *mut ffi::PyObject,
747 ) -> PyResult<*mut ffi::PyObject> {
748 Ok(py.NotImplemented().into_ptr())
749 }
750}
751
752#[doc(hidden)]
753#[macro_export]
754macro_rules! generate_pyclass_pow_slot {
755 ($cls:ty) => {{
756 unsafe extern "C" fn __wrap(
757 _slf: *mut $crate::ffi::PyObject,
758 _other: *mut $crate::ffi::PyObject,
759 _mod: *mut $crate::ffi::PyObject,
760 ) -> *mut $crate::ffi::PyObject {
761 $crate::impl_::trampoline::ternaryfunc(_slf, _other, _mod, |py, _slf, _other, _mod| {
762 use $crate::impl_::pyclass::*;
763 let collector = PyClassImplCollector::<$cls>::new();
764 let lhs_result = collector.__pow__(py, _slf, _other, _mod)?;
765 if lhs_result == $crate::ffi::Py_NotImplemented() {
766 $crate::ffi::Py_DECREF(lhs_result);
767 collector.__rpow__(py, _other, _slf, _mod)
768 } else {
769 ::std::result::Result::Ok(lhs_result)
770 }
771 })
772 }
773 $crate::ffi::PyType_Slot {
774 slot: $crate::ffi::Py_nb_power,
775 pfunc: __wrap as $crate::ffi::ternaryfunc as _,
776 }
777 }};
778}
779pub use generate_pyclass_pow_slot;
780
781slot_fragment_trait! {
782 PyClass__lt__SlotFragment,
783
784 #[inline]
786 unsafe fn __lt__(
787 self,
788 py: Python<'_>,
789 _slf: *mut ffi::PyObject,
790 _other: *mut ffi::PyObject,
791 ) -> PyResult<*mut ffi::PyObject> {
792 Ok(py.NotImplemented().into_ptr())
793 }
794}
795
796slot_fragment_trait! {
797 PyClass__le__SlotFragment,
798
799 #[inline]
801 unsafe fn __le__(
802 self,
803 py: Python<'_>,
804 _slf: *mut ffi::PyObject,
805 _other: *mut ffi::PyObject,
806 ) -> PyResult<*mut ffi::PyObject> {
807 Ok(py.NotImplemented().into_ptr())
808 }
809}
810
811slot_fragment_trait! {
812 PyClass__eq__SlotFragment,
813
814 #[inline]
816 unsafe fn __eq__(
817 self,
818 py: Python<'_>,
819 _slf: *mut ffi::PyObject,
820 _other: *mut ffi::PyObject,
821 ) -> PyResult<*mut ffi::PyObject> {
822 Ok(py.NotImplemented().into_ptr())
823 }
824}
825
826slot_fragment_trait! {
827 PyClass__ne__SlotFragment,
828
829 #[inline]
831 unsafe fn __ne__(
832 self,
833 py: Python<'_>,
834 slf: *mut ffi::PyObject,
835 other: *mut ffi::PyObject,
836 ) -> PyResult<*mut ffi::PyObject> {
837 let slf = Borrowed::from_ptr(py, slf);
839 let other = Borrowed::from_ptr(py, other);
840 slf.eq(other).map(|is_eq| PyBool::new(py, !is_eq).to_owned().into_ptr())
841 }
842}
843
844slot_fragment_trait! {
845 PyClass__gt__SlotFragment,
846
847 #[inline]
849 unsafe fn __gt__(
850 self,
851 py: Python<'_>,
852 _slf: *mut ffi::PyObject,
853 _other: *mut ffi::PyObject,
854 ) -> PyResult<*mut ffi::PyObject> {
855 Ok(py.NotImplemented().into_ptr())
856 }
857}
858
859slot_fragment_trait! {
860 PyClass__ge__SlotFragment,
861
862 #[inline]
864 unsafe fn __ge__(
865 self,
866 py: Python<'_>,
867 _slf: *mut ffi::PyObject,
868 _other: *mut ffi::PyObject,
869 ) -> PyResult<*mut ffi::PyObject> {
870 Ok(py.NotImplemented().into_ptr())
871 }
872}
873
874#[doc(hidden)]
875#[macro_export]
876macro_rules! generate_pyclass_richcompare_slot {
877 ($cls:ty) => {{
878 #[allow(unknown_lints, non_local_definitions)]
879 impl $cls {
880 #[allow(non_snake_case)]
881 unsafe extern "C" fn __pymethod___richcmp____(
882 slf: *mut $crate::ffi::PyObject,
883 other: *mut $crate::ffi::PyObject,
884 op: ::std::os::raw::c_int,
885 ) -> *mut $crate::ffi::PyObject {
886 $crate::impl_::trampoline::richcmpfunc(slf, other, op, |py, slf, other, op| {
887 use $crate::class::basic::CompareOp;
888 use $crate::impl_::pyclass::*;
889 let collector = PyClassImplCollector::<$cls>::new();
890 match CompareOp::from_raw(op).expect("invalid compareop") {
891 CompareOp::Lt => collector.__lt__(py, slf, other),
892 CompareOp::Le => collector.__le__(py, slf, other),
893 CompareOp::Eq => collector.__eq__(py, slf, other),
894 CompareOp::Ne => collector.__ne__(py, slf, other),
895 CompareOp::Gt => collector.__gt__(py, slf, other),
896 CompareOp::Ge => collector.__ge__(py, slf, other),
897 }
898 })
899 }
900 }
901 $crate::ffi::PyType_Slot {
902 slot: $crate::ffi::Py_tp_richcompare,
903 pfunc: <$cls>::__pymethod___richcmp____ as $crate::ffi::richcmpfunc as _,
904 }
905 }};
906}
907pub use generate_pyclass_richcompare_slot;
908
909use super::{pycell::PyClassObject, pymethods::BoundRef};
910
911pub trait PyClassWithFreeList: PyClass {
916 fn get_free_list(py: Python<'_>) -> &'static Mutex<PyObjectFreeList>;
917}
918
919pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
925 subtype: *mut ffi::PyTypeObject,
926 nitems: ffi::Py_ssize_t,
927) -> *mut ffi::PyObject {
928 let py = Python::assume_gil_acquired();
929
930 #[cfg(not(Py_3_8))]
931 bpo_35810_workaround(py, subtype);
932
933 let self_type = T::type_object_raw(py);
934 if nitems == 0 && subtype == self_type {
937 let mut free_list = T::get_free_list(py).lock().unwrap();
938 if let Some(obj) = free_list.pop() {
939 drop(free_list);
940 ffi::PyObject_Init(obj, subtype);
941 return obj as _;
942 }
943 }
944
945 ffi::PyType_GenericAlloc(subtype, nitems)
946}
947
948pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
954 let obj = obj as *mut ffi::PyObject;
955 debug_assert_eq!(
956 T::type_object_raw(Python::assume_gil_acquired()),
957 ffi::Py_TYPE(obj)
958 );
959 let mut free_list = T::get_free_list(Python::assume_gil_acquired())
960 .lock()
961 .unwrap();
962 if let Some(obj) = free_list.insert(obj) {
963 drop(free_list);
964 let ty = ffi::Py_TYPE(obj);
965
966 let free = if ffi::PyType_IS_GC(ty) != 0 {
968 ffi::PyObject_GC_Del
969 } else {
970 ffi::PyObject_Free
971 };
972 free(obj as *mut c_void);
973
974 #[cfg(Py_3_8)]
975 if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
976 ffi::Py_DECREF(ty as *mut ffi::PyObject);
977 }
978 }
979}
980
981#[inline]
983#[cfg(not(Py_3_8))]
984unsafe fn bpo_35810_workaround(py: Python<'_>, ty: *mut ffi::PyTypeObject) {
985 #[cfg(Py_LIMITED_API)]
986 {
987 use crate::sync::GILOnceCell;
990 static IS_PYTHON_3_8: GILOnceCell<bool> = GILOnceCell::new();
991
992 if *IS_PYTHON_3_8.get_or_init(py, || py.version_info() >= (3, 8)) {
993 return;
995 }
996 }
997 #[cfg(not(Py_LIMITED_API))]
998 {
999 let _ = py;
1001 }
1002
1003 ffi::Py_INCREF(ty as *mut ffi::PyObject);
1004}
1005
1006#[cfg(feature = "multiple-pymethods")]
1012pub trait PyClassInventory: inventory::Collect {
1013 fn items(&'static self) -> &'static PyClassItems;
1015}
1016
1017#[cfg(not(feature = "multiple-pymethods"))]
1019pub trait PyMethods<T> {
1020 fn py_methods(self) -> &'static PyClassItems;
1021}
1022
1023#[cfg(not(feature = "multiple-pymethods"))]
1024impl<T> PyMethods<T> for &'_ PyClassImplCollector<T> {
1025 fn py_methods(self) -> &'static PyClassItems {
1026 &PyClassItems {
1027 methods: &[],
1028 slots: &[],
1029 }
1030 }
1031}
1032
1033pub trait PyClassNewTextSignature<T> {
1035 fn new_text_signature(self) -> Option<&'static str>;
1036}
1037
1038impl<T> PyClassNewTextSignature<T> for &'_ PyClassImplCollector<T> {
1039 #[inline]
1040 fn new_text_signature(self) -> Option<&'static str> {
1041 None
1042 }
1043}
1044
1045#[doc(hidden)]
1048pub trait PyClassThreadChecker<T>: Sized + sealed::Sealed {
1049 fn ensure(&self);
1050 fn check(&self) -> bool;
1051 fn can_drop(&self, py: Python<'_>) -> bool;
1052 fn new() -> Self;
1053}
1054
1055#[doc(hidden)]
1061pub struct SendablePyClass<T: Send>(PhantomData<T>);
1062
1063impl<T: Send> PyClassThreadChecker<T> for SendablePyClass<T> {
1064 fn ensure(&self) {}
1065 fn check(&self) -> bool {
1066 true
1067 }
1068 fn can_drop(&self, _py: Python<'_>) -> bool {
1069 true
1070 }
1071 #[inline]
1072 fn new() -> Self {
1073 SendablePyClass(PhantomData)
1074 }
1075}
1076
1077#[doc(hidden)]
1080pub struct ThreadCheckerImpl(thread::ThreadId);
1081
1082impl ThreadCheckerImpl {
1083 fn ensure(&self, type_name: &'static str) {
1084 assert_eq!(
1085 thread::current().id(),
1086 self.0,
1087 "{} is unsendable, but sent to another thread",
1088 type_name
1089 );
1090 }
1091
1092 fn check(&self) -> bool {
1093 thread::current().id() == self.0
1094 }
1095
1096 fn can_drop(&self, py: Python<'_>, type_name: &'static str) -> bool {
1097 if thread::current().id() != self.0 {
1098 PyRuntimeError::new_err(format!(
1099 "{} is unsendable, but is being dropped on another thread",
1100 type_name
1101 ))
1102 .write_unraisable(py, None);
1103 return false;
1104 }
1105
1106 true
1107 }
1108}
1109
1110impl<T> PyClassThreadChecker<T> for ThreadCheckerImpl {
1111 fn ensure(&self) {
1112 self.ensure(std::any::type_name::<T>());
1113 }
1114 fn check(&self) -> bool {
1115 self.check()
1116 }
1117 fn can_drop(&self, py: Python<'_>) -> bool {
1118 self.can_drop(py, std::any::type_name::<T>())
1119 }
1120 fn new() -> Self {
1121 ThreadCheckerImpl(thread::current().id())
1122 }
1123}
1124
1125#[cfg_attr(
1128 all(diagnostic_namespace, Py_LIMITED_API),
1129 diagnostic::on_unimplemented(
1130 message = "pyclass `{Self}` cannot be subclassed",
1131 label = "required for `#[pyclass(extends={Self})]`",
1132 note = "`{Self}` must have `#[pyclass(subclass)]` to be eligible for subclassing",
1133 note = "with the `abi3` feature enabled, PyO3 does not support subclassing native types",
1134 )
1135)]
1136#[cfg_attr(
1137 all(diagnostic_namespace, not(Py_LIMITED_API)),
1138 diagnostic::on_unimplemented(
1139 message = "pyclass `{Self}` cannot be subclassed",
1140 label = "required for `#[pyclass(extends={Self})]`",
1141 note = "`{Self}` must have `#[pyclass(subclass)]` to be eligible for subclassing",
1142 )
1143)]
1144pub trait PyClassBaseType: Sized {
1145 type LayoutAsBase: PyClassObjectLayout<Self>;
1146 type BaseNativeType;
1147 type Initializer: PyObjectInit<Self>;
1148 type PyClassMutability: PyClassMutability;
1149}
1150
1151pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
1153 crate::impl_::trampoline::dealloc(obj, PyClassObject::<T>::tp_dealloc)
1154}
1155
1156pub(crate) unsafe extern "C" fn tp_dealloc_with_gc<T: PyClass>(obj: *mut ffi::PyObject) {
1158 #[cfg(not(PyPy))]
1159 {
1160 ffi::PyObject_GC_UnTrack(obj.cast());
1161 }
1162 crate::impl_::trampoline::dealloc(obj, PyClassObject::<T>::tp_dealloc)
1163}
1164
1165pub(crate) unsafe extern "C" fn get_sequence_item_from_mapping(
1166 obj: *mut ffi::PyObject,
1167 index: ffi::Py_ssize_t,
1168) -> *mut ffi::PyObject {
1169 let index = ffi::PyLong_FromSsize_t(index);
1170 if index.is_null() {
1171 return std::ptr::null_mut();
1172 }
1173 let result = ffi::PyObject_GetItem(obj, index);
1174 ffi::Py_DECREF(index);
1175 result
1176}
1177
1178pub(crate) unsafe extern "C" fn assign_sequence_item_from_mapping(
1179 obj: *mut ffi::PyObject,
1180 index: ffi::Py_ssize_t,
1181 value: *mut ffi::PyObject,
1182) -> c_int {
1183 let index = ffi::PyLong_FromSsize_t(index);
1184 if index.is_null() {
1185 return -1;
1186 }
1187 let result = if value.is_null() {
1188 ffi::PyObject_DelItem(obj, index)
1189 } else {
1190 ffi::PyObject_SetItem(obj, index, value)
1191 };
1192 ffi::Py_DECREF(index);
1193 result
1194}
1195
1196pub unsafe trait OffsetCalculator<T: PyClass, U> {
1205 fn offset() -> usize;
1207}
1208
1209pub fn class_offset<T: PyClass>() -> usize {
1211 offset_of!(PyClassObject<T>, contents)
1212}
1213
1214pub use memoffset::offset_of;
1216
1217pub struct PyClassGetterGenerator<
1220 ClassT: PyClass,
1223 FieldT,
1224 Offset: OffsetCalculator<ClassT, FieldT>, const IS_PY_T: bool,
1228 const IMPLEMENTS_TOPYOBJECT: bool,
1229 const IMPLEMENTS_INTOPY: bool,
1230 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1231 const IMPLEMENTS_INTOPYOBJECT: bool,
1232>(PhantomData<(ClassT, FieldT, Offset)>);
1233
1234impl<
1235 ClassT: PyClass,
1236 FieldT,
1237 Offset: OffsetCalculator<ClassT, FieldT>,
1238 const IS_PY_T: bool,
1239 const IMPLEMENTS_TOPYOBJECT: bool,
1240 const IMPLEMENTS_INTOPY: bool,
1241 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1242 const IMPLEMENTS_INTOPYOBJECT: bool,
1243 >
1244 PyClassGetterGenerator<
1245 ClassT,
1246 FieldT,
1247 Offset,
1248 IS_PY_T,
1249 IMPLEMENTS_TOPYOBJECT,
1250 IMPLEMENTS_INTOPY,
1251 IMPLEMENTS_INTOPYOBJECT_REF,
1252 IMPLEMENTS_INTOPYOBJECT,
1253 >
1254{
1255 pub const unsafe fn new() -> Self {
1258 Self(PhantomData)
1259 }
1260}
1261
1262impl<
1263 ClassT: PyClass,
1264 U,
1265 Offset: OffsetCalculator<ClassT, Py<U>>,
1266 const IMPLEMENTS_TOPYOBJECT: bool,
1267 const IMPLEMENTS_INTOPY: bool,
1268 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1269 const IMPLEMENTS_INTOPYOBJECT: bool,
1270 >
1271 PyClassGetterGenerator<
1272 ClassT,
1273 Py<U>,
1274 Offset,
1275 true,
1276 IMPLEMENTS_TOPYOBJECT,
1277 IMPLEMENTS_INTOPY,
1278 IMPLEMENTS_INTOPYOBJECT_REF,
1279 IMPLEMENTS_INTOPYOBJECT,
1280 >
1281{
1282 pub fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1288 use crate::pyclass::boolean_struct::private::Boolean;
1289 if ClassT::Frozen::VALUE {
1290 PyMethodDefType::StructMember(ffi::PyMemberDef {
1291 name: name.as_ptr(),
1292 type_code: ffi::Py_T_OBJECT_EX,
1293 offset: Offset::offset() as ffi::Py_ssize_t,
1294 flags: ffi::Py_READONLY,
1295 doc: doc.as_ptr(),
1296 })
1297 } else {
1298 PyMethodDefType::Getter(PyGetterDef {
1299 name,
1300 meth: pyo3_get_value_topyobject::<ClassT, Py<U>, Offset>,
1301 doc,
1302 })
1303 }
1304 }
1305}
1306
1307#[allow(deprecated)]
1310impl<
1311 ClassT: PyClass,
1312 FieldT: ToPyObject,
1313 Offset: OffsetCalculator<ClassT, FieldT>,
1314 const IMPLEMENTS_INTOPY: bool,
1315 > PyClassGetterGenerator<ClassT, FieldT, Offset, false, true, IMPLEMENTS_INTOPY, false, false>
1316{
1317 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1318 PyMethodDefType::Getter(PyGetterDef {
1319 name,
1320 meth: pyo3_get_value_topyobject::<ClassT, FieldT, Offset>,
1321 doc,
1322 })
1323 }
1324}
1325
1326impl<
1329 ClassT,
1330 FieldT,
1331 Offset,
1332 const IMPLEMENTS_TOPYOBJECT: bool,
1333 const IMPLEMENTS_INTOPY: bool,
1334 const IMPLEMENTS_INTOPYOBJECT: bool,
1335 >
1336 PyClassGetterGenerator<
1337 ClassT,
1338 FieldT,
1339 Offset,
1340 false,
1341 IMPLEMENTS_TOPYOBJECT,
1342 IMPLEMENTS_INTOPY,
1343 true,
1344 IMPLEMENTS_INTOPYOBJECT,
1345 >
1346where
1347 ClassT: PyClass,
1348 for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1349 Offset: OffsetCalculator<ClassT, FieldT>,
1350{
1351 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1352 PyMethodDefType::Getter(PyGetterDef {
1353 name,
1354 meth: pyo3_get_value_into_pyobject_ref::<ClassT, FieldT, Offset>,
1355 doc,
1356 })
1357 }
1358}
1359
1360impl<ClassT, FieldT, Offset, const IMPLEMENTS_TOPYOBJECT: bool, const IMPLEMENTS_INTOPY: bool>
1363 PyClassGetterGenerator<
1364 ClassT,
1365 FieldT,
1366 Offset,
1367 false,
1368 IMPLEMENTS_TOPYOBJECT,
1369 IMPLEMENTS_INTOPY,
1370 false,
1371 true,
1372 >
1373where
1374 ClassT: PyClass,
1375 Offset: OffsetCalculator<ClassT, FieldT>,
1376 for<'py> FieldT: IntoPyObject<'py> + Clone,
1377{
1378 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1379 PyMethodDefType::Getter(PyGetterDef {
1380 name,
1381 meth: pyo3_get_value_into_pyobject::<ClassT, FieldT, Offset>,
1382 doc,
1383 })
1384 }
1385}
1386
1387#[allow(deprecated)]
1389impl<ClassT, FieldT, Offset>
1390 PyClassGetterGenerator<ClassT, FieldT, Offset, false, false, true, false, false>
1391where
1392 ClassT: PyClass,
1393 Offset: OffsetCalculator<ClassT, FieldT>,
1394 FieldT: IntoPy<Py<PyAny>> + Clone,
1395{
1396 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1397 PyMethodDefType::Getter(PyGetterDef {
1398 name,
1399 meth: pyo3_get_value::<ClassT, FieldT, Offset>,
1400 doc,
1401 })
1402 }
1403}
1404
1405#[cfg_attr(
1406 diagnostic_namespace,
1407 diagnostic::on_unimplemented(
1408 message = "`{Self}` cannot be converted to a Python object",
1409 label = "required by `#[pyo3(get)]` to create a readable property from a field of type `{Self}`",
1410 note = "implement `IntoPyObject` for `&{Self}` or `IntoPyObject + Clone` for `{Self}` to define the conversion"
1411 )
1412)]
1413pub trait PyO3GetField<'py>: IntoPyObject<'py> + Clone {}
1414impl<'py, T> PyO3GetField<'py> for T where T: IntoPyObject<'py> + Clone {}
1415
1416impl<ClassT: PyClass, FieldT, Offset: OffsetCalculator<ClassT, FieldT>>
1418 PyClassGetterGenerator<ClassT, FieldT, Offset, false, false, false, false, false>
1419{
1420 pub const fn generate(&self, _name: &'static CStr, _doc: &'static CStr) -> PyMethodDefType
1421 where
1424 for<'py> FieldT: PyO3GetField<'py>,
1425 {
1426 panic!(
1428 "exists purely to emit diagnostics on unimplemented traits. When `ToPyObject` \
1429 and `IntoPy` are fully removed this will be replaced by the temporary `IntoPyObject` case above."
1430 )
1431 }
1432}
1433
1434#[inline]
1436unsafe fn ensure_no_mutable_alias<'py, ClassT: PyClass>(
1437 py: Python<'py>,
1438 obj: &*mut ffi::PyObject,
1439) -> Result<PyRef<'py, ClassT>, PyBorrowError> {
1440 BoundRef::ref_from_ptr(py, obj)
1441 .downcast_unchecked::<ClassT>()
1442 .try_borrow()
1443}
1444
1445#[inline]
1447fn field_from_object<ClassT, FieldT, Offset>(obj: *mut ffi::PyObject) -> *mut FieldT
1448where
1449 ClassT: PyClass,
1450 Offset: OffsetCalculator<ClassT, FieldT>,
1451{
1452 unsafe { obj.cast::<u8>().add(Offset::offset()).cast::<FieldT>() }
1453}
1454
1455#[allow(deprecated)]
1456fn pyo3_get_value_topyobject<
1457 ClassT: PyClass,
1458 FieldT: ToPyObject,
1459 Offset: OffsetCalculator<ClassT, FieldT>,
1460>(
1461 py: Python<'_>,
1462 obj: *mut ffi::PyObject,
1463) -> PyResult<*mut ffi::PyObject> {
1464 let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1465 let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1466
1467 Ok((unsafe { &*value }).to_object(py).into_ptr())
1470}
1471
1472fn pyo3_get_value_into_pyobject_ref<ClassT, FieldT, Offset>(
1473 py: Python<'_>,
1474 obj: *mut ffi::PyObject,
1475) -> PyResult<*mut ffi::PyObject>
1476where
1477 ClassT: PyClass,
1478 for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1479 Offset: OffsetCalculator<ClassT, FieldT>,
1480{
1481 let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1482 let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1483
1484 Ok((unsafe { &*value })
1487 .into_pyobject(py)
1488 .map_err(Into::into)?
1489 .into_ptr())
1490}
1491
1492fn pyo3_get_value_into_pyobject<ClassT, FieldT, Offset>(
1493 py: Python<'_>,
1494 obj: *mut ffi::PyObject,
1495) -> PyResult<*mut ffi::PyObject>
1496where
1497 ClassT: PyClass,
1498 for<'py> FieldT: IntoPyObject<'py> + Clone,
1499 Offset: OffsetCalculator<ClassT, FieldT>,
1500{
1501 let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1502 let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1503
1504 Ok((unsafe { &*value })
1507 .clone()
1508 .into_pyobject(py)
1509 .map_err(Into::into)?
1510 .into_ptr())
1511}
1512
1513#[allow(deprecated)]
1514fn pyo3_get_value<
1515 ClassT: PyClass,
1516 FieldT: IntoPy<Py<PyAny>> + Clone,
1517 Offset: OffsetCalculator<ClassT, FieldT>,
1518>(
1519 py: Python<'_>,
1520 obj: *mut ffi::PyObject,
1521) -> PyResult<*mut ffi::PyObject> {
1522 let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1523 let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1524
1525 Ok((unsafe { &*value }).clone().into_py(py).into_ptr())
1528}
1529
1530pub struct ConvertField<
1531 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1532 const IMPLEMENTS_INTOPYOBJECT: bool,
1533>;
1534
1535impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<true, IMPLEMENTS_INTOPYOBJECT> {
1536 #[inline]
1537 pub fn convert_field<'a, 'py, T>(obj: &'a T, py: Python<'py>) -> PyResult<Py<PyAny>>
1538 where
1539 &'a T: IntoPyObject<'py>,
1540 {
1541 obj.into_py_any(py)
1542 }
1543}
1544
1545impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<false, IMPLEMENTS_INTOPYOBJECT> {
1546 #[inline]
1547 pub fn convert_field<'py, T>(obj: &T, py: Python<'py>) -> PyResult<Py<PyAny>>
1548 where
1549 T: PyO3GetField<'py>,
1550 {
1551 obj.clone().into_py_any(py)
1552 }
1553}
1554
1555#[cfg(test)]
1556#[cfg(feature = "macros")]
1557mod tests {
1558 use super::*;
1559
1560 #[test]
1561 fn get_py_for_frozen_class() {
1562 #[crate::pyclass(crate = "crate", frozen)]
1563 struct FrozenClass {
1564 #[pyo3(get)]
1565 value: Py<PyAny>,
1566 }
1567
1568 let mut methods = Vec::new();
1569 let mut slots = Vec::new();
1570
1571 for items in FrozenClass::items_iter() {
1572 methods.extend(items.methods.iter().map(|m| match m {
1573 MaybeRuntimePyMethodDef::Static(m) => m.clone(),
1574 MaybeRuntimePyMethodDef::Runtime(r) => r(),
1575 }));
1576 slots.extend_from_slice(items.slots);
1577 }
1578
1579 assert_eq!(methods.len(), 1);
1580 assert!(slots.is_empty());
1581
1582 match methods.first() {
1583 Some(PyMethodDefType::StructMember(member)) => {
1584 assert_eq!(unsafe { CStr::from_ptr(member.name) }, ffi::c_str!("value"));
1585 assert_eq!(member.type_code, ffi::Py_T_OBJECT_EX);
1586 assert_eq!(
1587 member.offset,
1588 (memoffset::offset_of!(PyClassObject<FrozenClass>, contents)
1589 + memoffset::offset_of!(FrozenClass, value))
1590 as ffi::Py_ssize_t
1591 );
1592 assert_eq!(member.flags, ffi::Py_READONLY);
1593 }
1594 _ => panic!("Expected a StructMember"),
1595 }
1596 }
1597
1598 #[test]
1599 fn get_py_for_non_frozen_class() {
1600 #[crate::pyclass(crate = "crate")]
1601 struct FrozenClass {
1602 #[pyo3(get)]
1603 value: Py<PyAny>,
1604 }
1605
1606 let mut methods = Vec::new();
1607 let mut slots = Vec::new();
1608
1609 for items in FrozenClass::items_iter() {
1610 methods.extend(items.methods.iter().map(|m| match m {
1611 MaybeRuntimePyMethodDef::Static(m) => m.clone(),
1612 MaybeRuntimePyMethodDef::Runtime(r) => r(),
1613 }));
1614 slots.extend_from_slice(items.slots);
1615 }
1616
1617 assert_eq!(methods.len(), 1);
1618 assert!(slots.is_empty());
1619
1620 match methods.first() {
1621 Some(PyMethodDefType::Getter(getter)) => {
1622 assert_eq!(getter.name, ffi::c_str!("value"));
1623 assert_eq!(getter.doc, ffi::c_str!(""));
1624 }
1626 _ => panic!("Expected a StructMember"),
1627 }
1628 }
1629}