Skip to content

Commit 7c2a0d9

Browse files
committed
__hash__ to slot_wrapper
1 parent 3d7e521 commit 7c2a0d9

File tree

3 files changed

+67
-14
lines changed

3 files changed

+67
-14
lines changed

crates/vm/src/builtins/descriptor.rs

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use crate::{
66
common::hash::PyHash,
77
function::{FuncArgs, PyMethodDef, PyMethodFlags, PySetterValue},
88
types::{
9-
Callable, Comparable, GetDescriptor, Hashable, InitFunc, PyComparisonOp, Representable,
9+
Callable, Comparable, GetDescriptor, HashFunc, Hashable, InitFunc, PyComparisonOp,
10+
Representable,
1011
},
1112
};
1213
use rustpython_common::lock::PyRwLock;
@@ -391,14 +392,52 @@ pub fn init(ctx: &Context) {
391392

392393
// PySlotWrapper - wrapper_descriptor
393394

395+
/// Type-erased slot function - mirrors CPython's void* d_wrapped
396+
/// Each variant knows how to call the wrapped function with proper types
397+
#[derive(Clone, Copy)]
398+
pub enum SlotFunc {
399+
Init(InitFunc),
400+
Hash(HashFunc),
401+
}
402+
403+
impl std::fmt::Debug for SlotFunc {
404+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
405+
match self {
406+
SlotFunc::Init(_) => write!(f, "SlotFunc::Init(...)"),
407+
SlotFunc::Hash(_) => write!(f, "SlotFunc::Hash(...)"),
408+
}
409+
}
410+
}
411+
412+
impl SlotFunc {
413+
/// Call the wrapped slot function with proper type handling
414+
pub fn call(&self, obj: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
415+
match self {
416+
SlotFunc::Init(func) => {
417+
func(obj, args, vm)?;
418+
Ok(vm.ctx.none())
419+
}
420+
SlotFunc::Hash(func) => {
421+
if !args.args.is_empty() || !args.kwargs.is_empty() {
422+
return Err(
423+
vm.new_type_error("__hash__() takes no arguments (1 given)".to_owned())
424+
);
425+
}
426+
let hash = func(&obj, vm)?;
427+
Ok(vm.ctx.new_int(hash).into())
428+
}
429+
}
430+
}
431+
}
432+
394433
/// wrapper_descriptor: wraps a slot function as a Python method
395434
// = PyWrapperDescrObject
396435
#[pyclass(name = "wrapper_descriptor", module = false)]
397436
#[derive(Debug)]
398437
pub struct PySlotWrapper {
399438
pub typ: &'static Py<PyType>,
400439
pub name: &'static PyStrInterned,
401-
pub wrapped: InitFunc,
440+
pub wrapped: SlotFunc,
402441
pub doc: Option<&'static str>,
403442
}
404443

@@ -430,7 +469,7 @@ impl Callable for PySlotWrapper {
430469
type Args = FuncArgs;
431470

432471
fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
433-
// list.__init__(l, [1,2,3]) form
472+
// list.__init__(l, [1,2,3]) form - first arg is self
434473
let (obj, rest): (PyObjectRef, FuncArgs) = args.bind(vm)?;
435474

436475
if !obj.fast_isinstance(zelf.typ) {
@@ -442,8 +481,7 @@ impl Callable for PySlotWrapper {
442481
)));
443482
}
444483

445-
(zelf.wrapped)(obj, rest, vm)?;
446-
Ok(vm.ctx.none())
484+
zelf.wrapped.call(obj, rest, vm)
447485
}
448486
}
449487

@@ -506,8 +544,7 @@ impl Callable for PyMethodWrapper {
506544
type Args = FuncArgs;
507545

508546
fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
509-
(zelf.wrapper.wrapped)(zelf.obj.clone(), args, vm)?;
510-
Ok(vm.ctx.none())
547+
zelf.wrapper.wrapped.call(zelf.obj.clone(), args, vm)
511548
}
512549
}
513550

crates/vm/src/class.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
33
use crate::{
44
PyPayload,
5-
builtins::{PyBaseObject, PyType, PyTypeRef, descriptor::PySlotWrapper},
5+
builtins::{
6+
PyBaseObject, PyType, PyTypeRef,
7+
descriptor::{PySlotWrapper, SlotFunc},
8+
},
69
function::PyMethodDef,
710
object::Py,
811
types::{PyTypeFlags, PyTypeSlots, hash_not_implemented},
@@ -143,13 +146,30 @@ pub trait PyClassImpl: PyClassDef {
143146
let wrapper = PySlotWrapper {
144147
typ: class,
145148
name: ctx.intern_str("__init__"),
146-
wrapped: init_func,
149+
wrapped: SlotFunc::Init(init_func),
147150
doc: Some("Initialize self. See help(type(self)) for accurate signature."),
148151
};
149152
class.set_attr(init_name, wrapper.into_ref(ctx).into());
150153
}
151154
}
152155

156+
// Add __hash__ slot wrapper if slot exists and not already in dict
157+
// Note: hash_not_implemented is handled separately (sets __hash__ = None)
158+
if let Some(hash_func) = class.slots.hash.load() {
159+
if hash_func as usize != hash_not_implemented as usize {
160+
let hash_name = identifier!(ctx, __hash__);
161+
if !class.attributes.read().contains_key(hash_name) {
162+
let wrapper = PySlotWrapper {
163+
typ: class,
164+
name: ctx.intern_str("__hash__"),
165+
wrapped: SlotFunc::Hash(hash_func),
166+
doc: Some("Return hash(self)."),
167+
};
168+
class.set_attr(hash_name, wrapper.into_ref(ctx).into());
169+
}
170+
}
171+
}
172+
153173
if class.slots.hash.load().map_or(0, |h| h as usize) == hash_not_implemented as usize {
154174
class.set_attr(ctx.names.__hash__, ctx.none.clone().into());
155175
}

crates/vm/src/types/slot.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,11 +1098,7 @@ pub trait Hashable: PyPayload {
10981098
Self::hash(zelf, vm)
10991099
}
11001100

1101-
#[inline]
1102-
#[pymethod]
1103-
fn __hash__(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyHash> {
1104-
Self::slot_hash(&zelf, vm)
1105-
}
1101+
// __hash__ is now exposed via SlotFunc::Hash wrapper in extend_class()
11061102

11071103
fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash>;
11081104
}

0 commit comments

Comments
 (0)