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 type BaseType: PyTypeInfo + PyClassBaseType;
181
182 type PyClassMutability: PyClassMutability + GetBorrowChecker<Self>;
184
185 type Dict: PyClassDict;
187
188 type WeakRef: PyClassWeakRef;
190
191 type BaseNativeType: PyTypeInfo;
194
195 type ThreadChecker: PyClassThreadChecker<Self>;
203
204 #[cfg(feature = "multiple-pymethods")]
205 type Inventory: PyClassInventory;
206
207 fn doc(py: Python<'_>) -> PyResult<&'static CStr>;
209
210 fn items_iter() -> PyClassItemsIter;
211
212 #[inline]
213 fn dict_offset() -> Option<ffi::Py_ssize_t> {
214 None
215 }
216
217 #[inline]
218 fn weaklist_offset() -> Option<ffi::Py_ssize_t> {
219 None
220 }
221
222 fn lazy_type_object() -> &'static LazyTypeObject<Self>;
223}
224
225pub fn build_pyclass_doc(
230 class_name: &'static str,
231 doc: &'static CStr,
232 text_signature: Option<&'static str>,
233) -> PyResult<Cow<'static, CStr>> {
234 if let Some(text_signature) = text_signature {
235 let doc = CString::new(format!(
236 "{}{}\n--\n\n{}",
237 class_name,
238 text_signature,
239 doc.to_str().unwrap(),
240 ))
241 .map_err(|_| PyValueError::new_err("class doc cannot contain nul bytes"))?;
242 Ok(Cow::Owned(doc))
243 } else {
244 Ok(Cow::Borrowed(doc))
245 }
246}
247
248pub struct PyClassItemsIter {
250 idx: usize,
252 pyclass_items: &'static PyClassItems,
254 #[cfg(not(feature = "multiple-pymethods"))]
256 pymethods_items: &'static PyClassItems,
257 #[cfg(feature = "multiple-pymethods")]
259 pymethods_items: Box<dyn Iterator<Item = &'static PyClassItems>>,
260}
261
262impl PyClassItemsIter {
263 pub fn new(
264 pyclass_items: &'static PyClassItems,
265 #[cfg(not(feature = "multiple-pymethods"))] pymethods_items: &'static PyClassItems,
266 #[cfg(feature = "multiple-pymethods")] pymethods_items: Box<
267 dyn Iterator<Item = &'static PyClassItems>,
268 >,
269 ) -> Self {
270 Self {
271 idx: 0,
272 pyclass_items,
273 pymethods_items,
274 }
275 }
276}
277
278impl Iterator for PyClassItemsIter {
279 type Item = &'static PyClassItems;
280
281 #[cfg(not(feature = "multiple-pymethods"))]
282 fn next(&mut self) -> Option<Self::Item> {
283 match self.idx {
284 0 => {
285 self.idx += 1;
286 Some(self.pyclass_items)
287 }
288 1 => {
289 self.idx += 1;
290 Some(self.pymethods_items)
291 }
292 _ => None,
294 }
295 }
296
297 #[cfg(feature = "multiple-pymethods")]
298 fn next(&mut self) -> Option<Self::Item> {
299 match self.idx {
300 0 => {
301 self.idx += 1;
302 Some(self.pyclass_items)
303 }
304 _ => self.pymethods_items.next(),
306 }
307 }
308}
309
310macro_rules! slot_fragment_trait {
313 ($trait_name:ident, $($default_method:tt)*) => {
314 #[allow(non_camel_case_types)]
315 pub trait $trait_name<T>: Sized {
316 $($default_method)*
317 }
318
319 impl<T> $trait_name<T> for &'_ PyClassImplCollector<T> {}
320 }
321}
322
323slot_fragment_trait! {
324 PyClass__getattribute__SlotFragment,
325
326 #[inline]
328 unsafe fn __getattribute__(
329 self,
330 py: Python<'_>,
331 slf: *mut ffi::PyObject,
332 attr: *mut ffi::PyObject,
333 ) -> PyResult<*mut ffi::PyObject> {
334 let res = unsafe { ffi::PyObject_GenericGetAttr(slf, attr) };
335 if res.is_null() {
336 Err(PyErr::fetch(py))
337 } else {
338 Ok(res)
339 }
340 }
341}
342
343slot_fragment_trait! {
344 PyClass__getattr__SlotFragment,
345
346 #[inline]
348 unsafe fn __getattr__(
349 self,
350 py: Python<'_>,
351 _slf: *mut ffi::PyObject,
352 attr: *mut ffi::PyObject,
353 ) -> PyResult<*mut ffi::PyObject> {
354 Err(PyErr::new::<PyAttributeError, _>(
355 (unsafe {Py::<PyAny>::from_borrowed_ptr(py, attr)},)
356 ))
357 }
358}
359
360#[doc(hidden)]
361#[macro_export]
362macro_rules! generate_pyclass_getattro_slot {
363 ($cls:ty) => {{
364 unsafe extern "C" fn __wrap(
365 _slf: *mut $crate::ffi::PyObject,
366 attr: *mut $crate::ffi::PyObject,
367 ) -> *mut $crate::ffi::PyObject {
368 unsafe {
369 $crate::impl_::trampoline::getattrofunc(_slf, attr, |py, _slf, attr| {
370 use ::std::result::Result::*;
371 use $crate::impl_::pyclass::*;
372 let collector = PyClassImplCollector::<$cls>::new();
373
374 match collector.__getattribute__(py, _slf, attr) {
380 Ok(obj) => Ok(obj),
381 Err(e) if e.is_instance_of::<$crate::exceptions::PyAttributeError>(py) => {
382 collector.__getattr__(py, _slf, attr)
383 }
384 Err(e) => Err(e),
385 }
386 })
387 }
388 }
389 $crate::ffi::PyType_Slot {
390 slot: $crate::ffi::Py_tp_getattro,
391 pfunc: __wrap as $crate::ffi::getattrofunc as _,
392 }
393 }};
394}
395
396pub use generate_pyclass_getattro_slot;
397
398macro_rules! define_pyclass_setattr_slot {
403 (
404 $set_trait:ident,
405 $del_trait:ident,
406 $set:ident,
407 $del:ident,
408 $set_error:expr,
409 $del_error:expr,
410 $generate_macro:ident,
411 $slot:ident,
412 $func_ty:ident,
413 ) => {
414 slot_fragment_trait! {
415 $set_trait,
416
417 #[inline]
419 unsafe fn $set(
420 self,
421 _py: Python<'_>,
422 _slf: *mut ffi::PyObject,
423 _attr: *mut ffi::PyObject,
424 _value: NonNull<ffi::PyObject>,
425 ) -> PyResult<()> {
426 $set_error
427 }
428 }
429
430 slot_fragment_trait! {
431 $del_trait,
432
433 #[inline]
435 unsafe fn $del(
436 self,
437 _py: Python<'_>,
438 _slf: *mut ffi::PyObject,
439 _attr: *mut ffi::PyObject,
440 ) -> PyResult<()> {
441 $del_error
442 }
443 }
444
445 #[doc(hidden)]
446 #[macro_export]
447 macro_rules! $generate_macro {
448 ($cls:ty) => {{
449 unsafe extern "C" fn __wrap(
450 _slf: *mut $crate::ffi::PyObject,
451 attr: *mut $crate::ffi::PyObject,
452 value: *mut $crate::ffi::PyObject,
453 ) -> ::std::os::raw::c_int {
454 unsafe {
455 $crate::impl_::trampoline::setattrofunc(
456 _slf,
457 attr,
458 value,
459 |py, _slf, attr, value| {
460 use ::std::option::Option::*;
461 use $crate::impl_::callback::IntoPyCallbackOutput;
462 use $crate::impl_::pyclass::*;
463 let collector = PyClassImplCollector::<$cls>::new();
464 if let Some(value) = ::std::ptr::NonNull::new(value) {
465 collector.$set(py, _slf, attr, value).convert(py)
466 } else {
467 collector.$del(py, _slf, attr).convert(py)
468 }
469 },
470 )
471 }
472 }
473 $crate::ffi::PyType_Slot {
474 slot: $crate::ffi::$slot,
475 pfunc: __wrap as $crate::ffi::$func_ty as _,
476 }
477 }};
478 }
479 pub use $generate_macro;
480 };
481}
482
483define_pyclass_setattr_slot! {
484 PyClass__setattr__SlotFragment,
485 PyClass__delattr__SlotFragment,
486 __setattr__,
487 __delattr__,
488 Err(PyAttributeError::new_err("can't set attribute")),
489 Err(PyAttributeError::new_err("can't delete attribute")),
490 generate_pyclass_setattr_slot,
491 Py_tp_setattro,
492 setattrofunc,
493}
494
495define_pyclass_setattr_slot! {
496 PyClass__set__SlotFragment,
497 PyClass__delete__SlotFragment,
498 __set__,
499 __delete__,
500 Err(PyNotImplementedError::new_err("can't set descriptor")),
501 Err(PyNotImplementedError::new_err("can't delete descriptor")),
502 generate_pyclass_setdescr_slot,
503 Py_tp_descr_set,
504 descrsetfunc,
505}
506
507define_pyclass_setattr_slot! {
508 PyClass__setitem__SlotFragment,
509 PyClass__delitem__SlotFragment,
510 __setitem__,
511 __delitem__,
512 Err(PyNotImplementedError::new_err("can't set item")),
513 Err(PyNotImplementedError::new_err("can't delete item")),
514 generate_pyclass_setitem_slot,
515 Py_mp_ass_subscript,
516 objobjargproc,
517}
518
519macro_rules! define_pyclass_binary_operator_slot {
524 (
525 $lhs_trait:ident,
526 $rhs_trait:ident,
527 $lhs:ident,
528 $rhs:ident,
529 $generate_macro:ident,
530 $slot:ident,
531 $func_ty:ident,
532 ) => {
533 slot_fragment_trait! {
534 $lhs_trait,
535
536 #[inline]
538 unsafe fn $lhs(
539 self,
540 py: Python<'_>,
541 _slf: *mut ffi::PyObject,
542 _other: *mut ffi::PyObject,
543 ) -> PyResult<*mut ffi::PyObject> {
544 Ok(py.NotImplemented().into_ptr())
545 }
546 }
547
548 slot_fragment_trait! {
549 $rhs_trait,
550
551 #[inline]
553 unsafe fn $rhs(
554 self,
555 py: Python<'_>,
556 _slf: *mut ffi::PyObject,
557 _other: *mut ffi::PyObject,
558 ) -> PyResult<*mut ffi::PyObject> {
559 Ok(py.NotImplemented().into_ptr())
560 }
561 }
562
563 #[doc(hidden)]
564 #[macro_export]
565 macro_rules! $generate_macro {
566 ($cls:ty) => {{
567 unsafe extern "C" fn __wrap(
568 _slf: *mut $crate::ffi::PyObject,
569 _other: *mut $crate::ffi::PyObject,
570 ) -> *mut $crate::ffi::PyObject {
571 unsafe {
572 $crate::impl_::trampoline::binaryfunc(_slf, _other, |py, _slf, _other| {
573 use $crate::impl_::pyclass::*;
574 let collector = PyClassImplCollector::<$cls>::new();
575 let lhs_result = collector.$lhs(py, _slf, _other)?;
576 if lhs_result == $crate::ffi::Py_NotImplemented() {
577 $crate::ffi::Py_DECREF(lhs_result);
578 collector.$rhs(py, _other, _slf)
579 } else {
580 ::std::result::Result::Ok(lhs_result)
581 }
582 })
583 }
584 }
585 $crate::ffi::PyType_Slot {
586 slot: $crate::ffi::$slot,
587 pfunc: __wrap as $crate::ffi::$func_ty as _,
588 }
589 }};
590 }
591 pub use $generate_macro;
592 };
593}
594
595define_pyclass_binary_operator_slot! {
596 PyClass__add__SlotFragment,
597 PyClass__radd__SlotFragment,
598 __add__,
599 __radd__,
600 generate_pyclass_add_slot,
601 Py_nb_add,
602 binaryfunc,
603}
604
605define_pyclass_binary_operator_slot! {
606 PyClass__sub__SlotFragment,
607 PyClass__rsub__SlotFragment,
608 __sub__,
609 __rsub__,
610 generate_pyclass_sub_slot,
611 Py_nb_subtract,
612 binaryfunc,
613}
614
615define_pyclass_binary_operator_slot! {
616 PyClass__mul__SlotFragment,
617 PyClass__rmul__SlotFragment,
618 __mul__,
619 __rmul__,
620 generate_pyclass_mul_slot,
621 Py_nb_multiply,
622 binaryfunc,
623}
624
625define_pyclass_binary_operator_slot! {
626 PyClass__mod__SlotFragment,
627 PyClass__rmod__SlotFragment,
628 __mod__,
629 __rmod__,
630 generate_pyclass_mod_slot,
631 Py_nb_remainder,
632 binaryfunc,
633}
634
635define_pyclass_binary_operator_slot! {
636 PyClass__divmod__SlotFragment,
637 PyClass__rdivmod__SlotFragment,
638 __divmod__,
639 __rdivmod__,
640 generate_pyclass_divmod_slot,
641 Py_nb_divmod,
642 binaryfunc,
643}
644
645define_pyclass_binary_operator_slot! {
646 PyClass__lshift__SlotFragment,
647 PyClass__rlshift__SlotFragment,
648 __lshift__,
649 __rlshift__,
650 generate_pyclass_lshift_slot,
651 Py_nb_lshift,
652 binaryfunc,
653}
654
655define_pyclass_binary_operator_slot! {
656 PyClass__rshift__SlotFragment,
657 PyClass__rrshift__SlotFragment,
658 __rshift__,
659 __rrshift__,
660 generate_pyclass_rshift_slot,
661 Py_nb_rshift,
662 binaryfunc,
663}
664
665define_pyclass_binary_operator_slot! {
666 PyClass__and__SlotFragment,
667 PyClass__rand__SlotFragment,
668 __and__,
669 __rand__,
670 generate_pyclass_and_slot,
671 Py_nb_and,
672 binaryfunc,
673}
674
675define_pyclass_binary_operator_slot! {
676 PyClass__or__SlotFragment,
677 PyClass__ror__SlotFragment,
678 __or__,
679 __ror__,
680 generate_pyclass_or_slot,
681 Py_nb_or,
682 binaryfunc,
683}
684
685define_pyclass_binary_operator_slot! {
686 PyClass__xor__SlotFragment,
687 PyClass__rxor__SlotFragment,
688 __xor__,
689 __rxor__,
690 generate_pyclass_xor_slot,
691 Py_nb_xor,
692 binaryfunc,
693}
694
695define_pyclass_binary_operator_slot! {
696 PyClass__matmul__SlotFragment,
697 PyClass__rmatmul__SlotFragment,
698 __matmul__,
699 __rmatmul__,
700 generate_pyclass_matmul_slot,
701 Py_nb_matrix_multiply,
702 binaryfunc,
703}
704
705define_pyclass_binary_operator_slot! {
706 PyClass__truediv__SlotFragment,
707 PyClass__rtruediv__SlotFragment,
708 __truediv__,
709 __rtruediv__,
710 generate_pyclass_truediv_slot,
711 Py_nb_true_divide,
712 binaryfunc,
713}
714
715define_pyclass_binary_operator_slot! {
716 PyClass__floordiv__SlotFragment,
717 PyClass__rfloordiv__SlotFragment,
718 __floordiv__,
719 __rfloordiv__,
720 generate_pyclass_floordiv_slot,
721 Py_nb_floor_divide,
722 binaryfunc,
723}
724
725slot_fragment_trait! {
726 PyClass__pow__SlotFragment,
727
728 #[inline]
730 unsafe fn __pow__(
731 self,
732 py: Python<'_>,
733 _slf: *mut ffi::PyObject,
734 _other: *mut ffi::PyObject,
735 _mod: *mut ffi::PyObject,
736 ) -> PyResult<*mut ffi::PyObject> {
737 Ok(py.NotImplemented().into_ptr())
738 }
739}
740
741slot_fragment_trait! {
742 PyClass__rpow__SlotFragment,
743
744 #[inline]
746 unsafe fn __rpow__(
747 self,
748 py: Python<'_>,
749 _slf: *mut ffi::PyObject,
750 _other: *mut ffi::PyObject,
751 _mod: *mut ffi::PyObject,
752 ) -> PyResult<*mut ffi::PyObject> {
753 Ok(py.NotImplemented().into_ptr())
754 }
755}
756
757#[doc(hidden)]
758#[macro_export]
759macro_rules! generate_pyclass_pow_slot {
760 ($cls:ty) => {{
761 unsafe extern "C" fn __wrap(
762 _slf: *mut $crate::ffi::PyObject,
763 _other: *mut $crate::ffi::PyObject,
764 _mod: *mut $crate::ffi::PyObject,
765 ) -> *mut $crate::ffi::PyObject {
766 unsafe {
767 $crate::impl_::trampoline::ternaryfunc(
768 _slf,
769 _other,
770 _mod,
771 |py, _slf, _other, _mod| {
772 use $crate::impl_::pyclass::*;
773 let collector = PyClassImplCollector::<$cls>::new();
774 let lhs_result = collector.__pow__(py, _slf, _other, _mod)?;
775 if lhs_result == $crate::ffi::Py_NotImplemented() {
776 $crate::ffi::Py_DECREF(lhs_result);
777 collector.__rpow__(py, _other, _slf, _mod)
778 } else {
779 ::std::result::Result::Ok(lhs_result)
780 }
781 },
782 )
783 }
784 }
785 $crate::ffi::PyType_Slot {
786 slot: $crate::ffi::Py_nb_power,
787 pfunc: __wrap as $crate::ffi::ternaryfunc as _,
788 }
789 }};
790}
791pub use generate_pyclass_pow_slot;
792
793slot_fragment_trait! {
794 PyClass__lt__SlotFragment,
795
796 #[inline]
798 unsafe fn __lt__(
799 self,
800 py: Python<'_>,
801 _slf: *mut ffi::PyObject,
802 _other: *mut ffi::PyObject,
803 ) -> PyResult<*mut ffi::PyObject> {
804 Ok(py.NotImplemented().into_ptr())
805 }
806}
807
808slot_fragment_trait! {
809 PyClass__le__SlotFragment,
810
811 #[inline]
813 unsafe fn __le__(
814 self,
815 py: Python<'_>,
816 _slf: *mut ffi::PyObject,
817 _other: *mut ffi::PyObject,
818 ) -> PyResult<*mut ffi::PyObject> {
819 Ok(py.NotImplemented().into_ptr())
820 }
821}
822
823slot_fragment_trait! {
824 PyClass__eq__SlotFragment,
825
826 #[inline]
828 unsafe fn __eq__(
829 self,
830 py: Python<'_>,
831 _slf: *mut ffi::PyObject,
832 _other: *mut ffi::PyObject,
833 ) -> PyResult<*mut ffi::PyObject> {
834 Ok(py.NotImplemented().into_ptr())
835 }
836}
837
838slot_fragment_trait! {
839 PyClass__ne__SlotFragment,
840
841 #[inline]
843 unsafe fn __ne__(
844 self,
845 py: Python<'_>,
846 slf: *mut ffi::PyObject,
847 other: *mut ffi::PyObject,
848 ) -> PyResult<*mut ffi::PyObject> {
849 let slf = unsafe { Borrowed::from_ptr(py, slf)};
851 let other = unsafe { Borrowed::from_ptr(py, other)};
852 slf.eq(other).map(|is_eq| PyBool::new(py, !is_eq).to_owned().into_ptr())
853 }
854}
855
856slot_fragment_trait! {
857 PyClass__gt__SlotFragment,
858
859 #[inline]
861 unsafe fn __gt__(
862 self,
863 py: Python<'_>,
864 _slf: *mut ffi::PyObject,
865 _other: *mut ffi::PyObject,
866 ) -> PyResult<*mut ffi::PyObject> {
867 Ok(py.NotImplemented().into_ptr())
868 }
869}
870
871slot_fragment_trait! {
872 PyClass__ge__SlotFragment,
873
874 #[inline]
876 unsafe fn __ge__(
877 self,
878 py: Python<'_>,
879 _slf: *mut ffi::PyObject,
880 _other: *mut ffi::PyObject,
881 ) -> PyResult<*mut ffi::PyObject> {
882 Ok(py.NotImplemented().into_ptr())
883 }
884}
885
886#[doc(hidden)]
887#[macro_export]
888macro_rules! generate_pyclass_richcompare_slot {
889 ($cls:ty) => {{
890 #[allow(unknown_lints, non_local_definitions)]
891 impl $cls {
892 #[allow(non_snake_case)]
893 unsafe extern "C" fn __pymethod___richcmp____(
894 slf: *mut $crate::ffi::PyObject,
895 other: *mut $crate::ffi::PyObject,
896 op: ::std::os::raw::c_int,
897 ) -> *mut $crate::ffi::PyObject {
898 unsafe {
899 $crate::impl_::trampoline::richcmpfunc(slf, other, op, |py, slf, other, op| {
900 use $crate::class::basic::CompareOp;
901 use $crate::impl_::pyclass::*;
902 let collector = PyClassImplCollector::<$cls>::new();
903 match CompareOp::from_raw(op).expect("invalid compareop") {
904 CompareOp::Lt => collector.__lt__(py, slf, other),
905 CompareOp::Le => collector.__le__(py, slf, other),
906 CompareOp::Eq => collector.__eq__(py, slf, other),
907 CompareOp::Ne => collector.__ne__(py, slf, other),
908 CompareOp::Gt => collector.__gt__(py, slf, other),
909 CompareOp::Ge => collector.__ge__(py, slf, other),
910 }
911 })
912 }
913 }
914 }
915 $crate::ffi::PyType_Slot {
916 slot: $crate::ffi::Py_tp_richcompare,
917 pfunc: <$cls>::__pymethod___richcmp____ as $crate::ffi::richcmpfunc as _,
918 }
919 }};
920}
921pub use generate_pyclass_richcompare_slot;
922
923use super::{pycell::PyClassObject, pymethods::BoundRef};
924
925pub trait PyClassWithFreeList: PyClass {
930 fn get_free_list(py: Python<'_>) -> &'static Mutex<PyObjectFreeList>;
931}
932
933pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
939 subtype: *mut ffi::PyTypeObject,
940 nitems: ffi::Py_ssize_t,
941) -> *mut ffi::PyObject {
942 let py = unsafe { Python::assume_gil_acquired() };
943
944 #[cfg(not(Py_3_8))]
945 unsafe {
946 bpo_35810_workaround(py, subtype)
947 };
948
949 let self_type = T::type_object_raw(py);
950 if nitems == 0 && ptr::eq(subtype, self_type) {
953 let mut free_list = T::get_free_list(py).lock().unwrap();
954 if let Some(obj) = free_list.pop() {
955 drop(free_list);
956 unsafe { ffi::PyObject_Init(obj, subtype) };
957 unsafe { ffi::PyObject_Init(obj, subtype) };
958 return obj as _;
959 }
960 }
961
962 unsafe { ffi::PyType_GenericAlloc(subtype, nitems) }
963}
964
965pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
971 let obj = obj as *mut ffi::PyObject;
972 unsafe {
973 debug_assert_eq!(
974 T::type_object_raw(Python::assume_gil_acquired()),
975 ffi::Py_TYPE(obj)
976 );
977 let mut free_list = T::get_free_list(Python::assume_gil_acquired())
978 .lock()
979 .unwrap();
980 if let Some(obj) = free_list.insert(obj) {
981 drop(free_list);
982 let ty = ffi::Py_TYPE(obj);
983
984 let free = if ffi::PyType_IS_GC(ty) != 0 {
986 ffi::PyObject_GC_Del
987 } else {
988 ffi::PyObject_Free
989 };
990 free(obj as *mut c_void);
991
992 #[cfg(Py_3_8)]
993 if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
994 ffi::Py_DECREF(ty as *mut ffi::PyObject);
995 }
996 }
997 }
998}
999
1000#[inline]
1002#[cfg(not(Py_3_8))]
1003unsafe fn bpo_35810_workaround(py: Python<'_>, ty: *mut ffi::PyTypeObject) {
1004 #[cfg(Py_LIMITED_API)]
1005 {
1006 use crate::sync::GILOnceCell;
1009 static IS_PYTHON_3_8: GILOnceCell<bool> = GILOnceCell::new();
1010
1011 if *IS_PYTHON_3_8.get_or_init(py, || py.version_info() >= (3, 8)) {
1012 return;
1014 }
1015 }
1016 #[cfg(not(Py_LIMITED_API))]
1017 {
1018 let _ = py;
1020 }
1021
1022 unsafe { ffi::Py_INCREF(ty as *mut ffi::PyObject) };
1023}
1024
1025#[cfg(feature = "multiple-pymethods")]
1031pub trait PyClassInventory: inventory::Collect {
1032 fn items(&'static self) -> &'static PyClassItems;
1034}
1035
1036#[cfg(not(feature = "multiple-pymethods"))]
1038pub trait PyMethods<T> {
1039 fn py_methods(self) -> &'static PyClassItems;
1040}
1041
1042#[cfg(not(feature = "multiple-pymethods"))]
1043impl<T> PyMethods<T> for &'_ PyClassImplCollector<T> {
1044 fn py_methods(self) -> &'static PyClassItems {
1045 &PyClassItems {
1046 methods: &[],
1047 slots: &[],
1048 }
1049 }
1050}
1051
1052pub trait PyClassNewTextSignature<T> {
1054 fn new_text_signature(self) -> Option<&'static str>;
1055}
1056
1057impl<T> PyClassNewTextSignature<T> for &'_ PyClassImplCollector<T> {
1058 #[inline]
1059 fn new_text_signature(self) -> Option<&'static str> {
1060 None
1061 }
1062}
1063
1064#[doc(hidden)]
1067pub trait PyClassThreadChecker<T>: Sized + sealed::Sealed {
1068 fn ensure(&self);
1069 fn check(&self) -> bool;
1070 fn can_drop(&self, py: Python<'_>) -> bool;
1071 fn new() -> Self;
1072}
1073
1074#[doc(hidden)]
1080pub struct SendablePyClass<T: Send>(PhantomData<T>);
1081
1082impl<T: Send> PyClassThreadChecker<T> for SendablePyClass<T> {
1083 fn ensure(&self) {}
1084 fn check(&self) -> bool {
1085 true
1086 }
1087 fn can_drop(&self, _py: Python<'_>) -> bool {
1088 true
1089 }
1090 #[inline]
1091 fn new() -> Self {
1092 SendablePyClass(PhantomData)
1093 }
1094}
1095
1096#[doc(hidden)]
1099pub struct ThreadCheckerImpl(thread::ThreadId);
1100
1101impl ThreadCheckerImpl {
1102 fn ensure(&self, type_name: &'static str) {
1103 assert_eq!(
1104 thread::current().id(),
1105 self.0,
1106 "{} is unsendable, but sent to another thread",
1107 type_name
1108 );
1109 }
1110
1111 fn check(&self) -> bool {
1112 thread::current().id() == self.0
1113 }
1114
1115 fn can_drop(&self, py: Python<'_>, type_name: &'static str) -> bool {
1116 if thread::current().id() != self.0 {
1117 PyRuntimeError::new_err(format!(
1118 "{} is unsendable, but is being dropped on another thread",
1119 type_name
1120 ))
1121 .write_unraisable(py, None);
1122 return false;
1123 }
1124
1125 true
1126 }
1127}
1128
1129impl<T> PyClassThreadChecker<T> for ThreadCheckerImpl {
1130 fn ensure(&self) {
1131 self.ensure(std::any::type_name::<T>());
1132 }
1133 fn check(&self) -> bool {
1134 self.check()
1135 }
1136 fn can_drop(&self, py: Python<'_>) -> bool {
1137 self.can_drop(py, std::any::type_name::<T>())
1138 }
1139 fn new() -> Self {
1140 ThreadCheckerImpl(thread::current().id())
1141 }
1142}
1143
1144#[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}