Skip to content

Commit 8f82c49

Browse files
committed
Proof of concept of using PEP384s PyType_Spec
1 parent 4a05f27 commit 8f82c49

File tree

3 files changed

+153
-115
lines changed

3 files changed

+153
-115
lines changed

src/class/basic.rs

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
//! [typeobj docs](https://docs.python.org/3/c-api/typeobj.html)
1010
1111
use crate::callback::{HashCallbackOutput, IntoPyCallbackOutput};
12+
use crate::pyclass::maybe_push_slot;
1213
use crate::{exceptions, ffi, FromPyObject, PyAny, PyCell, PyClass, PyErr, PyObject, PyResult};
13-
use std::os::raw::c_int;
14+
use std::os::raw::{c_int, c_void};
1415

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

148149
#[doc(hidden)]
149150
impl PyObjectMethods {
150-
pub(crate) fn update_typeobj(&self, type_object: &mut ffi::PyTypeObject) {
151-
type_object.tp_str = self.tp_str;
152-
type_object.tp_repr = self.tp_repr;
153-
type_object.tp_hash = self.tp_hash;
154-
type_object.tp_getattro = self.tp_getattro;
155-
type_object.tp_richcompare = self.tp_richcompare;
156-
type_object.tp_setattro = self.tp_setattro;
151+
pub(crate) fn update_slots(&self, slots: &mut Vec<ffi::PyType_Slot>) {
152+
maybe_push_slot(slots, ffi::Py_tp_str, self.tp_str.map(|v| v as *mut c_void));
153+
maybe_push_slot(
154+
slots,
155+
ffi::Py_tp_repr,
156+
self.tp_repr.map(|v| v as *mut c_void),
157+
);
158+
maybe_push_slot(
159+
slots,
160+
ffi::Py_tp_hash,
161+
self.tp_hash.map(|v| v as *mut c_void),
162+
);
163+
maybe_push_slot(
164+
slots,
165+
ffi::Py_tp_getattro,
166+
self.tp_getattro.map(|v| v as *mut c_void),
167+
);
168+
maybe_push_slot(
169+
slots,
170+
ffi::Py_tp_richcompare,
171+
self.tp_richcompare.map(|v| v as *mut c_void),
172+
);
173+
maybe_push_slot(
174+
slots,
175+
ffi::Py_tp_setattro,
176+
self.tp_setattro.map(|v| v as *mut c_void),
177+
);
178+
maybe_push_slot(
179+
slots,
180+
ffi::Py_nb_bool,
181+
self.nb_bool.map(|v| v as *mut c_void),
182+
);
157183
}
158184
// Set functions used by `#[pyproto]`.
159185
pub fn set_str<T>(&mut self)

src/pyclass.rs

Lines changed: 116 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::types::PyAny;
99
use crate::{class, ffi, PyCell, PyErr, PyNativeType, PyResult, PyTypeInfo, Python};
1010
use std::ffi::CString;
1111
use std::marker::PhantomData;
12-
use std::os::raw::c_void;
12+
use std::os::raw::{c_int, c_void};
1313
use std::{ptr, thread};
1414

1515
#[inline]
@@ -107,120 +107,134 @@ pub trait PyClass:
107107
type BaseNativeType: PyTypeInfo + PyNativeType;
108108
}
109109

110-
#[cfg(not(Py_LIMITED_API))]
111-
pub(crate) fn initialize_type_object<T>(
110+
pub(crate) fn maybe_push_slot(
111+
slots: &mut Vec<ffi::PyType_Slot>,
112+
slot: c_int,
113+
val: Option<*mut c_void>,
114+
) {
115+
if let Some(v) = val {
116+
slots.push(ffi::PyType_Slot {
117+
slot: slot,
118+
pfunc: v,
119+
});
120+
}
121+
}
122+
123+
pub(crate) fn create_type_object<T>(
112124
py: Python,
113125
module_name: Option<&str>,
114-
type_object: &mut ffi::PyTypeObject,
115-
) -> PyResult<()>
126+
) -> PyResult<*mut ffi::PyTypeObject>
116127
where
117128
T: PyClass,
118129
{
119-
type_object.tp_doc = match T::DESCRIPTION {
120-
// PyPy will segfault if passed only a nul terminator as `tp_doc`, ptr::null() is OK though.
121-
"\0" => ptr::null(),
122-
s if s.as_bytes().ends_with(b"\0") => s.as_ptr() as _,
123-
// If the description is not null-terminated, create CString and leak it
124-
s => CString::new(s)?.into_raw(),
125-
};
126-
127-
type_object.tp_base = T::BaseType::type_object_raw(py);
128-
129-
type_object.tp_name = match module_name {
130-
Some(module_name) => CString::new(format!("{}.{}", module_name, T::NAME))?.into_raw(),
131-
None => CString::new(T::NAME)?.into_raw(),
132-
};
133-
134-
// dealloc
135-
type_object.tp_dealloc = tp_dealloc::<T>();
136-
137-
// type size
138-
type_object.tp_basicsize = std::mem::size_of::<T::Layout>() as ffi::Py_ssize_t;
139-
140-
// __dict__ support
141-
if let Some(dict_offset) = PyCell::<T>::dict_offset() {
142-
type_object.tp_dictoffset = dict_offset as ffi::Py_ssize_t;
143-
}
144-
145-
// weakref support
146-
if let Some(weakref_offset) = PyCell::<T>::weakref_offset() {
147-
type_object.tp_weaklistoffset = weakref_offset as ffi::Py_ssize_t;
148-
}
149-
150-
// GC support
151-
if let Some(gc) = T::gc_methods() {
152-
unsafe { gc.as_ref() }.update_typeobj(type_object);
153-
}
154-
155-
// descriptor protocol
156-
if let Some(descr) = T::descr_methods() {
157-
unsafe { descr.as_ref() }.update_typeobj(type_object);
158-
}
159-
160-
// iterator methods
161-
if let Some(iter) = T::iter_methods() {
162-
unsafe { iter.as_ref() }.update_typeobj(type_object);
163-
}
164-
165-
// nb_bool is a part of PyObjectProtocol, but should be placed under tp_as_number
166-
let mut nb_bool = None;
167-
// basic methods
130+
let mut slots = vec![];
168131
if let Some(basic) = T::basic_methods() {
169-
unsafe { basic.as_ref() }.update_typeobj(type_object);
170-
nb_bool = unsafe { basic.as_ref() }.nb_bool;
132+
unsafe { basic.as_ref() }.update_slots(&mut slots);
171133
}
172134

173-
// number methods
174-
type_object.tp_as_number = T::number_methods()
175-
.map(|mut p| {
176-
unsafe { p.as_mut() }.nb_bool = nb_bool;
177-
p.as_ptr()
178-
})
179-
.unwrap_or_else(|| nb_bool.map_or_else(ptr::null_mut, ffi::PyNumberMethods::from_nb_bool));
180-
// mapping methods
181-
type_object.tp_as_mapping = T::mapping_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
182-
// sequence methods
183-
type_object.tp_as_sequence = T::sequence_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
184-
// async methods
185-
type_object.tp_as_async = T::async_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
186-
// buffer protocol
187-
type_object.tp_as_buffer = T::buffer_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
188-
189-
let (new, call, mut methods) = py_class_method_defs::<T>();
190-
191-
// normal methods
192-
if !methods.is_empty() {
193-
methods.push(ffi::PyMethodDef_INIT);
194-
type_object.tp_methods = Box::into_raw(methods.into_boxed_slice()) as _;
135+
if let Some(number) = T::number_methods() {
136+
maybe_push_slot(
137+
&mut slots,
138+
ffi::Py_nb_add,
139+
unsafe { number.as_ref() }.nb_add.map(|v| v as *mut c_void),
140+
);
195141
}
196142

197-
// __new__ method
198-
type_object.tp_new = new;
199-
// __call__ method
200-
type_object.tp_call = call;
201-
202-
// properties
203-
let mut props = py_class_properties::<T>();
143+
slots.push(ffi::PyType_Slot {
144+
slot: 0,
145+
pfunc: ptr::null_mut(),
146+
});
147+
let mut spec = ffi::PyType_Spec {
148+
name: match module_name {
149+
Some(module_name) => CString::new(format!("{}.{}", module_name, T::NAME))?.into_raw(),
150+
None => CString::new(T::NAME)?.into_raw(),
151+
},
152+
basicsize: std::mem::size_of::<T::Layout>() as c_int,
153+
itemsize: 0,
154+
flags: 0, // XXXX: FILL ME IN PROPERLY,
155+
slots: slots.as_mut_slice().as_mut_ptr(),
156+
};
204157

205-
if !T::Dict::IS_DUMMY {
206-
props.push(ffi::PyGetSetDef_DICT);
207-
}
208-
if !props.is_empty() {
209-
props.push(ffi::PyGetSetDef_INIT);
210-
type_object.tp_getset = Box::into_raw(props.into_boxed_slice()) as _;
158+
let type_object = unsafe { ffi::PyType_FromSpec(&mut spec) };
159+
if type_object.is_null() {
160+
PyErr::fetch(py).into()
161+
} else {
162+
Ok(type_object as *mut ffi::PyTypeObject)
211163
}
212164

213-
// set type flags
214-
py_class_flags::<T>(type_object);
215-
216-
// register type object
217-
unsafe {
218-
if ffi::PyType_Ready(type_object) == 0 {
219-
Ok(())
220-
} else {
221-
PyErr::fetch(py).into()
222-
}
223-
}
165+
// type_object.tp_doc = match T::DESCRIPTION {
166+
// // PyPy will segfault if passed only a nul terminator as `tp_doc`, ptr::null() is OK though.
167+
// "\0" => ptr::null(),
168+
// s if s.as_bytes().ends_with(b"\0") => s.as_ptr() as _,
169+
// // If the description is not null-terminated, create CString and leak it
170+
// s => CString::new(s)?.into_raw(),
171+
// };
172+
173+
// type_object.tp_base = T::BaseType::type_object_raw(py);
174+
175+
// // dealloc
176+
// type_object.tp_dealloc = tp_dealloc::<T>();
177+
178+
// // __dict__ support
179+
// if let Some(dict_offset) = PyCell::<T>::dict_offset() {
180+
// type_object.tp_dictoffset = dict_offset as ffi::Py_ssize_t;
181+
// }
182+
183+
// // weakref support
184+
// if let Some(weakref_offset) = PyCell::<T>::weakref_offset() {
185+
// type_object.tp_weaklistoffset = weakref_offset as ffi::Py_ssize_t;
186+
// }
187+
188+
// // GC support
189+
// if let Some(gc) = T::gc_methods() {
190+
// unsafe { gc.as_ref() }.update_typeobj(type_object);
191+
// }
192+
193+
// // descriptor protocol
194+
// if let Some(descr) = T::descr_methods() {
195+
// unsafe { descr.as_ref() }.update_typeobj(type_object);
196+
// }
197+
198+
// // iterator methods
199+
// if let Some(iter) = T::iter_methods() {
200+
// unsafe { iter.as_ref() }.update_typeobj(type_object);
201+
// }
202+
203+
// // mapping methods
204+
// type_object.tp_as_mapping = T::mapping_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
205+
// // sequence methods
206+
// type_object.tp_as_sequence = T::sequence_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
207+
// // async methods
208+
// type_object.tp_as_async = T::async_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
209+
// // buffer protocol
210+
// type_object.tp_as_buffer = T::buffer_methods().map_or_else(ptr::null_mut, |p| p.as_ptr());
211+
212+
// let (new, call, mut methods) = py_class_method_defs::<T>();
213+
214+
// // normal methods
215+
// if !methods.is_empty() {
216+
// methods.push(ffi::PyMethodDef_INIT);
217+
// type_object.tp_methods = Box::into_raw(methods.into_boxed_slice()) as _;
218+
// }
219+
220+
// // __new__ method
221+
// type_object.tp_new = new;
222+
// // __call__ method
223+
// type_object.tp_call = call;
224+
225+
// // properties
226+
// let mut props = py_class_properties::<T>();
227+
228+
// if !T::Dict::IS_DUMMY {
229+
// props.push(ffi::PyGetSetDef_DICT);
230+
// }
231+
// if !props.is_empty() {
232+
// props.push(ffi::PyGetSetDef_INIT);
233+
// type_object.tp_getset = Box::into_raw(props.into_boxed_slice()) as _;
234+
// }
235+
236+
// // set type flags
237+
// py_class_flags::<T>(type_object);
224238
}
225239

226240
fn py_class_flags<T: PyTypeInfo>(type_object: &mut ffi::PyTypeObject) {

src/type_object.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
use crate::conversion::IntoPyPointer;
55
use crate::once_cell::GILOnceCell;
6-
use crate::pyclass::{initialize_type_object, py_class_attributes, PyClass};
6+
use crate::pyclass::{create_type_object, py_class_attributes, PyClass};
77
use crate::pyclass_init::PyObjectInit;
88
use crate::types::{PyAny, PyType};
99
use crate::{ffi, AsPyPointer, PyErr, PyNativeType, PyObject, PyResult, Python};
@@ -157,12 +157,10 @@ impl LazyStaticType {
157157

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

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

0 commit comments

Comments
 (0)