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