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