| 1 | use crate::{ |
| 2 | exceptions::PyTypeError, |
| 3 | ffi, |
| 4 | impl_::{ |
| 5 | pycell::PyClassObject, |
| 6 | pyclass::{ |
| 7 | assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc, |
| 8 | tp_dealloc_with_gc, MaybeRuntimePyMethodDef, PyClassItemsIter, |
| 9 | }, |
| 10 | pymethods::{Getter, PyGetterDef, PyMethodDefType, PySetterDef, Setter, _call_clear}, |
| 11 | trampoline::trampoline, |
| 12 | }, |
| 13 | internal_tricks::ptr_from_ref, |
| 14 | types::{typeobject::PyTypeMethods, PyType}, |
| 15 | Py, PyClass, PyResult, PyTypeInfo, Python, |
| 16 | }; |
| 17 | use std::{ |
| 18 | collections::HashMap, |
| 19 | ffi::{CStr, CString}, |
| 20 | os::raw::{c_char, c_int, c_ulong, c_void}, |
| 21 | ptr, |
| 22 | }; |
| 23 | |
| 24 | pub(crate) struct PyClassTypeObject { |
| 25 | pub type_object: Py<PyType>, |
| 26 | #[allow (dead_code)] // This is purely a cache that must live as long as the type object |
| 27 | getset_destructors: Vec<GetSetDefDestructor>, |
| 28 | } |
| 29 | |
| 30 | pub(crate) fn create_type_object<T>(py: Python<'_>) -> PyResult<PyClassTypeObject> |
| 31 | where |
| 32 | T: PyClass, |
| 33 | { |
| 34 | // Written this way to monomorphize the majority of the logic. |
| 35 | #[allow (clippy::too_many_arguments)] |
| 36 | unsafe fn inner( |
| 37 | py: Python<'_>, |
| 38 | base: *mut ffi::PyTypeObject, |
| 39 | dealloc: unsafe extern "C" fn(*mut ffi::PyObject), |
| 40 | dealloc_with_gc: unsafe extern "C" fn(*mut ffi::PyObject), |
| 41 | is_mapping: bool, |
| 42 | is_sequence: bool, |
| 43 | doc: &'static CStr, |
| 44 | dict_offset: Option<ffi::Py_ssize_t>, |
| 45 | weaklist_offset: Option<ffi::Py_ssize_t>, |
| 46 | is_basetype: bool, |
| 47 | items_iter: PyClassItemsIter, |
| 48 | name: &'static str, |
| 49 | module: Option<&'static str>, |
| 50 | size_of: usize, |
| 51 | ) -> PyResult<PyClassTypeObject> { |
| 52 | unsafe { |
| 53 | PyTypeBuilder { |
| 54 | slots: Vec::new(), |
| 55 | method_defs: Vec::new(), |
| 56 | member_defs: Vec::new(), |
| 57 | getset_builders: HashMap::new(), |
| 58 | cleanup: Vec::new(), |
| 59 | tp_base: base, |
| 60 | tp_dealloc: dealloc, |
| 61 | tp_dealloc_with_gc: dealloc_with_gc, |
| 62 | is_mapping, |
| 63 | is_sequence, |
| 64 | has_new: false, |
| 65 | has_dealloc: false, |
| 66 | has_getitem: false, |
| 67 | has_setitem: false, |
| 68 | has_traverse: false, |
| 69 | has_clear: false, |
| 70 | dict_offset: None, |
| 71 | class_flags: 0, |
| 72 | #[cfg (all(not(Py_3_9), not(Py_LIMITED_API)))] |
| 73 | buffer_procs: Default::default(), |
| 74 | } |
| 75 | .type_doc(doc) |
| 76 | .offsets(dict_offset, weaklist_offset) |
| 77 | .set_is_basetype(is_basetype) |
| 78 | .class_items(items_iter) |
| 79 | .build(py, name, module, size_of) |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | unsafe { |
| 84 | inner( |
| 85 | py, |
| 86 | T::BaseType::type_object_raw(py), |
| 87 | tp_dealloc::<T>, |
| 88 | tp_dealloc_with_gc::<T>, |
| 89 | T::IS_MAPPING, |
| 90 | T::IS_SEQUENCE, |
| 91 | T::doc(py)?, |
| 92 | T::dict_offset(), |
| 93 | T::weaklist_offset(), |
| 94 | T::IS_BASETYPE, |
| 95 | T::items_iter(), |
| 96 | T::NAME, |
| 97 | T::MODULE, |
| 98 | std::mem::size_of::<PyClassObject<T>>(), |
| 99 | ) |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | type PyTypeBuilderCleanup = Box<dyn Fn(&PyTypeBuilder, *mut ffi::PyTypeObject)>; |
| 104 | |
| 105 | struct PyTypeBuilder { |
| 106 | slots: Vec<ffi::PyType_Slot>, |
| 107 | method_defs: Vec<ffi::PyMethodDef>, |
| 108 | member_defs: Vec<ffi::PyMemberDef>, |
| 109 | getset_builders: HashMap<&'static CStr, GetSetDefBuilder>, |
| 110 | /// Used to patch the type objects for the things there's no |
| 111 | /// PyType_FromSpec API for... there's no reason this should work, |
| 112 | /// except for that it does and we have tests. |
| 113 | cleanup: Vec<PyTypeBuilderCleanup>, |
| 114 | tp_base: *mut ffi::PyTypeObject, |
| 115 | tp_dealloc: ffi::destructor, |
| 116 | tp_dealloc_with_gc: ffi::destructor, |
| 117 | is_mapping: bool, |
| 118 | is_sequence: bool, |
| 119 | has_new: bool, |
| 120 | has_dealloc: bool, |
| 121 | has_getitem: bool, |
| 122 | has_setitem: bool, |
| 123 | has_traverse: bool, |
| 124 | has_clear: bool, |
| 125 | dict_offset: Option<ffi::Py_ssize_t>, |
| 126 | class_flags: c_ulong, |
| 127 | // Before Python 3.9, need to patch in buffer methods manually (they don't work in slots) |
| 128 | #[cfg (all(not(Py_3_9), not(Py_LIMITED_API)))] |
| 129 | buffer_procs: ffi::PyBufferProcs, |
| 130 | } |
| 131 | |
| 132 | impl PyTypeBuilder { |
| 133 | /// # Safety |
| 134 | /// The given pointer must be of the correct type for the given slot |
| 135 | unsafe fn push_slot<T>(&mut self, slot: c_int, pfunc: *mut T) { |
| 136 | match slot { |
| 137 | ffi::Py_tp_new => self.has_new = true, |
| 138 | ffi::Py_tp_dealloc => self.has_dealloc = true, |
| 139 | ffi::Py_mp_subscript => self.has_getitem = true, |
| 140 | ffi::Py_mp_ass_subscript => self.has_setitem = true, |
| 141 | ffi::Py_tp_traverse => { |
| 142 | self.has_traverse = true; |
| 143 | self.class_flags |= ffi::Py_TPFLAGS_HAVE_GC; |
| 144 | } |
| 145 | ffi::Py_tp_clear => self.has_clear = true, |
| 146 | #[cfg (all(not(Py_3_9), not(Py_LIMITED_API)))] |
| 147 | ffi::Py_bf_getbuffer => { |
| 148 | // Safety: slot.pfunc is a valid function pointer |
| 149 | self.buffer_procs.bf_getbuffer = |
| 150 | Some(unsafe { std::mem::transmute::<*mut T, ffi::getbufferproc>(pfunc) }); |
| 151 | } |
| 152 | #[cfg (all(not(Py_3_9), not(Py_LIMITED_API)))] |
| 153 | ffi::Py_bf_releasebuffer => { |
| 154 | // Safety: slot.pfunc is a valid function pointer |
| 155 | self.buffer_procs.bf_releasebuffer = |
| 156 | Some(unsafe { std::mem::transmute::<*mut T, ffi::releasebufferproc>(pfunc) }); |
| 157 | } |
| 158 | _ => {} |
| 159 | } |
| 160 | |
| 161 | self.slots.push(ffi::PyType_Slot { |
| 162 | slot, |
| 163 | pfunc: pfunc as _, |
| 164 | }); |
| 165 | } |
| 166 | |
| 167 | /// # Safety |
| 168 | /// It is the caller's responsibility that `data` is of the correct type for the given slot. |
| 169 | unsafe fn push_raw_vec_slot<T>(&mut self, slot: c_int, mut data: Vec<T>) { |
| 170 | if !data.is_empty() { |
| 171 | // Python expects a zeroed entry to mark the end of the defs |
| 172 | unsafe { |
| 173 | data.push(std::mem::zeroed()); |
| 174 | self.push_slot(slot, Box::into_raw(data.into_boxed_slice()) as *mut c_void); |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | fn pymethod_def(&mut self, def: &PyMethodDefType) { |
| 180 | match def { |
| 181 | PyMethodDefType::Getter(getter) => self |
| 182 | .getset_builders |
| 183 | .entry(getter.name) |
| 184 | .or_default() |
| 185 | .add_getter(getter), |
| 186 | PyMethodDefType::Setter(setter) => self |
| 187 | .getset_builders |
| 188 | .entry(setter.name) |
| 189 | .or_default() |
| 190 | .add_setter(setter), |
| 191 | PyMethodDefType::Method(def) |
| 192 | | PyMethodDefType::Class(def) |
| 193 | | PyMethodDefType::Static(def) => self.method_defs.push(def.as_method_def()), |
| 194 | // These class attributes are added after the type gets created by LazyStaticType |
| 195 | PyMethodDefType::ClassAttribute(_) => {} |
| 196 | PyMethodDefType::StructMember(def) => self.member_defs.push(*def), |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | fn finalize_methods_and_properties(&mut self) -> Vec<GetSetDefDestructor> { |
| 201 | let method_defs: Vec<pyo3_ffi::PyMethodDef> = std::mem::take(&mut self.method_defs); |
| 202 | // Safety: Py_tp_methods expects a raw vec of PyMethodDef |
| 203 | unsafe { self.push_raw_vec_slot(ffi::Py_tp_methods, method_defs) }; |
| 204 | |
| 205 | let member_defs = std::mem::take(&mut self.member_defs); |
| 206 | // Safety: Py_tp_members expects a raw vec of PyMemberDef |
| 207 | unsafe { self.push_raw_vec_slot(ffi::Py_tp_members, member_defs) }; |
| 208 | |
| 209 | let mut getset_destructors = Vec::with_capacity(self.getset_builders.len()); |
| 210 | |
| 211 | #[allow (unused_mut)] |
| 212 | let mut property_defs: Vec<_> = self |
| 213 | .getset_builders |
| 214 | .iter() |
| 215 | .map(|(name, builder)| { |
| 216 | let (def, destructor) = builder.as_get_set_def(name); |
| 217 | getset_destructors.push(destructor); |
| 218 | def |
| 219 | }) |
| 220 | .collect(); |
| 221 | |
| 222 | // PyPy automatically adds __dict__ getter / setter. |
| 223 | #[cfg (not(PyPy))] |
| 224 | // Supported on unlimited API for all versions, and on 3.9+ for limited API |
| 225 | #[cfg (any(Py_3_9, not(Py_LIMITED_API)))] |
| 226 | if let Some(dict_offset) = self.dict_offset { |
| 227 | let get_dict; |
| 228 | let closure; |
| 229 | // PyObject_GenericGetDict not in the limited API until Python 3.10. |
| 230 | #[cfg (any(not(Py_LIMITED_API), Py_3_10))] |
| 231 | { |
| 232 | let _ = dict_offset; |
| 233 | get_dict = ffi::PyObject_GenericGetDict; |
| 234 | closure = ptr::null_mut(); |
| 235 | } |
| 236 | |
| 237 | // ... so we write a basic implementation ourselves |
| 238 | #[cfg (not(any(not(Py_LIMITED_API), Py_3_10)))] |
| 239 | { |
| 240 | extern "C" fn get_dict_impl( |
| 241 | object: *mut ffi::PyObject, |
| 242 | closure: *mut c_void, |
| 243 | ) -> *mut ffi::PyObject { |
| 244 | unsafe { |
| 245 | trampoline(|_| { |
| 246 | let dict_offset = closure as ffi::Py_ssize_t; |
| 247 | // we don't support negative dict_offset here; PyO3 doesn't set it negative |
| 248 | assert!(dict_offset > 0); |
| 249 | // TODO: use `.byte_offset` on MSRV 1.75 |
| 250 | let dict_ptr = object |
| 251 | .cast::<u8>() |
| 252 | .offset(dict_offset) |
| 253 | .cast::<*mut ffi::PyObject>(); |
| 254 | if (*dict_ptr).is_null() { |
| 255 | std::ptr::write(dict_ptr, ffi::PyDict_New()); |
| 256 | } |
| 257 | Ok(ffi::compat::Py_XNewRef(*dict_ptr)) |
| 258 | }) |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | get_dict = get_dict_impl; |
| 263 | closure = dict_offset as _; |
| 264 | } |
| 265 | |
| 266 | property_defs.push(ffi::PyGetSetDef { |
| 267 | name: ffi::c_str!("__dict__" ).as_ptr(), |
| 268 | get: Some(get_dict), |
| 269 | set: Some(ffi::PyObject_GenericSetDict), |
| 270 | doc: ptr::null(), |
| 271 | closure, |
| 272 | }); |
| 273 | } |
| 274 | |
| 275 | // Safety: Py_tp_getset expects a raw vec of PyGetSetDef |
| 276 | unsafe { self.push_raw_vec_slot(ffi::Py_tp_getset, property_defs) }; |
| 277 | |
| 278 | // If mapping methods implemented, define sequence methods get implemented too. |
| 279 | // CPython does the same for Python `class` statements. |
| 280 | |
| 281 | // NB we don't implement sq_length to avoid annoying CPython behaviour of automatically adding |
| 282 | // the length to negative indices. |
| 283 | |
| 284 | // Don't add these methods for "pure" mappings. |
| 285 | |
| 286 | if !self.is_mapping && self.has_getitem { |
| 287 | // Safety: This is the correct slot type for Py_sq_item |
| 288 | unsafe { |
| 289 | self.push_slot( |
| 290 | ffi::Py_sq_item, |
| 291 | get_sequence_item_from_mapping as *mut c_void, |
| 292 | ) |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | if !self.is_mapping && self.has_setitem { |
| 297 | // Safety: This is the correct slot type for Py_sq_ass_item |
| 298 | unsafe { |
| 299 | self.push_slot( |
| 300 | ffi::Py_sq_ass_item, |
| 301 | assign_sequence_item_from_mapping as *mut c_void, |
| 302 | ) |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | getset_destructors |
| 307 | } |
| 308 | |
| 309 | fn set_is_basetype(mut self, is_basetype: bool) -> Self { |
| 310 | if is_basetype { |
| 311 | self.class_flags |= ffi::Py_TPFLAGS_BASETYPE; |
| 312 | } |
| 313 | self |
| 314 | } |
| 315 | |
| 316 | /// # Safety |
| 317 | /// All slots in the PyClassItemsIter should be correct |
| 318 | unsafe fn class_items(mut self, iter: PyClassItemsIter) -> Self { |
| 319 | for items in iter { |
| 320 | for slot in items.slots { |
| 321 | unsafe { self.push_slot(slot.slot, slot.pfunc) }; |
| 322 | } |
| 323 | for method in items.methods { |
| 324 | let built_method; |
| 325 | let method = match method { |
| 326 | MaybeRuntimePyMethodDef::Runtime(builder) => { |
| 327 | built_method = builder(); |
| 328 | &built_method |
| 329 | } |
| 330 | MaybeRuntimePyMethodDef::Static(method) => method, |
| 331 | }; |
| 332 | self.pymethod_def(method); |
| 333 | } |
| 334 | } |
| 335 | self |
| 336 | } |
| 337 | |
| 338 | fn type_doc(mut self, type_doc: &'static CStr) -> Self { |
| 339 | let slice = type_doc.to_bytes(); |
| 340 | if !slice.is_empty() { |
| 341 | unsafe { self.push_slot(ffi::Py_tp_doc, type_doc.as_ptr() as *mut c_char) } |
| 342 | |
| 343 | // Running this causes PyPy to segfault. |
| 344 | #[cfg (all(not(PyPy), not(Py_LIMITED_API), not(Py_3_10)))] |
| 345 | { |
| 346 | // Until CPython 3.10, tp_doc was treated specially for |
| 347 | // heap-types, and it removed the text_signature value from it. |
| 348 | // We go in after the fact and replace tp_doc with something |
| 349 | // that _does_ include the text_signature value! |
| 350 | self.cleanup |
| 351 | .push(Box::new(move |_self, type_object| unsafe { |
| 352 | ffi::PyObject_Free((*type_object).tp_doc as _); |
| 353 | let data = ffi::PyMem_Malloc(slice.len()); |
| 354 | data.copy_from(slice.as_ptr() as _, slice.len()); |
| 355 | (*type_object).tp_doc = data as _; |
| 356 | })) |
| 357 | } |
| 358 | } |
| 359 | self |
| 360 | } |
| 361 | |
| 362 | fn offsets( |
| 363 | mut self, |
| 364 | dict_offset: Option<ffi::Py_ssize_t>, |
| 365 | #[allow (unused_variables)] weaklist_offset: Option<ffi::Py_ssize_t>, |
| 366 | ) -> Self { |
| 367 | self.dict_offset = dict_offset; |
| 368 | |
| 369 | #[cfg (Py_3_9)] |
| 370 | { |
| 371 | #[inline (always)] |
| 372 | fn offset_def(name: &'static CStr, offset: ffi::Py_ssize_t) -> ffi::PyMemberDef { |
| 373 | ffi::PyMemberDef { |
| 374 | name: name.as_ptr().cast(), |
| 375 | type_code: ffi::Py_T_PYSSIZET, |
| 376 | offset, |
| 377 | flags: ffi::Py_READONLY, |
| 378 | doc: std::ptr::null_mut(), |
| 379 | } |
| 380 | } |
| 381 | |
| 382 | // __dict__ support |
| 383 | if let Some(dict_offset) = dict_offset { |
| 384 | self.member_defs |
| 385 | .push(offset_def(ffi::c_str!("__dictoffset__" ), dict_offset)); |
| 386 | } |
| 387 | |
| 388 | // weakref support |
| 389 | if let Some(weaklist_offset) = weaklist_offset { |
| 390 | self.member_defs.push(offset_def( |
| 391 | ffi::c_str!("__weaklistoffset__" ), |
| 392 | weaklist_offset, |
| 393 | )); |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | // Setting buffer protocols, tp_dictoffset and tp_weaklistoffset via slots doesn't work until |
| 398 | // Python 3.9, so on older versions we must manually fixup the type object. |
| 399 | #[cfg (all(not(Py_LIMITED_API), not(Py_3_9)))] |
| 400 | { |
| 401 | self.cleanup |
| 402 | .push(Box::new(move |builder, type_object| unsafe { |
| 403 | (*(*type_object).tp_as_buffer).bf_getbuffer = builder.buffer_procs.bf_getbuffer; |
| 404 | (*(*type_object).tp_as_buffer).bf_releasebuffer = |
| 405 | builder.buffer_procs.bf_releasebuffer; |
| 406 | |
| 407 | if let Some(dict_offset) = dict_offset { |
| 408 | (*type_object).tp_dictoffset = dict_offset; |
| 409 | } |
| 410 | |
| 411 | if let Some(weaklist_offset) = weaklist_offset { |
| 412 | (*type_object).tp_weaklistoffset = weaklist_offset; |
| 413 | } |
| 414 | })); |
| 415 | } |
| 416 | self |
| 417 | } |
| 418 | |
| 419 | fn build( |
| 420 | mut self, |
| 421 | py: Python<'_>, |
| 422 | name: &'static str, |
| 423 | module_name: Option<&'static str>, |
| 424 | basicsize: usize, |
| 425 | ) -> PyResult<PyClassTypeObject> { |
| 426 | // `c_ulong` and `c_uint` have the same size |
| 427 | // on some platforms (like windows) |
| 428 | #![allow (clippy::useless_conversion)] |
| 429 | |
| 430 | let getset_destructors = self.finalize_methods_and_properties(); |
| 431 | |
| 432 | unsafe { self.push_slot(ffi::Py_tp_base, self.tp_base) } |
| 433 | |
| 434 | if !self.has_new { |
| 435 | // Safety: This is the correct slot type for Py_tp_new |
| 436 | unsafe { self.push_slot(ffi::Py_tp_new, no_constructor_defined as *mut c_void) } |
| 437 | } |
| 438 | |
| 439 | let base_is_gc = unsafe { ffi::PyType_IS_GC(self.tp_base) == 1 }; |
| 440 | let tp_dealloc = if self.has_traverse || base_is_gc { |
| 441 | self.tp_dealloc_with_gc |
| 442 | } else { |
| 443 | self.tp_dealloc |
| 444 | }; |
| 445 | unsafe { self.push_slot(ffi::Py_tp_dealloc, tp_dealloc as *mut c_void) } |
| 446 | |
| 447 | if self.has_clear && !self.has_traverse { |
| 448 | return Err(PyTypeError::new_err(format!( |
| 449 | "`#[pyclass]` {} implements __clear__ without __traverse__" , |
| 450 | name |
| 451 | ))); |
| 452 | } |
| 453 | |
| 454 | // If this type is a GC type, and the base also is, we may need to add |
| 455 | // `tp_traverse` / `tp_clear` implementations to call the base, if this type didn't |
| 456 | // define `__traverse__` or `__clear__`. |
| 457 | // |
| 458 | // This is because when Py_TPFLAGS_HAVE_GC is set, then `tp_traverse` and |
| 459 | // `tp_clear` are not inherited. |
| 460 | if ((self.class_flags & ffi::Py_TPFLAGS_HAVE_GC) != 0) && base_is_gc { |
| 461 | // If this assertion breaks, need to consider doing the same for __traverse__. |
| 462 | assert!(self.has_traverse); // Py_TPFLAGS_HAVE_GC is set when a `__traverse__` method is found |
| 463 | |
| 464 | if !self.has_clear { |
| 465 | // Safety: This is the correct slot type for Py_tp_clear |
| 466 | unsafe { self.push_slot(ffi::Py_tp_clear, call_super_clear as *mut c_void) } |
| 467 | } |
| 468 | } |
| 469 | |
| 470 | // For sequences, implement sq_length instead of mp_length |
| 471 | if self.is_sequence { |
| 472 | for slot in &mut self.slots { |
| 473 | if slot.slot == ffi::Py_mp_length { |
| 474 | slot.slot = ffi::Py_sq_length; |
| 475 | } |
| 476 | } |
| 477 | } |
| 478 | |
| 479 | // Add empty sentinel at the end |
| 480 | // Safety: python expects this empty slot |
| 481 | unsafe { self.push_slot(0, ptr::null_mut::<c_void>()) } |
| 482 | |
| 483 | let class_name = py_class_qualified_name(module_name, name)?; |
| 484 | let mut spec = ffi::PyType_Spec { |
| 485 | name: class_name.as_ptr() as _, |
| 486 | basicsize: basicsize as c_int, |
| 487 | itemsize: 0, |
| 488 | |
| 489 | flags: (ffi::Py_TPFLAGS_DEFAULT | self.class_flags) |
| 490 | .try_into() |
| 491 | .unwrap(), |
| 492 | slots: self.slots.as_mut_ptr(), |
| 493 | }; |
| 494 | |
| 495 | // Safety: We've correctly setup the PyType_Spec at this point |
| 496 | let type_object: Py<PyType> = |
| 497 | unsafe { Py::from_owned_ptr_or_err(py, ffi::PyType_FromSpec(&mut spec))? }; |
| 498 | |
| 499 | #[cfg (not(Py_3_11))] |
| 500 | bpo_45315_workaround(py, class_name); |
| 501 | |
| 502 | for cleanup in std::mem::take(&mut self.cleanup) { |
| 503 | cleanup(&self, type_object.bind(py).as_type_ptr()); |
| 504 | } |
| 505 | |
| 506 | Ok(PyClassTypeObject { |
| 507 | type_object, |
| 508 | getset_destructors, |
| 509 | }) |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | fn py_class_qualified_name(module_name: Option<&str>, class_name: &str) -> PyResult<CString> { |
| 514 | Ok(CString::new(format!( |
| 515 | " {}. {}" , |
| 516 | module_name.unwrap_or("builtins" ), |
| 517 | class_name |
| 518 | ))?) |
| 519 | } |
| 520 | |
| 521 | /// Workaround for Python issue 45315; no longer necessary in Python 3.11 |
| 522 | #[inline ] |
| 523 | #[cfg (not(Py_3_11))] |
| 524 | fn bpo_45315_workaround(py: Python<'_>, class_name: CString) { |
| 525 | #[cfg (Py_LIMITED_API)] |
| 526 | { |
| 527 | // Must check version at runtime for abi3 wheels - they could run against a higher version |
| 528 | // than the build config suggests. |
| 529 | use crate::sync::GILOnceCell; |
| 530 | static IS_PYTHON_3_11: GILOnceCell<bool> = GILOnceCell::new(); |
| 531 | |
| 532 | if *IS_PYTHON_3_11.get_or_init(py, || py.version_info() >= (3, 11)) { |
| 533 | // No fix needed - the wheel is running on a sufficiently new interpreter. |
| 534 | return; |
| 535 | } |
| 536 | } |
| 537 | #[cfg (not(Py_LIMITED_API))] |
| 538 | { |
| 539 | // suppress unused variable warning |
| 540 | let _ = py; |
| 541 | } |
| 542 | |
| 543 | std::mem::forget(class_name); |
| 544 | } |
| 545 | |
| 546 | /// Default new implementation |
| 547 | unsafe extern "C" fn no_constructor_defined( |
| 548 | subtype: *mut ffi::PyTypeObject, |
| 549 | _args: *mut ffi::PyObject, |
| 550 | _kwds: *mut ffi::PyObject, |
| 551 | ) -> *mut ffi::PyObject { |
| 552 | unsafe { |
| 553 | trampoline(|py: Python<'_>| { |
| 554 | let tpobj: Bound<'_, PyType> = PyType::from_borrowed_type_ptr(py, p:subtype); |
| 555 | let name: String = tpobj |
| 556 | .name() |
| 557 | .map_or_else(|_| "<unknown>" .into(), |name: Bound<'_, PyString>| name.to_string()); |
| 558 | Err(crate::exceptions::PyTypeError::new_err(args:format!( |
| 559 | "No constructor defined for {}" , |
| 560 | name |
| 561 | ))) |
| 562 | }) |
| 563 | } |
| 564 | } |
| 565 | |
| 566 | unsafe extern "C" fn call_super_clear(slf: *mut ffi::PyObject) -> c_int { |
| 567 | unsafe { _call_clear(slf, |_, _| Ok(()), current_clear:call_super_clear) } |
| 568 | } |
| 569 | |
| 570 | #[derive (Default)] |
| 571 | struct GetSetDefBuilder { |
| 572 | doc: Option<&'static CStr>, |
| 573 | getter: Option<Getter>, |
| 574 | setter: Option<Setter>, |
| 575 | } |
| 576 | |
| 577 | impl GetSetDefBuilder { |
| 578 | fn add_getter(&mut self, getter: &PyGetterDef) { |
| 579 | // TODO: be smarter about merging getter and setter docs |
| 580 | if self.doc.is_none() { |
| 581 | self.doc = Some(getter.doc); |
| 582 | } |
| 583 | // TODO: return an error if getter already defined? |
| 584 | self.getter = Some(getter.meth) |
| 585 | } |
| 586 | |
| 587 | fn add_setter(&mut self, setter: &PySetterDef) { |
| 588 | // TODO: be smarter about merging getter and setter docs |
| 589 | if self.doc.is_none() { |
| 590 | self.doc = Some(setter.doc); |
| 591 | } |
| 592 | // TODO: return an error if setter already defined? |
| 593 | self.setter = Some(setter.meth) |
| 594 | } |
| 595 | |
| 596 | fn as_get_set_def(&self, name: &'static CStr) -> (ffi::PyGetSetDef, GetSetDefDestructor) { |
| 597 | let getset_type = match (self.getter, self.setter) { |
| 598 | (Some(getter), None) => GetSetDefType::Getter(getter), |
| 599 | (None, Some(setter)) => GetSetDefType::Setter(setter), |
| 600 | (Some(getter), Some(setter)) => { |
| 601 | GetSetDefType::GetterAndSetter(Box::new(GetterAndSetter { getter, setter })) |
| 602 | } |
| 603 | (None, None) => { |
| 604 | unreachable!("GetSetDefBuilder expected to always have either getter or setter" ) |
| 605 | } |
| 606 | }; |
| 607 | |
| 608 | let getset_def = getset_type.create_py_get_set_def(name, self.doc); |
| 609 | let destructor = GetSetDefDestructor { |
| 610 | closure: getset_type, |
| 611 | }; |
| 612 | (getset_def, destructor) |
| 613 | } |
| 614 | } |
| 615 | |
| 616 | #[allow (dead_code)] // a stack of fields which are purely to cache until dropped |
| 617 | struct GetSetDefDestructor { |
| 618 | closure: GetSetDefType, |
| 619 | } |
| 620 | |
| 621 | /// Possible forms of property - either a getter, setter, or both |
| 622 | enum GetSetDefType { |
| 623 | Getter(Getter), |
| 624 | Setter(Setter), |
| 625 | // The box is here so that the `GetterAndSetter` has a stable |
| 626 | // memory address even if the `GetSetDefType` enum is moved |
| 627 | GetterAndSetter(Box<GetterAndSetter>), |
| 628 | } |
| 629 | |
| 630 | pub(crate) struct GetterAndSetter { |
| 631 | getter: Getter, |
| 632 | setter: Setter, |
| 633 | } |
| 634 | |
| 635 | impl GetSetDefType { |
| 636 | /// Fills a PyGetSetDef structure |
| 637 | /// It is only valid for as long as this GetSetDefType remains alive, |
| 638 | /// as well as name and doc members |
| 639 | pub(crate) fn create_py_get_set_def( |
| 640 | &self, |
| 641 | name: &CStr, |
| 642 | doc: Option<&CStr>, |
| 643 | ) -> ffi::PyGetSetDef { |
| 644 | let (get, set, closure): (Option<ffi::getter>, Option<ffi::setter>, *mut c_void) = |
| 645 | match self { |
| 646 | &Self::Getter(closure) => { |
| 647 | unsafe extern "C" fn getter( |
| 648 | slf: *mut ffi::PyObject, |
| 649 | closure: *mut c_void, |
| 650 | ) -> *mut ffi::PyObject { |
| 651 | // Safety: PyO3 sets the closure when constructing the ffi getter so this cast should always be valid |
| 652 | let getter: Getter = unsafe { std::mem::transmute(closure) }; |
| 653 | unsafe { trampoline(|py| getter(py, slf)) } |
| 654 | } |
| 655 | (Some(getter), None, closure as Getter as _) |
| 656 | } |
| 657 | &Self::Setter(closure) => { |
| 658 | unsafe extern "C" fn setter( |
| 659 | slf: *mut ffi::PyObject, |
| 660 | value: *mut ffi::PyObject, |
| 661 | closure: *mut c_void, |
| 662 | ) -> c_int { |
| 663 | // Safety: PyO3 sets the closure when constructing the ffi setter so this cast should always be valid |
| 664 | let setter: Setter = unsafe { std::mem::transmute(closure) }; |
| 665 | unsafe { trampoline(|py| setter(py, slf, value)) } |
| 666 | } |
| 667 | (None, Some(setter), closure as Setter as _) |
| 668 | } |
| 669 | Self::GetterAndSetter(closure) => { |
| 670 | unsafe extern "C" fn getset_getter( |
| 671 | slf: *mut ffi::PyObject, |
| 672 | closure: *mut c_void, |
| 673 | ) -> *mut ffi::PyObject { |
| 674 | let getset: &GetterAndSetter = unsafe { &*closure.cast() }; |
| 675 | unsafe { trampoline(|py| (getset.getter)(py, slf)) } |
| 676 | } |
| 677 | |
| 678 | unsafe extern "C" fn getset_setter( |
| 679 | slf: *mut ffi::PyObject, |
| 680 | value: *mut ffi::PyObject, |
| 681 | closure: *mut c_void, |
| 682 | ) -> c_int { |
| 683 | let getset: &GetterAndSetter = unsafe { &*closure.cast() }; |
| 684 | unsafe { trampoline(|py| (getset.setter)(py, slf, value)) } |
| 685 | } |
| 686 | ( |
| 687 | Some(getset_getter), |
| 688 | Some(getset_setter), |
| 689 | ptr_from_ref::<GetterAndSetter>(closure) as *mut _, |
| 690 | ) |
| 691 | } |
| 692 | }; |
| 693 | ffi::PyGetSetDef { |
| 694 | name: name.as_ptr(), |
| 695 | doc: doc.map_or(ptr::null(), CStr::as_ptr), |
| 696 | get, |
| 697 | set, |
| 698 | closure, |
| 699 | } |
| 700 | } |
| 701 | } |
| 702 | |