Skip to content

Commit

Permalink
Proof of concept of using PEP384s PyType_Spec
Browse files Browse the repository at this point in the history
  • Loading branch information
alex committed Sep 1, 2020
1 parent 4a05f27 commit b291801
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 115 deletions.
42 changes: 34 additions & 8 deletions src/class/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
//! [typeobj docs](https://docs.python.org/3/c-api/typeobj.html)

use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput};
use crate::pyclass::maybe_push_slot;
use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyErr, PyObject, PyResult};
use std::os::raw::c_int;
use std::os::raw::{c_int, c_void};

/// Operators for the __richcmp__ method
#[derive(Debug)]
Expand Down Expand Up @@ -147,13 +148,38 @@ pub struct PyObjectMethods {

#[doc(hidden)]
impl PyObjectMethods {
pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) {
type_object.tp_str = self.tp_str;
type_object.tp_repr = self.tp_repr;
type_object.tp_hash = self.tp_hash;
type_object.tp_getattro = self.tp_getattro;
type_object.tp_richcompare = self.tp_richcompare;
type_object.tp_setattro = self.tp_setattro;
pub(crate) fn update_slots(&self, slots: &mut Vec<ffi::PyType_Slot>) {
maybe_push_slot(slots, ffi::Py_tp_str, self.tp_str.map(|v| v as *mut c_void));
maybe_push_slot(
slots,
ffi::Py_tp_repr,
self.tp_repr.map(|v| v as *mut c_void),
);
maybe_push_slot(
slots,
ffi::Py_tp_hash,
self.tp_hash.map(|v| v as *mut c_void),
);
maybe_push_slot(
slots,
ffi::Py_tp_getattro,
self.tp_getattro.map(|v| v as *mut c_void),
);
maybe_push_slot(
slots,
ffi::Py_tp_richcompare,
self.tp_richcompare.map(|v| v as *mut c_void),
);
maybe_push_slot(
slots,
ffi::Py_tp_setattro,
self.tp_setattro.map(|v| v as *mut c_void),
);
maybe_push_slot(
slots,
ffi::Py_nb_bool,
self.nb_bool.map(|v| v as *mut c_void),
);
}
// Set functions used by `#[pyproto]`.
pub fn set_str<T>(&mut self)
Expand Down
215 changes: 113 additions & 102 deletions src/pyclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::types::PyAny;
use crate::{class, ffi, PyCell, PyErr, PyNativeType, PyResult, PyTypeInfo, Python};
use std::ffi::CString;
use std::marker::PhantomData;
use std::os::raw::c_void;
use std::os::raw::{c_int, c_void};
use std::{ptr, thread};

#[inline]
Expand Down Expand Up @@ -107,120 +107,131 @@ pub trait PyClass:
type BaseNativeType: PyTypeInfo + PyNativeType;
}

#[cfg(not(Py_LIMITED_API))]
pub(crate) fn initialize_type_object<T>(
pub(crate) fn maybe_push_slot(
slots: &mut Vec<ffi::PyType_Slot>,
slot: c_int,
val: Option<*mut c_void>,
) {
if let Some(v) = val {
slots.push(ffi::PyType_Slot { slot, pfunc: v });
}
}

pub(crate) fn create_type_object<T>(
py: Python,
module_name: Option<&str>,
type_object: &mut ffi::PyTypeObject,
) -> PyResult<()>
) -> PyResult<*mut ffi::PyTypeObject>
where
T: PyClass,
{
type_object.tp_doc = match T::DESCRIPTION {
// PyPy will segfault if passed only a nul terminator as `tp_doc`, ptr::null() is OK though.
"\0" => ptr::null(),
s if s.as_bytes().ends_with(b"\0") => s.as_ptr() as _,
// If the description is not null-terminated, create CString and leak it
s => CString::new(s)?.into_raw(),
};

type_object.tp_base = T::BaseType::type_object_raw(py);

type_object.tp_name = match module_name {
Some(module_name) => CString::new(format!("{}.{}", module_name, T::NAME))?.into_raw(),
None => CString::new(T::NAME)?.into_raw(),
};

// dealloc
type_object.tp_dealloc = tp_dealloc::<T>();

// type size
type_object.tp_basicsize = std::mem::size_of::<T::Layout>() as ffi::Py_ssize_t;

// __dict__ support
if let Some(dict_offset) = PyCell::<T>::dict_offset() {
type_object.tp_dictoffset = dict_offset as ffi::Py_ssize_t;
}

// weakref support
if let Some(weakref_offset) = PyCell::<T>::weakref_offset() {
type_object.tp_weaklistoffset = weakref_offset as ffi::Py_ssize_t;
}

// GC support
if let Some(gc) = T::gc_methods() {
unsafe { gc.as_ref() }.update_typeobj(type_object);
}

// descriptor protocol
if let Some(descr) = T::descr_methods() {
unsafe { descr.as_ref() }.update_typeobj(type_object);
}

// iterator methods
if let Some(iter) = T::iter_methods() {
unsafe { iter.as_ref() }.update_typeobj(type_object);
}

// nb_bool is a part of PyObjectProtocol, but should be placed under tp_as_number
let mut nb_bool = None;
// basic methods
let mut slots = vec![];
if let Some(basic) = T::basic_methods() {
unsafe { basic.as_ref() }.update_typeobj(type_object);
nb_bool = unsafe { basic.as_ref() }.nb_bool;
unsafe { basic.as_ref() }.update_slots(&mut slots);
}

// number methods
type_object.tp_as_number = T::number_methods()
.map(|mut p| {
unsafe { p.as_mut() }.nb_bool = nb_bool;
p.as_ptr()
})
.unwrap_or_else(|| nb_bool.map_or_else(ptr::null_mut, ffi::PyNumberMethods::from_nb_bool));
// mapping methods
type_object.tp_as_mapping = T::mapping_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
// sequence methods
type_object.tp_as_sequence = T::sequence_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
// async methods
type_object.tp_as_async = T::async_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
// buffer protocol
type_object.tp_as_buffer = T::buffer_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());

let (new, call, mut methods) = py_class_method_defs::<T>();

// normal methods
if !methods.is_empty() {
methods.push(ffi::PyMethodDef_INIT);
type_object.tp_methods = Box::into_raw(methods.into_boxed_slice()) as _;
if let Some(number) = T::number_methods() {
maybe_push_slot(
&mut slots,
ffi::Py_nb_add,
unsafe { number.as_ref() }.nb_add.map(|v| v as *mut c_void),
);
}

// __new__ method
type_object.tp_new = new;
// __call__ method
type_object.tp_call = call;

// properties
let mut props = py_class_properties::<T>();
slots.push(ffi::PyType_Slot {
slot: 0,
pfunc: ptr::null_mut(),
});
let mut spec = ffi::PyType_Spec {
name: match module_name {
Some(module_name) => CString::new(format!("{}.{}", module_name, T::NAME))?.into_raw(),
None => CString::new(T::NAME)?.into_raw(),
},
basicsize: std::mem::size_of::<T::Layout>() as c_int,
itemsize: 0,
flags: 0, // XXXX: FILL ME IN PROPERLY,
slots: slots.as_mut_slice().as_mut_ptr(),
};

if !T::Dict::IS_DUMMY {
props.push(ffi::PyGetSetDef_DICT);
}
if !props.is_empty() {
props.push(ffi::PyGetSetDef_INIT);
type_object.tp_getset = Box::into_raw(props.into_boxed_slice()) as _;
let type_object = unsafe { ffi::PyType_FromSpec(&mut spec) };
if type_object.is_null() {
PyErr::fetch(py).into()
} else {
Ok(type_object as *mut ffi::PyTypeObject)
}

// set type flags
py_class_flags::<T>(type_object);

// register type object
unsafe {
if ffi::PyType_Ready(type_object) == 0 {
Ok(())
} else {
PyErr::fetch(py).into()
}
}
// type_object.tp_doc = match T::DESCRIPTION {
// // PyPy will segfault if passed only a nul terminator as `tp_doc`, ptr::null() is OK though.
// "\0" => ptr::null(),
// s if s.as_bytes().ends_with(b"\0") => s.as_ptr() as _,
// // If the description is not null-terminated, create CString and leak it
// s => CString::new(s)?.into_raw(),
// };

// type_object.tp_base = T::BaseType::type_object_raw(py);

// // dealloc
// type_object.tp_dealloc = tp_dealloc::<T>();

// // __dict__ support
// if let Some(dict_offset) = PyCell::<T>::dict_offset() {
// type_object.tp_dictoffset = dict_offset as ffi::Py_ssize_t;
// }

// // weakref support
// if let Some(weakref_offset) = PyCell::<T>::weakref_offset() {
// type_object.tp_weaklistoffset = weakref_offset as ffi::Py_ssize_t;
// }

// // GC support
// if let Some(gc) = T::gc_methods() {
// unsafe { gc.as_ref() }.update_typeobj(type_object);
// }

// // descriptor protocol
// if let Some(descr) = T::descr_methods() {
// unsafe { descr.as_ref() }.update_typeobj(type_object);
// }

// // iterator methods
// if let Some(iter) = T::iter_methods() {
// unsafe { iter.as_ref() }.update_typeobj(type_object);
// }

// // mapping methods
// type_object.tp_as_mapping = T::mapping_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
// // sequence methods
// type_object.tp_as_sequence = T::sequence_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
// // async methods
// type_object.tp_as_async = T::async_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
// // buffer protocol
// type_object.tp_as_buffer = T::buffer_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());

// let (new, call, mut methods) = py_class_method_defs::<T>();

// // normal methods
// if !methods.is_empty() {
// methods.push(ffi::PyMethodDef_INIT);
// type_object.tp_methods = Box::into_raw(methods.into_boxed_slice()) as _;
// }

// // __new__ method
// type_object.tp_new = new;
// // __call__ method
// type_object.tp_call = call;

// // properties
// let mut props = py_class_properties::<T>();

// if !T::Dict::IS_DUMMY {
// props.push(ffi::PyGetSetDef_DICT);
// }
// if !props.is_empty() {
// props.push(ffi::PyGetSetDef_INIT);
// type_object.tp_getset = Box::into_raw(props.into_boxed_slice()) as _;
// }

// // set type flags
// py_class_flags::<T>(type_object);
}

fn py_class_flags<T: PyTypeInfo>(type_object: &mut ffi::PyTypeObject) {
Expand Down
8 changes: 3 additions & 5 deletions src/type_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use crate::conversion::IntoPyPointer;
use crate::once_cell::GILOnceCell;
use crate::pyclass::{initialize_type_object, py_class_attributes, PyClass};
use crate::pyclass::{create_type_object, py_class_attributes, PyClass};
use crate::pyclass_init::PyObjectInit;
use crate::types::{PyAny, PyType};
use crate::{ffi, AsPyPointer, PyErr, PyNativeType, PyObject, PyResult, Python};
Expand Down Expand Up @@ -157,12 +157,10 @@ impl LazyStaticType {

pub fn get_or_init<T: PyClass>(&self, py: Python) -> *mut ffi::PyTypeObject {
let type_object = *self.value.get_or_init(py, || {
let mut type_object = Box::new(ffi::PyTypeObject_INIT);
initialize_type_object::<T>(py, T::MODULE, type_object.as_mut()).unwrap_or_else(|e| {
create_type_object::<T>(py, T::MODULE).unwrap_or_else(|e| {
e.print(py);
panic!("An error occurred while initializing class {}", T::NAME)
});
Box::into_raw(type_object)
})
});

// We might want to fill the `tp_dict` with python instances of `T`
Expand Down

0 comments on commit b291801

Please sign in to comment.