Skip to content

Commit b4bc0d8

Browse files
Add basic support for new classes
1 parent d2c2c63 commit b4bc0d8

3 files changed

Lines changed: 201 additions & 3 deletions

File tree

crates/capi/src/object.rs

Lines changed: 191 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use crate::{PyObject, with_vm};
22
use core::ffi::{CStr, c_char, c_int, c_uint, c_ulong, c_void};
33
use core::ptr::NonNull;
4-
use rustpython_vm::builtins::{PyStr, PyType};
5-
use rustpython_vm::{AsObject, Context, Py};
4+
use rustpython_vm::builtins::{PyDict, PyStr, PyTuple, PyType};
5+
use rustpython_vm::function::FuncArgs;
6+
use rustpython_vm::types::{PyTypeFlags, PyTypeSlots, SlotAccessor};
7+
use rustpython_vm::{AsObject, Context, Py, PyObjectRef};
68

79
const PY_TPFLAGS_LONG_SUBCLASS: c_ulong = 1 << 24;
810
const PY_TPFLAGS_LIST_SUBCLASS: c_ulong = 1 << 25;
@@ -105,6 +107,168 @@ pub extern "C" fn PyType_IsSubtype(a: *const PyTypeObject, b: *const PyTypeObjec
105107
})
106108
}
107109

110+
#[unsafe(no_mangle)]
111+
pub extern "C" fn PyType_GetSlot(ty: *const PyTypeObject, slot: c_int) -> *mut c_void {
112+
with_vm(|_vm| -> Option<*mut c_void> {
113+
let ty = unsafe { &*ty };
114+
let slot: u8 = slot
115+
.try_into()
116+
.expect("slot number out of range for SlotAccessor");
117+
let slot_accessor: SlotAccessor = slot
118+
.try_into()
119+
.expect("invalid slot number for SlotAccessor");
120+
121+
match slot_accessor {
122+
SlotAccessor::TpNew => {
123+
extern "C" fn newfunc_wrapper(
124+
subtype: *mut PyTypeObject,
125+
args: *mut PyObject,
126+
kwargs: *mut PyObject,
127+
) -> *mut PyObject {
128+
with_vm(|vm| {
129+
let subtype = unsafe { &*subtype };
130+
let mut func_args = FuncArgs::default();
131+
132+
if let Some(args_obj) = unsafe { args.as_ref() } {
133+
let tuple = args_obj.try_downcast_ref::<PyTuple>(vm)?;
134+
func_args
135+
.args
136+
.extend(tuple.iter().map(|arg| arg.to_owned()));
137+
}
138+
139+
if let Some(kwargs_obj) = unsafe { kwargs.as_ref() } {
140+
let kwargs = kwargs_obj.try_downcast_ref::<PyDict>(vm)?;
141+
for (key, value) in kwargs.items_vec() {
142+
let key = key.try_downcast::<PyStr>(vm)?;
143+
func_args
144+
.kwargs
145+
.insert(key.to_string_lossy().into_owned(), value);
146+
}
147+
}
148+
149+
subtype
150+
.slots
151+
.new
152+
.load()
153+
.expect("tp_new slot function pointer is null")(
154+
subtype.to_owned(),
155+
func_args,
156+
vm,
157+
)
158+
})
159+
}
160+
161+
if let Some(vtable) = ty.get_type_data::<TypeVTable>() {
162+
vtable.new_func.map(|newfunc| newfunc as *mut c_void)
163+
} else {
164+
ty.slots.new.load().map(|_| newfunc_wrapper as *mut c_void)
165+
}
166+
}
167+
_ => {
168+
todo!("Slot {slot_accessor:?} for {ty:?} is not yet implemented in PyType_GetSlot")
169+
}
170+
}
171+
})
172+
}
173+
174+
#[repr(C)]
175+
pub struct PyType_Slot {
176+
slot: c_int,
177+
pfunc: *mut c_void,
178+
}
179+
180+
#[repr(C)]
181+
pub struct PyType_Spec {
182+
name: *const c_char,
183+
basicsize: c_int,
184+
itemsize: c_int,
185+
flags: c_uint,
186+
slots: *mut PyType_Slot,
187+
}
188+
189+
#[derive(Default)]
190+
struct TypeVTable {
191+
new_func: Option<newfunc>,
192+
}
193+
194+
type newfunc = unsafe extern "C" fn(
195+
ty: *mut PyTypeObject,
196+
args: *mut PyObject,
197+
kwargs: *mut PyObject,
198+
) -> *mut PyObject;
199+
200+
#[unsafe(no_mangle)]
201+
pub extern "C" fn PyType_FromSpec(spec: *mut PyType_Spec) -> *mut PyObject {
202+
with_vm(|vm| {
203+
let spec = unsafe { &*spec };
204+
let name = unsafe {
205+
CStr::from_ptr(spec.name)
206+
.to_str()
207+
.expect("type name must be valid UTF-8")
208+
};
209+
let mut base = vm.ctx.types.object_type;
210+
let mut slots = PyTypeSlots::heap_default();
211+
212+
slots.basicsize = spec.basicsize as _;
213+
slots.itemsize = spec.itemsize as _;
214+
slots.flags = PyTypeFlags::from_bits(spec.flags as u64).expect("invalid flags value");
215+
216+
let mut vtable = TypeVTable::default();
217+
let mut slot_ptr = spec.slots;
218+
while let slot = unsafe { &*slot_ptr }
219+
&& slot.slot != 0
220+
{
221+
let accessor = SlotAccessor::try_from(slot.slot as u8)
222+
.expect("invalid slot number in PyType_Spec");
223+
224+
match accessor {
225+
SlotAccessor::TpDealloc => {
226+
slots.del.store(Some(|ty, _vm| {
227+
todo!("tp_dealloc is not yet implemented in PyType_FromSpec for {ty:?}")
228+
}));
229+
}
230+
SlotAccessor::TpBase => base = unsafe { &*slot.pfunc.cast::<PyTypeObject>() },
231+
SlotAccessor::TpGetset => {}
232+
SlotAccessor::TpMethods => {}
233+
SlotAccessor::TpNew => {
234+
vtable.new_func = Some(unsafe { core::mem::transmute(slot.pfunc) });
235+
slots.new.store(Some(|ty, args, vm| {
236+
let new_func = ty.get_type_data::<TypeVTable>().unwrap().new_func.unwrap();
237+
let kwargs = vm.ctx.new_dict();
238+
for (name, value) in &args.kwargs {
239+
kwargs.set_item(&*vm.ctx.new_str(name.clone()), value.clone(), vm)?;
240+
}
241+
let args = vm.ctx.new_tuple(args.args);
242+
let result = unsafe {
243+
new_func(
244+
(&*ty) as *const _ as *mut _,
245+
args.as_object().as_raw().cast_mut(),
246+
kwargs.as_object().as_raw().cast_mut(),
247+
)
248+
};
249+
250+
unsafe { Ok(PyObjectRef::from_raw(NonNull::new(result).unwrap())) }
251+
}));
252+
}
253+
SlotAccessor::TpDoc => {}
254+
_ => todo!("Slot {accessor:?} is not yet supported in PyType_FromSpec"),
255+
}
256+
257+
slot_ptr = unsafe { slot_ptr.add(1) };
258+
}
259+
260+
let class = vm.ctx.new_class(None, name, base.to_owned(), slots);
261+
class.init_type_data(vtable).unwrap();
262+
class
263+
})
264+
}
265+
266+
#[unsafe(no_mangle)]
267+
pub extern "C" fn PyType_Freeze(_ty: *mut PyTypeObject) -> c_int {
268+
// TODO: Implement immutable type freezing semantics.
269+
0
270+
}
271+
108272
#[unsafe(no_mangle)]
109273
pub extern "C" fn PyObject_GetAttr(obj: *mut PyObject, name: *mut PyObject) -> *mut PyObject {
110274
with_vm(|vm| {
@@ -323,4 +487,29 @@ mod tests {
323487
assert!(dict.get_item("foo").is_ok());
324488
})
325489
}
490+
491+
#[test]
492+
fn test_rust_class() {
493+
#[pyclass]
494+
struct MyClass {
495+
#[pyo3(get)]
496+
num: i32,
497+
}
498+
499+
#[pymethods]
500+
impl MyClass {
501+
#[new]
502+
fn new(value: i32) -> Self {
503+
MyClass { num: value }
504+
}
505+
506+
fn method1(&self) -> PyResult<i32> {
507+
Ok(10)
508+
}
509+
}
510+
511+
Python::attach(|py| {
512+
let obj = Bound::new(py, MyClass { num: 3 }).unwrap();
513+
});
514+
}
326515
}

crates/capi/src/util.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,14 @@ impl FfiResult<()> for PyResult<Infallible> {
118118
}
119119
}
120120

121+
impl FfiResult<*mut c_void> for Option<*mut c_void> {
122+
const ERR_VALUE: *mut c_void = core::ptr::null_mut();
123+
124+
fn into_output(self, vm: &VirtualMachine) -> *mut c_void {
125+
self.map_or_else(|| Self::ERR_VALUE, |obj| obj.into_output(vm))
126+
}
127+
}
128+
121129
impl<T> FfiResult<*mut PyObject> for Option<T>
122130
where
123131
T: FfiResult<*mut PyObject>,

crates/vm/src/types/slot_defs.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//!
33
//! This module provides a centralized array of all slot definitions,
44
5+
use num_enum::TryFromPrimitive;
56
use super::{PyComparisonOp, PyTypeSlots};
67
use crate::builtins::descriptor::SlotFunc;
78

@@ -66,7 +67,7 @@ pub struct SlotDef {
6667
///
6768
/// Values match CPython's Py_* slot IDs from typeslots.h.
6869
/// Unused slots are included for value reservation.
69-
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70+
#[derive(Clone, Copy, Debug, PartialEq, Eq, TryFromPrimitive)]
7071
#[repr(u8)]
7172
pub enum SlotAccessor {
7273
// Buffer protocol (1-2) - Reserved, not used in RustPython

0 commit comments

Comments
 (0)