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 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
103type PyTypeBuilderCleanup = Box<dyn Fn(&PyTypeBuilder, *mut ffi::PyTypeObject)>;
104
105struct 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 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 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
129 buffer_procs: ffi::PyBufferProcs,
130}
131
132impl PyTypeBuilder {
133 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 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 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 unsafe fn push_raw_vec_slot<T>(&mut self, slot: c_int, mut data: Vec<T>) {
170 if !data.is_empty() {
171 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 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 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 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 #[cfg(not(PyPy))]
224 #[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 #[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 #[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 assert!(dict_offset > 0);
249 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 unsafe { self.push_raw_vec_slot(ffi::Py_tp_getset, property_defs) };
277
278 if !self.is_mapping && self.has_getitem {
287 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 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 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 #[cfg(all(not(PyPy), not(Py_LIMITED_API), not(Py_3_10)))]
345 {
346 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 if let Some(dict_offset) = dict_offset {
384 self.member_defs
385 .push(offset_def(ffi::c_str!("__dictoffset__"), dict_offset));
386 }
387
388 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 #[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 #![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 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 ((self.class_flags & ffi::Py_TPFLAGS_HAVE_GC) != 0) && base_is_gc {
461 assert!(self.has_traverse); if !self.has_clear {
465 unsafe { self.push_slot(ffi::Py_tp_clear, call_super_clear as *mut c_void) }
467 }
468 }
469
470 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 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 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
513fn 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#[inline]
523#[cfg(not(Py_3_11))]
524fn bpo_45315_workaround(py: Python<'_>, class_name: CString) {
525 #[cfg(Py_LIMITED_API)]
526 {
527 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 return;
535 }
536 }
537 #[cfg(not(Py_LIMITED_API))]
538 {
539 let _ = py;
541 }
542
543 std::mem::forget(class_name);
544}
545
546unsafe 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| {
554 let tpobj = PyType::from_borrowed_type_ptr(py, subtype);
555 let name = tpobj
556 .name()
557 .map_or_else(|_| "<unknown>".into(), |name| name.to_string());
558 Err(crate::exceptions::PyTypeError::new_err(format!(
559 "No constructor defined for {}",
560 name
561 )))
562 })
563 }
564}
565
566unsafe extern "C" fn call_super_clear(slf: *mut ffi::PyObject) -> c_int {
567 unsafe { _call_clear(slf, |_, _| Ok(()), call_super_clear) }
568}
569
570#[derive(Default)]
571struct GetSetDefBuilder {
572 doc: Option<&'static CStr>,
573 getter: Option<Getter>,
574 setter: Option<Setter>,
575}
576
577impl GetSetDefBuilder {
578 fn add_getter(&mut self, getter: &PyGetterDef) {
579 if self.doc.is_none() {
581 self.doc = Some(getter.doc);
582 }
583 self.getter = Some(getter.meth)
585 }
586
587 fn add_setter(&mut self, setter: &PySetterDef) {
588 if self.doc.is_none() {
590 self.doc = Some(setter.doc);
591 }
592 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)] struct GetSetDefDestructor {
618 closure: GetSetDefType,
619}
620
621enum GetSetDefType {
623 Getter(Getter),
624 Setter(Setter),
625 GetterAndSetter(Box<GetterAndSetter>),
628}
629
630pub(crate) struct GetterAndSetter {
631 getter: Getter,
632 setter: Setter,
633}
634
635impl GetSetDefType {
636 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 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 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}