1use crate::{
2 exceptions::{PyAttributeError, PyNotImplementedError, PyRuntimeError, PyValueError},
3 ffi,
4 impl_::freelist::FreeList,
5 impl_::pycell::{GetBorrowChecker, PyClassMutability},
6 internal_tricks::extract_c_string,
7 pycell::PyCellLayout,
8 pyclass_init::PyObjectInit,
9 types::PyBool,
10 Py, PyAny, PyCell, PyClass, PyErr, PyMethodDefType, PyNativeType, PyResult, PyTypeInfo, Python,
11};
12use std::{
13 borrow::Cow,
14 ffi::{CStr, CString},
15 marker::PhantomData,
16 os::raw::{c_int, c_void},
17 ptr::NonNull,
18 thread,
19};
20
21mod lazy_type_object;
22pub use lazy_type_object::LazyTypeObject;
23
24/// Gets the offset of the dictionary from the start of the object in bytes.
25#[inline]
26pub fn dict_offset<T: PyClass>() -> ffi::Py_ssize_t {
27 PyCell::<T>::dict_offset()
28}
29
30/// Gets the offset of the weakref list from the start of the object in bytes.
31#[inline]
32pub fn weaklist_offset<T: PyClass>() -> ffi::Py_ssize_t {
33 PyCell::<T>::weaklist_offset()
34}
35
36/// Represents the `__dict__` field for `#[pyclass]`.
37pub trait PyClassDict {
38 /// Initial form of a [PyObject](crate::ffi::PyObject) `__dict__` reference.
39 const INIT: Self;
40 /// Empties the dictionary of its key-value pairs.
41 #[inline]
42 fn clear_dict(&mut self, _py: Python<'_>) {}
43 private_decl! {}
44}
45
46/// Represents the `__weakref__` field for `#[pyclass]`.
47pub trait PyClassWeakRef {
48 /// Initializes a `weakref` instance.
49 const INIT: Self;
50 /// Clears the weak references to the given object.
51 ///
52 /// # Safety
53 /// - `_obj` must be a pointer to the pyclass instance which contains `self`.
54 /// - The GIL must be held.
55 #[inline]
56 unsafe fn clear_weakrefs(&mut self, _obj: *mut ffi::PyObject, _py: Python<'_>) {}
57 private_decl! {}
58}
59
60/// Zero-sized dummy field.
61pub struct PyClassDummySlot;
62
63impl PyClassDict for PyClassDummySlot {
64 private_impl! {}
65 const INIT: Self = PyClassDummySlot;
66}
67
68impl PyClassWeakRef for PyClassDummySlot {
69 private_impl! {}
70 const INIT: Self = PyClassDummySlot;
71}
72
73/// Actual dict field, which holds the pointer to `__dict__`.
74///
75/// `#[pyclass(dict)]` automatically adds this.
76#[repr(transparent)]
77pub struct PyClassDictSlot(*mut ffi::PyObject);
78
79impl PyClassDict for PyClassDictSlot {
80 private_impl! {}
81 const INIT: Self = Self(std::ptr::null_mut());
82 #[inline]
83 fn clear_dict(&mut self, _py: Python<'_>) {
84 if !self.0.is_null() {
85 unsafe { ffi::PyDict_Clear(self.0) }
86 }
87 }
88}
89
90/// Actual weakref field, which holds the pointer to `__weakref__`.
91///
92/// `#[pyclass(weakref)]` automatically adds this.
93#[repr(transparent)]
94pub struct PyClassWeakRefSlot(*mut ffi::PyObject);
95
96impl PyClassWeakRef for PyClassWeakRefSlot {
97 private_impl! {}
98 const INIT: Self = Self(std::ptr::null_mut());
99 #[inline]
100 unsafe fn clear_weakrefs(&mut self, obj: *mut ffi::PyObject, _py: Python<'_>) {
101 if !self.0.is_null() {
102 ffi::PyObject_ClearWeakRefs(arg1:obj)
103 }
104 }
105}
106
107/// This type is used as a "dummy" type on which dtolnay specializations are
108/// applied to apply implementations from `#[pymethods]`
109pub struct PyClassImplCollector<T>(PhantomData<T>);
110
111impl<T> PyClassImplCollector<T> {
112 pub fn new() -> Self {
113 Self(PhantomData)
114 }
115}
116
117impl<T> Default for PyClassImplCollector<T> {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123impl<T> Clone for PyClassImplCollector<T> {
124 fn clone(&self) -> Self {
125 *self
126 }
127}
128
129impl<T> Copy for PyClassImplCollector<T> {}
130
131pub struct PyClassItems {
132 pub methods: &'static [PyMethodDefType],
133 pub slots: &'static [ffi::PyType_Slot],
134}
135
136// Allow PyClassItems in statics
137unsafe impl Sync for PyClassItems {}
138
139/// Implements the underlying functionality of `#[pyclass]`, assembled by various proc macros.
140///
141/// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail
142/// and may be changed at any time.
143pub trait PyClassImpl: Sized + 'static {
144 /// #[pyclass(subclass)]
145 const IS_BASETYPE: bool = false;
146
147 /// #[pyclass(extends=...)]
148 const IS_SUBCLASS: bool = false;
149
150 /// #[pyclass(mapping)]
151 const IS_MAPPING: bool = false;
152
153 /// #[pyclass(sequence)]
154 const IS_SEQUENCE: bool = false;
155
156 /// Base class
157 type BaseType: PyTypeInfo + PyClassBaseType;
158
159 /// Immutable or mutable
160 type PyClassMutability: PyClassMutability + GetBorrowChecker<Self>;
161
162 /// Specify this class has `#[pyclass(dict)]` or not.
163 type Dict: PyClassDict;
164
165 /// Specify this class has `#[pyclass(weakref)]` or not.
166 type WeakRef: PyClassWeakRef;
167
168 /// The closest native ancestor. This is `PyAny` by default, and when you declare
169 /// `#[pyclass(extends=PyDict)]`, it's `PyDict`.
170 type BaseNativeType: PyTypeInfo + PyNativeType;
171
172 /// This handles following two situations:
173 /// 1. In case `T` is `Send`, stub `ThreadChecker` is used and does nothing.
174 /// This implementation is used by default. Compile fails if `T: !Send`.
175 /// 2. In case `T` is `!Send`, `ThreadChecker` panics when `T` is accessed by another thread.
176 /// This implementation is used when `#[pyclass(unsendable)]` is given.
177 /// Panicking makes it safe to expose `T: !Send` to the Python interpreter, where all objects
178 /// can be accessed by multiple threads by `threading` module.
179 type ThreadChecker: PyClassThreadChecker<Self>;
180
181 #[cfg(feature = "multiple-pymethods")]
182 type Inventory: PyClassInventory;
183
184 /// Rendered class doc
185 fn doc(py: Python<'_>) -> PyResult<&'static CStr>;
186
187 fn items_iter() -> PyClassItemsIter;
188
189 #[inline]
190 fn dict_offset() -> Option<ffi::Py_ssize_t> {
191 None
192 }
193
194 #[inline]
195 fn weaklist_offset() -> Option<ffi::Py_ssize_t> {
196 None
197 }
198
199 fn lazy_type_object() -> &'static LazyTypeObject<Self>;
200}
201
202/// Runtime helper to build a class docstring from the `doc` and `text_signature`.
203///
204/// This is done at runtime because the class text signature is collected via dtolnay
205/// specialization in to the `#[pyclass]` macro from the `#[pymethods]` macro.
206pub fn build_pyclass_doc(
207 class_name: &'static str,
208 doc: &'static str,
209 text_signature: Option<&'static str>,
210) -> PyResult<Cow<'static, CStr>> {
211 if let Some(text_signature: &str) = text_signature {
212 let doc: CString = CString::new(format!(
213 "{}{}\n--\n\n{}",
214 class_name,
215 text_signature,
216 doc.trim_end_matches('\0')
217 ))
218 .map_err(|_| PyValueError::new_err(args:"class doc cannot contain nul bytes"))?;
219 Ok(Cow::Owned(doc))
220 } else {
221 extract_c_string(src:doc, err_msg:"class doc cannot contain nul bytes")
222 }
223}
224
225/// Iterator used to process all class items during type instantiation.
226pub struct PyClassItemsIter {
227 /// Iteration state
228 idx: usize,
229 /// Items from the `#[pyclass]` macro
230 pyclass_items: &'static PyClassItems,
231 /// Items from the `#[pymethods]` macro
232 #[cfg(not(feature = "multiple-pymethods"))]
233 pymethods_items: &'static PyClassItems,
234 /// Items from the `#[pymethods]` macro with inventory
235 #[cfg(feature = "multiple-pymethods")]
236 pymethods_items: Box<dyn Iterator<Item = &'static PyClassItems>>,
237}
238
239impl PyClassItemsIter {
240 pub fn new(
241 pyclass_items: &'static PyClassItems,
242 #[cfg(not(feature = "multiple-pymethods"))] pymethods_items: &'static PyClassItems,
243 #[cfg(feature = "multiple-pymethods")] pymethods_items: Box<
244 dyn Iterator<Item = &'static PyClassItems>,
245 >,
246 ) -> Self {
247 Self {
248 idx: 0,
249 pyclass_items,
250 pymethods_items,
251 }
252 }
253}
254
255impl Iterator for PyClassItemsIter {
256 type Item = &'static PyClassItems;
257
258 #[cfg(not(feature = "multiple-pymethods"))]
259 fn next(&mut self) -> Option<Self::Item> {
260 match self.idx {
261 0 => {
262 self.idx += 1;
263 Some(self.pyclass_items)
264 }
265 1 => {
266 self.idx += 1;
267 Some(self.pymethods_items)
268 }
269 // Termination clause
270 _ => None,
271 }
272 }
273
274 #[cfg(feature = "multiple-pymethods")]
275 fn next(&mut self) -> Option<Self::Item> {
276 match self.idx {
277 0 => {
278 self.idx += 1;
279 Some(self.pyclass_items)
280 }
281 // Termination clause
282 _ => self.pymethods_items.next(),
283 }
284 }
285}
286
287// Traits describing known special methods.
288
289macro_rules! slot_fragment_trait {
290 ($trait_name:ident, $($default_method:tt)*) => {
291 #[allow(non_camel_case_types)]
292 pub trait $trait_name<T>: Sized {
293 $($default_method)*
294 }
295
296 impl<T> $trait_name<T> for &'_ PyClassImplCollector<T> {}
297 }
298}
299
300slot_fragment_trait! {
301 PyClass__getattribute__SlotFragment,
302
303 /// # Safety: _slf and _attr must be valid non-null Python objects
304 #[inline]
305 unsafe fn __getattribute__(
306 self,
307 py: Python<'_>,
308 slf: *mut ffi::PyObject,
309 attr: *mut ffi::PyObject,
310 ) -> PyResult<*mut ffi::PyObject> {
311 let res = ffi::PyObject_GenericGetAttr(slf, attr);
312 if res.is_null() {
313 Err(PyErr::fetch(py))
314 } else {
315 Ok(res)
316 }
317 }
318}
319
320slot_fragment_trait! {
321 PyClass__getattr__SlotFragment,
322
323 /// # Safety: _slf and _attr must be valid non-null Python objects
324 #[inline]
325 unsafe fn __getattr__(
326 self,
327 py: Python<'_>,
328 _slf: *mut ffi::PyObject,
329 attr: *mut ffi::PyObject,
330 ) -> PyResult<*mut ffi::PyObject> {
331 Err(PyErr::new::<PyAttributeError, _>(
332 (Py::<PyAny>::from_borrowed_ptr(py, attr),)
333 ))
334 }
335}
336
337#[doc(hidden)]
338#[macro_export]
339macro_rules! generate_pyclass_getattro_slot {
340 ($cls:ty) => {{
341 unsafe extern "C" fn __wrap(
342 _slf: *mut $crate::ffi::PyObject,
343 attr: *mut $crate::ffi::PyObject,
344 ) -> *mut $crate::ffi::PyObject {
345 $crate::impl_::trampoline::getattrofunc(_slf, attr, |py, _slf, attr| {
346 use ::std::result::Result::*;
347 use $crate::impl_::pyclass::*;
348 let collector = PyClassImplCollector::<$cls>::new();
349
350 // Strategy:
351 // - Try __getattribute__ first. Its default is PyObject_GenericGetAttr.
352 // - If it returns a result, use it.
353 // - If it fails with AttributeError, try __getattr__.
354 // - If it fails otherwise, reraise.
355 match collector.__getattribute__(py, _slf, attr) {
356 Ok(obj) => Ok(obj),
357 Err(e) if e.is_instance_of::<$crate::exceptions::PyAttributeError>(py) => {
358 collector.__getattr__(py, _slf, attr)
359 }
360 Err(e) => Err(e),
361 }
362 })
363 }
364 $crate::ffi::PyType_Slot {
365 slot: $crate::ffi::Py_tp_getattro,
366 pfunc: __wrap as $crate::ffi::getattrofunc as _,
367 }
368 }};
369}
370
371pub use generate_pyclass_getattro_slot;
372
373/// Macro which expands to three items
374/// - Trait for a __setitem__ dunder
375/// - Trait for the corresponding __delitem__ dunder
376/// - A macro which will use dtolnay specialisation to generate the shared slot for the two dunders
377macro_rules! define_pyclass_setattr_slot {
378 (
379 $set_trait:ident,
380 $del_trait:ident,
381 $set:ident,
382 $del:ident,
383 $set_error:expr,
384 $del_error:expr,
385 $generate_macro:ident,
386 $slot:ident,
387 $func_ty:ident,
388 ) => {
389 slot_fragment_trait! {
390 $set_trait,
391
392 /// # Safety: _slf and _attr must be valid non-null Python objects
393 #[inline]
394 unsafe fn $set(
395 self,
396 _py: Python<'_>,
397 _slf: *mut ffi::PyObject,
398 _attr: *mut ffi::PyObject,
399 _value: NonNull<ffi::PyObject>,
400 ) -> PyResult<()> {
401 $set_error
402 }
403 }
404
405 slot_fragment_trait! {
406 $del_trait,
407
408 /// # Safety: _slf and _attr must be valid non-null Python objects
409 #[inline]
410 unsafe fn $del(
411 self,
412 _py: Python<'_>,
413 _slf: *mut ffi::PyObject,
414 _attr: *mut ffi::PyObject,
415 ) -> PyResult<()> {
416 $del_error
417 }
418 }
419
420 #[doc(hidden)]
421 #[macro_export]
422 macro_rules! $generate_macro {
423 ($cls:ty) => {{
424 unsafe extern "C" fn __wrap(
425 _slf: *mut $crate::ffi::PyObject,
426 attr: *mut $crate::ffi::PyObject,
427 value: *mut $crate::ffi::PyObject,
428 ) -> ::std::os::raw::c_int {
429 $crate::impl_::trampoline::setattrofunc(
430 _slf,
431 attr,
432 value,
433 |py, _slf, attr, value| {
434 use ::std::option::Option::*;
435 use $crate::callback::IntoPyCallbackOutput;
436 use $crate::impl_::pyclass::*;
437 let collector = PyClassImplCollector::<$cls>::new();
438 if let Some(value) = ::std::ptr::NonNull::new(value) {
439 collector.$set(py, _slf, attr, value).convert(py)
440 } else {
441 collector.$del(py, _slf, attr).convert(py)
442 }
443 },
444 )
445 }
446 $crate::ffi::PyType_Slot {
447 slot: $crate::ffi::$slot,
448 pfunc: __wrap as $crate::ffi::$func_ty as _,
449 }
450 }};
451 }
452 pub use $generate_macro;
453 };
454}
455
456define_pyclass_setattr_slot! {
457 PyClass__setattr__SlotFragment,
458 PyClass__delattr__SlotFragment,
459 __setattr__,
460 __delattr__,
461 Err(PyAttributeError::new_err("can't set attribute")),
462 Err(PyAttributeError::new_err("can't delete attribute")),
463 generate_pyclass_setattr_slot,
464 Py_tp_setattro,
465 setattrofunc,
466}
467
468define_pyclass_setattr_slot! {
469 PyClass__set__SlotFragment,
470 PyClass__delete__SlotFragment,
471 __set__,
472 __delete__,
473 Err(PyNotImplementedError::new_err("can't set descriptor")),
474 Err(PyNotImplementedError::new_err("can't delete descriptor")),
475 generate_pyclass_setdescr_slot,
476 Py_tp_descr_set,
477 descrsetfunc,
478}
479
480define_pyclass_setattr_slot! {
481 PyClass__setitem__SlotFragment,
482 PyClass__delitem__SlotFragment,
483 __setitem__,
484 __delitem__,
485 Err(PyNotImplementedError::new_err("can't set item")),
486 Err(PyNotImplementedError::new_err("can't delete item")),
487 generate_pyclass_setitem_slot,
488 Py_mp_ass_subscript,
489 objobjargproc,
490}
491
492/// Macro which expands to three items
493/// - Trait for a lhs dunder e.g. __add__
494/// - Trait for the corresponding rhs e.g. __radd__
495/// - A macro which will use dtolnay specialisation to generate the shared slot for the two dunders
496macro_rules! define_pyclass_binary_operator_slot {
497 (
498 $lhs_trait:ident,
499 $rhs_trait:ident,
500 $lhs:ident,
501 $rhs:ident,
502 $generate_macro:ident,
503 $slot:ident,
504 $func_ty:ident,
505 ) => {
506 slot_fragment_trait! {
507 $lhs_trait,
508
509 /// # Safety: _slf and _other must be valid non-null Python objects
510 #[inline]
511 unsafe fn $lhs(
512 self,
513 _py: Python<'_>,
514 _slf: *mut ffi::PyObject,
515 _other: *mut ffi::PyObject,
516 ) -> PyResult<*mut ffi::PyObject> {
517 Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
518 }
519 }
520
521 slot_fragment_trait! {
522 $rhs_trait,
523
524 /// # Safety: _slf and _other must be valid non-null Python objects
525 #[inline]
526 unsafe fn $rhs(
527 self,
528 _py: Python<'_>,
529 _slf: *mut ffi::PyObject,
530 _other: *mut ffi::PyObject,
531 ) -> PyResult<*mut ffi::PyObject> {
532 Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
533 }
534 }
535
536 #[doc(hidden)]
537 #[macro_export]
538 macro_rules! $generate_macro {
539 ($cls:ty) => {{
540 unsafe extern "C" fn __wrap(
541 _slf: *mut $crate::ffi::PyObject,
542 _other: *mut $crate::ffi::PyObject,
543 ) -> *mut $crate::ffi::PyObject {
544 $crate::impl_::trampoline::binaryfunc(_slf, _other, |py, _slf, _other| {
545 use $crate::impl_::pyclass::*;
546 let collector = PyClassImplCollector::<$cls>::new();
547 let lhs_result = collector.$lhs(py, _slf, _other)?;
548 if lhs_result == $crate::ffi::Py_NotImplemented() {
549 $crate::ffi::Py_DECREF(lhs_result);
550 collector.$rhs(py, _other, _slf)
551 } else {
552 ::std::result::Result::Ok(lhs_result)
553 }
554 })
555 }
556 $crate::ffi::PyType_Slot {
557 slot: $crate::ffi::$slot,
558 pfunc: __wrap as $crate::ffi::$func_ty as _,
559 }
560 }};
561 }
562 pub use $generate_macro;
563 };
564}
565
566define_pyclass_binary_operator_slot! {
567 PyClass__add__SlotFragment,
568 PyClass__radd__SlotFragment,
569 __add__,
570 __radd__,
571 generate_pyclass_add_slot,
572 Py_nb_add,
573 binaryfunc,
574}
575
576define_pyclass_binary_operator_slot! {
577 PyClass__sub__SlotFragment,
578 PyClass__rsub__SlotFragment,
579 __sub__,
580 __rsub__,
581 generate_pyclass_sub_slot,
582 Py_nb_subtract,
583 binaryfunc,
584}
585
586define_pyclass_binary_operator_slot! {
587 PyClass__mul__SlotFragment,
588 PyClass__rmul__SlotFragment,
589 __mul__,
590 __rmul__,
591 generate_pyclass_mul_slot,
592 Py_nb_multiply,
593 binaryfunc,
594}
595
596define_pyclass_binary_operator_slot! {
597 PyClass__mod__SlotFragment,
598 PyClass__rmod__SlotFragment,
599 __mod__,
600 __rmod__,
601 generate_pyclass_mod_slot,
602 Py_nb_remainder,
603 binaryfunc,
604}
605
606define_pyclass_binary_operator_slot! {
607 PyClass__divmod__SlotFragment,
608 PyClass__rdivmod__SlotFragment,
609 __divmod__,
610 __rdivmod__,
611 generate_pyclass_divmod_slot,
612 Py_nb_divmod,
613 binaryfunc,
614}
615
616define_pyclass_binary_operator_slot! {
617 PyClass__lshift__SlotFragment,
618 PyClass__rlshift__SlotFragment,
619 __lshift__,
620 __rlshift__,
621 generate_pyclass_lshift_slot,
622 Py_nb_lshift,
623 binaryfunc,
624}
625
626define_pyclass_binary_operator_slot! {
627 PyClass__rshift__SlotFragment,
628 PyClass__rrshift__SlotFragment,
629 __rshift__,
630 __rrshift__,
631 generate_pyclass_rshift_slot,
632 Py_nb_rshift,
633 binaryfunc,
634}
635
636define_pyclass_binary_operator_slot! {
637 PyClass__and__SlotFragment,
638 PyClass__rand__SlotFragment,
639 __and__,
640 __rand__,
641 generate_pyclass_and_slot,
642 Py_nb_and,
643 binaryfunc,
644}
645
646define_pyclass_binary_operator_slot! {
647 PyClass__or__SlotFragment,
648 PyClass__ror__SlotFragment,
649 __or__,
650 __ror__,
651 generate_pyclass_or_slot,
652 Py_nb_or,
653 binaryfunc,
654}
655
656define_pyclass_binary_operator_slot! {
657 PyClass__xor__SlotFragment,
658 PyClass__rxor__SlotFragment,
659 __xor__,
660 __rxor__,
661 generate_pyclass_xor_slot,
662 Py_nb_xor,
663 binaryfunc,
664}
665
666define_pyclass_binary_operator_slot! {
667 PyClass__matmul__SlotFragment,
668 PyClass__rmatmul__SlotFragment,
669 __matmul__,
670 __rmatmul__,
671 generate_pyclass_matmul_slot,
672 Py_nb_matrix_multiply,
673 binaryfunc,
674}
675
676define_pyclass_binary_operator_slot! {
677 PyClass__truediv__SlotFragment,
678 PyClass__rtruediv__SlotFragment,
679 __truediv__,
680 __rtruediv__,
681 generate_pyclass_truediv_slot,
682 Py_nb_true_divide,
683 binaryfunc,
684}
685
686define_pyclass_binary_operator_slot! {
687 PyClass__floordiv__SlotFragment,
688 PyClass__rfloordiv__SlotFragment,
689 __floordiv__,
690 __rfloordiv__,
691 generate_pyclass_floordiv_slot,
692 Py_nb_floor_divide,
693 binaryfunc,
694}
695
696slot_fragment_trait! {
697 PyClass__pow__SlotFragment,
698
699 /// # Safety: _slf and _other must be valid non-null Python objects
700 #[inline]
701 unsafe fn __pow__(
702 self,
703 _py: Python<'_>,
704 _slf: *mut ffi::PyObject,
705 _other: *mut ffi::PyObject,
706 _mod: *mut ffi::PyObject,
707 ) -> PyResult<*mut ffi::PyObject> {
708 Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
709 }
710}
711
712slot_fragment_trait! {
713 PyClass__rpow__SlotFragment,
714
715 /// # Safety: _slf and _other must be valid non-null Python objects
716 #[inline]
717 unsafe fn __rpow__(
718 self,
719 _py: Python<'_>,
720 _slf: *mut ffi::PyObject,
721 _other: *mut ffi::PyObject,
722 _mod: *mut ffi::PyObject,
723 ) -> PyResult<*mut ffi::PyObject> {
724 Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
725 }
726}
727
728#[doc(hidden)]
729#[macro_export]
730macro_rules! generate_pyclass_pow_slot {
731 ($cls:ty) => {{
732 unsafe extern "C" fn __wrap(
733 _slf: *mut $crate::ffi::PyObject,
734 _other: *mut $crate::ffi::PyObject,
735 _mod: *mut $crate::ffi::PyObject,
736 ) -> *mut $crate::ffi::PyObject {
737 $crate::impl_::trampoline::ternaryfunc(_slf, _other, _mod, |py, _slf, _other, _mod| {
738 use $crate::impl_::pyclass::*;
739 let collector = PyClassImplCollector::<$cls>::new();
740 let lhs_result = collector.__pow__(py, _slf, _other, _mod)?;
741 if lhs_result == $crate::ffi::Py_NotImplemented() {
742 $crate::ffi::Py_DECREF(lhs_result);
743 collector.__rpow__(py, _other, _slf, _mod)
744 } else {
745 ::std::result::Result::Ok(lhs_result)
746 }
747 })
748 }
749 $crate::ffi::PyType_Slot {
750 slot: $crate::ffi::Py_nb_power,
751 pfunc: __wrap as $crate::ffi::ternaryfunc as _,
752 }
753 }};
754}
755pub use generate_pyclass_pow_slot;
756
757slot_fragment_trait! {
758 PyClass__lt__SlotFragment,
759
760 /// # Safety: _slf and _other must be valid non-null Python objects
761 #[inline]
762 unsafe fn __lt__(
763 self,
764 _py: Python<'_>,
765 _slf: *mut ffi::PyObject,
766 _other: *mut ffi::PyObject,
767 ) -> PyResult<*mut ffi::PyObject> {
768 Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
769 }
770}
771
772slot_fragment_trait! {
773 PyClass__le__SlotFragment,
774
775 /// # Safety: _slf and _other must be valid non-null Python objects
776 #[inline]
777 unsafe fn __le__(
778 self,
779 _py: Python<'_>,
780 _slf: *mut ffi::PyObject,
781 _other: *mut ffi::PyObject,
782 ) -> PyResult<*mut ffi::PyObject> {
783 Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
784 }
785}
786
787slot_fragment_trait! {
788 PyClass__eq__SlotFragment,
789
790 /// # Safety: _slf and _other must be valid non-null Python objects
791 #[inline]
792 unsafe fn __eq__(
793 self,
794 _py: Python<'_>,
795 _slf: *mut ffi::PyObject,
796 _other: *mut ffi::PyObject,
797 ) -> PyResult<*mut ffi::PyObject> {
798 Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
799 }
800}
801
802slot_fragment_trait! {
803 PyClass__ne__SlotFragment,
804
805 /// # Safety: _slf and _other must be valid non-null Python objects
806 #[inline]
807 unsafe fn __ne__(
808 self,
809 py: Python<'_>,
810 slf: *mut ffi::PyObject,
811 other: *mut ffi::PyObject,
812 ) -> PyResult<*mut ffi::PyObject> {
813 // By default `__ne__` will try `__eq__` and invert the result
814 let slf: &PyAny = py.from_borrowed_ptr(slf);
815 let other: &PyAny = py.from_borrowed_ptr(other);
816 slf.eq(other).map(|is_eq| PyBool::new(py, !is_eq).into_ptr())
817 }
818}
819
820slot_fragment_trait! {
821 PyClass__gt__SlotFragment,
822
823 /// # Safety: _slf and _other must be valid non-null Python objects
824 #[inline]
825 unsafe fn __gt__(
826 self,
827 _py: Python<'_>,
828 _slf: *mut ffi::PyObject,
829 _other: *mut ffi::PyObject,
830 ) -> PyResult<*mut ffi::PyObject> {
831 Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
832 }
833}
834
835slot_fragment_trait! {
836 PyClass__ge__SlotFragment,
837
838 /// # Safety: _slf and _other must be valid non-null Python objects
839 #[inline]
840 unsafe fn __ge__(
841 self,
842 _py: Python<'_>,
843 _slf: *mut ffi::PyObject,
844 _other: *mut ffi::PyObject,
845 ) -> PyResult<*mut ffi::PyObject> {
846 Ok(ffi::_Py_NewRef(ffi::Py_NotImplemented()))
847 }
848}
849
850#[doc(hidden)]
851#[macro_export]
852macro_rules! generate_pyclass_richcompare_slot {
853 ($cls:ty) => {{
854 impl $cls {
855 #[allow(non_snake_case)]
856 unsafe extern "C" fn __pymethod___richcmp____(
857 slf: *mut $crate::ffi::PyObject,
858 other: *mut $crate::ffi::PyObject,
859 op: ::std::os::raw::c_int,
860 ) -> *mut $crate::ffi::PyObject {
861 $crate::impl_::trampoline::richcmpfunc(slf, other, op, |py, slf, other, op| {
862 use $crate::class::basic::CompareOp;
863 use $crate::impl_::pyclass::*;
864 let collector = PyClassImplCollector::<$cls>::new();
865 match CompareOp::from_raw(op).expect("invalid compareop") {
866 CompareOp::Lt => collector.__lt__(py, slf, other),
867 CompareOp::Le => collector.__le__(py, slf, other),
868 CompareOp::Eq => collector.__eq__(py, slf, other),
869 CompareOp::Ne => collector.__ne__(py, slf, other),
870 CompareOp::Gt => collector.__gt__(py, slf, other),
871 CompareOp::Ge => collector.__ge__(py, slf, other),
872 }
873 })
874 }
875 }
876 $crate::ffi::PyType_Slot {
877 slot: $crate::ffi::Py_tp_richcompare,
878 pfunc: <$cls>::__pymethod___richcmp____ as $crate::ffi::richcmpfunc as _,
879 }
880 }};
881}
882pub use generate_pyclass_richcompare_slot;
883
884/// Implements a freelist.
885///
886/// Do not implement this trait manually. Instead, use `#[pyclass(freelist = N)]`
887/// on a Rust struct to implement it.
888pub trait PyClassWithFreeList: PyClass {
889 fn get_free_list(py: Python<'_>) -> &mut FreeList<*mut ffi::PyObject>;
890}
891
892/// Implementation of tp_alloc for `freelist` classes.
893///
894/// # Safety
895/// - `subtype` must be a valid pointer to the type object of T or a subclass.
896/// - The GIL must be held.
897pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
898 subtype: *mut ffi::PyTypeObject,
899 nitems: ffi::Py_ssize_t,
900) -> *mut ffi::PyObject {
901 let py: Python<'_> = Python::assume_gil_acquired();
902
903 #[cfg(not(Py_3_8))]
904 bpo_35810_workaround(py, subtype);
905
906 let self_type: *mut PyTypeObject = T::type_object_raw(py);
907 // If this type is a variable type or the subtype is not equal to this type, we cannot use the
908 // freelist
909 if nitems == 0 && subtype == self_type {
910 if let Some(obj: *mut PyObject) = T::get_free_list(py).pop() {
911 ffi::PyObject_Init(arg1:obj, arg2:subtype);
912 return obj as _;
913 }
914 }
915
916 ffi::PyType_GenericAlloc(t:subtype, nitems)
917}
918
919/// Implementation of tp_free for `freelist` classes.
920///
921/// # Safety
922/// - `obj` must be a valid pointer to an instance of T (not a subclass).
923/// - The GIL must be held.
924pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
925 let obj: *mut PyObject = obj as *mut ffi::PyObject;
926 debug_assert_eq!(
927 T::type_object_raw(Python::assume_gil_acquired()),
928 ffi::Py_TYPE(obj)
929 );
930 if let Some(obj: *mut PyObject) = T::get_free_list(Python::assume_gil_acquired()).insert(val:obj) {
931 let ty: *mut PyTypeObject = ffi::Py_TYPE(ob:obj);
932
933 // Deduce appropriate inverse of PyType_GenericAlloc
934 let free: fn(*mut c_void) = if ffi::PyType_IS_GC(ty) != 0 {
935 ffi::PyObject_GC_Del
936 } else {
937 ffi::PyObject_Free
938 };
939 free(obj as *mut c_void);
940
941 #[cfg(Py_3_8)]
942 if ffi::PyType_HasFeature(t:ty, f:ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
943 ffi::Py_DECREF(op:ty as *mut ffi::PyObject);
944 }
945 }
946}
947
948/// Workaround for Python issue 35810; no longer necessary in Python 3.8
949#[inline]
950#[cfg(not(Py_3_8))]
951unsafe fn bpo_35810_workaround(py: Python<'_>, ty: *mut ffi::PyTypeObject) {
952 #[cfg(Py_LIMITED_API)]
953 {
954 // Must check version at runtime for abi3 wheels - they could run against a higher version
955 // than the build config suggests.
956 use crate::sync::GILOnceCell;
957 static IS_PYTHON_3_8: GILOnceCell<bool> = GILOnceCell::new();
958
959 if *IS_PYTHON_3_8.get_or_init(py, || py.version_info() >= (3, 8)) {
960 // No fix needed - the wheel is running on a sufficiently new interpreter.
961 return;
962 }
963 }
964 #[cfg(not(Py_LIMITED_API))]
965 {
966 // suppress unused variable warning
967 let _ = py;
968 }
969
970 ffi::Py_INCREF(ty as *mut ffi::PyObject);
971}
972
973/// Implementation detail. Only to be used through our proc macro code.
974/// Method storage for `#[pyclass]`.
975/// Allows arbitrary `#[pymethod]` blocks to submit their methods,
976/// which are eventually collected by `#[pyclass]`.
977#[cfg(feature = "multiple-pymethods")]
978pub trait PyClassInventory: inventory::Collect {
979 /// Returns the items for a single `#[pymethods] impl` block
980 fn items(&'static self) -> &'static PyClassItems;
981}
982
983// Items from #[pymethods] if not using inventory.
984#[cfg(not(feature = "multiple-pymethods"))]
985pub trait PyMethods<T> {
986 fn py_methods(self) -> &'static PyClassItems;
987}
988
989#[cfg(not(feature = "multiple-pymethods"))]
990impl<T> PyMethods<T> for &'_ PyClassImplCollector<T> {
991 fn py_methods(self) -> &'static PyClassItems {
992 &PyClassItems {
993 methods: &[],
994 slots: &[],
995 }
996 }
997}
998
999// Text signature for __new__
1000pub trait PyClassNewTextSignature<T> {
1001 fn new_text_signature(self) -> Option<&'static str>;
1002}
1003
1004impl<T> PyClassNewTextSignature<T> for &'_ PyClassImplCollector<T> {
1005 #[inline]
1006 fn new_text_signature(self) -> Option<&'static str> {
1007 None
1008 }
1009}
1010
1011// Thread checkers
1012
1013#[doc(hidden)]
1014pub trait PyClassThreadChecker<T>: Sized {
1015 fn ensure(&self);
1016 fn can_drop(&self, py: Python<'_>) -> bool;
1017 fn new() -> Self;
1018 private_decl! {}
1019}
1020
1021/// Default thread checker for `#[pyclass]`.
1022///
1023/// Keeping the T: Send bound here slightly improves the compile
1024/// error message to hint to users to figure out what's wrong
1025/// when `#[pyclass]` types do not implement `Send`.
1026#[doc(hidden)]
1027pub struct SendablePyClass<T: Send>(PhantomData<T>);
1028
1029impl<T: Send> PyClassThreadChecker<T> for SendablePyClass<T> {
1030 fn ensure(&self) {}
1031 fn can_drop(&self, _py: Python<'_>) -> bool {
1032 true
1033 }
1034 #[inline]
1035 fn new() -> Self {
1036 SendablePyClass(PhantomData)
1037 }
1038 private_impl! {}
1039}
1040
1041/// Thread checker for `#[pyclass(unsendable)]` types.
1042/// Panics when the value is accessed by another thread.
1043#[doc(hidden)]
1044pub struct ThreadCheckerImpl(thread::ThreadId);
1045
1046impl ThreadCheckerImpl {
1047 fn ensure(&self, type_name: &'static str) {
1048 assert_eq!(
1049 thread::current().id(),
1050 self.0,
1051 "{} is unsendable, but sent to another thread",
1052 type_name
1053 );
1054 }
1055
1056 fn can_drop(&self, py: Python<'_>, type_name: &'static str) -> bool {
1057 if thread::current().id() != self.0 {
1058 PyRuntimeError::new_err(format!(
1059 "{} is unsendable, but is being dropped on another thread",
1060 type_name
1061 ))
1062 .write_unraisable(py, obj:None);
1063 return false;
1064 }
1065
1066 true
1067 }
1068}
1069
1070impl<T> PyClassThreadChecker<T> for ThreadCheckerImpl {
1071 fn ensure(&self) {
1072 self.ensure(std::any::type_name::<T>());
1073 }
1074 fn can_drop(&self, py: Python<'_>) -> bool {
1075 self.can_drop(py, std::any::type_name::<T>())
1076 }
1077 fn new() -> Self {
1078 ThreadCheckerImpl(thread::current().id())
1079 }
1080 private_impl! {}
1081}
1082
1083/// Trait denoting that this class is suitable to be used as a base type for PyClass.
1084pub trait PyClassBaseType: Sized {
1085 type LayoutAsBase: PyCellLayout<Self>;
1086 type BaseNativeType;
1087 type Initializer: PyObjectInit<Self>;
1088 type PyClassMutability: PyClassMutability;
1089}
1090
1091/// All mutable PyClasses can be used as a base type.
1092///
1093/// In the future this will be extended to immutable PyClasses too.
1094impl<T: PyClass> PyClassBaseType for T {
1095 type LayoutAsBase = crate::pycell::PyCell<T>;
1096 type BaseNativeType = T::BaseNativeType;
1097 type Initializer = crate::pyclass_init::PyClassInitializer<Self>;
1098 type PyClassMutability = T::PyClassMutability;
1099}
1100
1101/// Implementation of tp_dealloc for pyclasses without gc
1102pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
1103 crate::impl_::trampoline::dealloc(slf:obj, f:PyCell::<T>::tp_dealloc)
1104}
1105
1106/// Implementation of tp_dealloc for pyclasses with gc
1107pub(crate) unsafe extern "C" fn tp_dealloc_with_gc<T: PyClass>(obj: *mut ffi::PyObject) {
1108 #[cfg(not(PyPy))]
1109 {
1110 ffi::PyObject_GC_UnTrack(arg1:obj.cast());
1111 }
1112 crate::impl_::trampoline::dealloc(slf:obj, f:PyCell::<T>::tp_dealloc)
1113}
1114
1115pub(crate) unsafe extern "C" fn get_sequence_item_from_mapping(
1116 obj: *mut ffi::PyObject,
1117 index: ffi::Py_ssize_t,
1118) -> *mut ffi::PyObject {
1119 let index: *mut PyObject = ffi::PyLong_FromSsize_t(arg1:index);
1120 if index.is_null() {
1121 return std::ptr::null_mut();
1122 }
1123 let result: *mut PyObject = ffi::PyObject_GetItem(o:obj, key:index);
1124 ffi::Py_DECREF(op:index);
1125 result
1126}
1127
1128pub(crate) unsafe extern "C" fn assign_sequence_item_from_mapping(
1129 obj: *mut ffi::PyObject,
1130 index: ffi::Py_ssize_t,
1131 value: *mut ffi::PyObject,
1132) -> c_int {
1133 let index: *mut PyObject = ffi::PyLong_FromSsize_t(arg1:index);
1134 if index.is_null() {
1135 return -1;
1136 }
1137 let result: i32 = if value.is_null() {
1138 ffi::PyObject_DelItem(o:obj, key:index)
1139 } else {
1140 ffi::PyObject_SetItem(o:obj, key:index, v:value)
1141 };
1142 ffi::Py_DECREF(op:index);
1143 result
1144}
1145