use crate::{
exceptions::PyTypeError,
ffi,
impl_::pyclass::{
assign_sequence_item_from_mapping, get_sequence_item_from_mapping, tp_dealloc,
PyClassItemsIter,
},
types::PyType,
Py, PyClass, PyMethodDefType, PyResult, PyTypeInfo, Python,
};
use std::{
collections::HashMap,
convert::TryInto,
ffi::{CStr, CString},
os::raw::{c_char, c_int, c_ulong, c_void},
ptr,
};
pub(crate) fn create_type_object<T>(py: Python<'_>) -> PyResult<Py<PyType>>
where
T: PyClass,
{
unsafe {
PyTypeBuilder::default()
.type_doc(T::DOC)
.offsets(T::dict_offset(), T::weaklist_offset())
.slot(ffi::Py_tp_base, T::BaseType::type_object_raw(py))
.slot(ffi::Py_tp_dealloc, tp_dealloc::<T> as *mut c_void)
.set_is_basetype(T::IS_BASETYPE)
.set_is_mapping(T::IS_MAPPING)
.set_is_sequence(T::IS_SEQUENCE)
.class_items(T::items_iter())
.build(py, T::NAME, T::MODULE, std::mem::size_of::<T::Layout>())
}
}
type PyTypeBuilderCleanup = Box<dyn Fn(&PyTypeBuilder, *mut ffi::PyTypeObject)>;
#[derive(Default)]
struct PyTypeBuilder {
slots: Vec<ffi::PyType_Slot>,
method_defs: Vec<ffi::PyMethodDef>,
property_defs_map: HashMap<&'static str, ffi::PyGetSetDef>,
cleanup: Vec<PyTypeBuilderCleanup>,
is_mapping: bool,
is_sequence: bool,
has_new: bool,
has_dealloc: bool,
has_getitem: bool,
has_setitem: bool,
has_traverse: bool,
has_clear: bool,
has_dict: bool,
class_flags: c_ulong,
#[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
buffer_procs: ffi::PyBufferProcs,
}
impl PyTypeBuilder {
unsafe fn push_slot<T>(&mut self, slot: c_int, pfunc: *mut T) {
match slot {
ffi::Py_tp_new => self.has_new = true,
ffi::Py_tp_dealloc => self.has_dealloc = true,
ffi::Py_mp_subscript => self.has_getitem = true,
ffi::Py_mp_ass_subscript => self.has_setitem = true,
ffi::Py_tp_traverse => {
self.has_traverse = true;
self.class_flags |= ffi::Py_TPFLAGS_HAVE_GC;
}
ffi::Py_tp_clear => self.has_clear = true,
#[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
ffi::Py_bf_getbuffer => {
self.buffer_procs.bf_getbuffer = Some(std::mem::transmute(pfunc));
}
#[cfg(all(not(Py_3_9), not(Py_LIMITED_API)))]
ffi::Py_bf_releasebuffer => {
self.buffer_procs.bf_releasebuffer = Some(std::mem::transmute(pfunc));
}
_ => {}
}
self.slots.push(ffi::PyType_Slot {
slot,
pfunc: pfunc as _,
});
}
unsafe fn push_raw_vec_slot<T>(&mut self, slot: c_int, mut data: Vec<T>) {
if !data.is_empty() {
data.push(std::mem::zeroed());
self.push_slot(slot, Box::into_raw(data.into_boxed_slice()) as *mut c_void);
}
}
unsafe fn slot<T>(mut self, slot: c_int, pfunc: *mut T) -> Self {
self.push_slot(slot, pfunc);
self
}
fn pymethod_def(&mut self, def: &PyMethodDefType) {
const PY_GET_SET_DEF_INIT: ffi::PyGetSetDef = ffi::PyGetSetDef {
name: ptr::null_mut(),
get: None,
set: None,
doc: ptr::null(),
closure: ptr::null_mut(),
};
match def {
PyMethodDefType::Getter(getter) => {
getter.copy_to(
self.property_defs_map
.entry(getter.name)
.or_insert(PY_GET_SET_DEF_INIT),
);
}
PyMethodDefType::Setter(setter) => {
setter.copy_to(
self.property_defs_map
.entry(setter.name)
.or_insert(PY_GET_SET_DEF_INIT),
);
}
PyMethodDefType::Method(def)
| PyMethodDefType::Class(def)
| PyMethodDefType::Static(def) => {
let (def, destructor) = def.as_method_def().unwrap();
std::mem::forget(destructor);
self.method_defs.push(def);
}
PyMethodDefType::ClassAttribute(_) => {}
}
}
fn finalize_methods_and_properties(&mut self) {
let method_defs = std::mem::take(&mut self.method_defs);
unsafe { self.push_raw_vec_slot(ffi::Py_tp_methods, method_defs) };
let property_defs = std::mem::take(&mut self.property_defs_map);
#[allow(unused_mut)]
let mut property_defs: Vec<_> = property_defs.into_iter().map(|(_, value)| value).collect();
if self.has_dict {
#[cfg(not(any(PyPy, all(Py_LIMITED_API, not(Py_3_10)))))]
property_defs.push(ffi::PyGetSetDef {
name: "__dict__\0".as_ptr() as *mut c_char,
get: Some(ffi::PyObject_GenericGetDict),
set: Some(ffi::PyObject_GenericSetDict),
doc: ptr::null(),
closure: ptr::null_mut(),
});
}
unsafe { self.push_raw_vec_slot(ffi::Py_tp_getset, property_defs) };
if !self.is_mapping && self.has_getitem {
unsafe {
self.push_slot(
ffi::Py_sq_item,
get_sequence_item_from_mapping as *mut c_void,
)
}
}
if !self.is_mapping && self.has_setitem {
unsafe {
self.push_slot(
ffi::Py_sq_ass_item,
assign_sequence_item_from_mapping as *mut c_void,
)
}
}
}
fn set_is_basetype(mut self, is_basetype: bool) -> Self {
if is_basetype {
self.class_flags |= ffi::Py_TPFLAGS_BASETYPE;
}
self
}
fn set_is_mapping(mut self, is_mapping: bool) -> Self {
self.is_mapping = is_mapping;
self
}
fn set_is_sequence(mut self, is_sequence: bool) -> Self {
self.is_sequence = is_sequence;
self
}
unsafe fn class_items(mut self, iter: PyClassItemsIter) -> Self {
for items in iter {
for slot in items.slots {
self.push_slot(slot.slot, slot.pfunc);
}
for method in items.methods {
self.pymethod_def(method);
}
}
self
}
fn type_doc(mut self, type_doc: &'static str) -> Self {
if let Some(doc) = py_class_doc(type_doc) {
unsafe { self.push_slot(ffi::Py_tp_doc, doc) }
}
#[cfg(all(not(PyPy), not(Py_LIMITED_API), not(Py_3_10)))]
if type_doc != "\0" {
self.cleanup
.push(Box::new(move |_self, type_object| unsafe {
ffi::PyObject_Free((*type_object).tp_doc as _);
let data = ffi::PyObject_Malloc(type_doc.len());
data.copy_from(type_doc.as_ptr() as _, type_doc.len());
(*type_object).tp_doc = data as _;
}))
}
self
}
fn offsets(
mut self,
dict_offset: Option<ffi::Py_ssize_t>,
#[allow(unused_variables)] weaklist_offset: Option<ffi::Py_ssize_t>,
) -> Self {
self.has_dict = dict_offset.is_some();
#[cfg(Py_3_9)]
{
#[inline(always)]
fn offset_def(
name: &'static str,
offset: ffi::Py_ssize_t,
) -> ffi::structmember::PyMemberDef {
ffi::structmember::PyMemberDef {
name: name.as_ptr() as _,
type_code: ffi::structmember::T_PYSSIZET,
offset,
flags: ffi::structmember::READONLY,
doc: std::ptr::null_mut(),
}
}
let mut members = Vec::new();
if let Some(dict_offset) = dict_offset {
members.push(offset_def("__dictoffset__\0", dict_offset));
}
if let Some(weaklist_offset) = weaklist_offset {
members.push(offset_def("__weaklistoffset__\0", weaklist_offset));
}
unsafe { self.push_raw_vec_slot(ffi::Py_tp_members, members) };
}
#[cfg(all(not(Py_LIMITED_API), not(Py_3_9)))]
{
self.cleanup
.push(Box::new(move |builder, type_object| unsafe {
(*(*type_object).tp_as_buffer).bf_getbuffer = builder.buffer_procs.bf_getbuffer;
(*(*type_object).tp_as_buffer).bf_releasebuffer =
builder.buffer_procs.bf_releasebuffer;
if let Some(dict_offset) = dict_offset {
(*type_object).tp_dictoffset = dict_offset;
}
if let Some(weaklist_offset) = weaklist_offset {
(*type_object).tp_weaklistoffset = weaklist_offset;
}
}));
}
self
}
fn build(
mut self,
py: Python<'_>,
name: &'static str,
module_name: Option<&'static str>,
basicsize: usize,
) -> PyResult<Py<PyType>> {
#![allow(clippy::useless_conversion)]
self.finalize_methods_and_properties();
if !self.has_new {
unsafe { self.push_slot(ffi::Py_tp_new, no_constructor_defined as *mut c_void) }
}
assert!(
self.has_dealloc,
"PyTypeBuilder requires you to specify slot ffi::Py_tp_dealloc"
);
if self.has_clear && !self.has_traverse {
return Err(PyTypeError::new_err(format!(
"`#[pyclass]` {} implements __clear__ without __traverse__",
name
)));
}
if self.is_sequence {
for slot in &mut self.slots {
if slot.slot == ffi::Py_mp_length {
slot.slot = ffi::Py_sq_length;
}
}
}
unsafe { self.push_slot(0, ptr::null_mut::<c_void>()) }
let mut spec = ffi::PyType_Spec {
name: py_class_qualified_name(module_name, name)?,
basicsize: basicsize as c_int,
itemsize: 0,
flags: (ffi::Py_TPFLAGS_DEFAULT | self.class_flags)
.try_into()
.unwrap(),
slots: self.slots.as_mut_ptr(),
};
let type_object: Py<PyType> =
unsafe { Py::from_owned_ptr_or_err(py, ffi::PyType_FromSpec(&mut spec))? };
for cleanup in std::mem::take(&mut self.cleanup) {
cleanup(&self, type_object.as_ref(py).as_type_ptr());
}
Ok(type_object)
}
}
fn py_class_doc(class_doc: &str) -> Option<*mut c_char> {
match class_doc {
"\0" => None,
s => {
let cstring = if s.as_bytes().last() == Some(&0) {
CStr::from_bytes_with_nul(s.as_bytes())
.unwrap_or_else(|e| panic!("doc contains interior nul byte: {:?} in {}", e, s))
.to_owned()
} else {
CString::new(s)
.unwrap_or_else(|e| panic!("doc contains interior nul byte: {:?} in {}", e, s))
};
Some(cstring.into_raw())
}
}
}
fn py_class_qualified_name(module_name: Option<&str>, class_name: &str) -> PyResult<*mut c_char> {
Ok(CString::new(format!(
"{}.{}",
module_name.unwrap_or("builtins"),
class_name
))?
.into_raw())
}
unsafe extern "C" fn no_constructor_defined(
_subtype: *mut ffi::PyTypeObject,
_args: *mut ffi::PyObject,
_kwds: *mut ffi::PyObject,
) -> *mut ffi::PyObject {
crate::impl_::trampoline::trampoline_inner(|_| {
Err(crate::exceptions::PyTypeError::new_err(
"No constructor defined",
))
})
}