1use crate::exceptions::PyAttributeError;
2use crate::impl_::pymethods::{Deleter, PyDeleterDef};
3#[cfg(not(Py_3_10))]
4use crate::types::typeobject::PyTypeMethods;
5use crate::{
6 exceptions::PyTypeError,
7 ffi,
8 ffi_ptr_ext::FfiPtrExt,
9 impl_::{
10 pyclass::{
11 assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc,
12 tp_dealloc_with_gc, PyClassImpl, PyClassItemsIter, PyObjectOffset,
13 },
14 pymethods::{Getter, PyGetterDef, PyMethodDefType, PySetterDef, Setter, _call_clear},
15 trampoline::trampoline,
16 },
17 pycell::impl_::PyClassObjectLayout,
18 types::PyType,
19 Py, PyClass, PyResult, PyTypeInfo, Python,
20};
21use std::{
22 collections::HashMap,
23 ffi::{CStr, CString},
24 os::raw::{c_char, c_int, c_ulong, c_void},
25 ptr::{self, NonNull},
26};
27
28pub(crate) struct PyClassTypeObject {
29 pub type_object: Py<PyType>,
30 pub is_immutable_type: bool,
31 #[expect(
32 dead_code,
33 reason = "this is just storage that must live as long as the type object"
34 )]
35 getset_defs: Vec<GetSetDefType>,
36}
37
38pub(crate) fn create_type_object<T>(py: Python<'_>) -> PyResult<PyClassTypeObject>
39where
40 T: PyClass,
41{
42 #[expect(clippy::too_many_arguments)]
44 unsafe fn inner(
45 py: Python<'_>,
46 base: *mut ffi::PyTypeObject,
47 dealloc: unsafe extern "C" fn(*mut ffi::PyObject),
48 dealloc_with_gc: unsafe extern "C" fn(*mut ffi::PyObject),
49 is_mapping: bool,
50 is_sequence: bool,
51 is_immutable_type: bool,
52 doc: &'static CStr,
53 dict_offset: Option<PyObjectOffset>,
54 weaklist_offset: Option<PyObjectOffset>,
55 is_basetype: bool,
56 items_iter: PyClassItemsIter,
57 name: &'static str,
58 module: Option<&'static str>,
59 basicsize: ffi::Py_ssize_t,
60 ) -> PyResult<PyClassTypeObject> {
61 unsafe {
62 PyTypeBuilder {
63 slots: Vec::new(),
64 method_defs: Vec::new(),
65 member_defs: Vec::new(),
66 getset_builders: HashMap::new(),
67 #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
68 cleanup: Vec::new(),
69 tp_base: base,
70 tp_dealloc: dealloc,
71 tp_dealloc_with_gc: dealloc_with_gc,
72 is_mapping,
73 is_sequence,
74 is_immutable_type,
75 has_new: false,
76 has_dealloc: false,
77 has_getitem: false,
78 has_setitem: false,
79 has_traverse: false,
80 has_clear: false,
81 dict_offset: None,
82 class_flags: 0,
83 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
84 buffer_procs: Default::default(),
85 }
86 .type_doc(doc)
87 .offsets(dict_offset, weaklist_offset)
88 .set_is_basetype(is_basetype)
89 .class_items(items_iter)
90 .build(py, name, module, basicsize)
91 }
92 }
93
94 unsafe {
95 inner(
96 py,
97 T::BaseType::type_object_raw(py),
98 tp_dealloc::<T>,
99 tp_dealloc_with_gc::<T>,
100 T::IS_MAPPING,
101 T::IS_SEQUENCE,
102 T::IS_IMMUTABLE_TYPE,
103 T::DOC,
104 T::dict_offset(),
105 T::weaklist_offset(),
106 T::IS_BASETYPE,
107 T::items_iter(),
108 <T as PyClass>::NAME,
109 <T as PyClassImpl>::MODULE,
110 <T as PyClassImpl>::Layout::BASIC_SIZE,
111 )
112 }
113}
114
115#[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
116type PyTypeBuilderCleanup = Box<dyn Fn(&PyTypeBuilder, *mut ffi::PyTypeObject)>;
117
118struct PyTypeBuilder {
119 slots: Vec<ffi::PyType_Slot>,
120 method_defs: Vec<ffi::PyMethodDef>,
121 member_defs: Vec<ffi::PyMemberDef>,
122 getset_builders: HashMap<&'static CStr, GetSetDefBuilder>,
123 #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
127 cleanup: Vec<PyTypeBuilderCleanup>,
128 tp_base: *mut ffi::PyTypeObject,
129 tp_dealloc: ffi::destructor,
130 tp_dealloc_with_gc: ffi::destructor,
131 is_mapping: bool,
132 is_sequence: bool,
133 is_immutable_type: bool,
134 has_new: bool,
135 has_dealloc: bool,
136 has_getitem: bool,
137 has_setitem: bool,
138 has_traverse: bool,
139 has_clear: bool,
140 dict_offset: Option<PyObjectOffset>,
141 class_flags: c_ulong,
142 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
144 buffer_procs: ffi::PyBufferProcs,
145}
146
147impl PyTypeBuilder {
148 unsafe fn push_slot<T>(&mut self, slot: c_int, pfunc: *mut T) {
151 match slot {
152 ffi::Py_tp_new => self.has_new = true,
153 ffi::Py_tp_dealloc => self.has_dealloc = true,
154 ffi::Py_mp_subscript => self.has_getitem = true,
155 ffi::Py_mp_ass_subscript => self.has_setitem = true,
156 ffi::Py_tp_traverse => {
157 self.has_traverse = true;
158 self.class_flags |= ffi::Py_TPFLAGS_HAVE_GC;
159 }
160 ffi::Py_tp_clear => self.has_clear = true,
161 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
162 ffi::Py_bf_getbuffer => {
163 self.buffer_procs.bf_getbuffer =
165 Some(unsafe { std::mem::transmute::<*mut T, ffi::getbufferproc>(pfunc) });
166 }
167 #[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
168 ffi::Py_bf_releasebuffer => {
169 self.buffer_procs.bf_releasebuffer =
171 Some(unsafe { std::mem::transmute::<*mut T, ffi::releasebufferproc>(pfunc) });
172 }
173 _ => {}
174 }
175
176 self.slots.push(ffi::PyType_Slot {
177 slot,
178 pfunc: pfunc as _,
179 });
180 }
181
182 unsafe fn push_raw_vec_slot<T>(&mut self, slot: c_int, mut data: Vec<T>) {
185 if !data.is_empty() {
186 unsafe {
188 data.push(std::mem::zeroed());
189 self.push_slot(slot, Box::into_raw(data.into_boxed_slice()) as *mut c_void);
190 }
191 }
192 }
193
194 fn pymethod_def(&mut self, def: &PyMethodDefType) {
195 match def {
196 PyMethodDefType::Getter(getter) => self
197 .getset_builders
198 .entry(getter.name)
199 .or_default()
200 .add_getter(getter),
201 PyMethodDefType::Setter(setter) => self
202 .getset_builders
203 .entry(setter.name)
204 .or_default()
205 .add_setter(setter),
206 PyMethodDefType::Deleter(deleter) => self
207 .getset_builders
208 .entry(deleter.name)
209 .or_default()
210 .add_deleter(deleter),
211 PyMethodDefType::Method(def) => self.method_defs.push(def.into_raw()),
212 PyMethodDefType::ClassAttribute(_) => {}
214 PyMethodDefType::StructMember(def) => self.member_defs.push(*def),
215 }
216 }
217
218 fn finalize_methods_and_properties(&mut self) -> Vec<GetSetDefType> {
219 let method_defs: Vec<pyo3_ffi::PyMethodDef> = std::mem::take(&mut self.method_defs);
220 unsafe { self.push_raw_vec_slot(ffi::Py_tp_methods, method_defs) };
222
223 let member_defs = std::mem::take(&mut self.member_defs);
224 unsafe { self.push_raw_vec_slot(ffi::Py_tp_members, member_defs) };
226
227 let mut getset_destructors = Vec::with_capacity(self.getset_builders.len());
228
229 #[allow(unused_mut, reason = "not modified on PyPy")]
230 let mut property_defs: Vec<_> = self
231 .getset_builders
232 .iter()
233 .map(|(name, builder)| {
234 let (def, destructor) = builder.as_get_set_def(name);
235 getset_destructors.push(destructor);
236 def
237 })
238 .collect();
239
240 #[cfg(not(PyPy))]
242 #[cfg(any(Py_3_9, not(Py_LIMITED_API)))]
244 if let Some(dict_offset) = self.dict_offset {
245 let get_dict;
246 let closure;
247 #[cfg(any(not(Py_LIMITED_API), Py_3_10))]
249 {
250 let _ = dict_offset;
251 get_dict = ffi::PyObject_GenericGetDict;
252 closure = ptr::null_mut();
253 }
254
255 #[cfg(not(any(not(Py_LIMITED_API), Py_3_10)))]
257 {
258 extern "C" fn get_dict_impl(
259 object: *mut ffi::PyObject,
260 closure: *mut c_void,
261 ) -> *mut ffi::PyObject {
262 unsafe {
263 trampoline(|_| {
264 let dict_offset = closure as ffi::Py_ssize_t;
265 assert!(dict_offset > 0);
267 let dict_ptr =
268 object.byte_offset(dict_offset).cast::<*mut ffi::PyObject>();
269 if (*dict_ptr).is_null() {
270 std::ptr::write(dict_ptr, ffi::PyDict_New());
271 }
272 Ok(ffi::compat::Py_XNewRef(*dict_ptr))
273 })
274 }
275 }
276
277 get_dict = get_dict_impl;
278 let PyObjectOffset::Absolute(offset) = dict_offset;
279 closure = offset as _;
280 }
281
282 property_defs.push(ffi::PyGetSetDef {
283 name: c"__dict__".as_ptr(),
284 get: Some(get_dict),
285 set: Some(ffi::PyObject_GenericSetDict),
286 doc: ptr::null(),
287 closure,
288 });
289 }
290
291 unsafe { self.push_raw_vec_slot(ffi::Py_tp_getset, property_defs) };
293
294 if !self.is_mapping && self.has_getitem {
303 unsafe {
305 self.push_slot(
306 ffi::Py_sq_item,
307 get_sequence_item_from_mapping as *mut c_void,
308 )
309 }
310 }
311
312 if !self.is_mapping && self.has_setitem {
313 unsafe {
315 self.push_slot(
316 ffi::Py_sq_ass_item,
317 assign_sequence_item_from_mapping as *mut c_void,
318 )
319 }
320 }
321
322 getset_destructors
323 }
324
325 fn set_is_basetype(mut self, is_basetype: bool) -> Self {
326 if is_basetype {
327 self.class_flags |= ffi::Py_TPFLAGS_BASETYPE;
328 }
329 self
330 }
331
332 unsafe fn class_items(mut self, iter: PyClassItemsIter) -> Self {
335 for items in iter {
336 for slot in items.slots {
337 unsafe { self.push_slot(slot.slot, slot.pfunc) };
338 }
339 for method in items.methods {
340 self.pymethod_def(method);
341 }
342 }
343 self
344 }
345
346 fn type_doc(mut self, type_doc: &'static CStr) -> Self {
347 let slice = type_doc.to_bytes();
348 if !slice.is_empty() {
349 unsafe { self.push_slot(ffi::Py_tp_doc, type_doc.as_ptr() as *mut c_char) }
350
351 #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
352 {
353 self.cleanup
358 .push(Box::new(move |_self, type_object| unsafe {
359 ffi::PyObject_Free((*type_object).tp_doc as _);
360 let data = ffi::PyMem_Malloc(slice.len());
361 data.copy_from(slice.as_ptr() as _, slice.len());
362 (*type_object).tp_doc = data as _;
363 }))
364 }
365 }
366 self
367 }
368
369 fn offsets(
370 mut self,
371 dict_offset: Option<PyObjectOffset>,
372 #[allow(unused_variables)] weaklist_offset: Option<PyObjectOffset>,
373 ) -> Self {
374 self.dict_offset = dict_offset;
375
376 #[cfg(Py_3_9)]
377 {
378 #[inline(always)]
379 fn offset_def(name: &'static CStr, offset: PyObjectOffset) -> ffi::PyMemberDef {
380 let (offset, flags) = match offset {
381 PyObjectOffset::Absolute(offset) => (offset, ffi::Py_READONLY),
382 #[cfg(Py_3_12)]
383 PyObjectOffset::Relative(offset) => {
384 (offset, ffi::Py_READONLY | ffi::Py_RELATIVE_OFFSET)
385 }
386 };
387 ffi::PyMemberDef {
388 name: name.as_ptr().cast(),
389 type_code: ffi::Py_T_PYSSIZET,
390 offset,
391 flags,
392 doc: std::ptr::null_mut(),
393 }
394 }
395
396 if let Some(dict_offset) = dict_offset {
398 self.member_defs
399 .push(offset_def(c"__dictoffset__", dict_offset));
400 }
401
402 if let Some(weaklist_offset) = weaklist_offset {
404 self.member_defs
405 .push(offset_def(c"__weaklistoffset__", weaklist_offset));
406 }
407 }
408
409 #[cfg(all(not(Py_LIMITED_API), not(Py_3_9)))]
412 {
413 self.cleanup
414 .push(Box::new(move |builder, type_object| unsafe {
415 (*(*type_object).tp_as_buffer).bf_getbuffer = builder.buffer_procs.bf_getbuffer;
416 (*(*type_object).tp_as_buffer).bf_releasebuffer =
417 builder.buffer_procs.bf_releasebuffer;
418
419 match dict_offset {
420 Some(PyObjectOffset::Absolute(offset)) => {
421 (*type_object).tp_dictoffset = offset;
422 }
423 None => {}
424 }
425 match weaklist_offset {
426 Some(PyObjectOffset::Absolute(offset)) => {
427 (*type_object).tp_weaklistoffset = offset;
428 }
429 None => {}
430 }
431 }));
432 }
433 self
434 }
435
436 fn build(
437 mut self,
438 py: Python<'_>,
439 name: &'static str,
440 module_name: Option<&'static str>,
441 basicsize: ffi::Py_ssize_t,
442 ) -> PyResult<PyClassTypeObject> {
443 #![allow(clippy::useless_conversion)]
446
447 let getset_defs = self.finalize_methods_and_properties();
448
449 unsafe { self.push_slot(ffi::Py_tp_base, self.tp_base) }
450
451 if !self.has_new {
452 #[cfg(not(Py_3_10))]
453 {
454 unsafe { self.push_slot(ffi::Py_tp_new, no_constructor_defined as *mut c_void) }
456 }
457 #[cfg(Py_3_10)]
458 {
459 self.class_flags |= ffi::Py_TPFLAGS_DISALLOW_INSTANTIATION;
460 }
461 }
462
463 let base_is_gc = unsafe { ffi::PyType_IS_GC(self.tp_base) == 1 };
464 let tp_dealloc = if self.has_traverse || base_is_gc {
465 self.tp_dealloc_with_gc
466 } else {
467 self.tp_dealloc
468 };
469 unsafe { self.push_slot(ffi::Py_tp_dealloc, tp_dealloc as *mut c_void) }
470
471 if self.has_clear && !self.has_traverse {
472 return Err(PyTypeError::new_err(format!(
473 "`#[pyclass]` {name} implements __clear__ without __traverse__"
474 )));
475 }
476
477 if ((self.class_flags & ffi::Py_TPFLAGS_HAVE_GC) != 0) && base_is_gc {
484 assert!(self.has_traverse); if !self.has_clear {
488 unsafe { self.push_slot(ffi::Py_tp_clear, call_super_clear as *mut c_void) }
490 }
491 }
492
493 if self.is_sequence {
495 for slot in &mut self.slots {
496 if slot.slot == ffi::Py_mp_length {
497 slot.slot = ffi::Py_sq_length;
498 }
499 }
500 }
501
502 unsafe { self.push_slot(0, ptr::null_mut::<c_void>()) }
505
506 let class_name = py_class_qualified_name(module_name, name)?;
507 let mut spec = ffi::PyType_Spec {
508 name: class_name.as_ptr() as _,
509 basicsize: basicsize as c_int,
510 itemsize: 0,
511
512 flags: (ffi::Py_TPFLAGS_DEFAULT | self.class_flags)
513 .try_into()
514 .unwrap(),
515 slots: self.slots.as_mut_ptr(),
516 };
517
518 let type_object = unsafe {
521 ffi::PyType_FromSpec(&mut spec)
522 .assume_owned_or_err(py)?
523 .cast_into_unchecked::<PyType>()
524 };
525
526 #[cfg(not(Py_3_11))]
527 bpo_45315_workaround(py, class_name);
528
529 #[cfg(all(not(Py_LIMITED_API), not(Py_3_10)))]
530 for cleanup in std::mem::take(&mut self.cleanup) {
531 cleanup(&self, type_object.as_type_ptr());
532 }
533
534 Ok(PyClassTypeObject {
535 type_object: type_object.unbind(),
536 is_immutable_type: self.is_immutable_type,
537 getset_defs,
538 })
539 }
540}
541
542fn py_class_qualified_name(module_name: Option<&str>, class_name: &str) -> PyResult<CString> {
543 Ok(CString::new(format!(
544 "{}.{}",
545 module_name.unwrap_or("builtins"),
546 class_name
547 ))?)
548}
549
550#[inline]
552#[cfg(not(Py_3_11))]
553fn bpo_45315_workaround(py: Python<'_>, class_name: CString) {
554 #[cfg(Py_LIMITED_API)]
555 {
556 use crate::sync::PyOnceLock;
559 static IS_PYTHON_3_11: PyOnceLock<bool> = PyOnceLock::new();
560
561 if *IS_PYTHON_3_11.get_or_init(py, || py.version_info() >= (3, 11)) {
562 return;
564 }
565 }
566 #[cfg(not(Py_LIMITED_API))]
567 {
568 let _ = py;
570 }
571
572 std::mem::forget(class_name);
573}
574
575#[cfg(not(Py_3_10))]
577unsafe extern "C" fn no_constructor_defined(
578 subtype: *mut ffi::PyTypeObject,
579 _args: *mut ffi::PyObject,
580 _kwds: *mut ffi::PyObject,
581) -> *mut ffi::PyObject {
582 unsafe {
583 trampoline(|py| {
584 let tpobj = PyType::from_borrowed_type_ptr(py, subtype);
585 let module = tpobj
587 .module()
588 .map_or_else(|_| "<unknown>".into(), |s| s.to_string());
589 let qualname = tpobj.qualname();
590 let qualname = qualname.map_or_else(|_| "<unknown>".into(), |s| s.to_string());
591 Err(crate::exceptions::PyTypeError::new_err(format!(
592 "cannot create '{module}.{qualname}' instances"
593 )))
594 })
595 }
596}
597
598unsafe extern "C" fn call_super_clear(slf: *mut ffi::PyObject) -> c_int {
599 unsafe { _call_clear(slf, |_, _| Ok(()), call_super_clear) }
600}
601
602#[derive(Default)]
603struct GetSetDefBuilder {
604 doc: Option<&'static CStr>,
605 getter: Option<Getter>,
606 setter: Option<Setter>,
607 deleter: Option<Deleter>,
608}
609
610impl GetSetDefBuilder {
611 fn add_getter(&mut self, getter: &PyGetterDef) {
612 if self.doc.is_none() {
614 self.doc = getter.doc;
615 }
616 self.getter = Some(getter.meth)
618 }
619
620 fn add_setter(&mut self, setter: &PySetterDef) {
621 if self.doc.is_none() {
623 self.doc = setter.doc;
624 }
625 self.setter = Some(setter.meth)
627 }
628
629 fn add_deleter(&mut self, deleter: &PyDeleterDef) {
630 if self.doc.is_none() {
632 self.doc = deleter.doc;
633 }
634 self.deleter = Some(deleter.meth)
636 }
637
638 fn as_get_set_def(&self, name: &'static CStr) -> (ffi::PyGetSetDef, GetSetDefType) {
639 let getset_type = match (self.getter, self.setter, self.deleter) {
640 (None, None, None) => {
641 unreachable!("GetSetDefBuilder expected to always have either getter or setter")
642 }
643 (Some(getter), None, None) => GetSetDefType::Getter(getter),
644 (None, Some(setter), None) => GetSetDefType::Setter(setter),
645 (getter, setter, deleter) => {
646 GetSetDefType::Combination(Box::new(GetSetDeleteCombination {
647 getter,
648 setter,
649 deleter,
650 }))
651 }
652 };
653
654 let getset_def = getset_type.create_py_get_set_def(name, self.doc);
655 (getset_def, getset_type)
656 }
657}
658
659enum GetSetDefType {
661 Getter(Getter),
662 Setter(Setter),
663 Combination(Box<GetSetDeleteCombination>),
666}
667
668pub(crate) struct GetSetDeleteCombination {
669 getter: Option<Getter>,
670 setter: Option<Setter>,
671 deleter: Option<Deleter>,
672}
673
674impl GetSetDefType {
675 pub(crate) fn create_py_get_set_def(
679 &self,
680 name: &CStr,
681 doc: Option<&CStr>,
682 ) -> ffi::PyGetSetDef {
683 let (get, set, closure): (Option<ffi::getter>, Option<ffi::setter>, *mut c_void) =
684 match self {
685 &Self::Getter(closure) => {
686 unsafe extern "C" fn getter(
687 slf: *mut ffi::PyObject,
688 closure: *mut c_void,
689 ) -> *mut ffi::PyObject {
690 let slf = unsafe { NonNull::new_unchecked(slf) };
691 let getter: Getter = unsafe { std::mem::transmute(closure) };
693 unsafe { trampoline(|py| getter(py, slf)) }
694 }
695 (Some(getter), None, closure as Getter as _)
696 }
697 &Self::Setter(closure) => {
698 unsafe extern "C" fn setter(
699 slf: *mut ffi::PyObject,
700 value: *mut ffi::PyObject,
701 closure: *mut c_void,
702 ) -> c_int {
703 let slf = unsafe { NonNull::new_unchecked(slf) };
704 let setter: Setter = unsafe { std::mem::transmute(closure) };
706 unsafe {
707 trampoline(|py| {
708 if let Some(value) = NonNull::new(value) {
709 setter(py, slf, value)
710 } else {
711 Err(PyAttributeError::new_err("property has no deleter"))
712 }
713 })
714 }
715 }
716 (None, Some(setter), closure as Setter as _)
717 }
718 Self::Combination(closure) => {
719 unsafe extern "C" fn getset_getter(
720 slf: *mut ffi::PyObject,
721 closure: *mut c_void,
722 ) -> *mut ffi::PyObject {
723 let slf = unsafe { NonNull::new_unchecked(slf) };
724 let getset: &GetSetDeleteCombination = unsafe { &*closure.cast() };
725 unsafe { trampoline(|py| getset.getter.unwrap_unchecked()(py, slf)) }
727 }
728
729 unsafe extern "C" fn getset_setter(
730 slf: *mut ffi::PyObject,
731 value: *mut ffi::PyObject,
732 closure: *mut c_void,
733 ) -> c_int {
734 let slf = unsafe { NonNull::new_unchecked(slf) };
735 let getset: &GetSetDeleteCombination = unsafe { &*closure.cast() };
736 unsafe {
737 trampoline(|py| {
738 if let Some(value) = NonNull::new(value) {
739 getset.setter.ok_or_else(|| {
740 PyAttributeError::new_err("property has no setter")
741 })?(py, slf, value)
742 } else {
743 getset.deleter.ok_or_else(|| {
744 PyAttributeError::new_err("property has no deleter")
745 })?(py, slf)
746 }
747 })
748 }
749 }
750 (
751 closure.getter.is_some().then_some(getset_getter),
752 Some(getset_setter),
753 NonNull::<GetSetDeleteCombination>::from(closure.as_ref())
754 .cast()
755 .as_ptr(),
756 )
757 }
758 };
759 ffi::PyGetSetDef {
760 name: name.as_ptr(),
761 doc: doc.map_or(ptr::null(), CStr::as_ptr),
762 get,
763 set,
764 closure,
765 }
766 }
767}