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,
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 #[cfg(not(any(all(Py_3_10, not(PyPy)), all(Py_3_11, PyPy))))]
442 {
443 unsafe { self.push_slot(ffi::Py_tp_new, no_constructor_defined as *mut c_void) }
445 }
446 #[cfg(any(all(Py_3_10, not(PyPy)), all(Py_3_11, PyPy)))]
447 {
448 self.class_flags |= ffi::Py_TPFLAGS_DISALLOW_INSTANTIATION;
449 }
450 }
451
452 let base_is_gc = unsafe { ffi::PyType_IS_GC(self.tp_base) == 1 };
453 let tp_dealloc = if self.has_traverse || base_is_gc {
454 self.tp_dealloc_with_gc
455 } else {
456 self.tp_dealloc
457 };
458 unsafe { self.push_slot(ffi::Py_tp_dealloc, tp_dealloc as *mut c_void) }
459
460 if self.has_clear && !self.has_traverse {
461 return Err(PyTypeError::new_err(format!(
462 "`#[pyclass]` {name} implements __clear__ without __traverse__"
463 )));
464 }
465
466 if ((self.class_flags & ffi::Py_TPFLAGS_HAVE_GC) != 0) && base_is_gc {
473 assert!(self.has_traverse); if !self.has_clear {
477 unsafe { self.push_slot(ffi::Py_tp_clear, call_super_clear as *mut c_void) }
479 }
480 }
481
482 if self.is_sequence {
484 for slot in &mut self.slots {
485 if slot.slot == ffi::Py_mp_length {
486 slot.slot = ffi::Py_sq_length;
487 }
488 }
489 }
490
491 unsafe { self.push_slot(0, ptr::null_mut::<c_void>()) }
494
495 let class_name = py_class_qualified_name(module_name, name)?;
496 let mut spec = ffi::PyType_Spec {
497 name: class_name.as_ptr() as _,
498 basicsize: basicsize as c_int,
499 itemsize: 0,
500
501 flags: (ffi::Py_TPFLAGS_DEFAULT | self.class_flags)
502 .try_into()
503 .unwrap(),
504 slots: self.slots.as_mut_ptr(),
505 };
506
507 let type_object: Py<PyType> =
509 unsafe { Py::from_owned_ptr_or_err(py, ffi::PyType_FromSpec(&mut spec))? };
510
511 #[cfg(not(Py_3_11))]
512 bpo_45315_workaround(py, class_name);
513
514 for cleanup in std::mem::take(&mut self.cleanup) {
515 cleanup(&self, type_object.bind(py).as_type_ptr());
516 }
517
518 Ok(PyClassTypeObject {
519 type_object,
520 is_immutable_type: self.is_immutable_type,
521 getset_destructors,
522 })
523 }
524}
525
526fn py_class_qualified_name(module_name: Option<&str>, class_name: &str) -> PyResult<CString> {
527 Ok(CString::new(format!(
528 "{}.{}",
529 module_name.unwrap_or("builtins"),
530 class_name
531 ))?)
532}
533
534#[inline]
536#[cfg(not(Py_3_11))]
537fn bpo_45315_workaround(py: Python<'_>, class_name: CString) {
538 #[cfg(Py_LIMITED_API)]
539 {
540 use crate::sync::PyOnceLock;
543 static IS_PYTHON_3_11: PyOnceLock<bool> = PyOnceLock::new();
544
545 if *IS_PYTHON_3_11.get_or_init(py, || py.version_info() >= (3, 11)) {
546 return;
548 }
549 }
550 #[cfg(not(Py_LIMITED_API))]
551 {
552 let _ = py;
554 }
555
556 std::mem::forget(class_name);
557}
558
559#[cfg(not(any(all(Py_3_10, not(PyPy)), all(Py_3_11, PyPy))))]
561unsafe extern "C" fn no_constructor_defined(
562 subtype: *mut ffi::PyTypeObject,
563 _args: *mut ffi::PyObject,
564 _kwds: *mut ffi::PyObject,
565) -> *mut ffi::PyObject {
566 unsafe {
567 trampoline(|py| {
568 let tpobj = PyType::from_borrowed_type_ptr(py, subtype);
569 let module = tpobj
571 .module()
572 .map_or_else(|_| "<unknown>".into(), |s| s.to_string());
573 let qualname = tpobj.qualname();
574 let qualname = qualname.map_or_else(|_| "<unknown>".into(), |s| s.to_string());
575 Err(crate::exceptions::PyTypeError::new_err(format!(
576 "cannot create '{module}.{qualname}' instances"
577 )))
578 })
579 }
580}
581
582unsafe extern "C" fn call_super_clear(slf: *mut ffi::PyObject) -> c_int {
583 unsafe { _call_clear(slf, |_, _| Ok(()), call_super_clear) }
584}
585
586#[derive(Default)]
587struct GetSetDefBuilder {
588 doc: Option<&'static CStr>,
589 getter: Option<Getter>,
590 setter: Option<Setter>,
591}
592
593impl GetSetDefBuilder {
594 fn add_getter(&mut self, getter: &PyGetterDef) {
595 if self.doc.is_none() {
597 self.doc = Some(getter.doc);
598 }
599 self.getter = Some(getter.meth)
601 }
602
603 fn add_setter(&mut self, setter: &PySetterDef) {
604 if self.doc.is_none() {
606 self.doc = Some(setter.doc);
607 }
608 self.setter = Some(setter.meth)
610 }
611
612 fn as_get_set_def(&self, name: &'static CStr) -> (ffi::PyGetSetDef, GetSetDefDestructor) {
613 let getset_type = match (self.getter, self.setter) {
614 (Some(getter), None) => GetSetDefType::Getter(getter),
615 (None, Some(setter)) => GetSetDefType::Setter(setter),
616 (Some(getter), Some(setter)) => {
617 GetSetDefType::GetterAndSetter(Box::new(GetterAndSetter { getter, setter }))
618 }
619 (None, None) => {
620 unreachable!("GetSetDefBuilder expected to always have either getter or setter")
621 }
622 };
623
624 let getset_def = getset_type.create_py_get_set_def(name, self.doc);
625 let destructor = GetSetDefDestructor {
626 closure: getset_type,
627 };
628 (getset_def, destructor)
629 }
630}
631
632#[allow(dead_code)] struct GetSetDefDestructor {
634 closure: GetSetDefType,
635}
636
637enum GetSetDefType {
639 Getter(Getter),
640 Setter(Setter),
641 GetterAndSetter(Box<GetterAndSetter>),
644}
645
646pub(crate) struct GetterAndSetter {
647 getter: Getter,
648 setter: Setter,
649}
650
651impl GetSetDefType {
652 pub(crate) fn create_py_get_set_def(
656 &self,
657 name: &CStr,
658 doc: Option<&CStr>,
659 ) -> ffi::PyGetSetDef {
660 let (get, set, closure): (Option<ffi::getter>, Option<ffi::setter>, *mut c_void) =
661 match self {
662 &Self::Getter(closure) => {
663 unsafe extern "C" fn getter(
664 slf: *mut ffi::PyObject,
665 closure: *mut c_void,
666 ) -> *mut ffi::PyObject {
667 let getter: Getter = unsafe { std::mem::transmute(closure) };
669 unsafe { trampoline(|py| getter(py, slf)) }
670 }
671 (Some(getter), None, closure as Getter as _)
672 }
673 &Self::Setter(closure) => {
674 unsafe extern "C" fn setter(
675 slf: *mut ffi::PyObject,
676 value: *mut ffi::PyObject,
677 closure: *mut c_void,
678 ) -> c_int {
679 let setter: Setter = unsafe { std::mem::transmute(closure) };
681 unsafe { trampoline(|py| setter(py, slf, value)) }
682 }
683 (None, Some(setter), closure as Setter as _)
684 }
685 Self::GetterAndSetter(closure) => {
686 unsafe extern "C" fn getset_getter(
687 slf: *mut ffi::PyObject,
688 closure: *mut c_void,
689 ) -> *mut ffi::PyObject {
690 let getset: &GetterAndSetter = unsafe { &*closure.cast() };
691 unsafe { trampoline(|py| (getset.getter)(py, slf)) }
692 }
693
694 unsafe extern "C" fn getset_setter(
695 slf: *mut ffi::PyObject,
696 value: *mut ffi::PyObject,
697 closure: *mut c_void,
698 ) -> c_int {
699 let getset: &GetterAndSetter = unsafe { &*closure.cast() };
700 unsafe { trampoline(|py| (getset.setter)(py, slf, value)) }
701 }
702 (
703 Some(getset_getter),
704 Some(getset_setter),
705 ptr_from_ref::<GetterAndSetter>(closure) as *mut _,
706 )
707 }
708 };
709 ffi::PyGetSetDef {
710 name: name.as_ptr(),
711 doc: doc.map_or(ptr::null(), CStr::as_ptr),
712 get,
713 set,
714 closure,
715 }
716 }
717}