| 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 | |