1use crate::{
2 exceptions::{PyAttributeError, PyNotImplementedError, PyRuntimeError},
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, PyClassGuard, PyErr,
13 PyResult, PyTypeInfo, Python,
14};
15use std::{
16 ffi::CStr,
17 marker::PhantomData,
18 os::raw::{c_int, c_void},
19 ptr,
20 ptr::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 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 unsafe { 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 const IS_IMMUTABLE_TYPE: bool = false;
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 #[cfg(feature = "experimental-inspect")]
223 const TYPE_NAME: &'static str;
224
225 fn items_iter() -> PyClassItemsIter;
226
227 #[inline]
228 fn dict_offset() -> Option<ffi::Py_ssize_t> {
229 None
230 }
231
232 #[inline]
233 fn weaklist_offset() -> Option<ffi::Py_ssize_t> {
234 None
235 }
236
237 fn lazy_type_object() -> &'static LazyTypeObject<Self>;
238}
239
240pub struct PyClassItemsIter {
242 idx: usize,
244 pyclass_items: &'static PyClassItems,
246 #[cfg(not(feature = "multiple-pymethods"))]
248 pymethods_items: &'static PyClassItems,
249 #[cfg(feature = "multiple-pymethods")]
251 pymethods_items: Box<dyn Iterator<Item = &'static PyClassItems>>,
252}
253
254impl PyClassItemsIter {
255 pub fn new(
256 pyclass_items: &'static PyClassItems,
257 #[cfg(not(feature = "multiple-pymethods"))] pymethods_items: &'static PyClassItems,
258 #[cfg(feature = "multiple-pymethods")] pymethods_items: Box<
259 dyn Iterator<Item = &'static PyClassItems>,
260 >,
261 ) -> Self {
262 Self {
263 idx: 0,
264 pyclass_items,
265 pymethods_items,
266 }
267 }
268}
269
270impl Iterator for PyClassItemsIter {
271 type Item = &'static PyClassItems;
272
273 #[cfg(not(feature = "multiple-pymethods"))]
274 fn next(&mut self) -> Option<Self::Item> {
275 match self.idx {
276 0 => {
277 self.idx += 1;
278 Some(self.pyclass_items)
279 }
280 1 => {
281 self.idx += 1;
282 Some(self.pymethods_items)
283 }
284 _ => None,
286 }
287 }
288
289 #[cfg(feature = "multiple-pymethods")]
290 fn next(&mut self) -> Option<Self::Item> {
291 match self.idx {
292 0 => {
293 self.idx += 1;
294 Some(self.pyclass_items)
295 }
296 _ => self.pymethods_items.next(),
298 }
299 }
300}
301
302macro_rules! slot_fragment_trait {
305 ($trait_name:ident, $($default_method:tt)*) => {
306 #[allow(non_camel_case_types)]
307 pub trait $trait_name<T>: Sized {
308 $($default_method)*
309 }
310
311 impl<T> $trait_name<T> for &'_ PyClassImplCollector<T> {}
312 }
313}
314
315slot_fragment_trait! {
316 PyClass__getattribute__SlotFragment,
317
318 #[inline]
320 unsafe fn __getattribute__(
321 self,
322 py: Python<'_>,
323 slf: *mut ffi::PyObject,
324 attr: *mut ffi::PyObject,
325 ) -> PyResult<*mut ffi::PyObject> {
326 let res = unsafe { ffi::PyObject_GenericGetAttr(slf, attr) };
327 if res.is_null() {
328 Err(PyErr::fetch(py))
329 } else {
330 Ok(res)
331 }
332 }
333}
334
335slot_fragment_trait! {
336 PyClass__getattr__SlotFragment,
337
338 #[inline]
340 unsafe fn __getattr__(
341 self,
342 py: Python<'_>,
343 _slf: *mut ffi::PyObject,
344 attr: *mut ffi::PyObject,
345 ) -> PyResult<*mut ffi::PyObject> {
346 Err(PyErr::new::<PyAttributeError, _>(
347 (unsafe {Py::<PyAny>::from_borrowed_ptr(py, attr)},)
348 ))
349 }
350}
351
352#[doc(hidden)]
353#[macro_export]
354macro_rules! generate_pyclass_getattro_slot {
355 ($cls:ty) => {{
356 unsafe extern "C" fn __wrap(
357 _slf: *mut $crate::ffi::PyObject,
358 attr: *mut $crate::ffi::PyObject,
359 ) -> *mut $crate::ffi::PyObject {
360 unsafe {
361 $crate::impl_::trampoline::getattrofunc(_slf, attr, |py, _slf, attr| {
362 use ::std::result::Result::*;
363 use $crate::impl_::pyclass::*;
364 let collector = PyClassImplCollector::<$cls>::new();
365
366 match collector.__getattribute__(py, _slf, attr) {
372 Ok(obj) => Ok(obj),
373 Err(e) if e.is_instance_of::<$crate::exceptions::PyAttributeError>(py) => {
374 collector.__getattr__(py, _slf, attr)
375 }
376 Err(e) => Err(e),
377 }
378 })
379 }
380 }
381 $crate::ffi::PyType_Slot {
382 slot: $crate::ffi::Py_tp_getattro,
383 pfunc: __wrap as $crate::ffi::getattrofunc as _,
384 }
385 }};
386}
387
388pub use generate_pyclass_getattro_slot;
389
390macro_rules! define_pyclass_setattr_slot {
395 (
396 $set_trait:ident,
397 $del_trait:ident,
398 $set:ident,
399 $del:ident,
400 $set_error:expr,
401 $del_error:expr,
402 $generate_macro:ident,
403 $slot:ident,
404 $func_ty:ident,
405 ) => {
406 slot_fragment_trait! {
407 $set_trait,
408
409 #[inline]
411 unsafe fn $set(
412 self,
413 _py: Python<'_>,
414 _slf: *mut ffi::PyObject,
415 _attr: *mut ffi::PyObject,
416 _value: NonNull<ffi::PyObject>,
417 ) -> PyResult<()> {
418 $set_error
419 }
420 }
421
422 slot_fragment_trait! {
423 $del_trait,
424
425 #[inline]
427 unsafe fn $del(
428 self,
429 _py: Python<'_>,
430 _slf: *mut ffi::PyObject,
431 _attr: *mut ffi::PyObject,
432 ) -> PyResult<()> {
433 $del_error
434 }
435 }
436
437 #[doc(hidden)]
438 #[macro_export]
439 macro_rules! $generate_macro {
440 ($cls:ty) => {{
441 unsafe extern "C" fn __wrap(
442 _slf: *mut $crate::ffi::PyObject,
443 attr: *mut $crate::ffi::PyObject,
444 value: *mut $crate::ffi::PyObject,
445 ) -> ::std::ffi::c_int {
446 unsafe {
447 $crate::impl_::trampoline::setattrofunc(
448 _slf,
449 attr,
450 value,
451 |py, _slf, attr, value| {
452 use ::std::option::Option::*;
453 use $crate::impl_::callback::IntoPyCallbackOutput;
454 use $crate::impl_::pyclass::*;
455 let collector = PyClassImplCollector::<$cls>::new();
456 if let Some(value) = ::std::ptr::NonNull::new(value) {
457 collector.$set(py, _slf, attr, value).convert(py)
458 } else {
459 collector.$del(py, _slf, attr).convert(py)
460 }
461 },
462 )
463 }
464 }
465 $crate::ffi::PyType_Slot {
466 slot: $crate::ffi::$slot,
467 pfunc: __wrap as $crate::ffi::$func_ty as _,
468 }
469 }};
470 }
471 pub use $generate_macro;
472 };
473}
474
475define_pyclass_setattr_slot! {
476 PyClass__setattr__SlotFragment,
477 PyClass__delattr__SlotFragment,
478 __setattr__,
479 __delattr__,
480 Err(PyAttributeError::new_err("can't set attribute")),
481 Err(PyAttributeError::new_err("can't delete attribute")),
482 generate_pyclass_setattr_slot,
483 Py_tp_setattro,
484 setattrofunc,
485}
486
487define_pyclass_setattr_slot! {
488 PyClass__set__SlotFragment,
489 PyClass__delete__SlotFragment,
490 __set__,
491 __delete__,
492 Err(PyNotImplementedError::new_err("can't set descriptor")),
493 Err(PyNotImplementedError::new_err("can't delete descriptor")),
494 generate_pyclass_setdescr_slot,
495 Py_tp_descr_set,
496 descrsetfunc,
497}
498
499define_pyclass_setattr_slot! {
500 PyClass__setitem__SlotFragment,
501 PyClass__delitem__SlotFragment,
502 __setitem__,
503 __delitem__,
504 Err(PyNotImplementedError::new_err("can't set item")),
505 Err(PyNotImplementedError::new_err("can't delete item")),
506 generate_pyclass_setitem_slot,
507 Py_mp_ass_subscript,
508 objobjargproc,
509}
510
511macro_rules! define_pyclass_binary_operator_slot {
516 (
517 $lhs_trait:ident,
518 $rhs_trait:ident,
519 $lhs:ident,
520 $rhs:ident,
521 $generate_macro:ident,
522 $slot:ident,
523 $func_ty:ident,
524 ) => {
525 slot_fragment_trait! {
526 $lhs_trait,
527
528 #[inline]
530 unsafe fn $lhs(
531 self,
532 py: Python<'_>,
533 _slf: *mut ffi::PyObject,
534 _other: *mut ffi::PyObject,
535 ) -> PyResult<*mut ffi::PyObject> {
536 Ok(py.NotImplemented().into_ptr())
537 }
538 }
539
540 slot_fragment_trait! {
541 $rhs_trait,
542
543 #[inline]
545 unsafe fn $rhs(
546 self,
547 py: Python<'_>,
548 _slf: *mut ffi::PyObject,
549 _other: *mut ffi::PyObject,
550 ) -> PyResult<*mut ffi::PyObject> {
551 Ok(py.NotImplemented().into_ptr())
552 }
553 }
554
555 #[doc(hidden)]
556 #[macro_export]
557 macro_rules! $generate_macro {
558 ($cls:ty) => {{
559 unsafe extern "C" fn __wrap(
560 _slf: *mut $crate::ffi::PyObject,
561 _other: *mut $crate::ffi::PyObject,
562 ) -> *mut $crate::ffi::PyObject {
563 unsafe {
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 }
577 $crate::ffi::PyType_Slot {
578 slot: $crate::ffi::$slot,
579 pfunc: __wrap as $crate::ffi::$func_ty as _,
580 }
581 }};
582 }
583 pub use $generate_macro;
584 };
585}
586
587define_pyclass_binary_operator_slot! {
588 PyClass__add__SlotFragment,
589 PyClass__radd__SlotFragment,
590 __add__,
591 __radd__,
592 generate_pyclass_add_slot,
593 Py_nb_add,
594 binaryfunc,
595}
596
597define_pyclass_binary_operator_slot! {
598 PyClass__sub__SlotFragment,
599 PyClass__rsub__SlotFragment,
600 __sub__,
601 __rsub__,
602 generate_pyclass_sub_slot,
603 Py_nb_subtract,
604 binaryfunc,
605}
606
607define_pyclass_binary_operator_slot! {
608 PyClass__mul__SlotFragment,
609 PyClass__rmul__SlotFragment,
610 __mul__,
611 __rmul__,
612 generate_pyclass_mul_slot,
613 Py_nb_multiply,
614 binaryfunc,
615}
616
617define_pyclass_binary_operator_slot! {
618 PyClass__mod__SlotFragment,
619 PyClass__rmod__SlotFragment,
620 __mod__,
621 __rmod__,
622 generate_pyclass_mod_slot,
623 Py_nb_remainder,
624 binaryfunc,
625}
626
627define_pyclass_binary_operator_slot! {
628 PyClass__divmod__SlotFragment,
629 PyClass__rdivmod__SlotFragment,
630 __divmod__,
631 __rdivmod__,
632 generate_pyclass_divmod_slot,
633 Py_nb_divmod,
634 binaryfunc,
635}
636
637define_pyclass_binary_operator_slot! {
638 PyClass__lshift__SlotFragment,
639 PyClass__rlshift__SlotFragment,
640 __lshift__,
641 __rlshift__,
642 generate_pyclass_lshift_slot,
643 Py_nb_lshift,
644 binaryfunc,
645}
646
647define_pyclass_binary_operator_slot! {
648 PyClass__rshift__SlotFragment,
649 PyClass__rrshift__SlotFragment,
650 __rshift__,
651 __rrshift__,
652 generate_pyclass_rshift_slot,
653 Py_nb_rshift,
654 binaryfunc,
655}
656
657define_pyclass_binary_operator_slot! {
658 PyClass__and__SlotFragment,
659 PyClass__rand__SlotFragment,
660 __and__,
661 __rand__,
662 generate_pyclass_and_slot,
663 Py_nb_and,
664 binaryfunc,
665}
666
667define_pyclass_binary_operator_slot! {
668 PyClass__or__SlotFragment,
669 PyClass__ror__SlotFragment,
670 __or__,
671 __ror__,
672 generate_pyclass_or_slot,
673 Py_nb_or,
674 binaryfunc,
675}
676
677define_pyclass_binary_operator_slot! {
678 PyClass__xor__SlotFragment,
679 PyClass__rxor__SlotFragment,
680 __xor__,
681 __rxor__,
682 generate_pyclass_xor_slot,
683 Py_nb_xor,
684 binaryfunc,
685}
686
687define_pyclass_binary_operator_slot! {
688 PyClass__matmul__SlotFragment,
689 PyClass__rmatmul__SlotFragment,
690 __matmul__,
691 __rmatmul__,
692 generate_pyclass_matmul_slot,
693 Py_nb_matrix_multiply,
694 binaryfunc,
695}
696
697define_pyclass_binary_operator_slot! {
698 PyClass__truediv__SlotFragment,
699 PyClass__rtruediv__SlotFragment,
700 __truediv__,
701 __rtruediv__,
702 generate_pyclass_truediv_slot,
703 Py_nb_true_divide,
704 binaryfunc,
705}
706
707define_pyclass_binary_operator_slot! {
708 PyClass__floordiv__SlotFragment,
709 PyClass__rfloordiv__SlotFragment,
710 __floordiv__,
711 __rfloordiv__,
712 generate_pyclass_floordiv_slot,
713 Py_nb_floor_divide,
714 binaryfunc,
715}
716
717slot_fragment_trait! {
718 PyClass__pow__SlotFragment,
719
720 #[inline]
722 unsafe fn __pow__(
723 self,
724 py: Python<'_>,
725 _slf: *mut ffi::PyObject,
726 _other: *mut ffi::PyObject,
727 _mod: *mut ffi::PyObject,
728 ) -> PyResult<*mut ffi::PyObject> {
729 Ok(py.NotImplemented().into_ptr())
730 }
731}
732
733slot_fragment_trait! {
734 PyClass__rpow__SlotFragment,
735
736 #[inline]
738 unsafe fn __rpow__(
739 self,
740 py: Python<'_>,
741 _slf: *mut ffi::PyObject,
742 _other: *mut ffi::PyObject,
743 _mod: *mut ffi::PyObject,
744 ) -> PyResult<*mut ffi::PyObject> {
745 Ok(py.NotImplemented().into_ptr())
746 }
747}
748
749#[doc(hidden)]
750#[macro_export]
751macro_rules! generate_pyclass_pow_slot {
752 ($cls:ty) => {{
753 unsafe extern "C" fn __wrap(
754 _slf: *mut $crate::ffi::PyObject,
755 _other: *mut $crate::ffi::PyObject,
756 _mod: *mut $crate::ffi::PyObject,
757 ) -> *mut $crate::ffi::PyObject {
758 unsafe {
759 $crate::impl_::trampoline::ternaryfunc(
760 _slf,
761 _other,
762 _mod,
763 |py, _slf, _other, _mod| {
764 use $crate::impl_::pyclass::*;
765 let collector = PyClassImplCollector::<$cls>::new();
766 let lhs_result = collector.__pow__(py, _slf, _other, _mod)?;
767 if lhs_result == $crate::ffi::Py_NotImplemented() {
768 $crate::ffi::Py_DECREF(lhs_result);
769 collector.__rpow__(py, _other, _slf, _mod)
770 } else {
771 ::std::result::Result::Ok(lhs_result)
772 }
773 },
774 )
775 }
776 }
777 $crate::ffi::PyType_Slot {
778 slot: $crate::ffi::Py_nb_power,
779 pfunc: __wrap as $crate::ffi::ternaryfunc as _,
780 }
781 }};
782}
783pub use generate_pyclass_pow_slot;
784
785slot_fragment_trait! {
786 PyClass__lt__SlotFragment,
787
788 #[inline]
790 unsafe fn __lt__(
791 self,
792 py: Python<'_>,
793 _slf: *mut ffi::PyObject,
794 _other: *mut ffi::PyObject,
795 ) -> PyResult<*mut ffi::PyObject> {
796 Ok(py.NotImplemented().into_ptr())
797 }
798}
799
800slot_fragment_trait! {
801 PyClass__le__SlotFragment,
802
803 #[inline]
805 unsafe fn __le__(
806 self,
807 py: Python<'_>,
808 _slf: *mut ffi::PyObject,
809 _other: *mut ffi::PyObject,
810 ) -> PyResult<*mut ffi::PyObject> {
811 Ok(py.NotImplemented().into_ptr())
812 }
813}
814
815slot_fragment_trait! {
816 PyClass__eq__SlotFragment,
817
818 #[inline]
820 unsafe fn __eq__(
821 self,
822 py: Python<'_>,
823 _slf: *mut ffi::PyObject,
824 _other: *mut ffi::PyObject,
825 ) -> PyResult<*mut ffi::PyObject> {
826 Ok(py.NotImplemented().into_ptr())
827 }
828}
829
830slot_fragment_trait! {
831 PyClass__ne__SlotFragment,
832
833 #[inline]
835 unsafe fn __ne__(
836 self,
837 py: Python<'_>,
838 slf: *mut ffi::PyObject,
839 other: *mut ffi::PyObject,
840 ) -> PyResult<*mut ffi::PyObject> {
841 let slf = unsafe { Borrowed::from_ptr(py, slf)};
843 let other = unsafe { Borrowed::from_ptr(py, other)};
844 slf.eq(other).map(|is_eq| PyBool::new(py, !is_eq).to_owned().into_ptr())
845 }
846}
847
848slot_fragment_trait! {
849 PyClass__gt__SlotFragment,
850
851 #[inline]
853 unsafe fn __gt__(
854 self,
855 py: Python<'_>,
856 _slf: *mut ffi::PyObject,
857 _other: *mut ffi::PyObject,
858 ) -> PyResult<*mut ffi::PyObject> {
859 Ok(py.NotImplemented().into_ptr())
860 }
861}
862
863slot_fragment_trait! {
864 PyClass__ge__SlotFragment,
865
866 #[inline]
868 unsafe fn __ge__(
869 self,
870 py: Python<'_>,
871 _slf: *mut ffi::PyObject,
872 _other: *mut ffi::PyObject,
873 ) -> PyResult<*mut ffi::PyObject> {
874 Ok(py.NotImplemented().into_ptr())
875 }
876}
877
878#[doc(hidden)]
879#[macro_export]
880macro_rules! generate_pyclass_richcompare_slot {
881 ($cls:ty) => {{
882 #[allow(unknown_lints, non_local_definitions)]
883 impl $cls {
884 #[allow(non_snake_case)]
885 unsafe extern "C" fn __pymethod___richcmp____(
886 slf: *mut $crate::ffi::PyObject,
887 other: *mut $crate::ffi::PyObject,
888 op: ::std::ffi::c_int,
889 ) -> *mut $crate::ffi::PyObject {
890 unsafe {
891 $crate::impl_::trampoline::richcmpfunc(slf, other, op, |py, slf, other, op| {
892 use $crate::class::basic::CompareOp;
893 use $crate::impl_::pyclass::*;
894 let collector = PyClassImplCollector::<$cls>::new();
895 match CompareOp::from_raw(op).expect("invalid compareop") {
896 CompareOp::Lt => collector.__lt__(py, slf, other),
897 CompareOp::Le => collector.__le__(py, slf, other),
898 CompareOp::Eq => collector.__eq__(py, slf, other),
899 CompareOp::Ne => collector.__ne__(py, slf, other),
900 CompareOp::Gt => collector.__gt__(py, slf, other),
901 CompareOp::Ge => collector.__ge__(py, slf, other),
902 }
903 })
904 }
905 }
906 }
907 $crate::ffi::PyType_Slot {
908 slot: $crate::ffi::Py_tp_richcompare,
909 pfunc: <$cls>::__pymethod___richcmp____ as $crate::ffi::richcmpfunc as _,
910 }
911 }};
912}
913pub use generate_pyclass_richcompare_slot;
914
915use super::pycell::PyClassObject;
916
917pub trait PyClassWithFreeList: PyClass {
922 fn get_free_list(py: Python<'_>) -> &'static Mutex<PyObjectFreeList>;
923}
924
925pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
931 subtype: *mut ffi::PyTypeObject,
932 nitems: ffi::Py_ssize_t,
933) -> *mut ffi::PyObject {
934 let py = unsafe { Python::assume_attached() };
935
936 #[cfg(not(Py_3_8))]
937 unsafe {
938 bpo_35810_workaround(py, subtype)
939 };
940
941 let self_type = T::type_object_raw(py);
942 if nitems == 0 && ptr::eq(subtype, self_type) {
945 let mut free_list = T::get_free_list(py).lock().unwrap();
946 if let Some(obj) = free_list.pop() {
947 drop(free_list);
948 unsafe { ffi::PyObject_Init(obj, subtype) };
949 unsafe { ffi::PyObject_Init(obj, subtype) };
950 return obj as _;
951 }
952 }
953
954 unsafe { ffi::PyType_GenericAlloc(subtype, nitems) }
955}
956
957pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
963 let obj = obj as *mut ffi::PyObject;
964 unsafe {
965 debug_assert_eq!(
966 T::type_object_raw(Python::assume_attached()),
967 ffi::Py_TYPE(obj)
968 );
969 let mut free_list = T::get_free_list(Python::assume_attached()).lock().unwrap();
970 if let Some(obj) = free_list.insert(obj) {
971 drop(free_list);
972 let ty = ffi::Py_TYPE(obj);
973
974 let free = if ffi::PyType_IS_GC(ty) != 0 {
976 ffi::PyObject_GC_Del
977 } else {
978 ffi::PyObject_Free
979 };
980 free(obj as *mut c_void);
981
982 #[cfg(Py_3_8)]
983 if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
984 ffi::Py_DECREF(ty as *mut ffi::PyObject);
985 }
986 }
987 }
988}
989
990#[inline]
992#[cfg(not(Py_3_8))]
993unsafe fn bpo_35810_workaround(py: Python<'_>, ty: *mut ffi::PyTypeObject) {
994 #[cfg(Py_LIMITED_API)]
995 {
996 use crate::sync::PyOnceLock;
999 static IS_PYTHON_3_8: PyOnceLock<bool> = PyOnceLock::new();
1000
1001 if *IS_PYTHON_3_8.get_or_init(py, || py.version_info() >= (3, 8)) {
1002 return;
1004 }
1005 }
1006 #[cfg(not(Py_LIMITED_API))]
1007 {
1008 let _ = py;
1010 }
1011
1012 unsafe { ffi::Py_INCREF(ty as *mut ffi::PyObject) };
1013}
1014
1015#[cfg(feature = "multiple-pymethods")]
1021pub trait PyClassInventory: inventory::Collect {
1022 fn items(&'static self) -> &'static PyClassItems;
1024}
1025
1026#[cfg(not(feature = "multiple-pymethods"))]
1028pub trait PyMethods<T> {
1029 fn py_methods(self) -> &'static PyClassItems;
1030}
1031
1032#[cfg(not(feature = "multiple-pymethods"))]
1033impl<T> PyMethods<T> for &'_ PyClassImplCollector<T> {
1034 fn py_methods(self) -> &'static PyClassItems {
1035 &PyClassItems {
1036 methods: &[],
1037 slots: &[],
1038 }
1039 }
1040}
1041
1042#[doc(hidden)]
1045pub trait PyClassThreadChecker<T>: Sized + sealed::Sealed {
1046 fn ensure(&self);
1047 fn check(&self) -> bool;
1048 fn can_drop(&self, py: Python<'_>) -> bool;
1049 fn new() -> Self;
1050}
1051
1052#[doc(hidden)]
1058pub struct SendablePyClass<T: Send>(PhantomData<T>);
1059
1060impl<T: Send> PyClassThreadChecker<T> for SendablePyClass<T> {
1061 fn ensure(&self) {}
1062 fn check(&self) -> bool {
1063 true
1064 }
1065 fn can_drop(&self, _py: Python<'_>) -> bool {
1066 true
1067 }
1068 #[inline]
1069 fn new() -> Self {
1070 SendablePyClass(PhantomData)
1071 }
1072}
1073
1074#[doc(hidden)]
1077pub struct ThreadCheckerImpl(thread::ThreadId);
1078
1079impl ThreadCheckerImpl {
1080 fn ensure(&self, type_name: &'static str) {
1081 assert_eq!(
1082 thread::current().id(),
1083 self.0,
1084 "{type_name} is unsendable, but sent to another thread"
1085 );
1086 }
1087
1088 fn check(&self) -> bool {
1089 thread::current().id() == self.0
1090 }
1091
1092 fn can_drop(&self, py: Python<'_>, type_name: &'static str) -> bool {
1093 if thread::current().id() != self.0 {
1094 PyRuntimeError::new_err(format!(
1095 "{type_name} is unsendable, but is being dropped on another thread"
1096 ))
1097 .write_unraisable(py, None);
1098 return false;
1099 }
1100
1101 true
1102 }
1103}
1104
1105impl<T> PyClassThreadChecker<T> for ThreadCheckerImpl {
1106 fn ensure(&self) {
1107 self.ensure(std::any::type_name::<T>());
1108 }
1109 fn check(&self) -> bool {
1110 self.check()
1111 }
1112 fn can_drop(&self, py: Python<'_>) -> bool {
1113 self.can_drop(py, std::any::type_name::<T>())
1114 }
1115 fn new() -> Self {
1116 ThreadCheckerImpl(thread::current().id())
1117 }
1118}
1119
1120#[cfg_attr(
1122 all(diagnostic_namespace, Py_LIMITED_API),
1123 diagnostic::on_unimplemented(
1124 message = "pyclass `{Self}` cannot be subclassed",
1125 label = "required for `#[pyclass(extends={Self})]`",
1126 note = "`{Self}` must have `#[pyclass(subclass)]` to be eligible for subclassing",
1127 note = "with the `abi3` feature enabled, PyO3 does not support subclassing native types",
1128 )
1129)]
1130#[cfg_attr(
1131 all(diagnostic_namespace, not(Py_LIMITED_API)),
1132 diagnostic::on_unimplemented(
1133 message = "pyclass `{Self}` cannot be subclassed",
1134 label = "required for `#[pyclass(extends={Self})]`",
1135 note = "`{Self}` must have `#[pyclass(subclass)]` to be eligible for subclassing",
1136 )
1137)]
1138pub trait PyClassBaseType: Sized {
1139 type LayoutAsBase: PyClassObjectLayout<Self>;
1140 type BaseNativeType;
1141 type Initializer: PyObjectInit<Self>;
1142 type PyClassMutability: PyClassMutability;
1143}
1144
1145pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
1147 unsafe { crate::impl_::trampoline::dealloc(obj, PyClassObject::<T>::tp_dealloc) }
1148}
1149
1150pub(crate) unsafe extern "C" fn tp_dealloc_with_gc<T: PyClass>(obj: *mut ffi::PyObject) {
1152 #[cfg(not(PyPy))]
1153 unsafe {
1154 ffi::PyObject_GC_UnTrack(obj.cast());
1155 }
1156 unsafe { crate::impl_::trampoline::dealloc(obj, PyClassObject::<T>::tp_dealloc) }
1157}
1158
1159pub(crate) unsafe extern "C" fn get_sequence_item_from_mapping(
1160 obj: *mut ffi::PyObject,
1161 index: ffi::Py_ssize_t,
1162) -> *mut ffi::PyObject {
1163 let index = unsafe { ffi::PyLong_FromSsize_t(index) };
1164 if index.is_null() {
1165 return std::ptr::null_mut();
1166 }
1167 let result = unsafe { ffi::PyObject_GetItem(obj, index) };
1168 unsafe { ffi::Py_DECREF(index) };
1169 result
1170}
1171
1172pub(crate) unsafe extern "C" fn assign_sequence_item_from_mapping(
1173 obj: *mut ffi::PyObject,
1174 index: ffi::Py_ssize_t,
1175 value: *mut ffi::PyObject,
1176) -> c_int {
1177 unsafe {
1178 let index = ffi::PyLong_FromSsize_t(index);
1179 if index.is_null() {
1180 return -1;
1181 }
1182 let result = if value.is_null() {
1183 ffi::PyObject_DelItem(obj, index)
1184 } else {
1185 ffi::PyObject_SetItem(obj, index, value)
1186 };
1187 ffi::Py_DECREF(index);
1188 result
1189 }
1190}
1191
1192pub unsafe trait OffsetCalculator<T: PyClass, U> {
1201 fn offset() -> usize;
1203}
1204
1205pub fn class_offset<T: PyClass>() -> usize {
1207 offset_of!(PyClassObject<T>, contents)
1208}
1209
1210pub use memoffset::offset_of;
1212
1213pub struct PyClassGetterGenerator<
1216 ClassT: PyClass,
1219 FieldT,
1220 Offset: OffsetCalculator<ClassT, FieldT>, const IS_PY_T: bool,
1224 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1225 const IMPLEMENTS_INTOPYOBJECT: bool,
1226>(PhantomData<(ClassT, FieldT, Offset)>);
1227
1228impl<
1229 ClassT: PyClass,
1230 FieldT,
1231 Offset: OffsetCalculator<ClassT, FieldT>,
1232 const IS_PY_T: bool,
1233 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1234 const IMPLEMENTS_INTOPYOBJECT: bool,
1235 >
1236 PyClassGetterGenerator<
1237 ClassT,
1238 FieldT,
1239 Offset,
1240 IS_PY_T,
1241 IMPLEMENTS_INTOPYOBJECT_REF,
1242 IMPLEMENTS_INTOPYOBJECT,
1243 >
1244{
1245 pub const unsafe fn new() -> Self {
1248 Self(PhantomData)
1249 }
1250}
1251
1252impl<
1253 ClassT: PyClass,
1254 U,
1255 Offset: OffsetCalculator<ClassT, Py<U>>,
1256 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1257 const IMPLEMENTS_INTOPYOBJECT: bool,
1258 >
1259 PyClassGetterGenerator<
1260 ClassT,
1261 Py<U>,
1262 Offset,
1263 true,
1264 IMPLEMENTS_INTOPYOBJECT_REF,
1265 IMPLEMENTS_INTOPYOBJECT,
1266 >
1267{
1268 pub fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1274 use crate::pyclass::boolean_struct::private::Boolean;
1275 if ClassT::Frozen::VALUE {
1276 PyMethodDefType::StructMember(ffi::PyMemberDef {
1277 name: name.as_ptr(),
1278 type_code: ffi::Py_T_OBJECT_EX,
1279 offset: Offset::offset() as ffi::Py_ssize_t,
1280 flags: ffi::Py_READONLY,
1281 doc: doc.as_ptr(),
1282 })
1283 } else {
1284 PyMethodDefType::Getter(PyGetterDef {
1285 name,
1286 meth: pyo3_get_value_into_pyobject_ref::<ClassT, Py<U>, Offset>,
1287 doc,
1288 })
1289 }
1290 }
1291}
1292
1293impl<ClassT, FieldT, Offset, const IMPLEMENTS_INTOPYOBJECT: bool>
1296 PyClassGetterGenerator<ClassT, FieldT, Offset, false, true, IMPLEMENTS_INTOPYOBJECT>
1297where
1298 ClassT: PyClass,
1299 for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1300 Offset: OffsetCalculator<ClassT, FieldT>,
1301{
1302 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1303 PyMethodDefType::Getter(PyGetterDef {
1304 name,
1305 meth: pyo3_get_value_into_pyobject_ref::<ClassT, FieldT, Offset>,
1306 doc,
1307 })
1308 }
1309}
1310
1311#[cfg_attr(
1312 diagnostic_namespace,
1313 diagnostic::on_unimplemented(
1314 message = "`{Self}` cannot be converted to a Python object",
1315 label = "required by `#[pyo3(get)]` to create a readable property from a field of type `{Self}`",
1316 note = "implement `IntoPyObject` for `&{Self}` or `IntoPyObject + Clone` for `{Self}` to define the conversion"
1317 )
1318)]
1319pub trait PyO3GetField<'py>: IntoPyObject<'py> + Clone {}
1320impl<'py, T> PyO3GetField<'py> for T where T: IntoPyObject<'py> + Clone {}
1321
1322impl<
1324 ClassT: PyClass,
1325 FieldT,
1326 Offset: OffsetCalculator<ClassT, FieldT>,
1327 const IMPLEMENTS_INTOPYOBJECT: bool,
1328 > PyClassGetterGenerator<ClassT, FieldT, Offset, false, false, IMPLEMENTS_INTOPYOBJECT>
1329{
1330 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType
1331 where
1334 for<'py> FieldT: PyO3GetField<'py>,
1335 {
1336 PyMethodDefType::Getter(PyGetterDef {
1337 name,
1338 meth: pyo3_get_value_into_pyobject::<ClassT, FieldT, Offset>,
1339 doc,
1340 })
1341 }
1342}
1343
1344#[inline]
1346unsafe fn ensure_no_mutable_alias<'a, ClassT: PyClass>(
1347 _py: Python<'_>,
1348 obj: &'a *mut ffi::PyObject,
1349) -> Result<PyClassGuard<'a, ClassT>, PyBorrowError> {
1350 unsafe { PyClassGuard::try_borrow(NonNull::from(obj).cast::<Py<ClassT>>().as_ref()) }
1351}
1352
1353#[inline]
1355fn field_from_object<ClassT, FieldT, Offset>(obj: *mut ffi::PyObject) -> *mut FieldT
1356where
1357 ClassT: PyClass,
1358 Offset: OffsetCalculator<ClassT, FieldT>,
1359{
1360 unsafe { obj.cast::<u8>().add(Offset::offset()).cast::<FieldT>() }
1361}
1362
1363fn pyo3_get_value_into_pyobject_ref<ClassT, FieldT, Offset>(
1364 py: Python<'_>,
1365 obj: *mut ffi::PyObject,
1366) -> PyResult<*mut ffi::PyObject>
1367where
1368 ClassT: PyClass,
1369 for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1370 Offset: OffsetCalculator<ClassT, FieldT>,
1371{
1372 let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1373 let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1374
1375 Ok((unsafe { &*value })
1378 .into_pyobject(py)
1379 .map_err(Into::into)?
1380 .into_ptr())
1381}
1382
1383fn pyo3_get_value_into_pyobject<ClassT, FieldT, Offset>(
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 Offset: OffsetCalculator<ClassT, FieldT>,
1391{
1392 let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1393 let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1394
1395 Ok((unsafe { &*value })
1398 .clone()
1399 .into_pyobject(py)
1400 .map_err(Into::into)?
1401 .into_ptr())
1402}
1403
1404pub struct ConvertField<
1405 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1406 const IMPLEMENTS_INTOPYOBJECT: bool,
1407>;
1408
1409impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<true, IMPLEMENTS_INTOPYOBJECT> {
1410 #[inline]
1411 pub fn convert_field<'a, 'py, T>(obj: &'a T, py: Python<'py>) -> PyResult<Py<PyAny>>
1412 where
1413 &'a T: IntoPyObject<'py>,
1414 {
1415 obj.into_py_any(py)
1416 }
1417}
1418
1419impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<false, IMPLEMENTS_INTOPYOBJECT> {
1420 #[inline]
1421 pub fn convert_field<'py, T>(obj: &T, py: Python<'py>) -> PyResult<Py<PyAny>>
1422 where
1423 T: PyO3GetField<'py>,
1424 {
1425 obj.clone().into_py_any(py)
1426 }
1427}
1428
1429#[cfg(test)]
1430#[cfg(feature = "macros")]
1431mod tests {
1432 use super::*;
1433
1434 #[test]
1435 fn get_py_for_frozen_class() {
1436 #[crate::pyclass(crate = "crate", frozen)]
1437 struct FrozenClass {
1438 #[pyo3(get)]
1439 value: Py<PyAny>,
1440 }
1441
1442 let mut methods = Vec::new();
1443 let mut slots = Vec::new();
1444
1445 for items in FrozenClass::items_iter() {
1446 methods.extend(items.methods.iter().map(|m| match m {
1447 MaybeRuntimePyMethodDef::Static(m) => m.clone(),
1448 MaybeRuntimePyMethodDef::Runtime(r) => r(),
1449 }));
1450 slots.extend_from_slice(items.slots);
1451 }
1452
1453 assert_eq!(methods.len(), 1);
1454 assert!(slots.is_empty());
1455
1456 match methods.first() {
1457 Some(PyMethodDefType::StructMember(member)) => {
1458 assert_eq!(unsafe { CStr::from_ptr(member.name) }, ffi::c_str!("value"));
1459 assert_eq!(member.type_code, ffi::Py_T_OBJECT_EX);
1460 assert_eq!(
1461 member.offset,
1462 (memoffset::offset_of!(PyClassObject<FrozenClass>, contents)
1463 + memoffset::offset_of!(FrozenClass, value))
1464 as ffi::Py_ssize_t
1465 );
1466 assert_eq!(member.flags, ffi::Py_READONLY);
1467 }
1468 _ => panic!("Expected a StructMember"),
1469 }
1470 }
1471
1472 #[test]
1473 fn get_py_for_non_frozen_class() {
1474 #[crate::pyclass(crate = "crate")]
1475 struct FrozenClass {
1476 #[pyo3(get)]
1477 value: Py<PyAny>,
1478 }
1479
1480 let mut methods = Vec::new();
1481 let mut slots = Vec::new();
1482
1483 for items in FrozenClass::items_iter() {
1484 methods.extend(items.methods.iter().map(|m| match m {
1485 MaybeRuntimePyMethodDef::Static(m) => m.clone(),
1486 MaybeRuntimePyMethodDef::Runtime(r) => r(),
1487 }));
1488 slots.extend_from_slice(items.slots);
1489 }
1490
1491 assert_eq!(methods.len(), 1);
1492 assert!(slots.is_empty());
1493
1494 match methods.first() {
1495 Some(PyMethodDefType::Getter(getter)) => {
1496 assert_eq!(getter.name, ffi::c_str!("value"));
1497 assert_eq!(getter.doc, ffi::c_str!(""));
1498 }
1500 _ => panic!("Expected a StructMember"),
1501 }
1502 }
1503}