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};
15use std::{
16 borrow::Cow,
17 ffi::{CStr, CString},
18 marker::PhantomData,
19 os::raw::{c_int, c_void},
20 ptr,
21 ptr::NonNull,
22 sync::Mutex,
23 thread,
24};
25
26mod assertions;
27mod lazy_type_object;
28mod probes;
29
30pub use assertions::*;
31pub use lazy_type_object::LazyTypeObject;
32pub use probes::*;
33
34#[inline]
36pub fn dict_offset<T: PyClass>() -> ffi::Py_ssize_t {
37 PyClassObject::<T>::dict_offset()
38}
39
40#[inline]
42pub fn weaklist_offset<T: PyClass>() -> ffi::Py_ssize_t {
43 PyClassObject::<T>::weaklist_offset()
44}
45
46mod sealed {
47 pub trait Sealed {}
48
49 impl Sealed for super::PyClassDummySlot {}
50 impl Sealed for super::PyClassDictSlot {}
51 impl Sealed for super::PyClassWeakRefSlot {}
52 impl Sealed for super::ThreadCheckerImpl {}
53 impl<T: Send> Sealed for super::SendablePyClass<T> {}
54}
55
56pub trait PyClassDict: sealed::Sealed {
58 const INIT: Self;
60 #[inline]
62 fn clear_dict(&mut self, _py: Python<'_>) {}
63}
64
65pub trait PyClassWeakRef: sealed::Sealed {
67 const INIT: Self;
69 #[inline]
75 unsafe fn clear_weakrefs(&mut self, _obj: *mut ffi::PyObject, _py: Python<'_>) {}
76}
77
78pub struct PyClassDummySlot;
80
81impl PyClassDict for PyClassDummySlot {
82 const INIT: Self = PyClassDummySlot;
83}
84
85impl PyClassWeakRef for PyClassDummySlot {
86 const INIT: Self = PyClassDummySlot;
87}
88
89#[repr(transparent)]
93#[allow(dead_code)] pub struct PyClassDictSlot(*mut ffi::PyObject);
95
96impl PyClassDict for PyClassDictSlot {
97 const INIT: Self = Self(std::ptr::null_mut());
98 #[inline]
99 fn clear_dict(&mut self, _py: Python<'_>) {
100 if !self.0.is_null() {
101 unsafe { ffi::PyDict_Clear(self.0) }
102 }
103 }
104}
105
106#[repr(transparent)]
110#[allow(dead_code)] pub struct PyClassWeakRefSlot(*mut ffi::PyObject);
112
113impl PyClassWeakRef for PyClassWeakRefSlot {
114 const INIT: Self = Self(std::ptr::null_mut());
115 #[inline]
116 unsafe fn clear_weakrefs(&mut self, obj: *mut ffi::PyObject, _py: Python<'_>) {
117 if !self.0.is_null() {
118 unsafe { ffi::PyObject_ClearWeakRefs(obj) }
119 }
120 }
121}
122
123pub struct PyClassImplCollector<T>(PhantomData<T>);
126
127impl<T> PyClassImplCollector<T> {
128 pub fn new() -> Self {
129 Self(PhantomData)
130 }
131}
132
133impl<T> Default for PyClassImplCollector<T> {
134 fn default() -> Self {
135 Self::new()
136 }
137}
138
139impl<T> Clone for PyClassImplCollector<T> {
140 fn clone(&self) -> Self {
141 *self
142 }
143}
144
145impl<T> Copy for PyClassImplCollector<T> {}
146
147pub enum MaybeRuntimePyMethodDef {
148 Runtime(fn() -> PyMethodDefType),
151 Static(PyMethodDefType),
152}
153
154pub struct PyClassItems {
155 pub methods: &'static [MaybeRuntimePyMethodDef],
156 pub slots: &'static [ffi::PyType_Slot],
157}
158
159unsafe impl Sync for PyClassItems {}
161
162pub trait PyClassImpl: Sized + 'static {
167 const IS_BASETYPE: bool = false;
169
170 const IS_SUBCLASS: bool = false;
172
173 const IS_MAPPING: bool = false;
175
176 const IS_SEQUENCE: bool = false;
178
179 const IS_IMMUTABLE_TYPE: bool = false;
181
182 type BaseType: PyTypeInfo + PyClassBaseType;
184
185 type PyClassMutability: PyClassMutability + GetBorrowChecker<Self>;
187
188 type Dict: PyClassDict;
190
191 type WeakRef: PyClassWeakRef;
193
194 type BaseNativeType: PyTypeInfo;
197
198 type ThreadChecker: PyClassThreadChecker<Self>;
206
207 #[cfg(feature = "multiple-pymethods")]
208 type Inventory: PyClassInventory;
209
210 fn doc(py: Python<'_>) -> PyResult<&'static CStr>;
212
213 fn items_iter() -> PyClassItemsIter;
214
215 #[inline]
216 fn dict_offset() -> Option<ffi::Py_ssize_t> {
217 None
218 }
219
220 #[inline]
221 fn weaklist_offset() -> Option<ffi::Py_ssize_t> {
222 None
223 }
224
225 fn lazy_type_object() -> &'static LazyTypeObject<Self>;
226}
227
228pub fn build_pyclass_doc(
233 class_name: &'static str,
234 doc: &'static CStr,
235 text_signature: Option<&'static str>,
236) -> PyResult<Cow<'static, CStr>> {
237 if let Some(text_signature) = text_signature {
238 let doc = CString::new(format!(
239 "{}{}\n--\n\n{}",
240 class_name,
241 text_signature,
242 doc.to_str().unwrap(),
243 ))
244 .map_err(|_| PyValueError::new_err("class doc cannot contain nul bytes"))?;
245 Ok(Cow::Owned(doc))
246 } else {
247 Ok(Cow::Borrowed(doc))
248 }
249}
250
251pub struct PyClassItemsIter {
253 idx: usize,
255 pyclass_items: &'static PyClassItems,
257 #[cfg(not(feature = "multiple-pymethods"))]
259 pymethods_items: &'static PyClassItems,
260 #[cfg(feature = "multiple-pymethods")]
262 pymethods_items: Box<dyn Iterator<Item = &'static PyClassItems>>,
263}
264
265impl PyClassItemsIter {
266 pub fn new(
267 pyclass_items: &'static PyClassItems,
268 #[cfg(not(feature = "multiple-pymethods"))] pymethods_items: &'static PyClassItems,
269 #[cfg(feature = "multiple-pymethods")] pymethods_items: Box<
270 dyn Iterator<Item = &'static PyClassItems>,
271 >,
272 ) -> Self {
273 Self {
274 idx: 0,
275 pyclass_items,
276 pymethods_items,
277 }
278 }
279}
280
281impl Iterator for PyClassItemsIter {
282 type Item = &'static PyClassItems;
283
284 #[cfg(not(feature = "multiple-pymethods"))]
285 fn next(&mut self) -> Option<Self::Item> {
286 match self.idx {
287 0 => {
288 self.idx += 1;
289 Some(self.pyclass_items)
290 }
291 1 => {
292 self.idx += 1;
293 Some(self.pymethods_items)
294 }
295 _ => None,
297 }
298 }
299
300 #[cfg(feature = "multiple-pymethods")]
301 fn next(&mut self) -> Option<Self::Item> {
302 match self.idx {
303 0 => {
304 self.idx += 1;
305 Some(self.pyclass_items)
306 }
307 _ => self.pymethods_items.next(),
309 }
310 }
311}
312
313macro_rules! slot_fragment_trait {
316 ($trait_name:ident, $($default_method:tt)*) => {
317 #[allow(non_camel_case_types)]
318 pub trait $trait_name<T>: Sized {
319 $($default_method)*
320 }
321
322 impl<T> $trait_name<T> for &'_ PyClassImplCollector<T> {}
323 }
324}
325
326slot_fragment_trait! {
327 PyClass__getattribute__SlotFragment,
328
329 #[inline]
331 unsafe fn __getattribute__(
332 self,
333 py: Python<'_>,
334 slf: *mut ffi::PyObject,
335 attr: *mut ffi::PyObject,
336 ) -> PyResult<*mut ffi::PyObject> {
337 let res = unsafe { ffi::PyObject_GenericGetAttr(slf, attr) };
338 if res.is_null() {
339 Err(PyErr::fetch(py))
340 } else {
341 Ok(res)
342 }
343 }
344}
345
346slot_fragment_trait! {
347 PyClass__getattr__SlotFragment,
348
349 #[inline]
351 unsafe fn __getattr__(
352 self,
353 py: Python<'_>,
354 _slf: *mut ffi::PyObject,
355 attr: *mut ffi::PyObject,
356 ) -> PyResult<*mut ffi::PyObject> {
357 Err(PyErr::new::<PyAttributeError, _>(
358 (unsafe {Py::<PyAny>::from_borrowed_ptr(py, attr)},)
359 ))
360 }
361}
362
363#[doc(hidden)]
364#[macro_export]
365macro_rules! generate_pyclass_getattro_slot {
366 ($cls:ty) => {{
367 unsafe extern "C" fn __wrap(
368 _slf: *mut $crate::ffi::PyObject,
369 attr: *mut $crate::ffi::PyObject,
370 ) -> *mut $crate::ffi::PyObject {
371 unsafe {
372 $crate::impl_::trampoline::getattrofunc(_slf, attr, |py, _slf, attr| {
373 use ::std::result::Result::*;
374 use $crate::impl_::pyclass::*;
375 let collector = PyClassImplCollector::<$cls>::new();
376
377 match collector.__getattribute__(py, _slf, attr) {
383 Ok(obj) => Ok(obj),
384 Err(e) if e.is_instance_of::<$crate::exceptions::PyAttributeError>(py) => {
385 collector.__getattr__(py, _slf, attr)
386 }
387 Err(e) => Err(e),
388 }
389 })
390 }
391 }
392 $crate::ffi::PyType_Slot {
393 slot: $crate::ffi::Py_tp_getattro,
394 pfunc: __wrap as $crate::ffi::getattrofunc as _,
395 }
396 }};
397}
398
399pub use generate_pyclass_getattro_slot;
400
401macro_rules! define_pyclass_setattr_slot {
406 (
407 $set_trait:ident,
408 $del_trait:ident,
409 $set:ident,
410 $del:ident,
411 $set_error:expr,
412 $del_error:expr,
413 $generate_macro:ident,
414 $slot:ident,
415 $func_ty:ident,
416 ) => {
417 slot_fragment_trait! {
418 $set_trait,
419
420 #[inline]
422 unsafe fn $set(
423 self,
424 _py: Python<'_>,
425 _slf: *mut ffi::PyObject,
426 _attr: *mut ffi::PyObject,
427 _value: NonNull<ffi::PyObject>,
428 ) -> PyResult<()> {
429 $set_error
430 }
431 }
432
433 slot_fragment_trait! {
434 $del_trait,
435
436 #[inline]
438 unsafe fn $del(
439 self,
440 _py: Python<'_>,
441 _slf: *mut ffi::PyObject,
442 _attr: *mut ffi::PyObject,
443 ) -> PyResult<()> {
444 $del_error
445 }
446 }
447
448 #[doc(hidden)]
449 #[macro_export]
450 macro_rules! $generate_macro {
451 ($cls:ty) => {{
452 unsafe extern "C" fn __wrap(
453 _slf: *mut $crate::ffi::PyObject,
454 attr: *mut $crate::ffi::PyObject,
455 value: *mut $crate::ffi::PyObject,
456 ) -> ::std::os::raw::c_int {
457 unsafe {
458 $crate::impl_::trampoline::setattrofunc(
459 _slf,
460 attr,
461 value,
462 |py, _slf, attr, value| {
463 use ::std::option::Option::*;
464 use $crate::impl_::callback::IntoPyCallbackOutput;
465 use $crate::impl_::pyclass::*;
466 let collector = PyClassImplCollector::<$cls>::new();
467 if let Some(value) = ::std::ptr::NonNull::new(value) {
468 collector.$set(py, _slf, attr, value).convert(py)
469 } else {
470 collector.$del(py, _slf, attr).convert(py)
471 }
472 },
473 )
474 }
475 }
476 $crate::ffi::PyType_Slot {
477 slot: $crate::ffi::$slot,
478 pfunc: __wrap as $crate::ffi::$func_ty as _,
479 }
480 }};
481 }
482 pub use $generate_macro;
483 };
484}
485
486define_pyclass_setattr_slot! {
487 PyClass__setattr__SlotFragment,
488 PyClass__delattr__SlotFragment,
489 __setattr__,
490 __delattr__,
491 Err(PyAttributeError::new_err("can't set attribute")),
492 Err(PyAttributeError::new_err("can't delete attribute")),
493 generate_pyclass_setattr_slot,
494 Py_tp_setattro,
495 setattrofunc,
496}
497
498define_pyclass_setattr_slot! {
499 PyClass__set__SlotFragment,
500 PyClass__delete__SlotFragment,
501 __set__,
502 __delete__,
503 Err(PyNotImplementedError::new_err("can't set descriptor")),
504 Err(PyNotImplementedError::new_err("can't delete descriptor")),
505 generate_pyclass_setdescr_slot,
506 Py_tp_descr_set,
507 descrsetfunc,
508}
509
510define_pyclass_setattr_slot! {
511 PyClass__setitem__SlotFragment,
512 PyClass__delitem__SlotFragment,
513 __setitem__,
514 __delitem__,
515 Err(PyNotImplementedError::new_err("can't set item")),
516 Err(PyNotImplementedError::new_err("can't delete item")),
517 generate_pyclass_setitem_slot,
518 Py_mp_ass_subscript,
519 objobjargproc,
520}
521
522macro_rules! define_pyclass_binary_operator_slot {
527 (
528 $lhs_trait:ident,
529 $rhs_trait:ident,
530 $lhs:ident,
531 $rhs:ident,
532 $generate_macro:ident,
533 $slot:ident,
534 $func_ty:ident,
535 ) => {
536 slot_fragment_trait! {
537 $lhs_trait,
538
539 #[inline]
541 unsafe fn $lhs(
542 self,
543 py: Python<'_>,
544 _slf: *mut ffi::PyObject,
545 _other: *mut ffi::PyObject,
546 ) -> PyResult<*mut ffi::PyObject> {
547 Ok(py.NotImplemented().into_ptr())
548 }
549 }
550
551 slot_fragment_trait! {
552 $rhs_trait,
553
554 #[inline]
556 unsafe fn $rhs(
557 self,
558 py: Python<'_>,
559 _slf: *mut ffi::PyObject,
560 _other: *mut ffi::PyObject,
561 ) -> PyResult<*mut ffi::PyObject> {
562 Ok(py.NotImplemented().into_ptr())
563 }
564 }
565
566 #[doc(hidden)]
567 #[macro_export]
568 macro_rules! $generate_macro {
569 ($cls:ty) => {{
570 unsafe extern "C" fn __wrap(
571 _slf: *mut $crate::ffi::PyObject,
572 _other: *mut $crate::ffi::PyObject,
573 ) -> *mut $crate::ffi::PyObject {
574 unsafe {
575 $crate::impl_::trampoline::binaryfunc(_slf, _other, |py, _slf, _other| {
576 use $crate::impl_::pyclass::*;
577 let collector = PyClassImplCollector::<$cls>::new();
578 let lhs_result = collector.$lhs(py, _slf, _other)?;
579 if lhs_result == $crate::ffi::Py_NotImplemented() {
580 $crate::ffi::Py_DECREF(lhs_result);
581 collector.$rhs(py, _other, _slf)
582 } else {
583 ::std::result::Result::Ok(lhs_result)
584 }
585 })
586 }
587 }
588 $crate::ffi::PyType_Slot {
589 slot: $crate::ffi::$slot,
590 pfunc: __wrap as $crate::ffi::$func_ty as _,
591 }
592 }};
593 }
594 pub use $generate_macro;
595 };
596}
597
598define_pyclass_binary_operator_slot! {
599 PyClass__add__SlotFragment,
600 PyClass__radd__SlotFragment,
601 __add__,
602 __radd__,
603 generate_pyclass_add_slot,
604 Py_nb_add,
605 binaryfunc,
606}
607
608define_pyclass_binary_operator_slot! {
609 PyClass__sub__SlotFragment,
610 PyClass__rsub__SlotFragment,
611 __sub__,
612 __rsub__,
613 generate_pyclass_sub_slot,
614 Py_nb_subtract,
615 binaryfunc,
616}
617
618define_pyclass_binary_operator_slot! {
619 PyClass__mul__SlotFragment,
620 PyClass__rmul__SlotFragment,
621 __mul__,
622 __rmul__,
623 generate_pyclass_mul_slot,
624 Py_nb_multiply,
625 binaryfunc,
626}
627
628define_pyclass_binary_operator_slot! {
629 PyClass__mod__SlotFragment,
630 PyClass__rmod__SlotFragment,
631 __mod__,
632 __rmod__,
633 generate_pyclass_mod_slot,
634 Py_nb_remainder,
635 binaryfunc,
636}
637
638define_pyclass_binary_operator_slot! {
639 PyClass__divmod__SlotFragment,
640 PyClass__rdivmod__SlotFragment,
641 __divmod__,
642 __rdivmod__,
643 generate_pyclass_divmod_slot,
644 Py_nb_divmod,
645 binaryfunc,
646}
647
648define_pyclass_binary_operator_slot! {
649 PyClass__lshift__SlotFragment,
650 PyClass__rlshift__SlotFragment,
651 __lshift__,
652 __rlshift__,
653 generate_pyclass_lshift_slot,
654 Py_nb_lshift,
655 binaryfunc,
656}
657
658define_pyclass_binary_operator_slot! {
659 PyClass__rshift__SlotFragment,
660 PyClass__rrshift__SlotFragment,
661 __rshift__,
662 __rrshift__,
663 generate_pyclass_rshift_slot,
664 Py_nb_rshift,
665 binaryfunc,
666}
667
668define_pyclass_binary_operator_slot! {
669 PyClass__and__SlotFragment,
670 PyClass__rand__SlotFragment,
671 __and__,
672 __rand__,
673 generate_pyclass_and_slot,
674 Py_nb_and,
675 binaryfunc,
676}
677
678define_pyclass_binary_operator_slot! {
679 PyClass__or__SlotFragment,
680 PyClass__ror__SlotFragment,
681 __or__,
682 __ror__,
683 generate_pyclass_or_slot,
684 Py_nb_or,
685 binaryfunc,
686}
687
688define_pyclass_binary_operator_slot! {
689 PyClass__xor__SlotFragment,
690 PyClass__rxor__SlotFragment,
691 __xor__,
692 __rxor__,
693 generate_pyclass_xor_slot,
694 Py_nb_xor,
695 binaryfunc,
696}
697
698define_pyclass_binary_operator_slot! {
699 PyClass__matmul__SlotFragment,
700 PyClass__rmatmul__SlotFragment,
701 __matmul__,
702 __rmatmul__,
703 generate_pyclass_matmul_slot,
704 Py_nb_matrix_multiply,
705 binaryfunc,
706}
707
708define_pyclass_binary_operator_slot! {
709 PyClass__truediv__SlotFragment,
710 PyClass__rtruediv__SlotFragment,
711 __truediv__,
712 __rtruediv__,
713 generate_pyclass_truediv_slot,
714 Py_nb_true_divide,
715 binaryfunc,
716}
717
718define_pyclass_binary_operator_slot! {
719 PyClass__floordiv__SlotFragment,
720 PyClass__rfloordiv__SlotFragment,
721 __floordiv__,
722 __rfloordiv__,
723 generate_pyclass_floordiv_slot,
724 Py_nb_floor_divide,
725 binaryfunc,
726}
727
728slot_fragment_trait! {
729 PyClass__pow__SlotFragment,
730
731 #[inline]
733 unsafe fn __pow__(
734 self,
735 py: Python<'_>,
736 _slf: *mut ffi::PyObject,
737 _other: *mut ffi::PyObject,
738 _mod: *mut ffi::PyObject,
739 ) -> PyResult<*mut ffi::PyObject> {
740 Ok(py.NotImplemented().into_ptr())
741 }
742}
743
744slot_fragment_trait! {
745 PyClass__rpow__SlotFragment,
746
747 #[inline]
749 unsafe fn __rpow__(
750 self,
751 py: Python<'_>,
752 _slf: *mut ffi::PyObject,
753 _other: *mut ffi::PyObject,
754 _mod: *mut ffi::PyObject,
755 ) -> PyResult<*mut ffi::PyObject> {
756 Ok(py.NotImplemented().into_ptr())
757 }
758}
759
760#[doc(hidden)]
761#[macro_export]
762macro_rules! generate_pyclass_pow_slot {
763 ($cls:ty) => {{
764 unsafe extern "C" fn __wrap(
765 _slf: *mut $crate::ffi::PyObject,
766 _other: *mut $crate::ffi::PyObject,
767 _mod: *mut $crate::ffi::PyObject,
768 ) -> *mut $crate::ffi::PyObject {
769 unsafe {
770 $crate::impl_::trampoline::ternaryfunc(
771 _slf,
772 _other,
773 _mod,
774 |py, _slf, _other, _mod| {
775 use $crate::impl_::pyclass::*;
776 let collector = PyClassImplCollector::<$cls>::new();
777 let lhs_result = collector.__pow__(py, _slf, _other, _mod)?;
778 if lhs_result == $crate::ffi::Py_NotImplemented() {
779 $crate::ffi::Py_DECREF(lhs_result);
780 collector.__rpow__(py, _other, _slf, _mod)
781 } else {
782 ::std::result::Result::Ok(lhs_result)
783 }
784 },
785 )
786 }
787 }
788 $crate::ffi::PyType_Slot {
789 slot: $crate::ffi::Py_nb_power,
790 pfunc: __wrap as $crate::ffi::ternaryfunc as _,
791 }
792 }};
793}
794pub use generate_pyclass_pow_slot;
795
796slot_fragment_trait! {
797 PyClass__lt__SlotFragment,
798
799 #[inline]
801 unsafe fn __lt__(
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__le__SlotFragment,
813
814 #[inline]
816 unsafe fn __le__(
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__eq__SlotFragment,
828
829 #[inline]
831 unsafe fn __eq__(
832 self,
833 py: Python<'_>,
834 _slf: *mut ffi::PyObject,
835 _other: *mut ffi::PyObject,
836 ) -> PyResult<*mut ffi::PyObject> {
837 Ok(py.NotImplemented().into_ptr())
838 }
839}
840
841slot_fragment_trait! {
842 PyClass__ne__SlotFragment,
843
844 #[inline]
846 unsafe fn __ne__(
847 self,
848 py: Python<'_>,
849 slf: *mut ffi::PyObject,
850 other: *mut ffi::PyObject,
851 ) -> PyResult<*mut ffi::PyObject> {
852 let slf = unsafe { Borrowed::from_ptr(py, slf)};
854 let other = unsafe { Borrowed::from_ptr(py, other)};
855 slf.eq(other).map(|is_eq| PyBool::new(py, !is_eq).to_owned().into_ptr())
856 }
857}
858
859slot_fragment_trait! {
860 PyClass__gt__SlotFragment,
861
862 #[inline]
864 unsafe fn __gt__(
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
874slot_fragment_trait! {
875 PyClass__ge__SlotFragment,
876
877 #[inline]
879 unsafe fn __ge__(
880 self,
881 py: Python<'_>,
882 _slf: *mut ffi::PyObject,
883 _other: *mut ffi::PyObject,
884 ) -> PyResult<*mut ffi::PyObject> {
885 Ok(py.NotImplemented().into_ptr())
886 }
887}
888
889#[doc(hidden)]
890#[macro_export]
891macro_rules! generate_pyclass_richcompare_slot {
892 ($cls:ty) => {{
893 #[allow(unknown_lints, non_local_definitions)]
894 impl $cls {
895 #[allow(non_snake_case)]
896 unsafe extern "C" fn __pymethod___richcmp____(
897 slf: *mut $crate::ffi::PyObject,
898 other: *mut $crate::ffi::PyObject,
899 op: ::std::os::raw::c_int,
900 ) -> *mut $crate::ffi::PyObject {
901 unsafe {
902 $crate::impl_::trampoline::richcmpfunc(slf, other, op, |py, slf, other, op| {
903 use $crate::class::basic::CompareOp;
904 use $crate::impl_::pyclass::*;
905 let collector = PyClassImplCollector::<$cls>::new();
906 match CompareOp::from_raw(op).expect("invalid compareop") {
907 CompareOp::Lt => collector.__lt__(py, slf, other),
908 CompareOp::Le => collector.__le__(py, slf, other),
909 CompareOp::Eq => collector.__eq__(py, slf, other),
910 CompareOp::Ne => collector.__ne__(py, slf, other),
911 CompareOp::Gt => collector.__gt__(py, slf, other),
912 CompareOp::Ge => collector.__ge__(py, slf, other),
913 }
914 })
915 }
916 }
917 }
918 $crate::ffi::PyType_Slot {
919 slot: $crate::ffi::Py_tp_richcompare,
920 pfunc: <$cls>::__pymethod___richcmp____ as $crate::ffi::richcmpfunc as _,
921 }
922 }};
923}
924pub use generate_pyclass_richcompare_slot;
925
926use super::{pycell::PyClassObject, pymethods::BoundRef};
927
928pub trait PyClassWithFreeList: PyClass {
933 fn get_free_list(py: Python<'_>) -> &'static Mutex<PyObjectFreeList>;
934}
935
936pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
942 subtype: *mut ffi::PyTypeObject,
943 nitems: ffi::Py_ssize_t,
944) -> *mut ffi::PyObject {
945 let py = unsafe { Python::assume_gil_acquired() };
946
947 #[cfg(not(Py_3_8))]
948 unsafe {
949 bpo_35810_workaround(py, subtype)
950 };
951
952 let self_type = T::type_object_raw(py);
953 if nitems == 0 && ptr::eq(subtype, self_type) {
956 let mut free_list = T::get_free_list(py).lock().unwrap();
957 if let Some(obj) = free_list.pop() {
958 drop(free_list);
959 unsafe { ffi::PyObject_Init(obj, subtype) };
960 unsafe { ffi::PyObject_Init(obj, subtype) };
961 return obj as _;
962 }
963 }
964
965 unsafe { ffi::PyType_GenericAlloc(subtype, nitems) }
966}
967
968pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
974 let obj = obj as *mut ffi::PyObject;
975 unsafe {
976 debug_assert_eq!(
977 T::type_object_raw(Python::assume_gil_acquired()),
978 ffi::Py_TYPE(obj)
979 );
980 let mut free_list = T::get_free_list(Python::assume_gil_acquired())
981 .lock()
982 .unwrap();
983 if let Some(obj) = free_list.insert(obj) {
984 drop(free_list);
985 let ty = ffi::Py_TYPE(obj);
986
987 let free = if ffi::PyType_IS_GC(ty) != 0 {
989 ffi::PyObject_GC_Del
990 } else {
991 ffi::PyObject_Free
992 };
993 free(obj as *mut c_void);
994
995 #[cfg(Py_3_8)]
996 if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
997 ffi::Py_DECREF(ty as *mut ffi::PyObject);
998 }
999 }
1000 }
1001}
1002
1003#[inline]
1005#[cfg(not(Py_3_8))]
1006unsafe fn bpo_35810_workaround(py: Python<'_>, ty: *mut ffi::PyTypeObject) {
1007 #[cfg(Py_LIMITED_API)]
1008 {
1009 use crate::sync::GILOnceCell;
1012 static IS_PYTHON_3_8: GILOnceCell<bool> = GILOnceCell::new();
1013
1014 if *IS_PYTHON_3_8.get_or_init(py, || py.version_info() >= (3, 8)) {
1015 return;
1017 }
1018 }
1019 #[cfg(not(Py_LIMITED_API))]
1020 {
1021 let _ = py;
1023 }
1024
1025 unsafe { ffi::Py_INCREF(ty as *mut ffi::PyObject) };
1026}
1027
1028#[cfg(feature = "multiple-pymethods")]
1034pub trait PyClassInventory: inventory::Collect {
1035 fn items(&'static self) -> &'static PyClassItems;
1037}
1038
1039#[cfg(not(feature = "multiple-pymethods"))]
1041pub trait PyMethods<T> {
1042 fn py_methods(self) -> &'static PyClassItems;
1043}
1044
1045#[cfg(not(feature = "multiple-pymethods"))]
1046impl<T> PyMethods<T> for &'_ PyClassImplCollector<T> {
1047 fn py_methods(self) -> &'static PyClassItems {
1048 &PyClassItems {
1049 methods: &[],
1050 slots: &[],
1051 }
1052 }
1053}
1054
1055pub trait PyClassNewTextSignature<T> {
1057 fn new_text_signature(self) -> Option<&'static str>;
1058}
1059
1060impl<T> PyClassNewTextSignature<T> for &'_ PyClassImplCollector<T> {
1061 #[inline]
1062 fn new_text_signature(self) -> Option<&'static str> {
1063 None
1064 }
1065}
1066
1067#[doc(hidden)]
1070pub trait PyClassThreadChecker<T>: Sized + sealed::Sealed {
1071 fn ensure(&self);
1072 fn check(&self) -> bool;
1073 fn can_drop(&self, py: Python<'_>) -> bool;
1074 fn new() -> Self;
1075}
1076
1077#[doc(hidden)]
1083pub struct SendablePyClass<T: Send>(PhantomData<T>);
1084
1085impl<T: Send> PyClassThreadChecker<T> for SendablePyClass<T> {
1086 fn ensure(&self) {}
1087 fn check(&self) -> bool {
1088 true
1089 }
1090 fn can_drop(&self, _py: Python<'_>) -> bool {
1091 true
1092 }
1093 #[inline]
1094 fn new() -> Self {
1095 SendablePyClass(PhantomData)
1096 }
1097}
1098
1099#[doc(hidden)]
1102pub struct ThreadCheckerImpl(thread::ThreadId);
1103
1104impl ThreadCheckerImpl {
1105 fn ensure(&self, type_name: &'static str) {
1106 assert_eq!(
1107 thread::current().id(),
1108 self.0,
1109 "{type_name} is unsendable, but sent to another thread"
1110 );
1111 }
1112
1113 fn check(&self) -> bool {
1114 thread::current().id() == self.0
1115 }
1116
1117 fn can_drop(&self, py: Python<'_>, type_name: &'static str) -> bool {
1118 if thread::current().id() != self.0 {
1119 PyRuntimeError::new_err(format!(
1120 "{type_name} is unsendable, but is being dropped on another thread"
1121 ))
1122 .write_unraisable(py, None);
1123 return false;
1124 }
1125
1126 true
1127 }
1128}
1129
1130impl<T> PyClassThreadChecker<T> for ThreadCheckerImpl {
1131 fn ensure(&self) {
1132 self.ensure(std::any::type_name::<T>());
1133 }
1134 fn check(&self) -> bool {
1135 self.check()
1136 }
1137 fn can_drop(&self, py: Python<'_>) -> bool {
1138 self.can_drop(py, std::any::type_name::<T>())
1139 }
1140 fn new() -> Self {
1141 ThreadCheckerImpl(thread::current().id())
1142 }
1143}
1144
1145#[cfg_attr(
1147 all(diagnostic_namespace, Py_LIMITED_API),
1148 diagnostic::on_unimplemented(
1149 message = "pyclass `{Self}` cannot be subclassed",
1150 label = "required for `#[pyclass(extends={Self})]`",
1151 note = "`{Self}` must have `#[pyclass(subclass)]` to be eligible for subclassing",
1152 note = "with the `abi3` feature enabled, PyO3 does not support subclassing native types",
1153 )
1154)]
1155#[cfg_attr(
1156 all(diagnostic_namespace, not(Py_LIMITED_API)),
1157 diagnostic::on_unimplemented(
1158 message = "pyclass `{Self}` cannot be subclassed",
1159 label = "required for `#[pyclass(extends={Self})]`",
1160 note = "`{Self}` must have `#[pyclass(subclass)]` to be eligible for subclassing",
1161 )
1162)]
1163pub trait PyClassBaseType: Sized {
1164 type LayoutAsBase: PyClassObjectLayout<Self>;
1165 type BaseNativeType;
1166 type Initializer: PyObjectInit<Self>;
1167 type PyClassMutability: PyClassMutability;
1168}
1169
1170pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
1172 unsafe { crate::impl_::trampoline::dealloc(obj, PyClassObject::<T>::tp_dealloc) }
1173}
1174
1175pub(crate) unsafe extern "C" fn tp_dealloc_with_gc<T: PyClass>(obj: *mut ffi::PyObject) {
1177 #[cfg(not(PyPy))]
1178 unsafe {
1179 ffi::PyObject_GC_UnTrack(obj.cast());
1180 }
1181 unsafe { crate::impl_::trampoline::dealloc(obj, PyClassObject::<T>::tp_dealloc) }
1182}
1183
1184pub(crate) unsafe extern "C" fn get_sequence_item_from_mapping(
1185 obj: *mut ffi::PyObject,
1186 index: ffi::Py_ssize_t,
1187) -> *mut ffi::PyObject {
1188 let index = unsafe { ffi::PyLong_FromSsize_t(index) };
1189 if index.is_null() {
1190 return std::ptr::null_mut();
1191 }
1192 let result = unsafe { ffi::PyObject_GetItem(obj, index) };
1193 unsafe { ffi::Py_DECREF(index) };
1194 result
1195}
1196
1197pub(crate) unsafe extern "C" fn assign_sequence_item_from_mapping(
1198 obj: *mut ffi::PyObject,
1199 index: ffi::Py_ssize_t,
1200 value: *mut ffi::PyObject,
1201) -> c_int {
1202 unsafe {
1203 let index = ffi::PyLong_FromSsize_t(index);
1204 if index.is_null() {
1205 return -1;
1206 }
1207 let result = if value.is_null() {
1208 ffi::PyObject_DelItem(obj, index)
1209 } else {
1210 ffi::PyObject_SetItem(obj, index, value)
1211 };
1212 ffi::Py_DECREF(index);
1213 result
1214 }
1215}
1216
1217pub unsafe trait OffsetCalculator<T: PyClass, U> {
1226 fn offset() -> usize;
1228}
1229
1230pub fn class_offset<T: PyClass>() -> usize {
1232 offset_of!(PyClassObject<T>, contents)
1233}
1234
1235pub use memoffset::offset_of;
1237
1238pub struct PyClassGetterGenerator<
1241 ClassT: PyClass,
1244 FieldT,
1245 Offset: OffsetCalculator<ClassT, FieldT>, const IS_PY_T: bool,
1249 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1250 const IMPLEMENTS_INTOPYOBJECT: bool,
1251>(PhantomData<(ClassT, FieldT, Offset)>);
1252
1253impl<
1254 ClassT: PyClass,
1255 FieldT,
1256 Offset: OffsetCalculator<ClassT, FieldT>,
1257 const IS_PY_T: bool,
1258 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1259 const IMPLEMENTS_INTOPYOBJECT: bool,
1260 >
1261 PyClassGetterGenerator<
1262 ClassT,
1263 FieldT,
1264 Offset,
1265 IS_PY_T,
1266 IMPLEMENTS_INTOPYOBJECT_REF,
1267 IMPLEMENTS_INTOPYOBJECT,
1268 >
1269{
1270 pub const unsafe fn new() -> Self {
1273 Self(PhantomData)
1274 }
1275}
1276
1277impl<
1278 ClassT: PyClass,
1279 U,
1280 Offset: OffsetCalculator<ClassT, Py<U>>,
1281 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1282 const IMPLEMENTS_INTOPYOBJECT: bool,
1283 >
1284 PyClassGetterGenerator<
1285 ClassT,
1286 Py<U>,
1287 Offset,
1288 true,
1289 IMPLEMENTS_INTOPYOBJECT_REF,
1290 IMPLEMENTS_INTOPYOBJECT,
1291 >
1292{
1293 pub fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1299 use crate::pyclass::boolean_struct::private::Boolean;
1300 if ClassT::Frozen::VALUE {
1301 PyMethodDefType::StructMember(ffi::PyMemberDef {
1302 name: name.as_ptr(),
1303 type_code: ffi::Py_T_OBJECT_EX,
1304 offset: Offset::offset() as ffi::Py_ssize_t,
1305 flags: ffi::Py_READONLY,
1306 doc: doc.as_ptr(),
1307 })
1308 } else {
1309 PyMethodDefType::Getter(PyGetterDef {
1310 name,
1311 meth: pyo3_get_value_into_pyobject_ref::<ClassT, Py<U>, Offset>,
1312 doc,
1313 })
1314 }
1315 }
1316}
1317
1318impl<ClassT, FieldT, Offset, const IMPLEMENTS_INTOPYOBJECT: bool>
1321 PyClassGetterGenerator<ClassT, FieldT, Offset, false, true, IMPLEMENTS_INTOPYOBJECT>
1322where
1323 ClassT: PyClass,
1324 for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1325 Offset: OffsetCalculator<ClassT, FieldT>,
1326{
1327 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1328 PyMethodDefType::Getter(PyGetterDef {
1329 name,
1330 meth: pyo3_get_value_into_pyobject_ref::<ClassT, FieldT, Offset>,
1331 doc,
1332 })
1333 }
1334}
1335
1336#[cfg_attr(
1337 diagnostic_namespace,
1338 diagnostic::on_unimplemented(
1339 message = "`{Self}` cannot be converted to a Python object",
1340 label = "required by `#[pyo3(get)]` to create a readable property from a field of type `{Self}`",
1341 note = "implement `IntoPyObject` for `&{Self}` or `IntoPyObject + Clone` for `{Self}` to define the conversion"
1342 )
1343)]
1344pub trait PyO3GetField<'py>: IntoPyObject<'py> + Clone {}
1345impl<'py, T> PyO3GetField<'py> for T where T: IntoPyObject<'py> + Clone {}
1346
1347impl<
1349 ClassT: PyClass,
1350 FieldT,
1351 Offset: OffsetCalculator<ClassT, FieldT>,
1352 const IMPLEMENTS_INTOPYOBJECT: bool,
1353 > PyClassGetterGenerator<ClassT, FieldT, Offset, false, false, IMPLEMENTS_INTOPYOBJECT>
1354{
1355 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType
1356 where
1359 for<'py> FieldT: PyO3GetField<'py>,
1360 {
1361 PyMethodDefType::Getter(PyGetterDef {
1362 name,
1363 meth: pyo3_get_value_into_pyobject::<ClassT, FieldT, Offset>,
1364 doc,
1365 })
1366 }
1367}
1368
1369#[inline]
1371unsafe fn ensure_no_mutable_alias<'py, ClassT: PyClass>(
1372 py: Python<'py>,
1373 obj: &*mut ffi::PyObject,
1374) -> Result<PyRef<'py, ClassT>, PyBorrowError> {
1375 unsafe {
1376 BoundRef::ref_from_ptr(py, obj)
1377 .downcast_unchecked::<ClassT>()
1378 .try_borrow()
1379 }
1380}
1381
1382#[inline]
1384fn field_from_object<ClassT, FieldT, Offset>(obj: *mut ffi::PyObject) -> *mut FieldT
1385where
1386 ClassT: PyClass,
1387 Offset: OffsetCalculator<ClassT, FieldT>,
1388{
1389 unsafe { obj.cast::<u8>().add(Offset::offset()).cast::<FieldT>() }
1390}
1391
1392fn pyo3_get_value_into_pyobject_ref<ClassT, FieldT, Offset>(
1393 py: Python<'_>,
1394 obj: *mut ffi::PyObject,
1395) -> PyResult<*mut ffi::PyObject>
1396where
1397 ClassT: PyClass,
1398 for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1399 Offset: OffsetCalculator<ClassT, FieldT>,
1400{
1401 let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1402 let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1403
1404 Ok((unsafe { &*value })
1407 .into_pyobject(py)
1408 .map_err(Into::into)?
1409 .into_ptr())
1410}
1411
1412fn pyo3_get_value_into_pyobject<ClassT, FieldT, Offset>(
1413 py: Python<'_>,
1414 obj: *mut ffi::PyObject,
1415) -> PyResult<*mut ffi::PyObject>
1416where
1417 ClassT: PyClass,
1418 for<'py> FieldT: IntoPyObject<'py> + Clone,
1419 Offset: OffsetCalculator<ClassT, FieldT>,
1420{
1421 let _holder = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1422 let value = field_from_object::<ClassT, FieldT, Offset>(obj);
1423
1424 Ok((unsafe { &*value })
1427 .clone()
1428 .into_pyobject(py)
1429 .map_err(Into::into)?
1430 .into_ptr())
1431}
1432
1433pub struct ConvertField<
1434 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1435 const IMPLEMENTS_INTOPYOBJECT: bool,
1436>;
1437
1438impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<true, IMPLEMENTS_INTOPYOBJECT> {
1439 #[inline]
1440 pub fn convert_field<'a, 'py, T>(obj: &'a T, py: Python<'py>) -> PyResult<Py<PyAny>>
1441 where
1442 &'a T: IntoPyObject<'py>,
1443 {
1444 obj.into_py_any(py)
1445 }
1446}
1447
1448impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<false, IMPLEMENTS_INTOPYOBJECT> {
1449 #[inline]
1450 pub fn convert_field<'py, T>(obj: &T, py: Python<'py>) -> PyResult<Py<PyAny>>
1451 where
1452 T: PyO3GetField<'py>,
1453 {
1454 obj.clone().into_py_any(py)
1455 }
1456}
1457
1458#[cfg(test)]
1459#[cfg(feature = "macros")]
1460mod tests {
1461 use super::*;
1462
1463 #[test]
1464 fn get_py_for_frozen_class() {
1465 #[crate::pyclass(crate = "crate", frozen)]
1466 struct FrozenClass {
1467 #[pyo3(get)]
1468 value: Py<PyAny>,
1469 }
1470
1471 let mut methods = Vec::new();
1472 let mut slots = Vec::new();
1473
1474 for items in FrozenClass::items_iter() {
1475 methods.extend(items.methods.iter().map(|m| match m {
1476 MaybeRuntimePyMethodDef::Static(m) => m.clone(),
1477 MaybeRuntimePyMethodDef::Runtime(r) => r(),
1478 }));
1479 slots.extend_from_slice(items.slots);
1480 }
1481
1482 assert_eq!(methods.len(), 1);
1483 assert!(slots.is_empty());
1484
1485 match methods.first() {
1486 Some(PyMethodDefType::StructMember(member)) => {
1487 assert_eq!(unsafe { CStr::from_ptr(member.name) }, ffi::c_str!("value"));
1488 assert_eq!(member.type_code, ffi::Py_T_OBJECT_EX);
1489 assert_eq!(
1490 member.offset,
1491 (memoffset::offset_of!(PyClassObject<FrozenClass>, contents)
1492 + memoffset::offset_of!(FrozenClass, value))
1493 as ffi::Py_ssize_t
1494 );
1495 assert_eq!(member.flags, ffi::Py_READONLY);
1496 }
1497 _ => panic!("Expected a StructMember"),
1498 }
1499 }
1500
1501 #[test]
1502 fn get_py_for_non_frozen_class() {
1503 #[crate::pyclass(crate = "crate")]
1504 struct FrozenClass {
1505 #[pyo3(get)]
1506 value: Py<PyAny>,
1507 }
1508
1509 let mut methods = Vec::new();
1510 let mut slots = Vec::new();
1511
1512 for items in FrozenClass::items_iter() {
1513 methods.extend(items.methods.iter().map(|m| match m {
1514 MaybeRuntimePyMethodDef::Static(m) => m.clone(),
1515 MaybeRuntimePyMethodDef::Runtime(r) => r(),
1516 }));
1517 slots.extend_from_slice(items.slots);
1518 }
1519
1520 assert_eq!(methods.len(), 1);
1521 assert!(slots.is_empty());
1522
1523 match methods.first() {
1524 Some(PyMethodDefType::Getter(getter)) => {
1525 assert_eq!(getter.name, ffi::c_str!("value"));
1526 assert_eq!(getter.doc, ffi::c_str!(""));
1527 }
1529 _ => panic!("Expected a StructMember"),
1530 }
1531 }
1532}