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
47pub trait PyClassDict {
49 const INIT: Self;
51 #[inline]
53 fn clear_dict(&mut self, _py: Python<'_>) {}
54 private_decl! {}
55}
56
57pub trait PyClassWeakRef {
59 const INIT: Self;
61 #[inline]
67 unsafe fn clear_weakrefs(&mut self, _obj: *mut ffi::PyObject, _py: Python<'_>) {}
68 private_decl! {}
69}
70
71pub struct PyClassDummySlot;
73
74impl PyClassDict for PyClassDummySlot {
75 private_impl! {}
76 const INIT: Self = PyClassDummySlot;
77}
78
79impl PyClassWeakRef for PyClassDummySlot {
80 private_impl! {}
81 const INIT: Self = PyClassDummySlot;
82}
83
84#[repr(transparent)]
88#[allow(dead_code)] pub struct PyClassDictSlot(*mut ffi::PyObject);
90
91impl PyClassDict for PyClassDictSlot {
92 private_impl! {}
93 const INIT: Self = Self(std::ptr::null_mut());
94 #[inline]
95 fn clear_dict(&mut self, _py: Python<'_>) {
96 if !self.0.is_null() {
97 unsafe { ffi::PyDict_Clear(self.0) }
98 }
99 }
100}
101
102#[repr(transparent)]
106#[allow(dead_code)] pub struct PyClassWeakRefSlot(*mut ffi::PyObject);
108
109impl PyClassWeakRef for PyClassWeakRefSlot {
110 private_impl! {}
111 const INIT: Self = Self(std::ptr::null_mut());
112 #[inline]
113 unsafe fn clear_weakrefs(&mut self, obj: *mut ffi::PyObject, _py: Python<'_>) {
114 if !self.0.is_null() {
115 ffi::PyObject_ClearWeakRefs(obj)
116 }
117 }
118}
119
120pub struct PyClassImplCollector<T>(PhantomData<T>);
123
124impl<T> PyClassImplCollector<T> {
125 pub fn new() -> Self {
126 Self(PhantomData)
127 }
128}
129
130impl<T> Default for PyClassImplCollector<T> {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136impl<T> Clone for PyClassImplCollector<T> {
137 fn clone(&self) -> Self {
138 *self
139 }
140}
141
142impl<T> Copy for PyClassImplCollector<T> {}
143
144pub enum MaybeRuntimePyMethodDef {
145 Runtime(fn() -> PyMethodDefType),
148 Static(PyMethodDefType),
149}
150
151pub struct PyClassItems {
152 pub methods: &'static [MaybeRuntimePyMethodDef],
153 pub slots: &'static [ffi::PyType_Slot],
154}
155
156unsafe impl Sync for PyClassItems {}
158
159pub trait PyClassImpl: Sized + 'static {
164 const IS_BASETYPE: bool = false;
166
167 const IS_SUBCLASS: bool = false;
169
170 const IS_MAPPING: bool = false;
172
173 const IS_SEQUENCE: bool = false;
175
176 type BaseType: PyTypeInfo + PyClassBaseType;
178
179 type PyClassMutability: PyClassMutability + GetBorrowChecker<Self>;
181
182 type Dict: PyClassDict;
184
185 type WeakRef: PyClassWeakRef;
187
188 type BaseNativeType: PyTypeInfo;
191
192 type ThreadChecker: PyClassThreadChecker<Self>;
200
201 #[cfg(feature = "multiple-pymethods")]
202 type Inventory: PyClassInventory;
203
204 fn doc(py: Python<'_>) -> PyResult<&'static CStr>;
206
207 fn items_iter() -> PyClassItemsIter;
208
209 #[inline]
210 fn dict_offset() -> Option<ffi::Py_ssize_t> {
211 None
212 }
213
214 #[inline]
215 fn weaklist_offset() -> Option<ffi::Py_ssize_t> {
216 None
217 }
218
219 fn lazy_type_object() -> &'static LazyTypeObject<Self>;
220}
221
222pub fn build_pyclass_doc(
227 class_name: &'static str,
228 doc: &'static CStr,
229 text_signature: Option<&'static str>,
230) -> PyResult<Cow<'static, CStr>> {
231 if let Some(text_signature) = text_signature {
232 let doc = CString::new(format!(
233 "{}{}\n--\n\n{}",
234 class_name,
235 text_signature,
236 doc.to_str().unwrap(),
237 ))
238 .map_err(|_| PyValueError::new_err("class doc cannot contain nul bytes"))?;
239 Ok(Cow::Owned(doc))
240 } else {
241 Ok(Cow::Borrowed(doc))
242 }
243}
244
245pub struct PyClassItemsIter {
247 idx: usize,
249 pyclass_items: &'static PyClassItems,
251 #[cfg(not(feature = "multiple-pymethods"))]
253 pymethods_items: &'static PyClassItems,
254 #[cfg(feature = "multiple-pymethods")]
256 pymethods_items: Box<dyn Iterator<Item = &'static PyClassItems>>,
257}
258
259impl PyClassItemsIter {
260 pub fn new(
261 pyclass_items: &'static PyClassItems,
262 #[cfg(not(feature = "multiple-pymethods"))] pymethods_items: &'static PyClassItems,
263 #[cfg(feature = "multiple-pymethods")] pymethods_items: Box<
264 dyn Iterator<Item = &'static PyClassItems>,
265 >,
266 ) -> Self {
267 Self {
268 idx: 0,
269 pyclass_items,
270 pymethods_items,
271 }
272 }
273}
274
275impl Iterator for PyClassItemsIter {
276 type Item = &'static PyClassItems;
277
278 #[cfg(not(feature = "multiple-pymethods"))]
279 fn next(&mut self) -> Option<Self::Item> {
280 match self.idx {
281 0 => {
282 self.idx += 1;
283 Some(self.pyclass_items)
284 }
285 1 => {
286 self.idx += 1;
287 Some(self.pymethods_items)
288 }
289 _ => None,
291 }
292 }
293
294 #[cfg(feature = "multiple-pymethods")]
295 fn next(&mut self) -> Option<Self::Item> {
296 match self.idx {
297 0 => {
298 self.idx += 1;
299 Some(self.pyclass_items)
300 }
301 _ => self.pymethods_items.next(),
303 }
304 }
305}
306
307macro_rules! slot_fragment_trait {
310 ($trait_name:ident, $($default_method:tt)*) => {
311 #[allow(non_camel_case_types)]
312 pub trait $trait_name<T>: Sized {
313 $($default_method)*
314 }
315
316 impl<T> $trait_name<T> for &'_ PyClassImplCollector<T> {}
317 }
318}
319
320slot_fragment_trait! {
321 PyClass__getattribute__SlotFragment,
322
323 #[inline]
325 unsafe fn __getattribute__(
326 self,
327 py: Python<'_>,
328 slf: *mut ffi::PyObject,
329 attr: *mut ffi::PyObject,
330 ) -> PyResult<*mut ffi::PyObject> {
331 let res = ffi::PyObject_GenericGetAttr(slf, attr);
332 if res.is_null() {
333 Err(PyErr::fetch(py))
334 } else {
335 Ok(res)
336 }
337 }
338}
339
340slot_fragment_trait! {
341 PyClass__getattr__SlotFragment,
342
343 #[inline]
345 unsafe fn __getattr__(
346 self,
347 py: Python<'_>,
348 _slf: *mut ffi::PyObject,
349 attr: *mut ffi::PyObject,
350 ) -> PyResult<*mut ffi::PyObject> {
351 Err(PyErr::new::<PyAttributeError, _>(
352 (Py::<PyAny>::from_borrowed_ptr(py, attr),)
353 ))
354 }
355}
356
357#[doc(hidden)]
358#[macro_export]
359macro_rules! generate_pyclass_getattro_slot {
360 ($cls:ty) => {{
361 unsafe extern "C" fn __wrap(
362 _slf: *mut $crate::ffi::PyObject,
363 attr: *mut $crate::ffi::PyObject,
364 ) -> *mut $crate::ffi::PyObject {
365 $crate::impl_::trampoline::getattrofunc(_slf, attr, |py, _slf, attr| {
366 use ::std::result::Result::*;
367 use $crate::impl_::pyclass::*;
368 let collector = PyClassImplCollector::<$cls>::new();
369
370 match collector.__getattribute__(py, _slf, attr) {
376 Ok(obj) => Ok(obj),
377 Err(e) if e.is_instance_of::<$crate::exceptions::PyAttributeError>(py) => {
378 collector.__getattr__(py, _slf, attr)
379 }
380 Err(e) => Err(e),
381 }
382 })
383 }
384 $crate::ffi::PyType_Slot {
385 slot: $crate::ffi::Py_tp_getattro,
386 pfunc: __wrap as $crate::ffi::getattrofunc as _,
387 }
388 }};
389}
390
391pub use generate_pyclass_getattro_slot;
392
393macro_rules! define_pyclass_setattr_slot {
398 (
399 $set_trait:ident,
400 $del_trait:ident,
401 $set:ident,
402 $del:ident,
403 $set_error:expr,
404 $del_error:expr,
405 $generate_macro:ident,
406 $slot:ident,
407 $func_ty:ident,
408 ) => {
409 slot_fragment_trait! {
410 $set_trait,
411
412 #[inline]
414 unsafe fn $set(
415 self,
416 _py: Python<'_>,
417 _slf: *mut ffi::PyObject,
418 _attr: *mut ffi::PyObject,
419 _value: NonNull<ffi::PyObject>,
420 ) -> PyResult<()> {
421 $set_error
422 }
423 }
424
425 slot_fragment_trait! {
426 $del_trait,
427
428 #[inline]
430 unsafe fn $del(
431 self,
432 _py: Python<'_>,
433 _slf: *mut ffi::PyObject,
434 _attr: *mut ffi::PyObject,
435 ) -> PyResult<()> {
436 $del_error
437 }
438 }
439
440 #[doc(hidden)]
441 #[macro_export]
442 macro_rules! $generate_macro {
443 ($cls:ty) => {{
444 unsafe extern "C" fn __wrap(
445 _slf: *mut $crate::ffi::PyObject,
446 attr: *mut $crate::ffi::PyObject,
447 value: *mut $crate::ffi::PyObject,
448 ) -> ::std::os::raw::c_int {
449 $crate::impl_::trampoline::setattrofunc(
450 _slf,
451 attr,
452 value,
453 |py, _slf, attr, value| {
454 use ::std::option::Option::*;
455 use $crate::impl_::callback::IntoPyCallbackOutput;
456 use $crate::impl_::pyclass::*;
457 let collector = PyClassImplCollector::<$cls>::new();
458 if let Some(value) = ::std::ptr::NonNull::new(value) {
459 collector.$set(py, _slf, attr, value).convert(py)
460 } else {
461 collector.$del(py, _slf, attr).convert(py)
462 }
463 },
464 )
465 }
466 $crate::ffi::PyType_Slot {
467 slot: $crate::ffi::$slot,
468 pfunc: __wrap as $crate::ffi::$func_ty as _,
469 }
470 }};
471 }
472 pub use $generate_macro;
473 };
474}
475
476define_pyclass_setattr_slot! {
477 PyClass__setattr__SlotFragment,
478 PyClass__delattr__SlotFragment,
479 __setattr__,
480 __delattr__,
481 Err(PyAttributeError::new_err("can't set attribute")),
482 Err(PyAttributeError::new_err("can't delete attribute")),
483 generate_pyclass_setattr_slot,
484 Py_tp_setattro,
485 setattrofunc,
486}
487
488define_pyclass_setattr_slot! {
489 PyClass__set__SlotFragment,
490 PyClass__delete__SlotFragment,
491 __set__,
492 __delete__,
493 Err(PyNotImplementedError::new_err("can't set descriptor")),
494 Err(PyNotImplementedError::new_err("can't delete descriptor")),
495 generate_pyclass_setdescr_slot,
496 Py_tp_descr_set,
497 descrsetfunc,
498}
499
500define_pyclass_setattr_slot! {
501 PyClass__setitem__SlotFragment,
502 PyClass__delitem__SlotFragment,
503 __setitem__,
504 __delitem__,
505 Err(PyNotImplementedError::new_err("can't set item")),
506 Err(PyNotImplementedError::new_err("can't delete item")),
507 generate_pyclass_setitem_slot,
508 Py_mp_ass_subscript,
509 objobjargproc,
510}
511
512macro_rules! define_pyclass_binary_operator_slot {
517 (
518 $lhs_trait:ident,
519 $rhs_trait:ident,
520 $lhs:ident,
521 $rhs:ident,
522 $generate_macro:ident,
523 $slot:ident,
524 $func_ty:ident,
525 ) => {
526 slot_fragment_trait! {
527 $lhs_trait,
528
529 #[inline]
531 unsafe fn $lhs(
532 self,
533 py: Python<'_>,
534 _slf: *mut ffi::PyObject,
535 _other: *mut ffi::PyObject,
536 ) -> PyResult<*mut ffi::PyObject> {
537 Ok(py.NotImplemented().into_ptr())
538 }
539 }
540
541 slot_fragment_trait! {
542 $rhs_trait,
543
544 #[inline]
546 unsafe fn $rhs(
547 self,
548 py: Python<'_>,
549 _slf: *mut ffi::PyObject,
550 _other: *mut ffi::PyObject,
551 ) -> PyResult<*mut ffi::PyObject> {
552 Ok(py.NotImplemented().into_ptr())
553 }
554 }
555
556 #[doc(hidden)]
557 #[macro_export]
558 macro_rules! $generate_macro {
559 ($cls:ty) => {{
560 unsafe extern "C" fn __wrap(
561 _slf: *mut $crate::ffi::PyObject,
562 _other: *mut $crate::ffi::PyObject,
563 ) -> *mut $crate::ffi::PyObject {
564 $crate::impl_::trampoline::binaryfunc(_slf, _other, |py, _slf, _other| {
565 use $crate::impl_::pyclass::*;
566 let collector = PyClassImplCollector::<$cls>::new();
567 let lhs_result = collector.$lhs(py, _slf, _other)?;
568 if lhs_result == $crate::ffi::Py_NotImplemented() {
569 $crate::ffi::Py_DECREF(lhs_result);
570 collector.$rhs(py, _other, _slf)
571 } else {
572 ::std::result::Result::Ok(lhs_result)
573 }
574 })
575 }
576 $crate::ffi::PyType_Slot {
577 slot: $crate::ffi::$slot,
578 pfunc: __wrap as $crate::ffi::$func_ty as _,
579 }
580 }};
581 }
582 pub use $generate_macro;
583 };
584}
585
586define_pyclass_binary_operator_slot! {
587 PyClass__add__SlotFragment,
588 PyClass__radd__SlotFragment,
589 __add__,
590 __radd__,
591 generate_pyclass_add_slot,
592 Py_nb_add,
593 binaryfunc,
594}
595
596define_pyclass_binary_operator_slot! {
597 PyClass__sub__SlotFragment,
598 PyClass__rsub__SlotFragment,
599 __sub__,
600 __rsub__,
601 generate_pyclass_sub_slot,
602 Py_nb_subtract,
603 binaryfunc,
604}
605
606define_pyclass_binary_operator_slot! {
607 PyClass__mul__SlotFragment,
608 PyClass__rmul__SlotFragment,
609 __mul__,
610 __rmul__,
611 generate_pyclass_mul_slot,
612 Py_nb_multiply,
613 binaryfunc,
614}
615
616define_pyclass_binary_operator_slot! {
617 PyClass__mod__SlotFragment,
618 PyClass__rmod__SlotFragment,
619 __mod__,
620 __rmod__,
621 generate_pyclass_mod_slot,
622 Py_nb_remainder,
623 binaryfunc,
624}
625
626define_pyclass_binary_operator_slot! {
627 PyClass__divmod__SlotFragment,
628 PyClass__rdivmod__SlotFragment,
629 __divmod__,
630 __rdivmod__,
631 generate_pyclass_divmod_slot,
632 Py_nb_divmod,
633 binaryfunc,
634}
635
636define_pyclass_binary_operator_slot! {
637 PyClass__lshift__SlotFragment,
638 PyClass__rlshift__SlotFragment,
639 __lshift__,
640 __rlshift__,
641 generate_pyclass_lshift_slot,
642 Py_nb_lshift,
643 binaryfunc,
644}
645
646define_pyclass_binary_operator_slot! {
647 PyClass__rshift__SlotFragment,
648 PyClass__rrshift__SlotFragment,
649 __rshift__,
650 __rrshift__,
651 generate_pyclass_rshift_slot,
652 Py_nb_rshift,
653 binaryfunc,
654}
655
656define_pyclass_binary_operator_slot! {
657 PyClass__and__SlotFragment,
658 PyClass__rand__SlotFragment,
659 __and__,
660 __rand__,
661 generate_pyclass_and_slot,
662 Py_nb_and,
663 binaryfunc,
664}
665
666define_pyclass_binary_operator_slot! {
667 PyClass__or__SlotFragment,
668 PyClass__ror__SlotFragment,
669 __or__,
670 __ror__,
671 generate_pyclass_or_slot,
672 Py_nb_or,
673 binaryfunc,
674}
675
676define_pyclass_binary_operator_slot! {
677 PyClass__xor__SlotFragment,
678 PyClass__rxor__SlotFragment,
679 __xor__,
680 __rxor__,
681 generate_pyclass_xor_slot,
682 Py_nb_xor,
683 binaryfunc,
684}
685
686define_pyclass_binary_operator_slot! {
687 PyClass__matmul__SlotFragment,
688 PyClass__rmatmul__SlotFragment,
689 __matmul__,
690 __rmatmul__,
691 generate_pyclass_matmul_slot,
692 Py_nb_matrix_multiply,
693 binaryfunc,
694}
695
696define_pyclass_binary_operator_slot! {
697 PyClass__truediv__SlotFragment,
698 PyClass__rtruediv__SlotFragment,
699 __truediv__,
700 __rtruediv__,
701 generate_pyclass_truediv_slot,
702 Py_nb_true_divide,
703 binaryfunc,
704}
705
706define_pyclass_binary_operator_slot! {
707 PyClass__floordiv__SlotFragment,
708 PyClass__rfloordiv__SlotFragment,
709 __floordiv__,
710 __rfloordiv__,
711 generate_pyclass_floordiv_slot,
712 Py_nb_floor_divide,
713 binaryfunc,
714}
715
716slot_fragment_trait! {
717 PyClass__pow__SlotFragment,
718
719 #[inline]
721 unsafe fn __pow__(
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
732slot_fragment_trait! {
733 PyClass__rpow__SlotFragment,
734
735 #[inline]
737 unsafe fn __rpow__(
738 self,
739 py: Python<'_>,
740 _slf: *mut ffi::PyObject,
741 _other: *mut ffi::PyObject,
742 _mod: *mut ffi::PyObject,
743 ) -> PyResult<*mut ffi::PyObject> {
744 Ok(py.NotImplemented().into_ptr())
745 }
746}
747
748#[doc(hidden)]
749#[macro_export]
750macro_rules! generate_pyclass_pow_slot {
751 ($cls:ty) => {{
752 unsafe extern "C" fn __wrap(
753 _slf: *mut $crate::ffi::PyObject,
754 _other: *mut $crate::ffi::PyObject,
755 _mod: *mut $crate::ffi::PyObject,
756 ) -> *mut $crate::ffi::PyObject {
757 $crate::impl_::trampoline::ternaryfunc(_slf, _other, _mod, |py, _slf, _other, _mod| {
758 use $crate::impl_::pyclass::*;
759 let collector = PyClassImplCollector::<$cls>::new();
760 let lhs_result = collector.__pow__(py, _slf, _other, _mod)?;
761 if lhs_result == $crate::ffi::Py_NotImplemented() {
762 $crate::ffi::Py_DECREF(lhs_result);
763 collector.__rpow__(py, _other, _slf, _mod)
764 } else {
765 ::std::result::Result::Ok(lhs_result)
766 }
767 })
768 }
769 $crate::ffi::PyType_Slot {
770 slot: $crate::ffi::Py_nb_power,
771 pfunc: __wrap as $crate::ffi::ternaryfunc as _,
772 }
773 }};
774}
775pub use generate_pyclass_pow_slot;
776
777slot_fragment_trait! {
778 PyClass__lt__SlotFragment,
779
780 #[inline]
782 unsafe fn __lt__(
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__le__SlotFragment,
794
795 #[inline]
797 unsafe fn __le__(
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__eq__SlotFragment,
809
810 #[inline]
812 unsafe fn __eq__(
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__ne__SlotFragment,
824
825 #[inline]
827 unsafe fn __ne__(
828 self,
829 py: Python<'_>,
830 slf: *mut ffi::PyObject,
831 other: *mut ffi::PyObject,
832 ) -> PyResult<*mut ffi::PyObject> {
833 let slf = Borrowed::from_ptr(py, slf);
835 let other = Borrowed::from_ptr(py, other);
836 slf.eq(other).map(|is_eq| PyBool::new(py, !is_eq).to_owned().into_ptr())
837 }
838}
839
840slot_fragment_trait! {
841 PyClass__gt__SlotFragment,
842
843 #[inline]
845 unsafe fn __gt__(
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
855slot_fragment_trait! {
856 PyClass__ge__SlotFragment,
857
858 #[inline]
860 unsafe fn __ge__(
861 self,
862 py: Python<'_>,
863 _slf: *mut ffi::PyObject,
864 _other: *mut ffi::PyObject,
865 ) -> PyResult<*mut ffi::PyObject> {
866 Ok(py.NotImplemented().into_ptr())
867 }
868}
869
870#[doc(hidden)]
871#[macro_export]
872macro_rules! generate_pyclass_richcompare_slot {
873 ($cls:ty) => {{
874 #[allow(unknown_lints, non_local_definitions)]
875 impl $cls {
876 #[allow(non_snake_case)]
877 unsafe extern "C" fn __pymethod___richcmp____(
878 slf: *mut $crate::ffi::PyObject,
879 other: *mut $crate::ffi::PyObject,
880 op: ::std::os::raw::c_int,
881 ) -> *mut $crate::ffi::PyObject {
882 impl $crate::impl_::pyclass::HasCustomRichCmp for $cls {}
883
884 $crate::impl_::trampoline::richcmpfunc(slf, other, op, |py, slf, other, op| {
885 use $crate::class::basic::CompareOp;
886 use $crate::impl_::pyclass::*;
887 let collector = PyClassImplCollector::<$cls>::new();
888 match CompareOp::from_raw(op).expect("invalid compareop") {
889 CompareOp::Lt => collector.__lt__(py, slf, other),
890 CompareOp::Le => collector.__le__(py, slf, other),
891 CompareOp::Eq => collector.__eq__(py, slf, other),
892 CompareOp::Ne => collector.__ne__(py, slf, other),
893 CompareOp::Gt => collector.__gt__(py, slf, other),
894 CompareOp::Ge => collector.__ge__(py, slf, other),
895 }
896 })
897 }
898 }
899 $crate::ffi::PyType_Slot {
900 slot: $crate::ffi::Py_tp_richcompare,
901 pfunc: <$cls>::__pymethod___richcmp____ as $crate::ffi::richcmpfunc as _,
902 }
903 }};
904}
905pub use generate_pyclass_richcompare_slot;
906
907use super::{pycell::PyClassObject, pymethods::BoundRef};
908
909pub trait PyClassWithFreeList: PyClass {
914 fn get_free_list(py: Python<'_>) -> &'static Mutex<PyObjectFreeList>;
915}
916
917pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
923 subtype: *mut ffi::PyTypeObject,
924 nitems: ffi::Py_ssize_t,
925) -> *mut ffi::PyObject {
926 let py = Python::assume_gil_acquired();
927
928 #[cfg(not(Py_3_8))]
929 bpo_35810_workaround(py, subtype);
930
931 let self_type = T::type_object_raw(py);
932 if nitems == 0 && subtype == self_type {
935 let mut free_list = T::get_free_list(py).lock().unwrap();
936 if let Some(obj) = free_list.pop() {
937 drop(free_list);
938 ffi::PyObject_Init(obj, subtype);
939 return obj as _;
940 }
941 }
942
943 ffi::PyType_GenericAlloc(subtype, nitems)
944}
945
946pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
952 let obj = obj as *mut ffi::PyObject;
953 debug_assert_eq!(
954 T::type_object_raw(Python::assume_gil_acquired()),
955 ffi::Py_TYPE(obj)
956 );
957 let mut free_list = T::get_free_list(Python::assume_gil_acquired())
958 .lock()
959 .unwrap();
960 if let Some(obj) = free_list.insert(obj) {
961 drop(free_list);
962 let ty = ffi::Py_TYPE(obj);
963
964 let free = if ffi::PyType_IS_GC(ty) != 0 {
966 ffi::PyObject_GC_Del
967 } else {
968 ffi::PyObject_Free
969 };
970 free(obj as *mut c_void);
971
972 #[cfg(Py_3_8)]
973 if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
974 ffi::Py_DECREF(ty as *mut ffi::PyObject);
975 }
976 }
977}
978
979#[inline]
981#[cfg(not(Py_3_8))]
982unsafe fn bpo_35810_workaround(py: Python<'_>, ty: *mut ffi::PyTypeObject) {
983 #[cfg(Py_LIMITED_API)]
984 {
985 use crate::sync::GILOnceCell;
988 static IS_PYTHON_3_8: GILOnceCell<bool> = GILOnceCell::new();
989
990 if *IS_PYTHON_3_8.get_or_init(py, || py.version_info() >= (3, 8)) {
991 return;
993 }
994 }
995 #[cfg(not(Py_LIMITED_API))]
996 {
997 let _ = py;
999 }
1000
1001 ffi::Py_INCREF(ty as *mut ffi::PyObject);
1002}
1003
1004#[cfg(feature = "multiple-pymethods")]
1010pub trait PyClassInventory: inventory::Collect {
1011 fn items(&'static self) -> &'static PyClassItems;
1013}
1014
1015#[cfg(not(feature = "multiple-pymethods"))]
1017pub trait PyMethods<T> {
1018 fn py_methods(self) -> &'static PyClassItems;
1019}
1020
1021#[cfg(not(feature = "multiple-pymethods"))]
1022impl<T> PyMethods<T> for &'_ PyClassImplCollector<T> {
1023 fn py_methods(self) -> &'static PyClassItems {
1024 &PyClassItems {
1025 methods: &[],
1026 slots: &[],
1027 }
1028 }
1029}
1030
1031pub trait PyClassNewTextSignature<T> {
1033 fn new_text_signature(self) -> Option<&'static str>;
1034}
1035
1036impl<T> PyClassNewTextSignature<T> for &'_ PyClassImplCollector<T> {
1037 #[inline]
1038 fn new_text_signature(self) -> Option<&'static str> {
1039 None
1040 }
1041}
1042
1043#[doc(hidden)]
1046pub trait PyClassThreadChecker<T>: Sized {
1047 fn ensure(&self);
1048 fn check(&self) -> bool;
1049 fn can_drop(&self, py: Python<'_>) -> bool;
1050 fn new() -> Self;
1051 private_decl! {}
1052}
1053
1054#[doc(hidden)]
1060pub struct SendablePyClass<T: Send>(PhantomData<T>);
1061
1062impl<T: Send> PyClassThreadChecker<T> for SendablePyClass<T> {
1063 fn ensure(&self) {}
1064 fn check(&self) -> bool {
1065 true
1066 }
1067 fn can_drop(&self, _py: Python<'_>) -> bool {
1068 true
1069 }
1070 #[inline]
1071 fn new() -> Self {
1072 SendablePyClass(PhantomData)
1073 }
1074 private_impl! {}
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 private_impl! {}
1124}
1125
1126#[cfg_attr(
1129 all(diagnostic_namespace, Py_LIMITED_API),
1130 diagnostic::on_unimplemented(
1131 message = "pyclass `{Self}` cannot be subclassed",
1132 label = "required for `#[pyclass(extends={Self})]`",
1133 note = "`{Self}` must have `#[pyclass(subclass)]` to be eligible for subclassing",
1134 note = "with the `abi3` feature enabled, PyO3 does not support subclassing native types",
1135 )
1136)]
1137#[cfg_attr(
1138 all(diagnostic_namespace, not(Py_LIMITED_API)),
1139 diagnostic::on_unimplemented(
1140 message = "pyclass `{Self}` cannot be subclassed",
1141 label = "required for `#[pyclass(extends={Self})]`",
1142 note = "`{Self}` must have `#[pyclass(subclass)]` to be eligible for subclassing",
1143 )
1144)]
1145pub trait PyClassBaseType: Sized {
1146 type LayoutAsBase: PyClassObjectLayout<Self>;
1147 type BaseNativeType;
1148 type Initializer: PyObjectInit<Self>;
1149 type PyClassMutability: PyClassMutability;
1150}
1151
1152pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
1154 crate::impl_::trampoline::dealloc(obj, PyClassObject::<T>::tp_dealloc)
1155}
1156
1157pub(crate) unsafe extern "C" fn tp_dealloc_with_gc<T: PyClass>(obj: *mut ffi::PyObject) {
1159 #[cfg(not(PyPy))]
1160 {
1161 ffi::PyObject_GC_UnTrack(obj.cast());
1162 }
1163 crate::impl_::trampoline::dealloc(obj, PyClassObject::<T>::tp_dealloc)
1164}
1165
1166pub(crate) unsafe extern "C" fn get_sequence_item_from_mapping(
1167 obj: *mut ffi::PyObject,
1168 index: ffi::Py_ssize_t,
1169) -> *mut ffi::PyObject {
1170 let index = ffi::PyLong_FromSsize_t(index);
1171 if index.is_null() {
1172 return std::ptr::null_mut();
1173 }
1174 let result = ffi::PyObject_GetItem(obj, index);
1175 ffi::Py_DECREF(index);
1176 result
1177}
1178
1179pub(crate) unsafe extern "C" fn assign_sequence_item_from_mapping(
1180 obj: *mut ffi::PyObject,
1181 index: ffi::Py_ssize_t,
1182 value: *mut ffi::PyObject,
1183) -> c_int {
1184 let index = ffi::PyLong_FromSsize_t(index);
1185 if index.is_null() {
1186 return -1;
1187 }
1188 let result = if value.is_null() {
1189 ffi::PyObject_DelItem(obj, index)
1190 } else {
1191 ffi::PyObject_SetItem(obj, index, value)
1192 };
1193 ffi::Py_DECREF(index);
1194 result
1195}
1196
1197pub unsafe trait OffsetCalculator<T: PyClass, U> {
1206 fn offset() -> usize;
1208}
1209
1210pub fn class_offset<T: PyClass>() -> usize {
1212 offset_of!(PyClassObject<T>, contents)
1213}
1214
1215pub use memoffset::offset_of;
1217
1218pub struct PyClassGetterGenerator<
1221 ClassT: PyClass,
1224 FieldT,
1225 Offset: OffsetCalculator<ClassT, FieldT>, const IS_PY_T: bool,
1229 const IMPLEMENTS_TOPYOBJECT: bool,
1230 const IMPLEMENTS_INTOPY: bool,
1231 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1232 const IMPLEMENTS_INTOPYOBJECT: bool,
1233>(PhantomData<(ClassT, FieldT, Offset)>);
1234
1235impl<
1236 ClassT: PyClass,
1237 FieldT,
1238 Offset: OffsetCalculator<ClassT, FieldT>,
1239 const IS_PY_T: bool,
1240 const IMPLEMENTS_TOPYOBJECT: bool,
1241 const IMPLEMENTS_INTOPY: bool,
1242 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1243 const IMPLEMENTS_INTOPYOBJECT: bool,
1244 >
1245 PyClassGetterGenerator<
1246 ClassT,
1247 FieldT,
1248 Offset,
1249 IS_PY_T,
1250 IMPLEMENTS_TOPYOBJECT,
1251 IMPLEMENTS_INTOPY,
1252 IMPLEMENTS_INTOPYOBJECT_REF,
1253 IMPLEMENTS_INTOPYOBJECT,
1254 >
1255{
1256 pub const unsafe fn new() -> Self {
1259 Self(PhantomData)
1260 }
1261}
1262
1263impl<
1264 ClassT: PyClass,
1265 U,
1266 Offset: OffsetCalculator<ClassT, Py<U>>,
1267 const IMPLEMENTS_TOPYOBJECT: bool,
1268 const IMPLEMENTS_INTOPY: bool,
1269 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1270 const IMPLEMENTS_INTOPYOBJECT: bool,
1271 >
1272 PyClassGetterGenerator<
1273 ClassT,
1274 Py<U>,
1275 Offset,
1276 true,
1277 IMPLEMENTS_TOPYOBJECT,
1278 IMPLEMENTS_INTOPY,
1279 IMPLEMENTS_INTOPYOBJECT_REF,
1280 IMPLEMENTS_INTOPYOBJECT,
1281 >
1282{
1283 pub fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1289 use crate::pyclass::boolean_struct::private::Boolean;
1290 if ClassT::Frozen::VALUE {
1291 PyMethodDefType::StructMember(ffi::PyMemberDef {
1292 name: name.as_ptr(),
1293 type_code: ffi::Py_T_OBJECT_EX,
1294 offset: Offset::offset() as ffi::Py_ssize_t,
1295 flags: ffi::Py_READONLY,
1296 doc: doc.as_ptr(),
1297 })
1298 } else {
1299 PyMethodDefType::Getter(PyGetterDef {
1300 name,
1301 meth: pyo3_get_value_topyobject::<ClassT, Py<U>, Offset>,
1302 doc,
1303 })
1304 }
1305 }
1306}
1307
1308#[allow(deprecated)]
1311impl<
1312 ClassT: PyClass,
1313 FieldT: ToPyObject,
1314 Offset: OffsetCalculator<ClassT, FieldT>,
1315 const IMPLEMENTS_INTOPY: bool,
1316 > PyClassGetterGenerator<ClassT, FieldT, Offset, false, true, IMPLEMENTS_INTOPY, false, false>
1317{
1318 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1319 PyMethodDefType::Getter(PyGetterDef {
1320 name,
1321 meth: pyo3_get_value_topyobject::<ClassT, FieldT, Offset>,
1322 doc,
1323 })
1324 }
1325}
1326
1327impl<
1330 ClassT,
1331 FieldT,
1332 Offset,
1333 const IMPLEMENTS_TOPYOBJECT: bool,
1334 const IMPLEMENTS_INTOPY: bool,
1335 const IMPLEMENTS_INTOPYOBJECT: bool,
1336 >
1337 PyClassGetterGenerator<
1338 ClassT,
1339 FieldT,
1340 Offset,
1341 false,
1342 IMPLEMENTS_TOPYOBJECT,
1343 IMPLEMENTS_INTOPY,
1344 true,
1345 IMPLEMENTS_INTOPYOBJECT,
1346 >
1347where
1348 ClassT: PyClass,
1349 for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1350 Offset: OffsetCalculator<ClassT, FieldT>,
1351{
1352 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1353 PyMethodDefType::Getter(PyGetterDef {
1354 name,
1355 meth: pyo3_get_value_into_pyobject_ref::<ClassT, FieldT, Offset>,
1356 doc,
1357 })
1358 }
1359}
1360
1361impl<ClassT, FieldT, Offset, const IMPLEMENTS_TOPYOBJECT: bool, const IMPLEMENTS_INTOPY: bool>
1364 PyClassGetterGenerator<
1365 ClassT,
1366 FieldT,
1367 Offset,
1368 false,
1369 IMPLEMENTS_TOPYOBJECT,
1370 IMPLEMENTS_INTOPY,
1371 false,
1372 true,
1373 >
1374where
1375 ClassT: PyClass,
1376 Offset: OffsetCalculator<ClassT, FieldT>,
1377 for<'py> FieldT: IntoPyObject<'py> + Clone,
1378{
1379 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1380 PyMethodDefType::Getter(PyGetterDef {
1381 name,
1382 meth: pyo3_get_value_into_pyobject::<ClassT, FieldT, Offset>,
1383 doc,
1384 })
1385 }
1386}
1387
1388#[allow(deprecated)]
1390impl<ClassT, FieldT, Offset>
1391 PyClassGetterGenerator<ClassT, FieldT, Offset, false, false, true, false, false>
1392where
1393 ClassT: PyClass,
1394 Offset: OffsetCalculator<ClassT, FieldT>,
1395 FieldT: IntoPy<Py<PyAny>> + Clone,
1396{
1397 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1398 PyMethodDefType::Getter(PyGetterDef {
1399 name,
1400 meth: pyo3_get_value::<ClassT, FieldT, Offset>,
1401 doc,
1402 })
1403 }
1404}
1405
1406#[cfg_attr(
1407 diagnostic_namespace,
1408 diagnostic::on_unimplemented(
1409 message = "`{Self}` cannot be converted to a Python object",
1410 label = "required by `#[pyo3(get)]` to create a readable property from a field of type `{Self}`",
1411 note = "implement `IntoPyObject` for `&{Self}` or `IntoPyObject + Clone` for `{Self}` to define the conversion"
1412 )
1413)]
1414pub trait PyO3GetField<'py>: IntoPyObject<'py> + Clone {}
1415impl<'py, T> PyO3GetField<'py> for T where T: IntoPyObject<'py> + Clone {}
1416
1417impl<ClassT: PyClass, FieldT, Offset: OffsetCalculator<ClassT, FieldT>>
1419 PyClassGetterGenerator<ClassT, FieldT, Offset, false, false, false, false, false>
1420{
1421 pub const fn generate(&self, _name: &'static CStr, _doc: &'static CStr) -> PyMethodDefType
1422 where
1425 for<'py> FieldT: PyO3GetField<'py>,
1426 {
1427 panic!(
1429 "exists purely to emit diagnostics on unimplemented traits. When `ToPyObject` \
1430 and `IntoPy` are fully removed this will be replaced by the temporary `IntoPyObject` case above."
1431 )
1432 }
1433}
1434
1435#[inline]
1437unsafe fn ensure_no_mutable_alias<'py, ClassT: PyClass>(
1438 py: Python<'py>,
1439 obj: &*mut ffi::PyObject,
1440) -> Result<PyRef<'py, ClassT>, PyBorrowError> {
1441 BoundRef::ref_from_ptr(py, obj)
1442 .downcast_unchecked::<ClassT>()
1443 .try_borrow()
1444}
1445
1446#[inline]
1448fn field_from_object<ClassT, FieldT, Offset>(obj: *mut ffi::PyObject) -> *mut FieldT
1449where
1450 ClassT: PyClass,
1451 Offset: OffsetCalculator<ClassT, FieldT>,
1452{
1453 unsafe { obj.cast::<u8>().add(Offset::offset()).cast::<FieldT>() }
1454}
1455
1456#[allow(deprecated)]
1457fn pyo3_get_value_topyobject<
1458 ClassT: PyClass,
1459 FieldT: ToPyObject,
1460 Offset: OffsetCalculator<ClassT, FieldT>,
1461>(
1462 py: Python<'_>,
1463 obj: *mut ffi::PyObject,
1464) -> PyResult<*mut ffi::PyObject> {
1465 let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1466 let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1467
1468 Ok((unsafe { &*value }).to_object(py).into_ptr())
1471}
1472
1473fn pyo3_get_value_into_pyobject_ref<ClassT, FieldT, Offset>(
1474 py: Python<'_>,
1475 obj: *mut ffi::PyObject,
1476) -> PyResult<*mut ffi::PyObject>
1477where
1478 ClassT: PyClass,
1479 for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1480 Offset: OffsetCalculator<ClassT, FieldT>,
1481{
1482 let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1483 let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1484
1485 Ok((unsafe { &*value })
1488 .into_pyobject(py)
1489 .map_err(Into::into)?
1490 .into_ptr())
1491}
1492
1493fn pyo3_get_value_into_pyobject<ClassT, FieldT, Offset>(
1494 py: Python<'_>,
1495 obj: *mut ffi::PyObject,
1496) -> PyResult<*mut ffi::PyObject>
1497where
1498 ClassT: PyClass,
1499 for<'py> FieldT: IntoPyObject<'py> + Clone,
1500 Offset: OffsetCalculator<ClassT, FieldT>,
1501{
1502 let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1503 let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1504
1505 Ok((unsafe { &*value })
1508 .clone()
1509 .into_pyobject(py)
1510 .map_err(Into::into)?
1511 .into_ptr())
1512}
1513
1514#[allow(deprecated)]
1515fn pyo3_get_value<
1516 ClassT: PyClass,
1517 FieldT: IntoPy<Py<PyAny>> + Clone,
1518 Offset: OffsetCalculator<ClassT, FieldT>,
1519>(
1520 py: Python<'_>,
1521 obj: *mut ffi::PyObject,
1522) -> PyResult<*mut ffi::PyObject> {
1523 let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1524 let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1525
1526 Ok((unsafe { &*value }).clone().into_py(py).into_ptr())
1529}
1530
1531pub struct ConvertField<
1532 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1533 const IMPLEMENTS_INTOPYOBJECT: bool,
1534>;
1535
1536impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<true, IMPLEMENTS_INTOPYOBJECT> {
1537 #[inline]
1538 pub fn convert_field<'a, 'py, T>(obj: &'a T, py: Python<'py>) -> PyResult<Py<PyAny>>
1539 where
1540 &'a T: IntoPyObject<'py>,
1541 {
1542 obj.into_py_any(py)
1543 }
1544}
1545
1546impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<false, IMPLEMENTS_INTOPYOBJECT> {
1547 #[inline]
1548 pub fn convert_field<'py, T>(obj: &T, py: Python<'py>) -> PyResult<Py<PyAny>>
1549 where
1550 T: PyO3GetField<'py>,
1551 {
1552 obj.clone().into_py_any(py)
1553 }
1554}
1555
1556pub trait HasCustomRichCmp {}
1559
1560pub struct DeprecationTest<T>(Deprecation, ::std::marker::PhantomData<T>);
1563pub struct Deprecation;
1564
1565impl<T> DeprecationTest<T> {
1566 #[inline]
1567 #[allow(clippy::new_without_default)]
1568 pub const fn new() -> Self {
1569 DeprecationTest(Deprecation, ::std::marker::PhantomData)
1570 }
1571}
1572
1573impl<T> std::ops::Deref for DeprecationTest<T> {
1574 type Target = Deprecation;
1575 #[inline]
1576 fn deref(&self) -> &Self::Target {
1577 &self.0
1578 }
1579}
1580
1581impl<T> DeprecationTest<T>
1582where
1583 T: HasCustomRichCmp,
1584{
1585 #[inline]
1587 pub fn autogenerated_equality(&self) {}
1588}
1589
1590impl Deprecation {
1591 #[deprecated(
1592 since = "0.22.0",
1593 note = "Implicit equality for simple enums is deprecated. Use `#[pyclass(eq, eq_int)]` to keep the current behavior."
1594 )]
1595 #[inline]
1597 pub fn autogenerated_equality(&self) {}
1598}
1599
1600#[cfg(test)]
1601#[cfg(feature = "macros")]
1602mod tests {
1603 use super::*;
1604
1605 #[test]
1606 fn get_py_for_frozen_class() {
1607 #[crate::pyclass(crate = "crate", frozen)]
1608 struct FrozenClass {
1609 #[pyo3(get)]
1610 value: Py<PyAny>,
1611 }
1612
1613 let mut methods = Vec::new();
1614 let mut slots = Vec::new();
1615
1616 for items in FrozenClass::items_iter() {
1617 methods.extend(items.methods.iter().map(|m| match m {
1618 MaybeRuntimePyMethodDef::Static(m) => m.clone(),
1619 MaybeRuntimePyMethodDef::Runtime(r) => r(),
1620 }));
1621 slots.extend_from_slice(items.slots);
1622 }
1623
1624 assert_eq!(methods.len(), 1);
1625 assert!(slots.is_empty());
1626
1627 match methods.first() {
1628 Some(PyMethodDefType::StructMember(member)) => {
1629 assert_eq!(unsafe { CStr::from_ptr(member.name) }, ffi::c_str!("value"));
1630 assert_eq!(member.type_code, ffi::Py_T_OBJECT_EX);
1631 assert_eq!(
1632 member.offset,
1633 (memoffset::offset_of!(PyClassObject<FrozenClass>, contents)
1634 + memoffset::offset_of!(FrozenClass, value))
1635 as ffi::Py_ssize_t
1636 );
1637 assert_eq!(member.flags, ffi::Py_READONLY);
1638 }
1639 _ => panic!("Expected a StructMember"),
1640 }
1641 }
1642
1643 #[test]
1644 fn get_py_for_non_frozen_class() {
1645 #[crate::pyclass(crate = "crate")]
1646 struct FrozenClass {
1647 #[pyo3(get)]
1648 value: Py<PyAny>,
1649 }
1650
1651 let mut methods = Vec::new();
1652 let mut slots = Vec::new();
1653
1654 for items in FrozenClass::items_iter() {
1655 methods.extend(items.methods.iter().map(|m| match m {
1656 MaybeRuntimePyMethodDef::Static(m) => m.clone(),
1657 MaybeRuntimePyMethodDef::Runtime(r) => r(),
1658 }));
1659 slots.extend_from_slice(items.slots);
1660 }
1661
1662 assert_eq!(methods.len(), 1);
1663 assert!(slots.is_empty());
1664
1665 match methods.first() {
1666 Some(PyMethodDefType::Getter(getter)) => {
1667 assert_eq!(getter.name, ffi::c_str!("value"));
1668 assert_eq!(getter.doc, ffi::c_str!(""));
1669 }
1671 _ => panic!("Expected a StructMember"),
1672 }
1673 }
1674}