Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
SlotFunc
  • Loading branch information
youknowone committed Dec 28, 2025
commit da752273f0d20ca50a9c22602d73668873eea153
102 changes: 90 additions & 12 deletions crates/vm/src/builtins/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ use crate::{
builtins::{PyTypeRef, builtin_func::PyNativeMethod, type_},
class::PyClassImpl,
common::hash::PyHash,
convert::ToPyResult,
convert::{ToPyObject, ToPyResult},
function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue},
types::{
Callable, Comparable, GetDescriptor, HashFunc, Hashable, InitFunc, IterFunc, IterNextFunc,
PyComparisonOp, Representable, StringifyFunc,
Callable, Comparable, DelFunc, DescrGetFunc, DescrSetFunc, GenericMethod, GetDescriptor,
GetattroFunc, HashFunc, Hashable, InitFunc, IterFunc, IterNextFunc, PyComparisonOp,
Representable, RichCompareFunc, SetattroFunc, StringifyFunc,
},
};
use rustpython_common::lock::PyRwLock;
Expand Down Expand Up @@ -387,22 +388,38 @@ impl GetDescriptor for PyMemberDescriptor {
pub fn init(ctx: &Context) {
PyMemberDescriptor::extend_class(ctx, ctx.types.member_descriptor_type);
PyMethodDescriptor::extend_class(ctx, ctx.types.method_descriptor_type);
PySlotWrapper::extend_class(ctx, ctx.types.wrapper_descriptor_type);
PyWrapper::extend_class(ctx, ctx.types.wrapper_descriptor_type);
PyMethodWrapper::extend_class(ctx, ctx.types.method_wrapper_type);
}

// PySlotWrapper - wrapper_descriptor
// PyWrapper - wrapper_descriptor

/// Type-erased slot function - mirrors CPython's void* d_wrapped
/// Each variant knows how to call the wrapped function with proper types
#[derive(Clone, Copy)]
pub enum SlotFunc {
// Basic slots
Init(InitFunc),
Hash(HashFunc),
Str(StringifyFunc),
Repr(StringifyFunc),
Iter(IterFunc),
IterNext(IterNextFunc),
Call(GenericMethod),
Del(DelFunc),

// Attribute access slots
GetAttro(GetattroFunc),
SetAttro(SetattroFunc), // __setattr__
DelAttro(SetattroFunc), // __delattr__ (same func type, different PySetterValue)

// Rich comparison slots (with comparison op)
RichCompare(RichCompareFunc, PyComparisonOp),

// Descriptor slots
DescrGet(DescrGetFunc),
DescrSet(DescrSetFunc), // __set__
DescrDel(DescrSetFunc), // __delete__ (same func type, different PySetterValue)
}

impl std::fmt::Debug for SlotFunc {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand All @@ -414,6 +431,15 @@ impl std::fmt::Debug for SlotFunc {
SlotFunc::Repr(_) => write!(f, "SlotFunc::Repr(...)"),
SlotFunc::Iter(_) => write!(f, "SlotFunc::Iter(...)"),
SlotFunc::IterNext(_) => write!(f, "SlotFunc::IterNext(...)"),
SlotFunc::Call(_) => write!(f, "SlotFunc::Call(...)"),
SlotFunc::Del(_) => write!(f, "SlotFunc::Del(...)"),
SlotFunc::GetAttro(_) => write!(f, "SlotFunc::GetAttro(...)"),
SlotFunc::SetAttro(_) => write!(f, "SlotFunc::SetAttro(...)"),
SlotFunc::DelAttro(_) => write!(f, "SlotFunc::DelAttro(...)"),
SlotFunc::RichCompare(_, op) => write!(f, "SlotFunc::RichCompare(..., {:?})", op),
SlotFunc::DescrGet(_) => write!(f, "SlotFunc::DescrGet(...)"),
SlotFunc::DescrSet(_) => write!(f, "SlotFunc::DescrSet(...)"),
SlotFunc::DescrDel(_) => write!(f, "SlotFunc::DescrDel(...)"),
}
}
}
Expand Down Expand Up @@ -463,6 +489,58 @@ impl SlotFunc {
}
func(&obj, vm).to_pyresult(vm)
}
SlotFunc::Call(func) => func(&obj, args, vm),
SlotFunc::Del(func) => {
if !args.args.is_empty() || !args.kwargs.is_empty() {
return Err(
vm.new_type_error("__del__() takes no arguments (1 given)".to_owned())
);
}
func(&obj, vm)?;
Ok(vm.ctx.none())
}
SlotFunc::GetAttro(func) => {
let (name,): (PyRef<PyStr>,) = args.bind(vm)?;
func(&obj, &name, vm)
}
SlotFunc::SetAttro(func) => {
let (name, value): (PyRef<PyStr>, PyObjectRef) = args.bind(vm)?;
func(&obj, &name, PySetterValue::Assign(value), vm)?;
Ok(vm.ctx.none())
}
SlotFunc::DelAttro(func) => {
let (name,): (PyRef<PyStr>,) = args.bind(vm)?;
func(&obj, &name, PySetterValue::Delete, vm)?;
Ok(vm.ctx.none())
}
SlotFunc::RichCompare(func, op) => {
let (other,): (PyObjectRef,) = args.bind(vm)?;
func(&obj, &other, *op, vm).map(|r| match r {
crate::function::Either::A(obj) => obj,
crate::function::Either::B(cmp_val) => cmp_val.to_pyobject(vm),
})
}
SlotFunc::DescrGet(func) => {
let (instance, owner): (PyObjectRef, crate::function::OptionalArg<PyObjectRef>) =
args.bind(vm)?;
let owner = owner.into_option();
let instance_opt = if vm.is_none(&instance) {
None
} else {
Some(instance)
};
func(obj, instance_opt, owner, vm)
}
SlotFunc::DescrSet(func) => {
let (instance, value): (PyObjectRef, PyObjectRef) = args.bind(vm)?;
func(&obj, instance, PySetterValue::Assign(value), vm)?;
Ok(vm.ctx.none())
}
SlotFunc::DescrDel(func) => {
let (instance,): (PyObjectRef,) = args.bind(vm)?;
func(&obj, instance, PySetterValue::Delete, vm)?;
Ok(vm.ctx.none())
}
}
}
}
Expand All @@ -471,20 +549,20 @@ impl SlotFunc {
// = PyWrapperDescrObject
#[pyclass(name = "wrapper_descriptor", module = false)]
#[derive(Debug)]
pub struct PySlotWrapper {
pub struct PyWrapper {
pub typ: &'static Py<PyType>,
pub name: &'static PyStrInterned,
pub wrapped: SlotFunc,
pub doc: Option<&'static str>,
}

impl PyPayload for PySlotWrapper {
impl PyPayload for PyWrapper {
fn class(ctx: &Context) -> &'static Py<PyType> {
ctx.types.wrapper_descriptor_type
}
}

impl GetDescriptor for PySlotWrapper {
impl GetDescriptor for PyWrapper {
fn descr_get(
zelf: PyObjectRef,
obj: Option<PyObjectRef>,
Expand All @@ -501,7 +579,7 @@ impl GetDescriptor for PySlotWrapper {
}
}

impl Callable for PySlotWrapper {
impl Callable for PyWrapper {
type Args = FuncArgs;

fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
Expand All @@ -525,7 +603,7 @@ impl Callable for PySlotWrapper {
with(GetDescriptor, Callable, Representable),
flags(DISALLOW_INSTANTIATION)
)]
impl PySlotWrapper {
impl PyWrapper {
#[pygetset]
fn __name__(&self) -> &'static PyStrInterned {
self.name
Expand All @@ -547,7 +625,7 @@ impl PySlotWrapper {
}
}

impl Representable for PySlotWrapper {
impl Representable for PyWrapper {
#[inline]
fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
Ok(format!(
Expand All @@ -565,7 +643,7 @@ impl Representable for PySlotWrapper {
#[pyclass(name = "method-wrapper", module = false, traverse)]
#[derive(Debug)]
pub struct PyMethodWrapper {
pub wrapper: PyRef<PySlotWrapper>,
pub wrapper: PyRef<PyWrapper>,
#[pytraverse(skip)]
pub obj: PyObjectRef,
}
Expand Down
102 changes: 99 additions & 3 deletions crates/vm/src/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use crate::{
PyPayload,
builtins::{
PyBaseObject, PyType, PyTypeRef,
descriptor::{PySlotWrapper, SlotFunc},
descriptor::{PyWrapper, SlotFunc},
},
function::PyMethodDef,
object::Py,
types::{PyTypeFlags, PyTypeSlots, hash_not_implemented},
types::{PyComparisonOp, PyTypeFlags, PyTypeSlots, hash_not_implemented},
vm::Context,
};
use rustpython_common::static_cell;
Expand Down Expand Up @@ -147,7 +147,7 @@ pub trait PyClassImpl: PyClassDef {
if let Some(func) = class.slots.$slot.load() {
let attr_name = identifier!(ctx, $name);
if !class.attributes.read().contains_key(attr_name) {
let wrapper = PySlotWrapper {
let wrapper = PyWrapper {
typ: class,
name: ctx.intern_str(stringify!($name)),
wrapped: SlotFunc::$variant(func),
Expand Down Expand Up @@ -177,6 +177,102 @@ pub trait PyClassImpl: PyClassDef {
add_slot_wrapper!(hash, __hash__, Hash, "Return hash(self).");
}

add_slot_wrapper!(call, __call__, Call, "Call self as a function.");
add_slot_wrapper!(
del,
__del__,
Del,
"Called when the instance is about to be destroyed."
);

// Attribute access slots
add_slot_wrapper!(
getattro,
__getattribute__,
GetAttro,
"Return getattr(self, name)."
);
// setattro is shared by __setattr__ and __delattr__
if let Some(func) = class.slots.setattro.load() {
let attr_name = identifier!(ctx, __setattr__);
if !class.attributes.read().contains_key(attr_name) {
let wrapper = PyWrapper {
typ: class,
name: ctx.intern_str("__setattr__"),
wrapped: SlotFunc::SetAttro(func),
doc: Some("Implement setattr(self, name, value)."),
};
class.set_attr(attr_name, wrapper.into_ref(ctx).into());
}
let attr_name = identifier!(ctx, __delattr__);
if !class.attributes.read().contains_key(attr_name) {
let wrapper = PyWrapper {
typ: class,
name: ctx.intern_str("__delattr__"),
wrapped: SlotFunc::DelAttro(func),
doc: Some("Implement delattr(self, name)."),
};
class.set_attr(attr_name, wrapper.into_ref(ctx).into());
}
}

// Rich comparison slots
macro_rules! add_richcompare_wrapper {
($name:ident, $op:expr, $doc:expr) => {
if let Some(func) = class.slots.richcompare.load() {
let attr_name = identifier!(ctx, $name);
if !class.attributes.read().contains_key(attr_name) {
let wrapper = PyWrapper {
typ: class,
name: ctx.intern_str(stringify!($name)),
wrapped: SlotFunc::RichCompare(func, $op),
doc: Some($doc),
};
class.set_attr(attr_name, wrapper.into_ref(ctx).into());
}
}
};
}
add_richcompare_wrapper!(__eq__, PyComparisonOp::Eq, "Return self==value.");
add_richcompare_wrapper!(__ne__, PyComparisonOp::Ne, "Return self!=value.");
add_richcompare_wrapper!(__lt__, PyComparisonOp::Lt, "Return self<value.");
add_richcompare_wrapper!(__le__, PyComparisonOp::Le, "Return self<=value.");
add_richcompare_wrapper!(__gt__, PyComparisonOp::Gt, "Return self>value.");
add_richcompare_wrapper!(__ge__, PyComparisonOp::Ge, "Return self>=value.");

// Descriptor slots
add_slot_wrapper!(
descr_get,
__get__,
DescrGet,
"Return an attribute of instance, which is of type owner."
);
// descr_set is shared by __set__ and __delete__
if let Some(func) = class.slots.descr_set.load() {
let attr_name = identifier!(ctx, __set__);
if !class.attributes.read().contains_key(attr_name) {
let wrapper = PyWrapper {
typ: class,
name: ctx.intern_str("__set__"),
wrapped: SlotFunc::DescrSet(func),
doc: Some("Set an attribute of instance to value."),
};
class.set_attr(attr_name, wrapper.into_ref(ctx).into());
}
let attr_name = identifier!(ctx, __delete__);
if !class.attributes.read().contains_key(attr_name) {
let wrapper = PyWrapper {
typ: class,
name: ctx.intern_str("__delete__"),
wrapped: SlotFunc::DescrDel(func),
doc: Some("Delete an attribute of instance."),
};
class.set_attr(attr_name, wrapper.into_ref(ctx).into());
}
}

// Note: __new__ is handled specially at the beginning of extend_class

// Inherit slots from base types after slots are fully initialized
for base in class.bases.read().iter() {
class.inherit_slots(base);
Expand Down
Loading