Skip to content

Commit 2dc9a82

Browse files
Add support for class attributes
1 parent b4bc0d8 commit 2dc9a82

1 file changed

Lines changed: 76 additions & 6 deletions

File tree

crates/capi/src/object.rs

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
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::{PyDict, PyStr, PyTuple, PyType};
4+
use rustpython_vm::builtins::{PyDict, PyGetSet, PyStr, PyTuple, PyType};
5+
use rustpython_vm::convert::IntoObject;
56
use rustpython_vm::function::FuncArgs;
67
use rustpython_vm::types::{PyTypeFlags, PyTypeSlots, SlotAccessor};
7-
use rustpython_vm::{AsObject, Context, Py, PyObjectRef};
8+
use rustpython_vm::{AsObject, Context, Py, PyObjectRef, PyRef, PyResult, VirtualMachine};
89

910
const PY_TPFLAGS_LONG_SUBCLASS: c_ulong = 1 << 24;
1011
const PY_TPFLAGS_LIST_SUBCLASS: c_ulong = 1 << 25;
@@ -186,6 +187,15 @@ pub struct PyType_Spec {
186187
slots: *mut PyType_Slot,
187188
}
188189

190+
#[repr(C)]
191+
pub struct PyGetSetDef {
192+
name: *const c_char,
193+
get: extern "C" fn(*mut PyObject, usize) -> *mut PyObject,
194+
set: Option<extern "C" fn(*mut PyObject, *mut PyObject, usize) -> c_int>,
195+
doc: *const c_char,
196+
closure: usize,
197+
}
198+
189199
#[derive(Default)]
190200
struct TypeVTable {
191201
new_func: Option<newfunc>,
@@ -201,7 +211,7 @@ type newfunc = unsafe extern "C" fn(
201211
pub extern "C" fn PyType_FromSpec(spec: *mut PyType_Spec) -> *mut PyObject {
202212
with_vm(|vm| {
203213
let spec = unsafe { &*spec };
204-
let name = unsafe {
214+
let class_name = unsafe {
205215
CStr::from_ptr(spec.name)
206216
.to_str()
207217
.expect("type name must be valid UTF-8")
@@ -213,6 +223,7 @@ pub extern "C" fn PyType_FromSpec(spec: *mut PyType_Spec) -> *mut PyObject {
213223
slots.itemsize = spec.itemsize as _;
214224
slots.flags = PyTypeFlags::from_bits(spec.flags as u64).expect("invalid flags value");
215225

226+
let mut attributes: &[PyGetSetDef] = &[];
216227
let mut vtable = TypeVTable::default();
217228
let mut slot_ptr = spec.slots;
218229
while let slot = unsafe { &*slot_ptr }
@@ -228,7 +239,16 @@ pub extern "C" fn PyType_FromSpec(spec: *mut PyType_Spec) -> *mut PyObject {
228239
}));
229240
}
230241
SlotAccessor::TpBase => base = unsafe { &*slot.pfunc.cast::<PyTypeObject>() },
231-
SlotAccessor::TpGetset => {}
242+
SlotAccessor::TpGetset => {
243+
let start = slot.pfunc.cast::<PyGetSetDef>();
244+
let mut end = start;
245+
while unsafe { !(*end).name.is_null() } {
246+
end = unsafe { end.add(1) }
247+
}
248+
attributes = unsafe {
249+
core::slice::from_raw_parts(start, end.offset_from(start) as usize)
250+
};
251+
}
232252
SlotAccessor::TpMethods => {}
233253
SlotAccessor::TpNew => {
234254
vtable.new_func = Some(unsafe { core::mem::transmute(slot.pfunc) });
@@ -250,15 +270,60 @@ pub extern "C" fn PyType_FromSpec(spec: *mut PyType_Spec) -> *mut PyObject {
250270
unsafe { Ok(PyObjectRef::from_raw(NonNull::new(result).unwrap())) }
251271
}));
252272
}
253-
SlotAccessor::TpDoc => {}
273+
SlotAccessor::TpDoc => {
274+
let doc = unsafe {
275+
CStr::from_ptr(slot.pfunc.cast::<c_char>())
276+
.to_str()
277+
.expect("tp_doc must be a valid UTF-8 string")
278+
};
279+
slots.doc = Some(doc);
280+
}
254281
_ => todo!("Slot {accessor:?} is not yet supported in PyType_FromSpec"),
255282
}
256283

257284
slot_ptr = unsafe { slot_ptr.add(1) };
258285
}
259286

260-
let class = vm.ctx.new_class(None, name, base.to_owned(), slots);
287+
let class = vm.ctx.new_class(None, class_name, base.to_owned(), slots);
261288
class.init_type_data(vtable).unwrap();
289+
for attribute in attributes {
290+
let name = unsafe {
291+
CStr::from_ptr(attribute.name)
292+
.to_str()
293+
.expect("attribute name must be valid UTF-8")
294+
};
295+
let closure = attribute.closure;
296+
let getter = attribute.get;
297+
let getset = if let Some(setter) = attribute.set {
298+
todo!();
299+
unsafe {
300+
vm.ctx.new_getset(
301+
name,
302+
&class,
303+
|obj: PyObjectRef, vm: &VirtualMachine| {},
304+
|obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine| {},
305+
)
306+
}
307+
} else {
308+
let class = unsafe { &*((&*class) as *const _) };
309+
vm.ctx.new_readonly_getset(
310+
name,
311+
&class,
312+
move |obj: PyObjectRef, vm: &VirtualMachine| {
313+
let result = getter(obj.as_raw().cast_mut(), closure);
314+
unsafe {
315+
PyObjectRef::from_raw(
316+
NonNull::new(result).expect("TODO handle error from c function"),
317+
)
318+
}
319+
},
320+
)
321+
};
322+
class
323+
.attributes
324+
.write()
325+
.insert(vm.ctx.intern_str(name), getset.into_object());
326+
}
262327
class
263328
})
264329
}
@@ -510,6 +575,11 @@ mod tests {
510575

511576
Python::attach(|py| {
512577
let obj = Bound::new(py, MyClass { num: 3 }).unwrap();
578+
579+
let globals = PyDict::new(py);
580+
globals.set_item("instance", obj).unwrap();
581+
py.run(c"assert instance.num == 3", Some(&globals), None)
582+
.unwrap();
513583
});
514584
}
515585
}

0 commit comments

Comments
 (0)