1 | use 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)] |
16 | use crate::{IntoPy, ToPyObject}; |
17 | use 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 | |
28 | mod assertions; |
29 | mod lazy_type_object; |
30 | mod probes; |
31 | |
32 | pub use assertions::*; |
33 | pub use lazy_type_object::LazyTypeObject; |
34 | pub use probes::*; |
35 | |
36 | /// Gets the offset of the dictionary from the start of the object in bytes. |
37 | #[inline ] |
38 | pub 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 ] |
44 | pub fn weaklist_offset<T: PyClass>() -> ffi::Py_ssize_t { |
45 | PyClassObject::<T>::weaklist_offset() |
46 | } |
47 | |
48 | mod 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]`. |
59 | pub 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]`. |
68 | pub 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. |
81 | pub struct PyClassDummySlot; |
82 | |
83 | impl PyClassDict for PyClassDummySlot { |
84 | const INIT: Self = PyClassDummySlot; |
85 | } |
86 | |
87 | impl 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 |
96 | pub struct PyClassDictSlot(*mut ffi::PyObject); |
97 | |
98 | impl 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 |
113 | pub struct PyClassWeakRefSlot(*mut ffi::PyObject); |
114 | |
115 | impl 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]` |
127 | pub struct PyClassImplCollector<T>(PhantomData<T>); |
128 | |
129 | impl<T> PyClassImplCollector<T> { |
130 | pub fn new() -> Self { |
131 | Self(PhantomData) |
132 | } |
133 | } |
134 | |
135 | impl<T> Default for PyClassImplCollector<T> { |
136 | fn default() -> Self { |
137 | Self::new() |
138 | } |
139 | } |
140 | |
141 | impl<T> Clone for PyClassImplCollector<T> { |
142 | fn clone(&self) -> Self { |
143 | *self |
144 | } |
145 | } |
146 | |
147 | impl<T> Copy for PyClassImplCollector<T> {} |
148 | |
149 | pub 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 | |
156 | pub struct PyClassItems { |
157 | pub methods: &'static [MaybeRuntimePyMethodDef], |
158 | pub slots: &'static [ffi::PyType_Slot], |
159 | } |
160 | |
161 | // Allow PyClassItems in statics |
162 | unsafe 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. |
168 | pub 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. |
231 | pub 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. |
251 | pub 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 | |
264 | impl 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 | |
280 | impl 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 | |
314 | macro_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 | |
325 | slot_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 | |
345 | slot_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 ] |
364 | macro_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 | |
398 | pub 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 |
404 | macro_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 | |
485 | define_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 | |
497 | define_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 | |
509 | define_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 |
525 | macro_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 | |
597 | define_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 | |
607 | define_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 | |
617 | define_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 | |
627 | define_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 | |
637 | define_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 | |
647 | define_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 | |
657 | define_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 | |
667 | define_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 | |
677 | define_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 | |
687 | define_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 | |
697 | define_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 | |
707 | define_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 | |
717 | define_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 | |
727 | slot_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 | |
743 | slot_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 ] |
761 | macro_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 | } |
793 | pub use generate_pyclass_pow_slot; |
794 | |
795 | slot_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 | |
810 | slot_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 | |
825 | slot_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 | |
840 | slot_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 | |
858 | slot_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 | |
873 | slot_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 ] |
890 | macro_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 | } |
923 | pub use generate_pyclass_richcompare_slot; |
924 | |
925 | use 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. |
931 | pub 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. |
940 | pub 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. |
972 | pub 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))] |
1005 | unsafe 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" )] |
1033 | pub 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" ))] |
1040 | pub trait PyMethods<T> { |
1041 | fn py_methods(self) -> &'static PyClassItems; |
1042 | } |
1043 | |
1044 | #[cfg (not(feature = "multiple-pymethods" ))] |
1045 | impl<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__ |
1055 | pub trait PyClassNewTextSignature<T> { |
1056 | fn new_text_signature(self) -> Option<&'static str>; |
1057 | } |
1058 | |
1059 | impl<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)] |
1069 | pub 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)] |
1082 | pub struct SendablePyClass<T: Send>(PhantomData<T>); |
1083 | |
1084 | impl<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)] |
1101 | pub struct ThreadCheckerImpl(thread::ThreadId); |
1102 | |
1103 | impl 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 | |
1131 | impl<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 | )] |
1164 | pub 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 |
1172 | pub(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 |
1177 | pub(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 | |
1185 | pub(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 | |
1198 | pub(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. |
1226 | pub 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 |
1232 | pub fn class_offset<T: PyClass>() -> usize { |
1233 | offset_of!(PyClassObject<T>, contents) |
1234 | } |
1235 | |
1236 | // Used in generated implementations of OffsetCalculator |
1237 | pub 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. |
1241 | pub 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 | |
1256 | impl< |
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 | |
1284 | impl< |
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)] |
1332 | impl< |
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` |
1350 | impl< |
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 | > |
1368 | where |
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; |
1384 | impl<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 | > |
1395 | where |
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)] |
1411 | impl<ClassT, FieldT, Offset> |
1412 | PyClassGetterGenerator<ClassT, FieldT, Offset, false, false, true, false, false> |
1413 | where |
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 | )] |
1435 | pub trait PyO3GetField<'py>: IntoPyObject<'py> + Clone {} |
1436 | impl<'py, T> PyO3GetField<'py> for T where T: IntoPyObject<'py> + Clone {} |
1437 | |
1438 | /// Base case attempts to use IntoPyObject + Clone |
1439 | impl<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 ] |
1458 | unsafe 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 ] |
1471 | fn field_from_object<ClassT, FieldT, Offset>(obj: *mut ffi::PyObject) -> *mut FieldT |
1472 | where |
1473 | ClassT: PyClass, |
1474 | Offset: OffsetCalculator<ClassT, FieldT>, |
1475 | { |
1476 | unsafe { obj.cast::<u8>().add(Offset::offset()).cast::<FieldT>() } |
1477 | } |
1478 | |
1479 | #[allow (deprecated)] |
1480 | fn 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 | |
1496 | fn pyo3_get_value_into_pyobject_ref<ClassT, FieldT, Offset>( |
1497 | py: Python<'_>, |
1498 | obj: *mut ffi::PyObject, |
1499 | ) -> PyResult<*mut ffi::PyObject> |
1500 | where |
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 | |
1516 | fn pyo3_get_value_into_pyobject<ClassT, FieldT, Offset>( |
1517 | py: Python<'_>, |
1518 | obj: *mut ffi::PyObject, |
1519 | ) -> PyResult<*mut ffi::PyObject> |
1520 | where |
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)] |
1538 | fn 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 | |
1554 | pub struct ConvertField< |
1555 | const IMPLEMENTS_INTOPYOBJECT_REF: bool, |
1556 | const IMPLEMENTS_INTOPYOBJECT: bool, |
1557 | >; |
1558 | |
1559 | impl<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 | |
1569 | impl<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" )] |
1581 | mod 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 | |