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 pub is_immutable_type: bool,
27 #[allow(dead_code)] getset_destructors: Vec<GetSetDefDestructor>,
29}
30
31pub(crate) fn create_type_object<T>(py: Python<'_>) -> PyResult<PyClassTypeObject>
32where
33 T: PyClass,
34{
35 #[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 is_immutable_type: bool,
45 doc: &'static CStr,
46 dict_offset: Option<ffi::Py_ssize_t>,
47 weaklist_offset: Option<ffi::Py_ssize_t>,
48 is_basetype: bool,
49 items_iter: PyClassItemsIter,
50 name: &'static str,
51 module: Option<&'static str>,
52 size_of: usize,
53 ) -> PyResult<PyClassTypeObject> {
54 unsafe {
55 PyTypeBuilder {
56 slots: Vec::new(),
57 method_defs: Vec::new(),
58 member_defs: Vec::new(),
59 getset_builders: HashMap::new(),
60 cleanup: Vec::new(),
61 tp_base: base,
62 tp_dealloc: dealloc,
63 tp_dealloc_with_gc: dealloc_with_gc,
64 is_mapping,
65 is_sequence,
66 is_immutable_type,
67 has_new: false,
68 has_dealloc: false,
69 has_getitem: false,
70 has_setitem: false,
71 has_traverse: false,
72 has_clear: false,
73 dict_offset: None,
74 class_flags: 0,
75 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
76 buffer_procs: Default::default(),
77 }
78 .type_doc(doc)
79 .offsets(dict_offset, weaklist_offset)
80 .set_is_basetype(is_basetype)
81 .class_items(items_iter)
82 .build(py, name, module, size_of)
83 }
84 }
85
86 unsafe {
87 inner(
88 py,
89 T::BaseType::type_object_raw(py),
90 tp_dealloc::<T>,
91 tp_dealloc_with_gc::<T>,
92 T::IS_MAPPING,
93 T::IS_SEQUENCE,
94 T::IS_IMMUTABLE_TYPE,
95 T::doc(py)?,
96 T::dict_offset(),
97 T::weaklist_offset(),
98 T::IS_BASETYPE,
99 T::items_iter(),
100 T::NAME,
101 T::MODULE,
102 std::mem::size_of::<PyClassObject<T>>(),
103 )
104 }
105}
106
107type PyTypeBuilderCleanup = Box<dyn Fn(&PyTypeBuilder, *mut ffi::PyTypeObject)>;
108
109struct PyTypeBuilder {
110 slots: Vec<ffi::PyType_Slot>,
111 method_defs: Vec<ffi::PyMethodDef>,
112 member_defs: Vec<ffi::PyMemberDef>,
113 getset_builders: HashMap<&'static CStr, GetSetDefBuilder>,
114 cleanup: Vec<PyTypeBuilderCleanup>,
118 tp_base: *mut ffi::PyTypeObject,
119 tp_dealloc: ffi::destructor,
120 tp_dealloc_with_gc: ffi::destructor,
121 is_mapping: bool,
122 is_sequence: bool,
123 is_immutable_type: bool,
124 has_new: bool,
125 has_dealloc: bool,
126 has_getitem: bool,
127 has_setitem: bool,
128 has_traverse: bool,
129 has_clear: bool,
130 dict_offset: Option<ffi::Py_ssize_t>,
131 class_flags: c_ulong,
132 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
134 buffer_procs: ffi::PyBufferProcs,
135}
136
137impl PyTypeBuilder {
138 unsafe fn push_slot<T>(&mut self, slot: c_int, pfunc: *mut T) {
141 match slot {
142 ffi::Py_tp_new => self.has_new = true,
143 ffi::Py_tp_dealloc => self.has_dealloc = true,
144 ffi::Py_mp_subscript => self.has_getitem = true,
145 ffi::Py_mp_ass_subscript => self.has_setitem = true,
146 ffi::Py_tp_traverse => {
147 self.has_traverse = true;
148 self.class_flags |= ffi::Py_TPFLAGS_HAVE_GC;
149 }
150 ffi::Py_tp_clear => self.has_clear = true,
151 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
152 ffi::Py_bf_getbuffer => {
153 self.buffer_procs.bf_getbuffer =
155 Some(unsafe { std::mem::transmute::<*mut T, ffi::getbufferproc>(pfunc) });
156 }
157 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
158 ffi::Py_bf_releasebuffer => {
159 self.buffer_procs.bf_releasebuffer =
161 Some(unsafe { std::mem::transmute::<*mut T, ffi::releasebufferproc>(pfunc) });
162 }
163 _ => {}
164 }
165
166 self.slots.push(ffi::PyType_Slot {
167 slot,
168 pfunc: pfunc as _,
169 });
170 }
171
172 unsafe fn push_raw_vec_slot<T>(&mut self, slot: c_int, mut data: Vec<T>) {
175 if !data.is_empty() {
176 unsafe {
178 data.push(std::mem::zeroed());
179 self.push_slot(slot, Box::into_raw(data.into_boxed_slice()) as *mut c_void);
180 }
181 }
182 }
183
184 fn pymethod_def(&mut self, def: &PyMethodDefType) {
185 match def {
186 PyMethodDefType::Getter(getter) => self
187 .getset_builders
188 .entry(getter.name)
189 .or_default()
190 .add_getter(getter),
191 PyMethodDefType::Setter(setter) => self
192 .getset_builders
193 .entry(setter.name)
194 .or_default()
195 .add_setter(setter),
196 PyMethodDefType::Method(def)
197 | PyMethodDefType::Class(def)
198 | PyMethodDefType::Static(def) => self.method_defs.push(def.as_method_def()),
199 PyMethodDefType::ClassAttribute(_) => {}
201 PyMethodDefType::StructMember(def) => self.member_defs.push(*def),
202 }
203 }
204
205 fn finalize_methods_and_properties(&mut self) -> Vec<GetSetDefDestructor> {
206 let method_defs: Vec<pyo3_ffi::PyMethodDef> = std::mem::take(&mut self.method_defs);
207 unsafe { self.push_raw_vec_slot(ffi::Py_tp_methods, method_defs) };
209
210 let member_defs = std::mem::take(&mut self.member_defs);
211 unsafe { self.push_raw_vec_slot(ffi::Py_tp_members, member_defs) };
213
214 let mut getset_destructors = Vec::with_capacity(self.getset_builders.len());
215
216 #[allow(unused_mut)]
217 let mut property_defs: Vec<_> = self
218 .getset_builders
219 .iter()
220 .map(|(name, builder)| {
221 let (def, destructor) = builder.as_get_set_def(name);
222 getset_destructors.push(destructor);
223 def
224 })
225 .collect();
226
227 #[cfg(not(PyPy))]
229 #[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
231 if let Some(dict_offset) = self.dict_offset {
232 let get_dict;
233 let closure;
234 #[cfg(any(not(Py_LIMITED_API), Py_3_10))]
236 {
237 let _ = dict_offset;
238 get_dict = ffi::PyObject_GenericGetDict;
239 closure = ptr::null_mut();
240 }
241
242 #[cfg(not(any(not(Py_LIMITED_API), Py_3_10)))]
244 {
245 extern "C" fn get_dict_impl(
246 object: *mut ffi::PyObject,
247 closure: *mut c_void,
248 ) -> *mut ffi::PyObject {
249 unsafe {
250 trampoline(|_| {
251 let dict_offset = closure as ffi::Py_ssize_t;
252 assert!(dict_offset > 0);
254 let dict_ptr = object
256 .cast::<u8>()
257 .offset(dict_offset)
258 .cast::<*mut ffi::PyObject>();
259 if (*dict_ptr).is_null() {
260 std::ptr::write(dict_ptr, ffi::PyDict_New());
261 }
262 Ok(ffi::compat::Py_XNewRef(*dict_ptr))
263 })
264 }
265 }
266
267 get_dict = get_dict_impl;
268 closure = dict_offset as _;
269 }
270
271 property_defs.push(ffi::PyGetSetDef {
272 name: ffi::c_str!("__dict__").as_ptr(),
273 get: Some(get_dict),
274 set: Some(ffi::PyObject_GenericSetDict),
275 doc: ptr::null(),
276 closure,
277 });
278 }
279
280 unsafe { self.push_raw_vec_slot(ffi::Py_tp_getset, property_defs) };
282
283 if !self.is_mapping && self.has_getitem {
292 unsafe {
294 self.push_slot(
295 ffi::Py_sq_item,
296 get_sequence_item_from_mapping as *mut c_void,
297 )
298 }
299 }
300
301 if !self.is_mapping && self.has_setitem {
302 unsafe {
304 self.push_slot(
305 ffi::Py_sq_ass_item,
306 assign_sequence_item_from_mapping as *mut c_void,
307 )
308 }
309 }
310
311 getset_destructors
312 }
313
314 fn set_is_basetype(mut self, is_basetype: bool) -> Self {
315 if is_basetype {
316 self.class_flags |= ffi::Py_TPFLAGS_BASETYPE;
317 }
318 self
319 }
320
321 unsafe fn class_items(mut self, iter: PyClassItemsIter) -> Self {
324 for items in iter {
325 for slot in items.slots {
326 unsafe { self.push_slot(slot.slot, slot.pfunc) };
327 }
328 for method in items.methods {
329 let built_method;
330 let method = match method {
331 MaybeRuntimePyMethodDef::Runtime(builder) => {
332 built_method = builder();
333 &built_method
334 }
335 MaybeRuntimePyMethodDef::Static(method) => method,
336 };
337 self.pymethod_def(method);
338 }
339 }
340 self
341 }
342
343 fn type_doc(mut self, type_doc: &'static CStr) -> Self {
344 let slice = type_doc.to_bytes();
345 if !slice.is_empty() {
346 unsafe { self.push_slot(ffi::Py_tp_doc, type_doc.as_ptr() as *mut c_char) }
347
348 #[cfg(all(not(PyPy), not(Py_LIMITED_API), not(Py_3_10)))]
350 {
351 self.cleanup
356 .push(Box::new(move |_self, type_object| unsafe {
357 ffi::PyObject_Free((*type_object).tp_doc as _);
358 let data = ffi::PyMem_Malloc(slice.len());
359 data.copy_from(slice.as_ptr() as _, slice.len());
360 (*type_object).tp_doc = data as _;
361 }))
362 }
363 }
364 self
365 }
366
367 fn offsets(
368 mut self,
369 dict_offset: Option<ffi::Py_ssize_t>,
370 #[allow(unused_variables)] weaklist_offset: Option<ffi::Py_ssize_t>,
371 ) -> Self {
372 self.dict_offset = dict_offset;
373
374 #[cfg(Py_3_9)]
375 {
376 #[inline(always)]
377 fn offset_def(name: &'static CStr, offset: ffi::Py_ssize_t) -> ffi::PyMemberDef {
378 ffi::PyMemberDef {
379 name: name.as_ptr().cast(),
380 type_code: ffi::Py_T_PYSSIZET,
381 offset,
382 flags: ffi::Py_READONLY,
383 doc: std::ptr::null_mut(),
384 }
385 }
386
387 if let Some(dict_offset) = dict_offset {
389 self.member_defs
390 .push(offset_def(ffi::c_str!("__dictoffset__"), dict_offset));
391 }
392
393 if let Some(weaklist_offset) = weaklist_offset {
395 self.member_defs.push(offset_def(
396 ffi::c_str!("__weaklistoffset__"),
397 weaklist_offset,
398 ));
399 }
400 }
401
402 #[cfg(all(not(Py_LIMITED_API), not(Py_3_9)))]
405 {
406 self.cleanup
407 .push(Box::new(move |builder, type_object| unsafe {
408 (*(*type_object).tp_as_buffer).bf_getbuffer = builder.buffer_procs.bf_getbuffer;
409 (*(*type_object).tp_as_buffer).bf_releasebuffer =
410 builder.buffer_procs.bf_releasebuffer;
411
412 if let Some(dict_offset) = dict_offset {
413 (*type_object).tp_dictoffset = dict_offset;
414 }
415
416 if let Some(weaklist_offset) = weaklist_offset {
417 (*type_object).tp_weaklistoffset = weaklist_offset;
418 }
419 }));
420 }
421 self
422 }
423
424 fn build(
425 mut self,
426 py: Python<'_>,
427 name: &'static str,
428 module_name: Option<&'static str>,
429 basicsize: usize,
430 ) -> PyResult<PyClassTypeObject> {
431 #![allow(clippy::useless_conversion)]
434
435 let getset_destructors = self.finalize_methods_and_properties();
436
437 unsafe { self.push_slot(ffi::Py_tp_base, self.tp_base) }
438
439 if !self.has_new {
440 unsafe { self.push_slot(ffi::Py_tp_new, no_constructor_defined as *mut c_void) }
442 }
443
444 let base_is_gc = unsafe { ffi::PyType_IS_GC(self.tp_base) == 1 };
445 let tp_dealloc = if self.has_traverse || base_is_gc {
446 self.tp_dealloc_with_gc
447 } else {
448 self.tp_dealloc
449 };
450 unsafe { self.push_slot(ffi::Py_tp_dealloc, tp_dealloc as *mut c_void) }
451
452 if self.has_clear && !self.has_traverse {
453 return Err(PyTypeError::new_err(format!(
454 "`#[pyclass]` {} implements __clear__ without __traverse__",
455 name
456 )));
457 }
458
459 if ((self.class_flags & ffi::Py_TPFLAGS_HAVE_GC) != 0) && base_is_gc {
466 assert!(self.has_traverse); if !self.has_clear {
470 unsafe { self.push_slot(ffi::Py_tp_clear, call_super_clear as *mut c_void) }
472 }
473 }
474
475 if self.is_sequence {
477 for slot in &mut self.slots {
478 if slot.slot == ffi::Py_mp_length {
479 slot.slot = ffi::Py_sq_length;
480 }
481 }
482 }
483
484 unsafe { self.push_slot(0, ptr::null_mut::<c_void>()) }
487
488 let class_name = py_class_qualified_name(module_name, name)?;
489 let mut spec = ffi::PyType_Spec {
490 name: class_name.as_ptr() as _,
491 basicsize: basicsize as c_int,
492 itemsize: 0,
493
494 flags: (ffi::Py_TPFLAGS_DEFAULT | self.class_flags)
495 .try_into()
496 .unwrap(),
497 slots: self.slots.as_mut_ptr(),
498 };
499
500 let type_object: Py<PyType> =
502 unsafe { Py::from_owned_ptr_or_err(py, ffi::PyType_FromSpec(&mut spec))? };
503
504 #[cfg(not(Py_3_11))]
505 bpo_45315_workaround(py, class_name);
506
507 for cleanup in std::mem::take(&mut self.cleanup) {
508 cleanup(&self, type_object.bind(py).as_type_ptr());
509 }
510
511 Ok(PyClassTypeObject {
512 type_object,
513 is_immutable_type: self.is_immutable_type,
514 getset_destructors,
515 })
516 }
517}
518
519fn py_class_qualified_name(module_name: Option<&str>, class_name: &str) -> PyResult<CString> {
520 Ok(CString::new(format!(
521 "{}.{}",
522 module_name.unwrap_or("builtins"),
523 class_name
524 ))?)
525}
526
527#[inline]
529#[cfg(not(Py_3_11))]
530fn bpo_45315_workaround(py: Python<'_>, class_name: CString) {
531 #[cfg(Py_LIMITED_API)]
532 {
533 use crate::sync::GILOnceCell;
536 static IS_PYTHON_3_11: GILOnceCell<bool> = GILOnceCell::new();
537
538 if *IS_PYTHON_3_11.get_or_init(py, || py.version_info() >= (3, 11)) {
539 return;
541 }
542 }
543 #[cfg(not(Py_LIMITED_API))]
544 {
545 let _ = py;
547 }
548
549 std::mem::forget(class_name);
550}
551
552unsafe extern "C" fn no_constructor_defined(
554 subtype: *mut ffi::PyTypeObject,
555 _args: *mut ffi::PyObject,
556 _kwds: *mut ffi::PyObject,
557) -> *mut ffi::PyObject {
558 unsafe {
559 trampoline(|py| {
560 let tpobj = PyType::from_borrowed_type_ptr(py, subtype);
561 let name = tpobj
562 .name()
563 .map_or_else(|_| "<unknown>".into(), |name| name.to_string());
564 Err(crate::exceptions::PyTypeError::new_err(format!(
565 "No constructor defined for {}",
566 name
567 )))
568 })
569 }
570}
571
572unsafe extern "C" fn call_super_clear(slf: *mut ffi::PyObject) -> c_int {
573 unsafe { _call_clear(slf, |_, _| Ok(()), call_super_clear) }
574}
575
576#[derive(Default)]
577struct GetSetDefBuilder {
578 doc: Option<&'static CStr>,
579 getter: Option<Getter>,
580 setter: Option<Setter>,
581}
582
583impl GetSetDefBuilder {
584 fn add_getter(&mut self, getter: &PyGetterDef) {
585 if self.doc.is_none() {
587 self.doc = Some(getter.doc);
588 }
589 self.getter = Some(getter.meth)
591 }
592
593 fn add_setter(&mut self, setter: &PySetterDef) {
594 if self.doc.is_none() {
596 self.doc = Some(setter.doc);
597 }
598 self.setter = Some(setter.meth)
600 }
601
602 fn as_get_set_def(&self, name: &'static CStr) -> (ffi::PyGetSetDef, GetSetDefDestructor) {
603 let getset_type = match (self.getter, self.setter) {
604 (Some(getter), None) => GetSetDefType::Getter(getter),
605 (None, Some(setter)) => GetSetDefType::Setter(setter),
606 (Some(getter), Some(setter)) => {
607 GetSetDefType::GetterAndSetter(Box::new(GetterAndSetter { getter, setter }))
608 }
609 (None, None) => {
610 unreachable!("GetSetDefBuilder expected to always have either getter or setter")
611 }
612 };
613
614 let getset_def = getset_type.create_py_get_set_def(name, self.doc);
615 let destructor = GetSetDefDestructor {
616 closure: getset_type,
617 };
618 (getset_def, destructor)
619 }
620}
621
622#[allow(dead_code)] struct GetSetDefDestructor {
624 closure: GetSetDefType,
625}
626
627enum GetSetDefType {
629 Getter(Getter),
630 Setter(Setter),
631 GetterAndSetter(Box<GetterAndSetter>),
634}
635
636pub(crate) struct GetterAndSetter {
637 getter: Getter,
638 setter: Setter,
639}
640
641impl GetSetDefType {
642 pub(crate) fn create_py_get_set_def(
646 &self,
647 name: &CStr,
648 doc: Option<&CStr>,
649 ) -> ffi::PyGetSetDef {
650 let (get, set, closure): (Option<ffi::getter>, Option<ffi::setter>, *mut c_void) =
651 match self {
652 &Self::Getter(closure) => {
653 unsafe extern "C" fn getter(
654 slf: *mut ffi::PyObject,
655 closure: *mut c_void,
656 ) -> *mut ffi::PyObject {
657 let getter: Getter = unsafe { std::mem::transmute(closure) };
659 unsafe { trampoline(|py| getter(py, slf)) }
660 }
661 (Some(getter), None, closure as Getter as _)
662 }
663 &Self::Setter(closure) => {
664 unsafe extern "C" fn setter(
665 slf: *mut ffi::PyObject,
666 value: *mut ffi::PyObject,
667 closure: *mut c_void,
668 ) -> c_int {
669 let setter: Setter = unsafe { std::mem::transmute(closure) };
671 unsafe { trampoline(|py| setter(py, slf, value)) }
672 }
673 (None, Some(setter), closure as Setter as _)
674 }
675 Self::GetterAndSetter(closure) => {
676 unsafe extern "C" fn getset_getter(
677 slf: *mut ffi::PyObject,
678 closure: *mut c_void,
679 ) -> *mut ffi::PyObject {
680 let getset: &GetterAndSetter = unsafe { &*closure.cast() };
681 unsafe { trampoline(|py| (getset.getter)(py, slf)) }
682 }
683
684 unsafe extern "C" fn getset_setter(
685 slf: *mut ffi::PyObject,
686 value: *mut ffi::PyObject,
687 closure: *mut c_void,
688 ) -> c_int {
689 let getset: &GetterAndSetter = unsafe { &*closure.cast() };
690 unsafe { trampoline(|py| (getset.setter)(py, slf, value)) }
691 }
692 (
693 Some(getset_getter),
694 Some(getset_setter),
695 ptr_from_ref::<GetterAndSetter>(closure) as *mut _,
696 )
697 }
698 };
699 ffi::PyGetSetDef {
700 name: name.as_ptr(),
701 doc: doc.map_or(ptr::null(), CStr::as_ptr),
702 get,
703 set,
704 closure,
705 }
706 }
707}