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