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};
15#[allow(deprecated)]
16use crate::{IntoPy, ToPyObject};
17use std::{
18 borrow::Cow,
19 ffi::{CStr, CString},
20 marker::PhantomData,
21 os::raw::{c_int, c_void},
22 ptr,
23 ptr::NonNull,
24 sync::Mutex,
25 thread,
26};
27
28mod assertions;
29mod lazy_type_object;
30mod probes;
31
32pub use assertions::*;
33pub use lazy_type_object::LazyTypeObject;
34pub use probes::*;
35
36/// Gets the offset of the dictionary from the start of the object in bytes.
37#[inline]
38pub fn dict_offset<T: PyClass>() -> ffi::Py_ssize_t {
39 PyClassObject::<T>::dict_offset()
40}
41
42/// Gets the offset of the weakref list from the start of the object in bytes.
43#[inline]
44pub fn weaklist_offset<T: PyClass>() -> ffi::Py_ssize_t {
45 PyClassObject::<T>::weaklist_offset()
46}
47
48mod sealed {
49 pub trait Sealed {}
50
51 impl Sealed for super::PyClassDummySlot {}
52 impl Sealed for super::PyClassDictSlot {}
53 impl Sealed for super::PyClassWeakRefSlot {}
54 impl Sealed for super::ThreadCheckerImpl {}
55 impl<T: Send> Sealed for super::SendablePyClass<T> {}
56}
57
58/// Represents the `__dict__` field for `#[pyclass]`.
59pub trait PyClassDict: sealed::Sealed {
60 /// Initial form of a [PyObject](crate::ffi::PyObject) `__dict__` reference.
61 const INIT: Self;
62 /// Empties the dictionary of its key-value pairs.
63 #[inline]
64 fn clear_dict(&mut self, _py: Python<'_>) {}
65}
66
67/// Represents the `__weakref__` field for `#[pyclass]`.
68pub trait PyClassWeakRef: sealed::Sealed {
69 /// Initializes a `weakref` instance.
70 const INIT: Self;
71 /// Clears the weak references to the given object.
72 ///
73 /// # Safety
74 /// - `_obj` must be a pointer to the pyclass instance which contains `self`.
75 /// - The GIL must be held.
76 #[inline]
77 unsafe fn clear_weakrefs(&mut self, _obj: *mut ffi::PyObject, _py: Python<'_>) {}
78}
79
80/// Zero-sized dummy field.
81pub struct PyClassDummySlot;
82
83impl PyClassDict for PyClassDummySlot {
84 const INIT: Self = PyClassDummySlot;
85}
86
87impl PyClassWeakRef for PyClassDummySlot {
88 const INIT: Self = PyClassDummySlot;
89}
90
91/// Actual dict field, which holds the pointer to `__dict__`.
92///
93/// `#[pyclass(dict)]` automatically adds this.
94#[repr(transparent)]
95#[allow(dead_code)] // These are constructed in INIT and used by the macro code
96pub struct PyClassDictSlot(*mut ffi::PyObject);
97
98impl PyClassDict for PyClassDictSlot {
99 const INIT: Self = Self(std::ptr::null_mut());
100 #[inline]
101 fn clear_dict(&mut self, _py: Python<'_>) {
102 if !self.0.is_null() {
103 unsafe { ffi::PyDict_Clear(self.0) }
104 }
105 }
106}
107
108/// Actual weakref field, which holds the pointer to `__weakref__`.
109///
110/// `#[pyclass(weakref)]` automatically adds this.
111#[repr(transparent)]
112#[allow(dead_code)] // These are constructed in INIT and used by the macro code
113pub struct PyClassWeakRefSlot(*mut ffi::PyObject);
114
115impl PyClassWeakRef for PyClassWeakRefSlot {
116 const INIT: Self = Self(std::ptr::null_mut());
117 #[inline]
118 unsafe fn clear_weakrefs(&mut self, obj: *mut ffi::PyObject, _py: Python<'_>) {
119 if !self.0.is_null() {
120 unsafe { ffi::PyObject_ClearWeakRefs(arg1:obj) }
121 }
122 }
123}
124
125/// This type is used as a "dummy" type on which dtolnay specializations are
126/// applied to apply implementations from `#[pymethods]`
127pub struct PyClassImplCollector<T>(PhantomData<T>);
128
129impl<T> PyClassImplCollector<T> {
130 pub fn new() -> Self {
131 Self(PhantomData)
132 }
133}
134
135impl<T> Default for PyClassImplCollector<T> {
136 fn default() -> Self {
137 Self::new()
138 }
139}
140
141impl<T> Clone for PyClassImplCollector<T> {
142 fn clone(&self) -> Self {
143 *self
144 }
145}
146
147impl<T> Copy for PyClassImplCollector<T> {}
148
149pub enum MaybeRuntimePyMethodDef {
150 /// Used in cases where const functionality is not sufficient to define the method
151 /// purely at compile time.
152 Runtime(fn() -> PyMethodDefType),
153 Static(PyMethodDefType),
154}
155
156pub struct PyClassItems {
157 pub methods: &'static [MaybeRuntimePyMethodDef],
158 pub slots: &'static [ffi::PyType_Slot],
159}
160
161// Allow PyClassItems in statics
162unsafe impl Sync for PyClassItems {}
163
164/// Implements the underlying functionality of `#[pyclass]`, assembled by various proc macros.
165///
166/// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail
167/// and may be changed at any time.
168pub trait PyClassImpl: Sized + 'static {
169 /// #[pyclass(subclass)]
170 const IS_BASETYPE: bool = false;
171
172 /// #[pyclass(extends=...)]
173 const IS_SUBCLASS: bool = false;
174
175 /// #[pyclass(mapping)]
176 const IS_MAPPING: bool = false;
177
178 /// #[pyclass(sequence)]
179 const IS_SEQUENCE: bool = false;
180
181 /// Base class
182 type BaseType: PyTypeInfo + PyClassBaseType;
183
184 /// Immutable or mutable
185 type PyClassMutability: PyClassMutability + GetBorrowChecker<Self>;
186
187 /// Specify this class has `#[pyclass(dict)]` or not.
188 type Dict: PyClassDict;
189
190 /// Specify this class has `#[pyclass(weakref)]` or not.
191 type WeakRef: PyClassWeakRef;
192
193 /// The closest native ancestor. This is `PyAny` by default, and when you declare
194 /// `#[pyclass(extends=PyDict)]`, it's `PyDict`.
195 type BaseNativeType: PyTypeInfo;
196
197 /// This handles following two situations:
198 /// 1. In case `T` is `Send`, stub `ThreadChecker` is used and does nothing.
199 /// This implementation is used by default. Compile fails if `T: !Send`.
200 /// 2. In case `T` is `!Send`, `ThreadChecker` panics when `T` is accessed by another thread.
201 /// This implementation is used when `#[pyclass(unsendable)]` is given.
202 /// Panicking makes it safe to expose `T: !Send` to the Python interpreter, where all objects
203 /// can be accessed by multiple threads by `threading` module.
204 type ThreadChecker: PyClassThreadChecker<Self>;
205
206 #[cfg(feature = "multiple-pymethods")]
207 type Inventory: PyClassInventory;
208
209 /// Rendered class doc
210 fn doc(py: Python<'_>) -> PyResult<&'static CStr>;
211
212 fn items_iter() -> PyClassItemsIter;
213
214 #[inline]
215 fn dict_offset() -> Option<ffi::Py_ssize_t> {
216 None
217 }
218
219 #[inline]
220 fn weaklist_offset() -> Option<ffi::Py_ssize_t> {
221 None
222 }
223
224 fn lazy_type_object() -> &'static LazyTypeObject<Self>;
225}
226
227/// Runtime helper to build a class docstring from the `doc` and `text_signature`.
228///
229/// This is done at runtime because the class text signature is collected via dtolnay
230/// specialization in to the `#[pyclass]` macro from the `#[pymethods]` macro.
231pub fn build_pyclass_doc(
232 class_name: &'static str,
233 doc: &'static CStr,
234 text_signature: Option<&'static str>,
235) -> PyResult<Cow<'static, CStr>> {
236 if let Some(text_signature: &'static str) = text_signature {
237 let doc: CString = CString::new(format!(
238 "{}{}\n--\n\n{}",
239 class_name,
240 text_signature,
241 doc.to_str().unwrap(),
242 ))
243 .map_err(|_| PyValueError::new_err(args:"class doc cannot contain nul bytes"))?;
244 Ok(Cow::Owned(doc))
245 } else {
246 Ok(Cow::Borrowed(doc))
247 }
248}
249
250/// Iterator used to process all class items during type instantiation.
251pub struct PyClassItemsIter {
252 /// Iteration state
253 idx: usize,
254 /// Items from the `#[pyclass]` macro
255 pyclass_items: &'static PyClassItems,
256 /// Items from the `#[pymethods]` macro
257 #[cfg(not(feature = "multiple-pymethods"))]
258 pymethods_items: &'static PyClassItems,
259 /// Items from the `#[pymethods]` macro with inventory
260 #[cfg(feature = "multiple-pymethods")]
261 pymethods_items: Box<dyn Iterator<Item = &'static PyClassItems>>,
262}
263
264impl PyClassItemsIter {
265 pub fn new(
266 pyclass_items: &'static PyClassItems,
267 #[cfg(not(feature = "multiple-pymethods"))] pymethods_items: &'static PyClassItems,
268 #[cfg(feature = "multiple-pymethods")] pymethods_items: Box<
269 dyn Iterator<Item = &'static PyClassItems>,
270 >,
271 ) -> Self {
272 Self {
273 idx: 0,
274 pyclass_items,
275 pymethods_items,
276 }
277 }
278}
279
280impl Iterator for PyClassItemsIter {
281 type Item = &'static PyClassItems;
282
283 #[cfg(not(feature = "multiple-pymethods"))]
284 fn next(&mut self) -> Option<Self::Item> {
285 match self.idx {
286 0 => {
287 self.idx += 1;
288 Some(self.pyclass_items)
289 }
290 1 => {
291 self.idx += 1;
292 Some(self.pymethods_items)
293 }
294 // Termination clause
295 _ => None,
296 }
297 }
298
299 #[cfg(feature = "multiple-pymethods")]
300 fn next(&mut self) -> Option<Self::Item> {
301 match self.idx {
302 0 => {
303 self.idx += 1;
304 Some(self.pyclass_items)
305 }
306 // Termination clause
307 _ => self.pymethods_items.next(),
308 }
309 }
310}
311
312// Traits describing known special methods.
313
314macro_rules! slot_fragment_trait {
315 ($trait_name:ident, $($default_method:tt)*) => {
316 #[allow(non_camel_case_types)]
317 pub trait $trait_name<T>: Sized {
318 $($default_method)*
319 }
320
321 impl<T> $trait_name<T> for &'_ PyClassImplCollector<T> {}
322 }
323}
324
325slot_fragment_trait! {
326 PyClass__getattribute__SlotFragment,
327
328 /// # Safety: _slf and _attr must be valid non-null Python objects
329 #[inline]
330 unsafe fn __getattribute__(
331 self,
332 py: Python<'_>,
333 slf: *mut ffi::PyObject,
334 attr: *mut ffi::PyObject,
335 ) -> PyResult<*mut ffi::PyObject> {
336 let res = unsafe { ffi::PyObject_GenericGetAttr(slf, attr) };
337 if res.is_null() {
338 Err(PyErr::fetch(py))
339 } else {
340 Ok(res)
341 }
342 }
343}
344
345slot_fragment_trait! {
346 PyClass__getattr__SlotFragment,
347
348 /// # Safety: _slf and _attr must be valid non-null Python objects
349 #[inline]
350 unsafe fn __getattr__(
351 self,
352 py: Python<'_>,
353 _slf: *mut ffi::PyObject,
354 attr: *mut ffi::PyObject,
355 ) -> PyResult<*mut ffi::PyObject> {
356 Err(PyErr::new::<PyAttributeError, _>(
357 (unsafe {Py::<PyAny>::from_borrowed_ptr(py, attr)},)
358 ))
359 }
360}
361
362#[doc(hidden)]
363#[macro_export]
364macro_rules! generate_pyclass_getattro_slot {
365 ($cls:ty) => {{
366 unsafe extern "C" fn __wrap(
367 _slf: *mut $crate::ffi::PyObject,
368 attr: *mut $crate::ffi::PyObject,
369 ) -> *mut $crate::ffi::PyObject {
370 unsafe {
371 $crate::impl_::trampoline::getattrofunc(_slf, attr, |py, _slf, attr| {
372 use ::std::result::Result::*;
373 use $crate::impl_::pyclass::*;
374 let collector = PyClassImplCollector::<$cls>::new();
375
376 // Strategy:
377 // - Try __getattribute__ first. Its default is PyObject_GenericGetAttr.
378 // - If it returns a result, use it.
379 // - If it fails with AttributeError, try __getattr__.
380 // - If it fails otherwise, reraise.
381 match collector.__getattribute__(py, _slf, attr) {
382 Ok(obj) => Ok(obj),
383 Err(e) if e.is_instance_of::<$crate::exceptions::PyAttributeError>(py) => {
384 collector.__getattr__(py, _slf, attr)
385 }
386 Err(e) => Err(e),
387 }
388 })
389 }
390 }
391 $crate::ffi::PyType_Slot {
392 slot: $crate::ffi::Py_tp_getattro,
393 pfunc: __wrap as $crate::ffi::getattrofunc as _,
394 }
395 }};
396}
397
398pub use generate_pyclass_getattro_slot;
399
400/// Macro which expands to three items
401/// - Trait for a __setitem__ dunder
402/// - Trait for the corresponding __delitem__ dunder
403/// - A macro which will use dtolnay specialisation to generate the shared slot for the two dunders
404macro_rules! define_pyclass_setattr_slot {
405 (
406 $set_trait:ident,
407 $del_trait:ident,
408 $set:ident,
409 $del:ident,
410 $set_error:expr,
411 $del_error:expr,
412 $generate_macro:ident,
413 $slot:ident,
414 $func_ty:ident,
415 ) => {
416 slot_fragment_trait! {
417 $set_trait,
418
419 /// # Safety: _slf and _attr must be valid non-null Python objects
420 #[inline]
421 unsafe fn $set(
422 self,
423 _py: Python<'_>,
424 _slf: *mut ffi::PyObject,
425 _attr: *mut ffi::PyObject,
426 _value: NonNull<ffi::PyObject>,
427 ) -> PyResult<()> {
428 $set_error
429 }
430 }
431
432 slot_fragment_trait! {
433 $del_trait,
434
435 /// # Safety: _slf and _attr must be valid non-null Python objects
436 #[inline]
437 unsafe fn $del(
438 self,
439 _py: Python<'_>,
440 _slf: *mut ffi::PyObject,
441 _attr: *mut ffi::PyObject,
442 ) -> PyResult<()> {
443 $del_error
444 }
445 }
446
447 #[doc(hidden)]
448 #[macro_export]
449 macro_rules! $generate_macro {
450 ($cls:ty) => {{
451 unsafe extern "C" fn __wrap(
452 _slf: *mut $crate::ffi::PyObject,
453 attr: *mut $crate::ffi::PyObject,
454 value: *mut $crate::ffi::PyObject,
455 ) -> ::std::os::raw::c_int {
456 unsafe {
457 $crate::impl_::trampoline::setattrofunc(
458 _slf,
459 attr,
460 value,
461 |py, _slf, attr, value| {
462 use ::std::option::Option::*;
463 use $crate::impl_::callback::IntoPyCallbackOutput;
464 use $crate::impl_::pyclass::*;
465 let collector = PyClassImplCollector::<$cls>::new();
466 if let Some(value) = ::std::ptr::NonNull::new(value) {
467 collector.$set(py, _slf, attr, value).convert(py)
468 } else {
469 collector.$del(py, _slf, attr).convert(py)
470 }
471 },
472 )
473 }
474 }
475 $crate::ffi::PyType_Slot {
476 slot: $crate::ffi::$slot,
477 pfunc: __wrap as $crate::ffi::$func_ty as _,
478 }
479 }};
480 }
481 pub use $generate_macro;
482 };
483}
484
485define_pyclass_setattr_slot! {
486 PyClass__setattr__SlotFragment,
487 PyClass__delattr__SlotFragment,
488 __setattr__,
489 __delattr__,
490 Err(PyAttributeError::new_err("can't set attribute")),
491 Err(PyAttributeError::new_err("can't delete attribute")),
492 generate_pyclass_setattr_slot,
493 Py_tp_setattro,
494 setattrofunc,
495}
496
497define_pyclass_setattr_slot! {
498 PyClass__set__SlotFragment,
499 PyClass__delete__SlotFragment,
500 __set__,
501 __delete__,
502 Err(PyNotImplementedError::new_err("can't set descriptor")),
503 Err(PyNotImplementedError::new_err("can't delete descriptor")),
504 generate_pyclass_setdescr_slot,
505 Py_tp_descr_set,
506 descrsetfunc,
507}
508
509define_pyclass_setattr_slot! {
510 PyClass__setitem__SlotFragment,
511 PyClass__delitem__SlotFragment,
512 __setitem__,
513 __delitem__,
514 Err(PyNotImplementedError::new_err("can't set item")),
515 Err(PyNotImplementedError::new_err("can't delete item")),
516 generate_pyclass_setitem_slot,
517 Py_mp_ass_subscript,
518 objobjargproc,
519}
520
521/// Macro which expands to three items
522/// - Trait for a lhs dunder e.g. __add__
523/// - Trait for the corresponding rhs e.g. __radd__
524/// - A macro which will use dtolnay specialisation to generate the shared slot for the two dunders
525macro_rules! define_pyclass_binary_operator_slot {
526 (
527 $lhs_trait:ident,
528 $rhs_trait:ident,
529 $lhs:ident,
530 $rhs:ident,
531 $generate_macro:ident,
532 $slot:ident,
533 $func_ty:ident,
534 ) => {
535 slot_fragment_trait! {
536 $lhs_trait,
537
538 /// # Safety: _slf and _other must be valid non-null Python objects
539 #[inline]
540 unsafe fn $lhs(
541 self,
542 py: Python<'_>,
543 _slf: *mut ffi::PyObject,
544 _other: *mut ffi::PyObject,
545 ) -> PyResult<*mut ffi::PyObject> {
546 Ok(py.NotImplemented().into_ptr())
547 }
548 }
549
550 slot_fragment_trait! {
551 $rhs_trait,
552
553 /// # Safety: _slf and _other must be valid non-null Python objects
554 #[inline]
555 unsafe fn $rhs(
556 self,
557 py: Python<'_>,
558 _slf: *mut ffi::PyObject,
559 _other: *mut ffi::PyObject,
560 ) -> PyResult<*mut ffi::PyObject> {
561 Ok(py.NotImplemented().into_ptr())
562 }
563 }
564
565 #[doc(hidden)]
566 #[macro_export]
567 macro_rules! $generate_macro {
568 ($cls:ty) => {{
569 unsafe extern "C" fn __wrap(
570 _slf: *mut $crate::ffi::PyObject,
571 _other: *mut $crate::ffi::PyObject,
572 ) -> *mut $crate::ffi::PyObject {
573 unsafe {
574 $crate::impl_::trampoline::binaryfunc(_slf, _other, |py, _slf, _other| {
575 use $crate::impl_::pyclass::*;
576 let collector = PyClassImplCollector::<$cls>::new();
577 let lhs_result = collector.$lhs(py, _slf, _other)?;
578 if lhs_result == $crate::ffi::Py_NotImplemented() {
579 $crate::ffi::Py_DECREF(lhs_result);
580 collector.$rhs(py, _other, _slf)
581 } else {
582 ::std::result::Result::Ok(lhs_result)
583 }
584 })
585 }
586 }
587 $crate::ffi::PyType_Slot {
588 slot: $crate::ffi::$slot,
589 pfunc: __wrap as $crate::ffi::$func_ty as _,
590 }
591 }};
592 }
593 pub use $generate_macro;
594 };
595}
596
597define_pyclass_binary_operator_slot! {
598 PyClass__add__SlotFragment,
599 PyClass__radd__SlotFragment,
600 __add__,
601 __radd__,
602 generate_pyclass_add_slot,
603 Py_nb_add,
604 binaryfunc,
605}
606
607define_pyclass_binary_operator_slot! {
608 PyClass__sub__SlotFragment,
609 PyClass__rsub__SlotFragment,
610 __sub__,
611 __rsub__,
612 generate_pyclass_sub_slot,
613 Py_nb_subtract,
614 binaryfunc,
615}
616
617define_pyclass_binary_operator_slot! {
618 PyClass__mul__SlotFragment,
619 PyClass__rmul__SlotFragment,
620 __mul__,
621 __rmul__,
622 generate_pyclass_mul_slot,
623 Py_nb_multiply,
624 binaryfunc,
625}
626
627define_pyclass_binary_operator_slot! {
628 PyClass__mod__SlotFragment,
629 PyClass__rmod__SlotFragment,
630 __mod__,
631 __rmod__,
632 generate_pyclass_mod_slot,
633 Py_nb_remainder,
634 binaryfunc,
635}
636
637define_pyclass_binary_operator_slot! {
638 PyClass__divmod__SlotFragment,
639 PyClass__rdivmod__SlotFragment,
640 __divmod__,
641 __rdivmod__,
642 generate_pyclass_divmod_slot,
643 Py_nb_divmod,
644 binaryfunc,
645}
646
647define_pyclass_binary_operator_slot! {
648 PyClass__lshift__SlotFragment,
649 PyClass__rlshift__SlotFragment,
650 __lshift__,
651 __rlshift__,
652 generate_pyclass_lshift_slot,
653 Py_nb_lshift,
654 binaryfunc,
655}
656
657define_pyclass_binary_operator_slot! {
658 PyClass__rshift__SlotFragment,
659 PyClass__rrshift__SlotFragment,
660 __rshift__,
661 __rrshift__,
662 generate_pyclass_rshift_slot,
663 Py_nb_rshift,
664 binaryfunc,
665}
666
667define_pyclass_binary_operator_slot! {
668 PyClass__and__SlotFragment,
669 PyClass__rand__SlotFragment,
670 __and__,
671 __rand__,
672 generate_pyclass_and_slot,
673 Py_nb_and,
674 binaryfunc,
675}
676
677define_pyclass_binary_operator_slot! {
678 PyClass__or__SlotFragment,
679 PyClass__ror__SlotFragment,
680 __or__,
681 __ror__,
682 generate_pyclass_or_slot,
683 Py_nb_or,
684 binaryfunc,
685}
686
687define_pyclass_binary_operator_slot! {
688 PyClass__xor__SlotFragment,
689 PyClass__rxor__SlotFragment,
690 __xor__,
691 __rxor__,
692 generate_pyclass_xor_slot,
693 Py_nb_xor,
694 binaryfunc,
695}
696
697define_pyclass_binary_operator_slot! {
698 PyClass__matmul__SlotFragment,
699 PyClass__rmatmul__SlotFragment,
700 __matmul__,
701 __rmatmul__,
702 generate_pyclass_matmul_slot,
703 Py_nb_matrix_multiply,
704 binaryfunc,
705}
706
707define_pyclass_binary_operator_slot! {
708 PyClass__truediv__SlotFragment,
709 PyClass__rtruediv__SlotFragment,
710 __truediv__,
711 __rtruediv__,
712 generate_pyclass_truediv_slot,
713 Py_nb_true_divide,
714 binaryfunc,
715}
716
717define_pyclass_binary_operator_slot! {
718 PyClass__floordiv__SlotFragment,
719 PyClass__rfloordiv__SlotFragment,
720 __floordiv__,
721 __rfloordiv__,
722 generate_pyclass_floordiv_slot,
723 Py_nb_floor_divide,
724 binaryfunc,
725}
726
727slot_fragment_trait! {
728 PyClass__pow__SlotFragment,
729
730 /// # Safety: _slf and _other must be valid non-null Python objects
731 #[inline]
732 unsafe fn __pow__(
733 self,
734 py: Python<'_>,
735 _slf: *mut ffi::PyObject,
736 _other: *mut ffi::PyObject,
737 _mod: *mut ffi::PyObject,
738 ) -> PyResult<*mut ffi::PyObject> {
739 Ok(py.NotImplemented().into_ptr())
740 }
741}
742
743slot_fragment_trait! {
744 PyClass__rpow__SlotFragment,
745
746 /// # Safety: _slf and _other must be valid non-null Python objects
747 #[inline]
748 unsafe fn __rpow__(
749 self,
750 py: Python<'_>,
751 _slf: *mut ffi::PyObject,
752 _other: *mut ffi::PyObject,
753 _mod: *mut ffi::PyObject,
754 ) -> PyResult<*mut ffi::PyObject> {
755 Ok(py.NotImplemented().into_ptr())
756 }
757}
758
759#[doc(hidden)]
760#[macro_export]
761macro_rules! generate_pyclass_pow_slot {
762 ($cls:ty) => {{
763 unsafe extern "C" fn __wrap(
764 _slf: *mut $crate::ffi::PyObject,
765 _other: *mut $crate::ffi::PyObject,
766 _mod: *mut $crate::ffi::PyObject,
767 ) -> *mut $crate::ffi::PyObject {
768 unsafe {
769 $crate::impl_::trampoline::ternaryfunc(
770 _slf,
771 _other,
772 _mod,
773 |py, _slf, _other, _mod| {
774 use $crate::impl_::pyclass::*;
775 let collector = PyClassImplCollector::<$cls>::new();
776 let lhs_result = collector.__pow__(py, _slf, _other, _mod)?;
777 if lhs_result == $crate::ffi::Py_NotImplemented() {
778 $crate::ffi::Py_DECREF(lhs_result);
779 collector.__rpow__(py, _other, _slf, _mod)
780 } else {
781 ::std::result::Result::Ok(lhs_result)
782 }
783 },
784 )
785 }
786 }
787 $crate::ffi::PyType_Slot {
788 slot: $crate::ffi::Py_nb_power,
789 pfunc: __wrap as $crate::ffi::ternaryfunc as _,
790 }
791 }};
792}
793pub use generate_pyclass_pow_slot;
794
795slot_fragment_trait! {
796 PyClass__lt__SlotFragment,
797
798 /// # Safety: _slf and _other must be valid non-null Python objects
799 #[inline]
800 unsafe fn __lt__(
801 self,
802 py: Python<'_>,
803 _slf: *mut ffi::PyObject,
804 _other: *mut ffi::PyObject,
805 ) -> PyResult<*mut ffi::PyObject> {
806 Ok(py.NotImplemented().into_ptr())
807 }
808}
809
810slot_fragment_trait! {
811 PyClass__le__SlotFragment,
812
813 /// # Safety: _slf and _other must be valid non-null Python objects
814 #[inline]
815 unsafe fn __le__(
816 self,
817 py: Python<'_>,
818 _slf: *mut ffi::PyObject,
819 _other: *mut ffi::PyObject,
820 ) -> PyResult<*mut ffi::PyObject> {
821 Ok(py.NotImplemented().into_ptr())
822 }
823}
824
825slot_fragment_trait! {
826 PyClass__eq__SlotFragment,
827
828 /// # Safety: _slf and _other must be valid non-null Python objects
829 #[inline]
830 unsafe fn __eq__(
831 self,
832 py: Python<'_>,
833 _slf: *mut ffi::PyObject,
834 _other: *mut ffi::PyObject,
835 ) -> PyResult<*mut ffi::PyObject> {
836 Ok(py.NotImplemented().into_ptr())
837 }
838}
839
840slot_fragment_trait! {
841 PyClass__ne__SlotFragment,
842
843 /// # Safety: _slf and _other must be valid non-null Python objects
844 #[inline]
845 unsafe fn __ne__(
846 self,
847 py: Python<'_>,
848 slf: *mut ffi::PyObject,
849 other: *mut ffi::PyObject,
850 ) -> PyResult<*mut ffi::PyObject> {
851 // By default `__ne__` will try `__eq__` and invert the result
852 let slf = unsafe { Borrowed::from_ptr(py, slf)};
853 let other = unsafe { Borrowed::from_ptr(py, other)};
854 slf.eq(other).map(|is_eq| PyBool::new(py, !is_eq).to_owned().into_ptr())
855 }
856}
857
858slot_fragment_trait! {
859 PyClass__gt__SlotFragment,
860
861 /// # Safety: _slf and _other must be valid non-null Python objects
862 #[inline]
863 unsafe fn __gt__(
864 self,
865 py: Python<'_>,
866 _slf: *mut ffi::PyObject,
867 _other: *mut ffi::PyObject,
868 ) -> PyResult<*mut ffi::PyObject> {
869 Ok(py.NotImplemented().into_ptr())
870 }
871}
872
873slot_fragment_trait! {
874 PyClass__ge__SlotFragment,
875
876 /// # Safety: _slf and _other must be valid non-null Python objects
877 #[inline]
878 unsafe fn __ge__(
879 self,
880 py: Python<'_>,
881 _slf: *mut ffi::PyObject,
882 _other: *mut ffi::PyObject,
883 ) -> PyResult<*mut ffi::PyObject> {
884 Ok(py.NotImplemented().into_ptr())
885 }
886}
887
888#[doc(hidden)]
889#[macro_export]
890macro_rules! generate_pyclass_richcompare_slot {
891 ($cls:ty) => {{
892 #[allow(unknown_lints, non_local_definitions)]
893 impl $cls {
894 #[allow(non_snake_case)]
895 unsafe extern "C" fn __pymethod___richcmp____(
896 slf: *mut $crate::ffi::PyObject,
897 other: *mut $crate::ffi::PyObject,
898 op: ::std::os::raw::c_int,
899 ) -> *mut $crate::ffi::PyObject {
900 unsafe {
901 $crate::impl_::trampoline::richcmpfunc(slf, other, op, |py, slf, other, op| {
902 use $crate::class::basic::CompareOp;
903 use $crate::impl_::pyclass::*;
904 let collector = PyClassImplCollector::<$cls>::new();
905 match CompareOp::from_raw(op).expect("invalid compareop") {
906 CompareOp::Lt => collector.__lt__(py, slf, other),
907 CompareOp::Le => collector.__le__(py, slf, other),
908 CompareOp::Eq => collector.__eq__(py, slf, other),
909 CompareOp::Ne => collector.__ne__(py, slf, other),
910 CompareOp::Gt => collector.__gt__(py, slf, other),
911 CompareOp::Ge => collector.__ge__(py, slf, other),
912 }
913 })
914 }
915 }
916 }
917 $crate::ffi::PyType_Slot {
918 slot: $crate::ffi::Py_tp_richcompare,
919 pfunc: <$cls>::__pymethod___richcmp____ as $crate::ffi::richcmpfunc as _,
920 }
921 }};
922}
923pub use generate_pyclass_richcompare_slot;
924
925use super::{pycell::PyClassObject, pymethods::BoundRef};
926
927/// Implements a freelist.
928///
929/// Do not implement this trait manually. Instead, use `#[pyclass(freelist = N)]`
930/// on a Rust struct to implement it.
931pub trait PyClassWithFreeList: PyClass {
932 fn get_free_list(py: Python<'_>) -> &'static Mutex<PyObjectFreeList>;
933}
934
935/// Implementation of tp_alloc for `freelist` classes.
936///
937/// # Safety
938/// - `subtype` must be a valid pointer to the type object of T or a subclass.
939/// - The GIL must be held.
940pub unsafe extern "C" fn alloc_with_freelist<T: PyClassWithFreeList>(
941 subtype: *mut ffi::PyTypeObject,
942 nitems: ffi::Py_ssize_t,
943) -> *mut ffi::PyObject {
944 let py: Python<'_> = unsafe { Python::assume_gil_acquired() };
945
946 #[cfg(not(Py_3_8))]
947 unsafe {
948 bpo_35810_workaround(py, subtype)
949 };
950
951 let self_type: *mut PyTypeObject = T::type_object_raw(py);
952 // If this type is a variable type or the subtype is not equal to this type, we cannot use the
953 // freelist
954 if nitems == 0 && ptr::eq(a:subtype, b:self_type) {
955 let mut free_list: MutexGuard<'_, PyObjectFreeList> = T::get_free_list(py).lock().unwrap();
956 if let Some(obj: *mut PyObject) = free_list.pop() {
957 drop(free_list);
958 unsafe { ffi::PyObject_Init(arg1:obj, arg2:subtype) };
959 unsafe { ffi::PyObject_Init(arg1:obj, arg2:subtype) };
960 return obj as _;
961 }
962 }
963
964 unsafe { ffi::PyType_GenericAlloc(t:subtype, nitems) }
965}
966
967/// Implementation of tp_free for `freelist` classes.
968///
969/// # Safety
970/// - `obj` must be a valid pointer to an instance of T (not a subclass).
971/// - The GIL must be held.
972pub unsafe extern "C" fn free_with_freelist<T: PyClassWithFreeList>(obj: *mut c_void) {
973 let obj = obj as *mut ffi::PyObject;
974 unsafe {
975 debug_assert_eq!(
976 T::type_object_raw(Python::assume_gil_acquired()),
977 ffi::Py_TYPE(obj)
978 );
979 let mut free_list = T::get_free_list(Python::assume_gil_acquired())
980 .lock()
981 .unwrap();
982 if let Some(obj) = free_list.insert(obj) {
983 drop(free_list);
984 let ty = ffi::Py_TYPE(obj);
985
986 // Deduce appropriate inverse of PyType_GenericAlloc
987 let free = if ffi::PyType_IS_GC(ty) != 0 {
988 ffi::PyObject_GC_Del
989 } else {
990 ffi::PyObject_Free
991 };
992 free(obj as *mut c_void);
993
994 #[cfg(Py_3_8)]
995 if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 {
996 ffi::Py_DECREF(ty as *mut ffi::PyObject);
997 }
998 }
999 }
1000}
1001
1002/// Workaround for Python issue 35810; no longer necessary in Python 3.8
1003#[inline]
1004#[cfg(not(Py_3_8))]
1005unsafe fn bpo_35810_workaround(py: Python<'_>, ty: *mut ffi::PyTypeObject) {
1006 #[cfg(Py_LIMITED_API)]
1007 {
1008 // Must check version at runtime for abi3 wheels - they could run against a higher version
1009 // than the build config suggests.
1010 use crate::sync::GILOnceCell;
1011 static IS_PYTHON_3_8: GILOnceCell<bool> = GILOnceCell::new();
1012
1013 if *IS_PYTHON_3_8.get_or_init(py, || py.version_info() >= (3, 8)) {
1014 // No fix needed - the wheel is running on a sufficiently new interpreter.
1015 return;
1016 }
1017 }
1018 #[cfg(not(Py_LIMITED_API))]
1019 {
1020 // suppress unused variable warning
1021 let _ = py;
1022 }
1023
1024 unsafe { ffi::Py_INCREF(ty as *mut ffi::PyObject) };
1025}
1026
1027/// Method storage for `#[pyclass]`.
1028///
1029/// Implementation detail. Only to be used through our proc macro code.
1030/// Allows arbitrary `#[pymethod]` blocks to submit their methods,
1031/// which are eventually collected by `#[pyclass]`.
1032#[cfg(feature = "multiple-pymethods")]
1033pub trait PyClassInventory: inventory::Collect {
1034 /// Returns the items for a single `#[pymethods] impl` block
1035 fn items(&'static self) -> &'static PyClassItems;
1036}
1037
1038// Items from #[pymethods] if not using inventory.
1039#[cfg(not(feature = "multiple-pymethods"))]
1040pub trait PyMethods<T> {
1041 fn py_methods(self) -> &'static PyClassItems;
1042}
1043
1044#[cfg(not(feature = "multiple-pymethods"))]
1045impl<T> PyMethods<T> for &'_ PyClassImplCollector<T> {
1046 fn py_methods(self) -> &'static PyClassItems {
1047 &PyClassItems {
1048 methods: &[],
1049 slots: &[],
1050 }
1051 }
1052}
1053
1054// Text signature for __new__
1055pub trait PyClassNewTextSignature<T> {
1056 fn new_text_signature(self) -> Option<&'static str>;
1057}
1058
1059impl<T> PyClassNewTextSignature<T> for &'_ PyClassImplCollector<T> {
1060 #[inline]
1061 fn new_text_signature(self) -> Option<&'static str> {
1062 None
1063 }
1064}
1065
1066// Thread checkers
1067
1068#[doc(hidden)]
1069pub trait PyClassThreadChecker<T>: Sized + sealed::Sealed {
1070 fn ensure(&self);
1071 fn check(&self) -> bool;
1072 fn can_drop(&self, py: Python<'_>) -> bool;
1073 fn new() -> Self;
1074}
1075
1076/// Default thread checker for `#[pyclass]`.
1077///
1078/// Keeping the T: Send bound here slightly improves the compile
1079/// error message to hint to users to figure out what's wrong
1080/// when `#[pyclass]` types do not implement `Send`.
1081#[doc(hidden)]
1082pub struct SendablePyClass<T: Send>(PhantomData<T>);
1083
1084impl<T: Send> PyClassThreadChecker<T> for SendablePyClass<T> {
1085 fn ensure(&self) {}
1086 fn check(&self) -> bool {
1087 true
1088 }
1089 fn can_drop(&self, _py: Python<'_>) -> bool {
1090 true
1091 }
1092 #[inline]
1093 fn new() -> Self {
1094 SendablePyClass(PhantomData)
1095 }
1096}
1097
1098/// Thread checker for `#[pyclass(unsendable)]` types.
1099/// Panics when the value is accessed by another thread.
1100#[doc(hidden)]
1101pub struct ThreadCheckerImpl(thread::ThreadId);
1102
1103impl ThreadCheckerImpl {
1104 fn ensure(&self, type_name: &'static str) {
1105 assert_eq!(
1106 thread::current().id(),
1107 self.0,
1108 "{} is unsendable, but sent to another thread",
1109 type_name
1110 );
1111 }
1112
1113 fn check(&self) -> bool {
1114 thread::current().id() == self.0
1115 }
1116
1117 fn can_drop(&self, py: Python<'_>, type_name: &'static str) -> bool {
1118 if thread::current().id() != self.0 {
1119 PyRuntimeError::new_err(format!(
1120 "{} is unsendable, but is being dropped on another thread",
1121 type_name
1122 ))
1123 .write_unraisable(py, None);
1124 return false;
1125 }
1126
1127 true
1128 }
1129}
1130
1131impl<T> PyClassThreadChecker<T> for ThreadCheckerImpl {
1132 fn ensure(&self) {
1133 self.ensure(std::any::type_name::<T>());
1134 }
1135 fn check(&self) -> bool {
1136 self.check()
1137 }
1138 fn can_drop(&self, py: Python<'_>) -> bool {
1139 self.can_drop(py, std::any::type_name::<T>())
1140 }
1141 fn new() -> Self {
1142 ThreadCheckerImpl(thread::current().id())
1143 }
1144}
1145
1146/// Trait denoting that this class is suitable to be used as a base type for PyClass.
1147#[cfg_attr(
1148 all(diagnostic_namespace, Py_LIMITED_API),
1149 diagnostic::on_unimplemented(
1150 message = "pyclass `{Self}` cannot be subclassed",
1151 label = "required for `#[pyclass(extends={Self})]`",
1152 note = "`{Self}` must have `#[pyclass(subclass)]` to be eligible for subclassing",
1153 note = "with the `abi3` feature enabled, PyO3 does not support subclassing native types",
1154 )
1155)]
1156#[cfg_attr(
1157 all(diagnostic_namespace, not(Py_LIMITED_API)),
1158 diagnostic::on_unimplemented(
1159 message = "pyclass `{Self}` cannot be subclassed",
1160 label = "required for `#[pyclass(extends={Self})]`",
1161 note = "`{Self}` must have `#[pyclass(subclass)]` to be eligible for subclassing",
1162 )
1163)]
1164pub trait PyClassBaseType: Sized {
1165 type LayoutAsBase: PyClassObjectLayout<Self>;
1166 type BaseNativeType;
1167 type Initializer: PyObjectInit<Self>;
1168 type PyClassMutability: PyClassMutability;
1169}
1170
1171/// Implementation of tp_dealloc for pyclasses without gc
1172pub(crate) unsafe extern "C" fn tp_dealloc<T: PyClass>(obj: *mut ffi::PyObject) {
1173 unsafe { crate::impl_::trampoline::dealloc(slf:obj, f:PyClassObject::<T>::tp_dealloc) }
1174}
1175
1176/// Implementation of tp_dealloc for pyclasses with gc
1177pub(crate) unsafe extern "C" fn tp_dealloc_with_gc<T: PyClass>(obj: *mut ffi::PyObject) {
1178 #[cfg(not(PyPy))]
1179 unsafe {
1180 ffi::PyObject_GC_UnTrack(arg1:obj.cast());
1181 }
1182 unsafe { crate::impl_::trampoline::dealloc(slf:obj, f:PyClassObject::<T>::tp_dealloc) }
1183}
1184
1185pub(crate) unsafe extern "C" fn get_sequence_item_from_mapping(
1186 obj: *mut ffi::PyObject,
1187 index: ffi::Py_ssize_t,
1188) -> *mut ffi::PyObject {
1189 let index: *mut PyObject = unsafe { ffi::PyLong_FromSsize_t(arg1:index) };
1190 if index.is_null() {
1191 return std::ptr::null_mut();
1192 }
1193 let result: *mut PyObject = unsafe { ffi::PyObject_GetItem(o:obj, key:index) };
1194 unsafe { ffi::Py_DECREF(op:index) };
1195 result
1196}
1197
1198pub(crate) unsafe extern "C" fn assign_sequence_item_from_mapping(
1199 obj: *mut ffi::PyObject,
1200 index: ffi::Py_ssize_t,
1201 value: *mut ffi::PyObject,
1202) -> c_int {
1203 unsafe {
1204 let index: *mut PyObject = ffi::PyLong_FromSsize_t(arg1:index);
1205 if index.is_null() {
1206 return -1;
1207 }
1208 let result: i32 = if value.is_null() {
1209 ffi::PyObject_DelItem(o:obj, key:index)
1210 } else {
1211 ffi::PyObject_SetItem(o:obj, key:index, v:value)
1212 };
1213 ffi::Py_DECREF(op:index);
1214 result
1215 }
1216}
1217
1218/// Helper trait to locate field within a `#[pyclass]` for a `#[pyo3(get)]`.
1219///
1220/// Below MSRV 1.77 we can't use `std::mem::offset_of!`, and the replacement in
1221/// `memoffset::offset_of` doesn't work in const contexts for types containing `UnsafeCell`.
1222///
1223/// # Safety
1224///
1225/// The trait is unsafe to implement because producing an incorrect offset will lead to UB.
1226pub unsafe trait OffsetCalculator<T: PyClass, U> {
1227 /// Offset to the field within a `PyClassObject<T>`, in bytes.
1228 fn offset() -> usize;
1229}
1230
1231// Used in generated implementations of OffsetCalculator
1232pub fn class_offset<T: PyClass>() -> usize {
1233 offset_of!(PyClassObject<T>, contents)
1234}
1235
1236// Used in generated implementations of OffsetCalculator
1237pub use memoffset::offset_of;
1238
1239/// Type which uses specialization on impl blocks to determine how to read a field from a Rust pyclass
1240/// as part of a `#[pyo3(get)]` annotation.
1241pub struct PyClassGetterGenerator<
1242 // structural information about the field: class type, field type, where the field is within the
1243 // class struct
1244 ClassT: PyClass,
1245 FieldT,
1246 Offset: OffsetCalculator<ClassT, FieldT>, // on Rust 1.77+ this could be a const OFFSET: usize
1247 // additional metadata about the field which is used to switch between different implementations
1248 // at compile time
1249 const IS_PY_T: bool,
1250 const IMPLEMENTS_TOPYOBJECT: bool,
1251 const IMPLEMENTS_INTOPY: bool,
1252 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1253 const IMPLEMENTS_INTOPYOBJECT: bool,
1254>(PhantomData<(ClassT, FieldT, Offset)>);
1255
1256impl<
1257 ClassT: PyClass,
1258 FieldT,
1259 Offset: OffsetCalculator<ClassT, FieldT>,
1260 const IS_PY_T: bool,
1261 const IMPLEMENTS_TOPYOBJECT: bool,
1262 const IMPLEMENTS_INTOPY: bool,
1263 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1264 const IMPLEMENTS_INTOPYOBJECT: bool,
1265 >
1266 PyClassGetterGenerator<
1267 ClassT,
1268 FieldT,
1269 Offset,
1270 IS_PY_T,
1271 IMPLEMENTS_TOPYOBJECT,
1272 IMPLEMENTS_INTOPY,
1273 IMPLEMENTS_INTOPYOBJECT_REF,
1274 IMPLEMENTS_INTOPYOBJECT,
1275 >
1276{
1277 /// Safety: constructing this type requires that there exists a value of type FieldT
1278 /// at the calculated offset within the type ClassT.
1279 pub const unsafe fn new() -> Self {
1280 Self(PhantomData)
1281 }
1282}
1283
1284impl<
1285 ClassT: PyClass,
1286 U,
1287 Offset: OffsetCalculator<ClassT, Py<U>>,
1288 const IMPLEMENTS_TOPYOBJECT: bool,
1289 const IMPLEMENTS_INTOPY: bool,
1290 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1291 const IMPLEMENTS_INTOPYOBJECT: bool,
1292 >
1293 PyClassGetterGenerator<
1294 ClassT,
1295 Py<U>,
1296 Offset,
1297 true,
1298 IMPLEMENTS_TOPYOBJECT,
1299 IMPLEMENTS_INTOPY,
1300 IMPLEMENTS_INTOPYOBJECT_REF,
1301 IMPLEMENTS_INTOPYOBJECT,
1302 >
1303{
1304 /// `Py<T>` fields have a potential optimization to use Python's "struct members" to read
1305 /// the field directly from the struct, rather than using a getter function.
1306 ///
1307 /// This is the most efficient operation the Python interpreter could possibly do to
1308 /// read a field, but it's only possible for us to allow this for frozen classes.
1309 pub fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1310 use crate::pyclass::boolean_struct::private::Boolean;
1311 if ClassT::Frozen::VALUE {
1312 PyMethodDefType::StructMember(ffi::PyMemberDef {
1313 name: name.as_ptr(),
1314 type_code: ffi::Py_T_OBJECT_EX,
1315 offset: Offset::offset() as ffi::Py_ssize_t,
1316 flags: ffi::Py_READONLY,
1317 doc: doc.as_ptr(),
1318 })
1319 } else {
1320 PyMethodDefType::Getter(PyGetterDef {
1321 name,
1322 meth: pyo3_get_value_topyobject::<ClassT, Py<U>, Offset>,
1323 doc,
1324 })
1325 }
1326 }
1327}
1328
1329/// Fallback case; Field is not `Py<T>`; try to use `ToPyObject` to avoid potentially expensive
1330/// clones of containers like `Vec`
1331#[allow(deprecated)]
1332impl<
1333 ClassT: PyClass,
1334 FieldT: ToPyObject,
1335 Offset: OffsetCalculator<ClassT, FieldT>,
1336 const IMPLEMENTS_INTOPY: bool,
1337 > PyClassGetterGenerator<ClassT, FieldT, Offset, false, true, IMPLEMENTS_INTOPY, false, false>
1338{
1339 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1340 PyMethodDefType::Getter(PyGetterDef {
1341 name,
1342 meth: pyo3_get_value_topyobject::<ClassT, FieldT, Offset>,
1343 doc,
1344 })
1345 }
1346}
1347
1348/// Field is not `Py<T>`; try to use `IntoPyObject` for `&T` (prefered over `ToPyObject`) to avoid
1349/// potentially expensive clones of containers like `Vec`
1350impl<
1351 ClassT,
1352 FieldT,
1353 Offset,
1354 const IMPLEMENTS_TOPYOBJECT: bool,
1355 const IMPLEMENTS_INTOPY: bool,
1356 const IMPLEMENTS_INTOPYOBJECT: bool,
1357 >
1358 PyClassGetterGenerator<
1359 ClassT,
1360 FieldT,
1361 Offset,
1362 false,
1363 IMPLEMENTS_TOPYOBJECT,
1364 IMPLEMENTS_INTOPY,
1365 true,
1366 IMPLEMENTS_INTOPYOBJECT,
1367 >
1368where
1369 ClassT: PyClass,
1370 for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1371 Offset: OffsetCalculator<ClassT, FieldT>,
1372{
1373 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1374 PyMethodDefType::Getter(PyGetterDef {
1375 name,
1376 meth: pyo3_get_value_into_pyobject_ref::<ClassT, FieldT, Offset>,
1377 doc,
1378 })
1379 }
1380}
1381
1382/// Temporary case to prefer `IntoPyObject + Clone` over `IntoPy + Clone`, while still showing the
1383/// `IntoPyObject` suggestion if neither is implemented;
1384impl<ClassT, FieldT, Offset, const IMPLEMENTS_TOPYOBJECT: bool, const IMPLEMENTS_INTOPY: bool>
1385 PyClassGetterGenerator<
1386 ClassT,
1387 FieldT,
1388 Offset,
1389 false,
1390 IMPLEMENTS_TOPYOBJECT,
1391 IMPLEMENTS_INTOPY,
1392 false,
1393 true,
1394 >
1395where
1396 ClassT: PyClass,
1397 Offset: OffsetCalculator<ClassT, FieldT>,
1398 for<'py> FieldT: IntoPyObject<'py> + Clone,
1399{
1400 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1401 PyMethodDefType::Getter(PyGetterDef {
1402 name,
1403 meth: pyo3_get_value_into_pyobject::<ClassT, FieldT, Offset>,
1404 doc,
1405 })
1406 }
1407}
1408
1409/// IntoPy + Clone fallback case, which was the only behaviour before PyO3 0.22.
1410#[allow(deprecated)]
1411impl<ClassT, FieldT, Offset>
1412 PyClassGetterGenerator<ClassT, FieldT, Offset, false, false, true, false, false>
1413where
1414 ClassT: PyClass,
1415 Offset: OffsetCalculator<ClassT, FieldT>,
1416 FieldT: IntoPy<Py<PyAny>> + Clone,
1417{
1418 pub const fn generate(&self, name: &'static CStr, doc: &'static CStr) -> PyMethodDefType {
1419 PyMethodDefType::Getter(PyGetterDef {
1420 name,
1421 meth: pyo3_get_value::<ClassT, FieldT, Offset>,
1422 doc,
1423 })
1424 }
1425}
1426
1427#[cfg_attr(
1428 diagnostic_namespace,
1429 diagnostic::on_unimplemented(
1430 message = "`{Self}` cannot be converted to a Python object",
1431 label = "required by `#[pyo3(get)]` to create a readable property from a field of type `{Self}`",
1432 note = "implement `IntoPyObject` for `&{Self}` or `IntoPyObject + Clone` for `{Self}` to define the conversion"
1433 )
1434)]
1435pub trait PyO3GetField<'py>: IntoPyObject<'py> + Clone {}
1436impl<'py, T> PyO3GetField<'py> for T where T: IntoPyObject<'py> + Clone {}
1437
1438/// Base case attempts to use IntoPyObject + Clone
1439impl<ClassT: PyClass, FieldT, Offset: OffsetCalculator<ClassT, FieldT>>
1440 PyClassGetterGenerator<ClassT, FieldT, Offset, false, false, false, false, false>
1441{
1442 pub const fn generate(&self, _name: &'static CStr, _doc: &'static CStr) -> PyMethodDefType
1443 // The bound goes here rather than on the block so that this impl is always available
1444 // if no specialization is used instead
1445 where
1446 for<'py> FieldT: PyO3GetField<'py>,
1447 {
1448 // unreachable not allowed in const
1449 panic!(
1450 "exists purely to emit diagnostics on unimplemented traits. When `ToPyObject` \
1451 and `IntoPy` are fully removed this will be replaced by the temporary `IntoPyObject` case above."
1452 )
1453 }
1454}
1455
1456/// ensures `obj` is not mutably aliased
1457#[inline]
1458unsafe fn ensure_no_mutable_alias<'py, ClassT: PyClass>(
1459 py: Python<'py>,
1460 obj: &*mut ffi::PyObject,
1461) -> Result<PyRef<'py, ClassT>, PyBorrowError> {
1462 unsafe {
1463 BoundRefBoundRef<'_, '_, ClassT>::ref_from_ptr(py, ptr:obj)
1464 .downcast_unchecked::<ClassT>()
1465 .try_borrow()
1466 }
1467}
1468
1469/// calculates the field pointer from an PyObject pointer
1470#[inline]
1471fn field_from_object<ClassT, FieldT, Offset>(obj: *mut ffi::PyObject) -> *mut FieldT
1472where
1473 ClassT: PyClass,
1474 Offset: OffsetCalculator<ClassT, FieldT>,
1475{
1476 unsafe { obj.cast::<u8>().add(Offset::offset()).cast::<FieldT>() }
1477}
1478
1479#[allow(deprecated)]
1480fn pyo3_get_value_topyobject<
1481 ClassT: PyClass,
1482 FieldT: ToPyObject,
1483 Offset: OffsetCalculator<ClassT, FieldT>,
1484>(
1485 py: Python<'_>,
1486 obj: *mut ffi::PyObject,
1487) -> PyResult<*mut ffi::PyObject> {
1488 let _holder: PyRef<'_, ClassT> = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1489 let value: *mut FieldT = field_from_object::<ClassT, FieldT, Offset>(obj);
1490
1491 // SAFETY: Offset is known to describe the location of the value, and
1492 // _holder is preventing mutable aliasing
1493 Ok((unsafe { &*value }).to_object(py).into_ptr())
1494}
1495
1496fn pyo3_get_value_into_pyobject_ref<ClassT, FieldT, Offset>(
1497 py: Python<'_>,
1498 obj: *mut ffi::PyObject,
1499) -> PyResult<*mut ffi::PyObject>
1500where
1501 ClassT: PyClass,
1502 for<'a, 'py> &'a FieldT: IntoPyObject<'py>,
1503 Offset: OffsetCalculator<ClassT, FieldT>,
1504{
1505 let _holder: PyRef<'_, ClassT> = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1506 let value: *mut FieldT = field_from_object::<ClassT, FieldT, Offset>(obj);
1507
1508 // SAFETY: Offset is known to describe the location of the value, and
1509 // _holder is preventing mutable aliasing
1510 Ok((unsafe { &*value })
1511 .into_pyobject(py)
1512 .map_err(op:Into::into)?
1513 .into_ptr())
1514}
1515
1516fn pyo3_get_value_into_pyobject<ClassT, FieldT, Offset>(
1517 py: Python<'_>,
1518 obj: *mut ffi::PyObject,
1519) -> PyResult<*mut ffi::PyObject>
1520where
1521 ClassT: PyClass,
1522 for<'py> FieldT: IntoPyObject<'py> + Clone,
1523 Offset: OffsetCalculator<ClassT, FieldT>,
1524{
1525 let _holder: PyRef<'_, ClassT> = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1526 let value: *mut FieldT = field_from_object::<ClassT, FieldT, Offset>(obj);
1527
1528 // SAFETY: Offset is known to describe the location of the value, and
1529 // _holder is preventing mutable aliasing
1530 Ok((unsafe { &*value })
1531 .clone()
1532 .into_pyobject(py)
1533 .map_err(op:Into::into)?
1534 .into_ptr())
1535}
1536
1537#[allow(deprecated)]
1538fn pyo3_get_value<
1539 ClassT: PyClass,
1540 FieldT: IntoPy<Py<PyAny>> + Clone,
1541 Offset: OffsetCalculator<ClassT, FieldT>,
1542>(
1543 py: Python<'_>,
1544 obj: *mut ffi::PyObject,
1545) -> PyResult<*mut ffi::PyObject> {
1546 let _holder: PyRef<'_, ClassT> = unsafe { ensure_no_mutable_alias::<ClassT>(py, &obj)? };
1547 let value: *mut FieldT = field_from_object::<ClassT, FieldT, Offset>(obj);
1548
1549 // SAFETY: Offset is known to describe the location of the value, and
1550 // _holder is preventing mutable aliasing
1551 Ok((unsafe { &*value }).clone().into_py(py).into_ptr())
1552}
1553
1554pub struct ConvertField<
1555 const IMPLEMENTS_INTOPYOBJECT_REF: bool,
1556 const IMPLEMENTS_INTOPYOBJECT: bool,
1557>;
1558
1559impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<true, IMPLEMENTS_INTOPYOBJECT> {
1560 #[inline]
1561 pub fn convert_field<'a, 'py, T>(obj: &'a T, py: Python<'py>) -> PyResult<Py<PyAny>>
1562 where
1563 &'a T: IntoPyObject<'py>,
1564 {
1565 obj.into_py_any(py)
1566 }
1567}
1568
1569impl<const IMPLEMENTS_INTOPYOBJECT: bool> ConvertField<false, IMPLEMENTS_INTOPYOBJECT> {
1570 #[inline]
1571 pub fn convert_field<'py, T>(obj: &T, py: Python<'py>) -> PyResult<Py<PyAny>>
1572 where
1573 T: PyO3GetField<'py>,
1574 {
1575 obj.clone().into_py_any(py)
1576 }
1577}
1578
1579#[cfg(test)]
1580#[cfg(feature = "macros")]
1581mod tests {
1582 use super::*;
1583
1584 #[test]
1585 fn get_py_for_frozen_class() {
1586 #[crate::pyclass(crate = "crate", frozen)]
1587 struct FrozenClass {
1588 #[pyo3(get)]
1589 value: Py<PyAny>,
1590 }
1591
1592 let mut methods = Vec::new();
1593 let mut slots = Vec::new();
1594
1595 for items in FrozenClass::items_iter() {
1596 methods.extend(items.methods.iter().map(|m| match m {
1597 MaybeRuntimePyMethodDef::Static(m) => m.clone(),
1598 MaybeRuntimePyMethodDef::Runtime(r) => r(),
1599 }));
1600 slots.extend_from_slice(items.slots);
1601 }
1602
1603 assert_eq!(methods.len(), 1);
1604 assert!(slots.is_empty());
1605
1606 match methods.first() {
1607 Some(PyMethodDefType::StructMember(member)) => {
1608 assert_eq!(unsafe { CStr::from_ptr(member.name) }, ffi::c_str!("value"));
1609 assert_eq!(member.type_code, ffi::Py_T_OBJECT_EX);
1610 assert_eq!(
1611 member.offset,
1612 (memoffset::offset_of!(PyClassObject<FrozenClass>, contents)
1613 + memoffset::offset_of!(FrozenClass, value))
1614 as ffi::Py_ssize_t
1615 );
1616 assert_eq!(member.flags, ffi::Py_READONLY);
1617 }
1618 _ => panic!("Expected a StructMember"),
1619 }
1620 }
1621
1622 #[test]
1623 fn get_py_for_non_frozen_class() {
1624 #[crate::pyclass(crate = "crate")]
1625 struct FrozenClass {
1626 #[pyo3(get)]
1627 value: Py<PyAny>,
1628 }
1629
1630 let mut methods = Vec::new();
1631 let mut slots = Vec::new();
1632
1633 for items in FrozenClass::items_iter() {
1634 methods.extend(items.methods.iter().map(|m| match m {
1635 MaybeRuntimePyMethodDef::Static(m) => m.clone(),
1636 MaybeRuntimePyMethodDef::Runtime(r) => r(),
1637 }));
1638 slots.extend_from_slice(items.slots);
1639 }
1640
1641 assert_eq!(methods.len(), 1);
1642 assert!(slots.is_empty());
1643
1644 match methods.first() {
1645 Some(PyMethodDefType::Getter(getter)) => {
1646 assert_eq!(getter.name, ffi::c_str!("value"));
1647 assert_eq!(getter.doc, ffi::c_str!(""));
1648 // tests for the function pointer are in test_getter_setter.py
1649 }
1650 _ => panic!("Expected a StructMember"),
1651 }
1652 }
1653}
1654