Skip to content

Commit 039e64e

Browse files
Add basic type creation support
1 parent 47f24e5 commit 039e64e

4 files changed

Lines changed: 207 additions & 59 deletions

File tree

crates/capi/src/abstract_.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ mod sequence;
1616

1717
const PY_VECTORCALL_ARGUMENTS_OFFSET: usize = 1usize << (usize::BITS as usize - 1);
1818

19-
fn tuple_to_args(tuple: &Py<PyTuple>) -> PosArgs {
19+
pub(crate) fn tuple_to_args(tuple: &Py<PyTuple>) -> PosArgs {
2020
tuple.iter().cloned().collect::<Vec<_>>().into()
2121
}
2222

23-
fn dict_to_kwargs(vm: &VirtualMachine, dict: &Py<PyDict>) -> PyResult<KwArgs> {
23+
pub(crate) fn dict_to_kwargs(vm: &VirtualMachine, dict: &Py<PyDict>) -> PyResult<KwArgs> {
2424
dict.items_vec()
2525
.into_iter()
2626
.map(|(key, value)| {

crates/capi/src/moduleobject.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ pub unsafe extern "C" fn PyModule_FromSlotsAndSpec(
4242
| PySlotKind::TypeSlots { .. }
4343
| PySlotKind::TypeName { .. }
4444
| PySlotKind::TypeMetaclass { .. }
45+
| PySlotKind::TypeFlags { .. }
46+
| PySlotKind::TypeExtraBasicSize(_)
4547
| PySlotKind::TypeModule { .. }) => {
4648
return Err(vm.new_system_error(format!(
4749
"Got type slot while module slots are expected: {kind:?}"

crates/capi/src/object/pytype.rs

Lines changed: 188 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
1+
use crate::abstract_::{dict_to_kwargs, tuple_to_args};
2+
use crate::methodobject::{PyMethodDef, build_method_def};
13
use crate::object::define_py_check;
24
use crate::pystate::with_vm;
3-
use core::ffi::{c_int, c_ulong};
4-
use rustpython_vm::builtins::{PyStr, PyType};
5+
use crate::slots::{PySlot, PySlotKind};
6+
use core::ffi::{CStr, c_char, c_int, c_ulong, c_void};
7+
use rustpython_vm::builtins::{PyDict, PyStr, PyTuple, PyType};
8+
use rustpython_vm::function::{FuncArgs, PyMethodFlags};
9+
use rustpython_vm::types::{PyTypeFlags, PyTypeSlots, SlotAccessor};
510
use rustpython_vm::{AsObject, Py, PyObject};
6-
use std::ffi::c_void;
711

812
pub type PyTypeObject = Py<PyType>;
913

10-
pub struct PyTypeSlot {
14+
define_py_check!(fn PyType_Check, types.type_type);
15+
define_py_check!(exact fn PyType_CheckExact, types.type_type);
16+
17+
#[repr(C)]
18+
pub struct PyType_Slot {
1119
pub slot: c_int,
1220
pub pfunc: *mut c_void,
1321
}
1422

15-
define_py_check!(fn PyType_Check, types.type_type);
16-
define_py_check!(exact fn PyType_CheckExact, types.type_type);
17-
1823
#[unsafe(no_mangle)]
1924
pub unsafe extern "C" fn Py_TYPE(op: *mut PyObject) -> *const PyTypeObject {
2025
unsafe { (*op).class() }
@@ -77,18 +82,14 @@ pub unsafe extern "C" fn PyType_GetFullyQualifiedName(ptr: *const PyTypeObject)
7782
}
7883

7984
#[unsafe(no_mangle)]
80-
#[cfg(false)]
81-
pub extern "C" fn PyType_GetSlot(ty: *const PyTypeObject, slot: c_int) -> *mut c_void {
85+
pub unsafe extern "C" fn PyType_GetSlot(ty: *const PyTypeObject, slot: c_int) -> *mut c_void {
8286
with_vm(|_vm| {
8387
let ty = unsafe { &*ty };
8488
let slot: u8 = slot
8589
.try_into()
8690
.expect("slot number out of range for SlotAccessor");
87-
let slot_accessor: SlotAccessor = slot
88-
.try_into()
89-
.expect("invalid slot number for SlotAccessor");
9091

91-
match slot_accessor {
92+
match slot.try_into().unwrap() {
9293
SlotAccessor::TpNew => {
9394
extern "C" fn newfunc_wrapper(
9495
subtype: *mut PyTypeObject,
@@ -97,61 +98,176 @@ pub extern "C" fn PyType_GetSlot(ty: *const PyTypeObject, slot: c_int) -> *mut c
9798
) -> *mut PyObject {
9899
with_vm(|vm| {
99100
let subtype = unsafe { &*subtype };
100-
let mut func_args = FuncArgs::default();
101101

102-
if let Some(args_obj) = unsafe { args.as_ref() } {
103-
let tuple = args_obj.try_downcast_ref::<PyTuple>(vm)?;
104-
func_args
105-
.args
106-
.extend(tuple.iter().map(|arg| arg.to_owned()));
107-
}
102+
let args = if let Some(args_obj) = unsafe { args.as_ref() } {
103+
tuple_to_args(args_obj.try_downcast_ref::<PyTuple>(vm)?)
104+
} else {
105+
().into()
106+
};
108107

109-
if let Some(kwargs_obj) = unsafe { kwargs.as_ref() } {
110-
let kwargs = kwargs_obj.try_downcast_ref::<PyDict>(vm)?;
111-
for (key, value) in kwargs.items_vec() {
112-
let key = key.try_downcast::<PyStr>(vm)?;
113-
func_args
114-
.kwargs
115-
.insert(key.to_string_lossy().into_owned(), value);
116-
}
117-
}
108+
let kwargs = unsafe { kwargs.as_ref() }
109+
.map(|obj| dict_to_kwargs(vm, obj.try_downcast_ref::<PyDict>(vm)?))
110+
.transpose()?
111+
.unwrap_or_default();
118112

119113
subtype
120114
.slots
121115
.new
122116
.load()
123117
.expect("tp_new slot function pointer is null")(
124118
subtype.to_owned(),
125-
func_args,
119+
FuncArgs::new(args, kwargs),
126120
vm,
127121
)
128122
})
129123
}
130124

131-
if let Some(vtable) = ty.get_type_data::<TypeVTable>() {
132-
vtable.new_func.map(|newfunc| newfunc as *mut c_void)
133-
} else {
134-
ty.slots.new.load().map(|_| newfunc_wrapper as *mut c_void)
135-
}
125+
ty.slots.new.load().map(|_| newfunc_wrapper as *mut c_void)
136126
}
137127
_ => {
138128
todo!("Slot {slot_accessor:?} for {ty:?} is not yet implemented in PyType_GetSlot")
139129
}
140130
}
141-
.unwrap_or_default()
131+
.unwrap()
132+
})
133+
}
134+
135+
#[unsafe(no_mangle)]
136+
pub extern "C" fn PyType_FromSlots(slots: *const PySlot) -> *mut PyObject {
137+
with_vm(|vm| {
138+
let mut name = None;
139+
let mut base = None;
140+
let mut methods = Vec::new();
141+
let mut type_slots: PyTypeSlots = Default::default();
142+
let attrs = Default::default();
143+
144+
for slot in PySlot::iter(slots) {
145+
match (slot, vm).try_into()? {
146+
PySlotKind::TypeName { value, .. } => {
147+
name = unsafe { Some(CStr::from_ptr(value).to_str().unwrap()) }
148+
}
149+
PySlotKind::TypeFlags { value } => {
150+
type_slots.flags = PyTypeFlags::from_bits(value).ok_or_else(|| {
151+
vm.new_value_error(format!(
152+
"Invalid type flags: {value:#x} for PyType_FromSlots"
153+
))
154+
})?;
155+
}
156+
PySlotKind::TypeSlots { mut value, .. } => {
157+
while let slot = unsafe { &*value }
158+
&& slot.slot != 0
159+
{
160+
let slot_id: u8 = slot.slot.try_into().unwrap();
161+
match slot_id.try_into().unwrap() {
162+
SlotAccessor::TpDoc => {
163+
let doc = unsafe {
164+
CStr::from_ptr(slot.pfunc.cast::<c_char>())
165+
.to_str()
166+
.expect("tp_doc must be a valid UTF-8 string")
167+
};
168+
type_slots.doc = Some(doc);
169+
}
170+
SlotAccessor::TpNew => {
171+
type_slots.new.store(Some(|ty, _args, vm| {
172+
Err(vm.new_not_implemented_error(format!("tp_new is not yet implemented in PyType_FromSlots for {ty:?}")))
173+
}));
174+
}
175+
SlotAccessor::TpBase => {
176+
base = unsafe { Some(&*slot.pfunc.cast::<PyTypeObject>()) }
177+
}
178+
SlotAccessor::TpDealloc => {
179+
type_slots.del.store(Some(|_ty, _vm| {
180+
// TODO
181+
Ok(())
182+
}));
183+
}
184+
SlotAccessor::TpMethods => {
185+
let mut def_ptr = slot.pfunc.cast::<PyMethodDef>();
186+
while let def = unsafe { &*def_ptr }
187+
&& !def.ml_name.is_null()
188+
{
189+
let name = unsafe {
190+
CStr::from_ptr(def.ml_name)
191+
.to_str()
192+
.expect("method name must be valid UTF-8")
193+
};
194+
let is_static =
195+
PyMethodFlags::from_bits_retain(def.ml_flags as _)
196+
.contains(PyMethodFlags::STATIC);
197+
let method = build_method_def(vm, def, !is_static)?;
198+
methods.push((name, method));
199+
def_ptr = unsafe { def_ptr.add(1) }
200+
}
201+
}
202+
slot => {
203+
return Err(vm.new_not_implemented_error(format!(
204+
"PyType_FromSlots with PyType_Slot {slot:?} not implemented yet"
205+
)));
206+
}
207+
}
208+
value = unsafe { value.add(1) };
209+
}
210+
}
211+
PySlotKind::TypeExtraBasicSize(size) => {
212+
if size != 0 {
213+
return Err(vm.new_not_implemented_error(
214+
"PyType_FromSlots with non-zero Py_tp_extra_basicsize is not supported",
215+
));
216+
}
217+
}
218+
kind => {
219+
return Err(vm.new_not_implemented_error(format!(
220+
"PyType_FromSlots with slot {kind:?} not implemented yet"
221+
)));
222+
}
223+
}
224+
}
225+
226+
let bases = if let Some(base) = base {
227+
vec![base.to_owned()]
228+
} else {
229+
vec![vm.ctx.types.object_type.to_owned()]
230+
};
231+
232+
let metaclass = vm.ctx.types.type_type.to_owned();
233+
let class = PyType::new_heap(name.unwrap(), bases, attrs, type_slots, metaclass, &vm.ctx)
234+
.map_err(|msg| {
235+
vm.new_system_error(format!("Failed to create type from slots: {msg}"))
236+
})?;
237+
238+
for (name, method) in methods {
239+
let class_static = unsafe { &*((&*class) as *const _) };
240+
class.attributes.write().insert(
241+
vm.ctx.intern_str(name),
242+
method.build_method(class_static, vm).into(),
243+
);
244+
}
245+
246+
Ok(class)
142247
})
143248
}
144249

250+
#[unsafe(no_mangle)]
251+
pub unsafe extern "C" fn PyObject_GetTypeData(
252+
obj: *mut PyObject,
253+
cls: *mut PyTypeObject,
254+
) -> *mut c_void {
255+
if unsafe { &*cls }.slots.basicsize == 0 {
256+
obj.cast()
257+
} else {
258+
todo!("PyObject_GetTypeData for non-zero sized types is not yet implemented")
259+
}
260+
}
261+
145262
#[unsafe(no_mangle)]
146263
pub extern "C" fn PyType_Freeze(_ty: *mut PyTypeObject) -> c_int {
147-
// TODO: Implement immutable type freezing semantics.
148264
0
149265
}
150266

151267
#[cfg(test)]
152268
mod tests {
153269
use pyo3::prelude::*;
154-
use pyo3::types::{PyDict, PyInt, PyString, PyTypeMethods};
270+
use pyo3::types::{PyInt, PyString, PyType, PyTypeMethods};
155271

156272
#[test]
157273
fn type_name() {
@@ -194,22 +310,6 @@ mod tests {
194310
fn method2(&self, a: i32) -> PyResult<i32> {
195311
Ok(self.num + a)
196312
}
197-
198-
#[staticmethod]
199-
fn static_method1(a: i32, b: i32) -> PyResult<i32> {
200-
Ok(a + b)
201-
}
202-
203-
#[staticmethod]
204-
fn static_method2() -> PyResult<i32> {
205-
Ok(0)
206-
}
207-
208-
#[classmethod]
209-
fn cls_method(cls: &Bound<'_, PyType>) -> PyResult<i32> {
210-
assert!(cls.is_subclass_of::<MyClass>()?);
211-
Ok(10)
212-
}
213313
}
214314

215315
Python::attach(|py| {
@@ -235,6 +335,40 @@ mod tests {
235335
.unwrap(),
236336
8
237337
);
338+
});
339+
}
340+
341+
#[test]
342+
fn test_zero_sized_class() {
343+
#[pyclass(frozen)]
344+
struct MyEmptyClass {}
345+
346+
#[pymethods]
347+
impl MyEmptyClass {
348+
#[new]
349+
fn new() -> Self {
350+
MyEmptyClass {}
351+
}
352+
353+
#[staticmethod]
354+
fn static_method1(a: i32, b: i32) -> PyResult<i32> {
355+
Ok(a + b)
356+
}
357+
358+
#[staticmethod]
359+
fn static_method2() -> PyResult<i32> {
360+
Ok(0)
361+
}
362+
363+
#[classmethod]
364+
fn cls_method(cls: &Bound<'_, PyType>) -> PyResult<i32> {
365+
assert!(cls.is_subclass_of::<MyEmptyClass>()?);
366+
Ok(10)
367+
}
368+
}
369+
370+
Python::attach(|py| {
371+
let obj = Bound::new(py, MyEmptyClass {}).unwrap();
238372

239373
assert_eq!(
240374
obj.call_method1("static_method1", (5, 8))

crates/capi/src/slots.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::PyObject;
22
use crate::methodobject::PyMethodDef;
3-
use crate::object::PyTypeSlot;
3+
use crate::object::PyType_Slot;
44
use core::ffi::{c_char, c_int, c_void};
55
use rustpython_vm::builtins::PyBaseExceptionRef;
66
use rustpython_vm::{PyResult, VirtualMachine};
@@ -123,13 +123,17 @@ pub(crate) enum PySlotKind {
123123
is_static: bool,
124124
},
125125
TypeSlots {
126-
value: *mut PyTypeSlot,
126+
value: *mut PyType_Slot,
127127
is_static: bool,
128128
},
129129
TypeName {
130130
value: *const c_char,
131131
is_static: bool,
132132
},
133+
TypeFlags {
134+
value: u64,
135+
},
136+
TypeExtraBasicSize(isize),
133137
TypeMetaclass {
134138
value: *mut PyObject,
135139
is_static: bool,
@@ -150,7 +154,11 @@ impl PySlotKind {
150154
#[must_use]
151155
pub(crate) fn is_static(&self) -> bool {
152156
match self {
153-
Self::ModuleCreate(_) | Self::ModuleExec(_) | Self::ModuleMethods(_) => true,
157+
Self::ModuleCreate(_)
158+
| Self::ModuleExec(_)
159+
| Self::ModuleMethods(_)
160+
| Self::TypeFlags { .. }
161+
| Self::TypeExtraBasicSize(_) => true,
154162
Self::ModuleName { is_static, .. }
155163
| Self::ModuleDoc { is_static, .. }
156164
| Self::ModuleAbi { is_static, .. }
@@ -225,6 +233,10 @@ impl TryFrom<(&PySlot, &VirtualMachine)> for PySlotKind {
225233
value: value_ptr.cast(),
226234
is_static,
227235
},
236+
97 => Self::TypeExtraBasicSize(unsafe { slot.value.sl_size }),
237+
99 => Self::TypeFlags {
238+
value: unsafe { slot.value.sl_uint64 },
239+
},
228240
107 => Self::TypeMetaclass {
229241
value: value_ptr.cast(),
230242
is_static,

0 commit comments

Comments
 (0)