1use 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};
17use std::{
18 collections::HashMap,
19 ffi::{CStr, CString},
20 os::raw::{c_char, c_int, c_ulong, c_void},
21 ptr,
22};
23
24pub(crate) struct PyClassTypeObject {
25 pub type_object: Py<PyType>,
26 #[allow(dead_code)] getset_destructors: Vec<GetSetDefDestructor>,
28}
29
30pub(crate) fn create_type_object<T>(py: Python<'_>) -> PyResult<PyClassTypeObject>
31where
32 T: PyClass,
33{
34 #[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 PyTypeBuilder {
53 slots: Vec::new(),
54 method_defs: Vec::new(),
55 member_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 dict_offset: None,
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::<PyClassObject<T>>(),
97 )
98 }
99}
100
101type PyTypeBuilderCleanup = Box<dyn Fn(&PyTypeBuilder, *mut ffi::PyTypeObject)>;
102
103struct PyTypeBuilder {
104 slots: Vec<ffi::PyType_Slot>,
105 method_defs: Vec<ffi::PyMethodDef>,
106 member_defs: Vec<ffi::PyMemberDef>,
107 getset_builders: HashMap<&'static CStr, GetSetDefBuilder>,
108 cleanup: Vec<PyTypeBuilderCleanup>,
112 tp_base: *mut ffi::PyTypeObject,
113 tp_dealloc: ffi::destructor,
114 tp_dealloc_with_gc: ffi::destructor,
115 is_mapping: bool,
116 is_sequence: bool,
117 has_new: bool,
118 has_dealloc: bool,
119 has_getitem: bool,
120 has_setitem: bool,
121 has_traverse: bool,
122 has_clear: bool,
123 dict_offset: Option<ffi::Py_ssize_t>,
124 class_flags: c_ulong,
125 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
127 buffer_procs: ffi::PyBufferProcs,
128}
129
130impl PyTypeBuilder {
131 unsafe fn push_slot<T>(&mut self, slot: c_int, pfunc: *mut T) {
134 match slot {
135 ffi::Py_tp_new => self.has_new = true,
136 ffi::Py_tp_dealloc => self.has_dealloc = true,
137 ffi::Py_mp_subscript => self.has_getitem = true,
138 ffi::Py_mp_ass_subscript => self.has_setitem = true,
139 ffi::Py_tp_traverse => {
140 self.has_traverse = true;
141 self.class_flags |= ffi::Py_TPFLAGS_HAVE_GC;
142 }
143 ffi::Py_tp_clear => self.has_clear = true,
144 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
145 ffi::Py_bf_getbuffer => {
146 self.buffer_procs.bf_getbuffer =
148 Some(std::mem::transmute::<*mut T, ffi::getbufferproc>(pfunc));
149 }
150 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
151 ffi::Py_bf_releasebuffer => {
152 self.buffer_procs.bf_releasebuffer =
154 Some(std::mem::transmute::<*mut T, ffi::releasebufferproc>(pfunc));
155 }
156 _ => {}
157 }
158
159 self.slots.push(ffi::PyType_Slot {
160 slot,
161 pfunc: pfunc as _,
162 });
163 }
164
165 unsafe fn push_raw_vec_slot<T>(&mut self, slot: c_int, mut data: Vec<T>) {
168 if !data.is_empty() {
169 data.push(std::mem::zeroed());
171 self.push_slot(slot, Box::into_raw(data.into_boxed_slice()) as *mut c_void);
172 }
173 }
174
175 fn pymethod_def(&mut self, def: &PyMethodDefType) {
176 match def {
177 PyMethodDefType::Getter(getter) => self
178 .getset_builders
179 .entry(getter.name)
180 .or_default()
181 .add_getter(getter),
182 PyMethodDefType::Setter(setter) => self
183 .getset_builders
184 .entry(setter.name)
185 .or_default()
186 .add_setter(setter),
187 PyMethodDefType::Method(def)
188 | PyMethodDefType::Class(def)
189 | PyMethodDefType::Static(def) => self.method_defs.push(def.as_method_def()),
190 PyMethodDefType::ClassAttribute(_) => {}
192 PyMethodDefType::StructMember(def) => self.member_defs.push(*def),
193 }
194 }
195
196 fn finalize_methods_and_properties(&mut self) -> Vec<GetSetDefDestructor> {
197 let method_defs: Vec<pyo3_ffi::PyMethodDef> = std::mem::take(&mut self.method_defs);
198 unsafe { self.push_raw_vec_slot(ffi::Py_tp_methods, method_defs) };
200
201 let member_defs = std::mem::take(&mut self.member_defs);
202 unsafe { self.push_raw_vec_slot(ffi::Py_tp_members, member_defs) };
204
205 let mut getset_destructors = Vec::with_capacity(self.getset_builders.len());
206
207 #[allow(unused_mut)]
208 let mut property_defs: Vec<_> = self
209 .getset_builders
210 .iter()
211 .map(|(name, builder)| {
212 let (def, destructor) = builder.as_get_set_def(name);
213 getset_destructors.push(destructor);
214 def
215 })
216 .collect();
217
218 #[cfg(not(PyPy))]
220 #[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
222 if let Some(dict_offset) = self.dict_offset {
223 let get_dict;
224 let closure;
225 #[cfg(any(not(Py_LIMITED_API), Py_3_10))]
227 {
228 let _ = dict_offset;
229 get_dict = ffi::PyObject_GenericGetDict;
230 closure = ptr::null_mut();
231 }
232
233 #[cfg(not(any(not(Py_LIMITED_API), Py_3_10)))]
235 {
236 extern "C" fn get_dict_impl(
237 object: *mut ffi::PyObject,
238 closure: *mut c_void,
239 ) -> *mut ffi::PyObject {
240 unsafe {
241 trampoline(|_| {
242 let dict_offset = closure as ffi::Py_ssize_t;
243 assert!(dict_offset > 0);
245 let dict_ptr = object
247 .cast::<u8>()
248 .offset(dict_offset)
249 .cast::<*mut ffi::PyObject>();
250 if (*dict_ptr).is_null() {
251 std::ptr::write(dict_ptr, ffi::PyDict_New());
252 }
253 Ok(ffi::compat::Py_XNewRef(*dict_ptr))
254 })
255 }
256 }
257
258 get_dict = get_dict_impl;
259 closure = dict_offset as _;
260 }
261
262 property_defs.push(ffi::PyGetSetDef {
263 name: ffi::c_str!("__dict__").as_ptr(),
264 get: Some(get_dict),
265 set: Some(ffi::PyObject_GenericSetDict),
266 doc: ptr::null(),
267 closure,
268 });
269 }
270
271 unsafe { self.push_raw_vec_slot(ffi::Py_tp_getset, property_defs) };
273
274 if !self.is_mapping && self.has_getitem {
283 unsafe {
285 self.push_slot(
286 ffi::Py_sq_item,
287 get_sequence_item_from_mapping as *mut c_void,
288 )
289 }
290 }
291
292 if !self.is_mapping && self.has_setitem {
293 unsafe {
295 self.push_slot(
296 ffi::Py_sq_ass_item,
297 assign_sequence_item_from_mapping as *mut c_void,
298 )
299 }
300 }
301
302 getset_destructors
303 }
304
305 fn set_is_basetype(mut self, is_basetype: bool) -> Self {
306 if is_basetype {
307 self.class_flags |= ffi::Py_TPFLAGS_BASETYPE;
308 }
309 self
310 }
311
312 unsafe fn class_items(mut self, iter: PyClassItemsIter) -> Self {
315 for items in iter {
316 for slot in items.slots {
317 self.push_slot(slot.slot, slot.pfunc);
318 }
319 for method in items.methods {
320 let built_method;
321 let method = match method {
322 MaybeRuntimePyMethodDef::Runtime(builder) => {
323 built_method = builder();
324 &built_method
325 }
326 MaybeRuntimePyMethodDef::Static(method) => method,
327 };
328 self.pymethod_def(method);
329 }
330 }
331 self
332 }
333
334 fn type_doc(mut self, type_doc: &'static CStr) -> Self {
335 let slice = type_doc.to_bytes();
336 if !slice.is_empty() {
337 unsafe { self.push_slot(ffi::Py_tp_doc, type_doc.as_ptr() as *mut c_char) }
338
339 #[cfg(all(not(PyPy), not(Py_LIMITED_API), not(Py_3_10)))]
341 {
342 self.cleanup
347 .push(Box::new(move |_self, type_object| unsafe {
348 ffi::PyObject_Free((*type_object).tp_doc as _);
349 let data = ffi::PyMem_Malloc(slice.len());
350 data.copy_from(slice.as_ptr() as _, slice.len());
351 (*type_object).tp_doc = data as _;
352 }))
353 }
354 }
355 self
356 }
357
358 fn offsets(
359 mut self,
360 dict_offset: Option<ffi::Py_ssize_t>,
361 #[allow(unused_variables)] weaklist_offset: Option<ffi::Py_ssize_t>,
362 ) -> Self {
363 self.dict_offset = dict_offset;
364
365 #[cfg(Py_3_9)]
366 {
367 #[inline(always)]
368 fn offset_def(name: &'static CStr, offset: ffi::Py_ssize_t) -> ffi::PyMemberDef {
369 ffi::PyMemberDef {
370 name: name.as_ptr().cast(),
371 type_code: ffi::Py_T_PYSSIZET,
372 offset,
373 flags: ffi::Py_READONLY,
374 doc: std::ptr::null_mut(),
375 }
376 }
377
378 if let Some(dict_offset) = dict_offset {
380 self.member_defs
381 .push(offset_def(ffi::c_str!("__dictoffset__"), dict_offset));
382 }
383
384 if let Some(weaklist_offset) = weaklist_offset {
386 self.member_defs.push(offset_def(
387 ffi::c_str!("__weaklistoffset__"),
388 weaklist_offset,
389 ));
390 }
391 }
392
393 #[cfg(all(not(Py_LIMITED_API), not(Py_3_9)))]
396 {
397 self.cleanup
398 .push(Box::new(move |builder, type_object| unsafe {
399 (*(*type_object).tp_as_buffer).bf_getbuffer = builder.buffer_procs.bf_getbuffer;
400 (*(*type_object).tp_as_buffer).bf_releasebuffer =
401 builder.buffer_procs.bf_releasebuffer;
402
403 if let Some(dict_offset) = dict_offset {
404 (*type_object).tp_dictoffset = dict_offset;
405 }
406
407 if let Some(weaklist_offset) = weaklist_offset {
408 (*type_object).tp_weaklistoffset = weaklist_offset;
409 }
410 }));
411 }
412 self
413 }
414
415 fn build(
416 mut self,
417 py: Python<'_>,
418 name: &'static str,
419 module_name: Option<&'static str>,
420 basicsize: usize,
421 ) -> PyResult<PyClassTypeObject> {
422 #![allow(clippy::useless_conversion)]
425
426 let getset_destructors = self.finalize_methods_and_properties();
427
428 unsafe { self.push_slot(ffi::Py_tp_base, self.tp_base) }
429
430 if !self.has_new {
431 unsafe { self.push_slot(ffi::Py_tp_new, no_constructor_defined as *mut c_void) }
433 }
434
435 let base_is_gc = unsafe { ffi::PyType_IS_GC(self.tp_base) == 1 };
436 let tp_dealloc = if self.has_traverse || base_is_gc {
437 self.tp_dealloc_with_gc
438 } else {
439 self.tp_dealloc
440 };
441 unsafe { self.push_slot(ffi::Py_tp_dealloc, tp_dealloc as *mut c_void) }
442
443 if self.has_clear && !self.has_traverse {
444 return Err(PyTypeError::new_err(format!(
445 "`#[pyclass]` {} implements __clear__ without __traverse__",
446 name
447 )));
448 }
449
450 if ((self.class_flags & ffi::Py_TPFLAGS_HAVE_GC) != 0) && base_is_gc {
457 assert!(self.has_traverse); if !self.has_clear {
461 unsafe { self.push_slot(ffi::Py_tp_clear, call_super_clear as *mut c_void) }
463 }
464 }
465
466 if self.is_sequence {
468 for slot in &mut self.slots {
469 if slot.slot == ffi::Py_mp_length {
470 slot.slot = ffi::Py_sq_length;
471 }
472 }
473 }
474
475 unsafe { self.push_slot(0, ptr::null_mut::<c_void>()) }
478
479 let class_name = py_class_qualified_name(module_name, name)?;
480 let mut spec = ffi::PyType_Spec {
481 name: class_name.as_ptr() as _,
482 basicsize: basicsize as c_int,
483 itemsize: 0,
484
485 flags: (ffi::Py_TPFLAGS_DEFAULT | self.class_flags)
486 .try_into()
487 .unwrap(),
488 slots: self.slots.as_mut_ptr(),
489 };
490
491 let type_object: Py<PyType> =
493 unsafe { Py::from_owned_ptr_or_err(py, ffi::PyType_FromSpec(&mut spec))? };
494
495 #[cfg(not(Py_3_11))]
496 bpo_45315_workaround(py, class_name);
497
498 for cleanup in std::mem::take(&mut self.cleanup) {
499 cleanup(&self, type_object.bind(py).as_type_ptr());
500 }
501
502 Ok(PyClassTypeObject {
503 type_object,
504 getset_destructors,
505 })
506 }
507}
508
509fn py_class_qualified_name(module_name: Option<&str>, class_name: &str) -> PyResult<CString> {
510 Ok(CString::new(format!(
511 "{}.{}",
512 module_name.unwrap_or("builtins"),
513 class_name
514 ))?)
515}
516
517#[inline]
519#[cfg(not(Py_3_11))]
520fn bpo_45315_workaround(py: Python<'_>, class_name: CString) {
521 #[cfg(Py_LIMITED_API)]
522 {
523 use crate::sync::GILOnceCell;
526 static IS_PYTHON_3_11: GILOnceCell<bool> = GILOnceCell::new();
527
528 if *IS_PYTHON_3_11.get_or_init(py, || py.version_info() >= (3, 11)) {
529 return;
531 }
532 }
533 #[cfg(not(Py_LIMITED_API))]
534 {
535 let _ = py;
537 }
538
539 std::mem::forget(class_name);
540}
541
542unsafe extern "C" fn no_constructor_defined(
544 subtype: *mut ffi::PyTypeObject,
545 _args: *mut ffi::PyObject,
546 _kwds: *mut ffi::PyObject,
547) -> *mut ffi::PyObject {
548 trampoline(|py| {
549 let tpobj = PyType::from_borrowed_type_ptr(py, subtype);
550 let name = tpobj
551 .name()
552 .map_or_else(|_| "<unknown>".into(), |name| name.to_string());
553 Err(crate::exceptions::PyTypeError::new_err(format!(
554 "No constructor defined for {}",
555 name
556 )))
557 })
558}
559
560unsafe extern "C" fn call_super_clear(slf: *mut ffi::PyObject) -> c_int {
561 _call_clear(slf, |_, _| Ok(()), call_super_clear)
562}
563
564#[derive(Default)]
565struct GetSetDefBuilder {
566 doc: Option<&'static CStr>,
567 getter: Option<Getter>,
568 setter: Option<Setter>,
569}
570
571impl GetSetDefBuilder {
572 fn add_getter(&mut self, getter: &PyGetterDef) {
573 if self.doc.is_none() {
575 self.doc = Some(getter.doc);
576 }
577 self.getter = Some(getter.meth)
579 }
580
581 fn add_setter(&mut self, setter: &PySetterDef) {
582 if self.doc.is_none() {
584 self.doc = Some(setter.doc);
585 }
586 self.setter = Some(setter.meth)
588 }
589
590 fn as_get_set_def(&self, name: &'static CStr) -> (ffi::PyGetSetDef, GetSetDefDestructor) {
591 let getset_type = match (self.getter, self.setter) {
592 (Some(getter), None) => GetSetDefType::Getter(getter),
593 (None, Some(setter)) => GetSetDefType::Setter(setter),
594 (Some(getter), Some(setter)) => {
595 GetSetDefType::GetterAndSetter(Box::new(GetterAndSetter { getter, setter }))
596 }
597 (None, None) => {
598 unreachable!("GetSetDefBuilder expected to always have either getter or setter")
599 }
600 };
601
602 let getset_def = getset_type.create_py_get_set_def(name, self.doc);
603 let destructor = GetSetDefDestructor {
604 closure: getset_type,
605 };
606 (getset_def, destructor)
607 }
608}
609
610#[allow(dead_code)] struct GetSetDefDestructor {
612 closure: GetSetDefType,
613}
614
615enum GetSetDefType {
617 Getter(Getter),
618 Setter(Setter),
619 GetterAndSetter(Box<GetterAndSetter>),
622}
623
624pub(crate) struct GetterAndSetter {
625 getter: Getter,
626 setter: Setter,
627}
628
629impl GetSetDefType {
630 pub(crate) fn create_py_get_set_def(
634 &self,
635 name: &CStr,
636 doc: Option<&CStr>,
637 ) -> ffi::PyGetSetDef {
638 let (get, set, closure): (Option<ffi::getter>, Option<ffi::setter>, *mut c_void) =
639 match self {
640 &Self::Getter(closure) => {
641 unsafe extern "C" fn getter(
642 slf: *mut ffi::PyObject,
643 closure: *mut c_void,
644 ) -> *mut ffi::PyObject {
645 let getter: Getter = std::mem::transmute(closure);
647 trampoline(|py| getter(py, slf))
648 }
649 (Some(getter), None, closure as Getter as _)
650 }
651 &Self::Setter(closure) => {
652 unsafe extern "C" fn setter(
653 slf: *mut ffi::PyObject,
654 value: *mut ffi::PyObject,
655 closure: *mut c_void,
656 ) -> c_int {
657 let setter: Setter = std::mem::transmute(closure);
659 trampoline(|py| setter(py, slf, value))
660 }
661 (None, Some(setter), closure as Setter as _)
662 }
663 Self::GetterAndSetter(closure) => {
664 unsafe extern "C" fn getset_getter(
665 slf: *mut ffi::PyObject,
666 closure: *mut c_void,
667 ) -> *mut ffi::PyObject {
668 let getset: &GetterAndSetter = &*closure.cast();
669 trampoline(|py| (getset.getter)(py, slf))
670 }
671
672 unsafe extern "C" fn getset_setter(
673 slf: *mut ffi::PyObject,
674 value: *mut ffi::PyObject,
675 closure: *mut c_void,
676 ) -> c_int {
677 let getset: &GetterAndSetter = &*closure.cast();
678 trampoline(|py| (getset.setter)(py, slf, value))
679 }
680 (
681 Some(getset_getter),
682 Some(getset_setter),
683 ptr_from_ref::<GetterAndSetter>(closure) as *mut _,
684 )
685 }
686 };
687 ffi::PyGetSetDef {
688 name: name.as_ptr(),
689 doc: doc.map_or(ptr::null(), CStr::as_ptr),
690 get,
691 set,
692 closure,
693 }
694 }
695}