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