From 2515109f7060ee4528684386e066131d110dac93 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 24 Dec 2025 16:42:52 +0900 Subject: [PATCH] __hash__ to slot_wrapper --- crates/vm/src/builtins/descriptor.rs | 51 ++++++++++++++++++++++++---- crates/vm/src/class.rs | 24 +++++++++++-- crates/vm/src/types/slot.rs | 6 +--- 3 files changed, 67 insertions(+), 14 deletions(-) diff --git a/crates/vm/src/builtins/descriptor.rs b/crates/vm/src/builtins/descriptor.rs index 2ccd1dcc0e9..5cd54bb753d 100644 --- a/crates/vm/src/builtins/descriptor.rs +++ b/crates/vm/src/builtins/descriptor.rs @@ -6,7 +6,8 @@ use crate::{ common::hash::PyHash, function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue}, types::{ - Callable, Comparable, GetDescriptor, Hashable, InitFunc, PyComparisonOp, Representable, + Callable, Comparable, GetDescriptor, HashFunc, Hashable, InitFunc, PyComparisonOp, + Representable, }, }; use rustpython_common::lock::PyRwLock; @@ -391,6 +392,44 @@ pub fn init(ctx: &Context) { // PySlotWrapper - 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 { + Init(InitFunc), + Hash(HashFunc), +} + +impl std::fmt::Debug for SlotFunc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SlotFunc::Init(_) => write!(f, "SlotFunc::Init(...)"), + SlotFunc::Hash(_) => write!(f, "SlotFunc::Hash(...)"), + } + } +} + +impl SlotFunc { + /// Call the wrapped slot function with proper type handling + pub fn call(&self, obj: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult { + match self { + SlotFunc::Init(func) => { + func(obj, args, vm)?; + Ok(vm.ctx.none()) + } + SlotFunc::Hash(func) => { + if !args.args.is_empty() || !args.kwargs.is_empty() { + return Err( + vm.new_type_error("__hash__() takes no arguments (1 given)".to_owned()) + ); + } + let hash = func(&obj, vm)?; + Ok(vm.ctx.new_int(hash).into()) + } + } + } +} + /// wrapper_descriptor: wraps a slot function as a Python method // = PyWrapperDescrObject #[pyclass(name = "wrapper_descriptor", module = false)] @@ -398,7 +437,7 @@ pub fn init(ctx: &Context) { pub struct PySlotWrapper { pub typ: &'static Py, pub name: &'static PyStrInterned, - pub wrapped: InitFunc, + pub wrapped: SlotFunc, pub doc: Option<&'static str>, } @@ -430,7 +469,7 @@ impl Callable for PySlotWrapper { type Args = FuncArgs; fn call(zelf: &Py, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - // list.__init__(l, [1,2,3]) form + // list.__init__(l, [1,2,3]) form - first arg is self let (obj, rest): (PyObjectRef, FuncArgs) = args.bind(vm)?; if !obj.fast_isinstance(zelf.typ) { @@ -442,8 +481,7 @@ impl Callable for PySlotWrapper { ))); } - (zelf.wrapped)(obj, rest, vm)?; - Ok(vm.ctx.none()) + zelf.wrapped.call(obj, rest, vm) } } @@ -506,8 +544,7 @@ impl Callable for PyMethodWrapper { type Args = FuncArgs; fn call(zelf: &Py, args: FuncArgs, vm: &VirtualMachine) -> PyResult { - (zelf.wrapper.wrapped)(zelf.obj.clone(), args, vm)?; - Ok(vm.ctx.none()) + zelf.wrapper.wrapped.call(zelf.obj.clone(), args, vm) } } diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 92e9f6a15be..f258281712f 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -2,7 +2,10 @@ use crate::{ PyPayload, - builtins::{PyBaseObject, PyType, PyTypeRef, descriptor::PySlotWrapper}, + builtins::{ + PyBaseObject, PyType, PyTypeRef, + descriptor::{PySlotWrapper, SlotFunc}, + }, function::PyMethodDef, object::Py, types::{PyTypeFlags, PyTypeSlots, hash_not_implemented}, @@ -143,13 +146,30 @@ pub trait PyClassImpl: PyClassDef { let wrapper = PySlotWrapper { typ: class, name: ctx.intern_str("__init__"), - wrapped: init_func, + wrapped: SlotFunc::Init(init_func), doc: Some("Initialize self. See help(type(self)) for accurate signature."), }; class.set_attr(init_name, wrapper.into_ref(ctx).into()); } } + // Add __hash__ slot wrapper if slot exists and not already in dict + // Note: hash_not_implemented is handled separately (sets __hash__ = None) + if let Some(hash_func) = class.slots.hash.load() + && hash_func as usize != hash_not_implemented as usize + { + let hash_name = identifier!(ctx, __hash__); + if !class.attributes.read().contains_key(hash_name) { + let wrapper = PySlotWrapper { + typ: class, + name: ctx.intern_str("__hash__"), + wrapped: SlotFunc::Hash(hash_func), + doc: Some("Return hash(self)."), + }; + class.set_attr(hash_name, wrapper.into_ref(ctx).into()); + } + } + if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize { class.set_attr(ctx.names.__hash__, ctx.none.clone().into()); } diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index d09f6925eef..2954d08abf0 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -1098,11 +1098,7 @@ pub trait Hashable: PyPayload { Self::hash(zelf, vm) } - #[inline] - #[pymethod] - fn __hash__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Self::slot_hash(&zelf, vm) - } + // __hash__ is now exposed via SlotFunc::Hash wrapper in extend_class() fn hash(zelf: &Py, vm: &VirtualMachine) -> PyResult; }