From bdaadd48c033cf3a96c9463ca3537c67d7cc4c2a Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 25 Dec 2025 12:17:05 +0900 Subject: [PATCH 1/9] strwrapper --- crates/vm/src/types/slot.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 57ac24f461a..16b2b584fa7 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -358,6 +358,16 @@ fn repr_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult> }) } +fn str_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult> { + let ret = vm.call_special_method(zelf, identifier!(vm, __str__), ())?; + ret.downcast::().map_err(|obj| { + vm.new_type_error(format!( + "__str__ returned non-string (type {})", + obj.class() + )) + }) +} + fn hash_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult { let hash_obj = vm.call_special_method(zelf, identifier!(vm, __hash__), ())?; let py_int = hash_obj @@ -570,6 +580,9 @@ impl PyType { _ if name == identifier!(ctx, __repr__) => { update_slot!(repr, repr_wrapper); } + _ if name == identifier!(ctx, __str__) => { + update_slot!(str, str_wrapper); + } _ if name == identifier!(ctx, __hash__) => { let is_unhashable = self .attributes From 7b08c72fcbede96fbc474edf783e93092c9e2985 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 25 Dec 2025 08:38:15 +0900 Subject: [PATCH 2/9] impl copyslot --- crates/vm/src/builtins/classmethod.rs | 2 +- crates/vm/src/builtins/object.rs | 5 +- crates/vm/src/builtins/type.rs | 140 ++++++++++++++-- crates/vm/src/class.rs | 5 + crates/vm/src/frame.rs | 2 +- crates/vm/src/function/protocol.rs | 13 +- crates/vm/src/object/core.rs | 2 +- crates/vm/src/protocol/callable.rs | 2 +- crates/vm/src/protocol/iter.rs | 34 ++-- crates/vm/src/protocol/number.rs | 197 ++++++++++------------- crates/vm/src/protocol/object.rs | 66 ++++---- crates/vm/src/stdlib/ctypes/structure.rs | 2 +- crates/vm/src/stdlib/ctypes/union.rs | 2 +- crates/vm/src/vm/method.rs | 8 +- crates/vm/src/vm/vm_object.rs | 4 +- crates/vm/src/vm/vm_ops.rs | 24 ++- 16 files changed, 287 insertions(+), 221 deletions(-) diff --git a/crates/vm/src/builtins/classmethod.rs b/crates/vm/src/builtins/classmethod.rs index 911960bf691..f1a2d220064 100644 --- a/crates/vm/src/builtins/classmethod.rs +++ b/crates/vm/src/builtins/classmethod.rs @@ -124,7 +124,7 @@ impl PyClassMethod { } #[pyclass( - with(GetDescriptor, Constructor, Representable), + with(GetDescriptor, Constructor, Initializer, Representable), flags(BASETYPE, HAS_DICT) )] impl PyClassMethod { diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index 65a11d55314..ca208790f4b 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -320,10 +320,7 @@ impl PyBaseObject { } } PyComparisonOp::Ne => { - let cmp = zelf - .class() - .mro_find_map(|cls| cls.slots.richcompare.load()) - .unwrap(); + let cmp = zelf.class().slots.richcompare.load().unwrap(); let value = match cmp(zelf, other, PyComparisonOp::Eq, vm)? { Either::A(obj) => PyArithmeticValue::from_object(vm, obj) .map(|obj| obj.try_to_bool(vm)) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 68de17f60b6..f8a9cf53aa8 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -404,6 +404,9 @@ impl PyType { None, ); + // Note: inherit_slots is called in PyClassImpl::init_class after + // slots are fully initialized by make_slots() + Self::set_new(&new_type.slots, &new_type.base); let weakref_type = super::PyWeak::static_type(); @@ -420,6 +423,12 @@ impl PyType { } pub(crate) fn init_slots(&self, ctx: &Context) { + // Inherit slots from direct bases (not MRO) + for base in self.bases.read().iter() { + self.inherit_slots(base); + } + + // Wire dunder methods to slots #[allow(clippy::mutable_key_type)] let mut slot_name_set = std::collections::HashSet::new(); @@ -454,6 +463,114 @@ impl PyType { } } + /// Inherit slots from base type. typeobject.c: inherit_slots + pub(crate) fn inherit_slots(&self, base: &Self) { + macro_rules! copyslot { + ($slot:ident) => { + if self.slots.$slot.load().is_none() { + if let Some(base_val) = base.slots.$slot.load() { + self.slots.$slot.store(Some(base_val)); + } + } + }; + } + + // Core slots + copyslot!(hash); + copyslot!(call); + copyslot!(str); + copyslot!(repr); + copyslot!(getattro); + copyslot!(setattro); + copyslot!(richcompare); + copyslot!(iter); + copyslot!(iternext); + copyslot!(descr_get); + copyslot!(descr_set); + // Note: init is NOT inherited here because object_init has special + // handling in CPython (checks if type->tp_init != object_init). + // TODO: implement proper init inheritance with object_init check + copyslot!(del); + // new is handled by set_new() + + // Number slots + self.inherit_number_slots(base); + } + + /// Inherit number sub-slots from base type + fn inherit_number_slots(&self, base: &Self) { + macro_rules! copy_num_slot { + ($slot:ident) => { + if self.slots.as_number.$slot.load().is_none() { + if let Some(base_val) = base.slots.as_number.$slot.load() { + self.slots.as_number.$slot.store(Some(base_val)); + } + } + }; + } + + // Binary operations + copy_num_slot!(add); + copy_num_slot!(right_add); + copy_num_slot!(inplace_add); + copy_num_slot!(subtract); + copy_num_slot!(right_subtract); + copy_num_slot!(inplace_subtract); + copy_num_slot!(multiply); + copy_num_slot!(right_multiply); + copy_num_slot!(inplace_multiply); + copy_num_slot!(remainder); + copy_num_slot!(right_remainder); + copy_num_slot!(inplace_remainder); + copy_num_slot!(divmod); + copy_num_slot!(right_divmod); + copy_num_slot!(power); + copy_num_slot!(right_power); + copy_num_slot!(inplace_power); + + // Bitwise operations + copy_num_slot!(lshift); + copy_num_slot!(right_lshift); + copy_num_slot!(inplace_lshift); + copy_num_slot!(rshift); + copy_num_slot!(right_rshift); + copy_num_slot!(inplace_rshift); + copy_num_slot!(and); + copy_num_slot!(right_and); + copy_num_slot!(inplace_and); + copy_num_slot!(xor); + copy_num_slot!(right_xor); + copy_num_slot!(inplace_xor); + copy_num_slot!(or); + copy_num_slot!(right_or); + copy_num_slot!(inplace_or); + + // Division operations + copy_num_slot!(floor_divide); + copy_num_slot!(right_floor_divide); + copy_num_slot!(inplace_floor_divide); + copy_num_slot!(true_divide); + copy_num_slot!(right_true_divide); + copy_num_slot!(inplace_true_divide); + + // Matrix multiplication + copy_num_slot!(matrix_multiply); + copy_num_slot!(right_matrix_multiply); + copy_num_slot!(inplace_matrix_multiply); + + // Unary operations + copy_num_slot!(negative); + copy_num_slot!(positive); + copy_num_slot!(absolute); + copy_num_slot!(boolean); + copy_num_slot!(invert); + + // Conversion + copy_num_slot!(int); + copy_num_slot!(float); + copy_num_slot!(index); + } + // This is used for class initialization where the vm is not yet available. pub fn set_str_attr>( &self, @@ -1414,11 +1531,9 @@ impl GetAttr for PyType { if let Some(ref attr) = mcl_attr { let attr_class = attr.class(); - let has_descr_set = attr_class - .mro_find_map(|cls| cls.slots.descr_set.load()) - .is_some(); + let has_descr_set = attr_class.slots.descr_set.load().is_some(); if has_descr_set { - let descr_get = attr_class.mro_find_map(|cls| cls.slots.descr_get.load()); + let descr_get = attr_class.slots.descr_get.load(); if let Some(descr_get) = descr_get { let mcl = mcl.to_owned().into(); return descr_get(attr.clone(), Some(zelf.to_owned().into()), Some(mcl), vm); @@ -1429,7 +1544,7 @@ impl GetAttr for PyType { let zelf_attr = zelf.get_attr(name); if let Some(attr) = zelf_attr { - let descr_get = attr.class().mro_find_map(|cls| cls.slots.descr_get.load()); + let descr_get = attr.class().slots.descr_get.load(); if let Some(descr_get) = descr_get { descr_get(attr, None, Some(zelf.to_owned().into()), vm) } else { @@ -1467,9 +1582,7 @@ impl Py { // CPython returns None if __doc__ is not in the type's own dict if let Some(doc_attr) = self.get_direct_attr(vm.ctx.intern_str("__doc__")) { // If it's a descriptor, call its __get__ method - let descr_get = doc_attr - .class() - .mro_find_map(|cls| cls.slots.descr_get.load()); + let descr_get = doc_attr.class().slots.descr_get.load(); if let Some(descr_get) = descr_get { descr_get(doc_attr, None, Some(self.to_owned().into()), vm) } else { @@ -1545,7 +1658,7 @@ impl SetAttr for PyType { // TODO: pass PyRefExact instead of &str let attr_name = vm.ctx.intern_str(attr_name.as_str()); if let Some(attr) = zelf.get_class_attr(attr_name) { - let descr_set = attr.class().mro_find_map(|cls| cls.slots.descr_set.load()); + let descr_set = attr.class().slots.descr_set.load(); if let Some(descriptor) = descr_set { return descriptor(&attr, zelf.to_owned().into(), value, vm); } @@ -1702,7 +1815,9 @@ fn subtype_set_dict(obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) - // Call the descriptor's tp_descr_set let descr_set = descr .class() - .mro_find_map(|cls| cls.slots.descr_set.load()) + .slots + .descr_set + .load() .ok_or_else(|| raise_dict_descriptor_error(&obj, vm))?; descr_set(&descr, obj, PySetterValue::Assign(value), vm) } else { @@ -1764,8 +1879,9 @@ pub(crate) fn call_slot_new( } let slot_new = typ - .deref() - .mro_find_map(|cls| cls.slots.new.load()) + .slots + .new + .load() .expect("Should be able to find a new slot somewhere in the mro"); slot_new(subtype, args, vm) } diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index 40f01d98b00..da860a96289 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -176,6 +176,11 @@ pub trait PyClassImpl: PyClassDef { add_slot_wrapper!(hash, __hash__, Hash, "Return hash(self)."); } + // Inherit slots from base types after slots are fully initialized + for base in class.bases.read().iter() { + class.inherit_slots(base); + } + class.extend_methods(class.slots.methods, ctx); } diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index ad50f972aef..d9cc404b47a 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -1900,7 +1900,7 @@ impl ExecutingFrame<'_> { // TODO: It was PyMethod before #4873. Check if it's correct. let func = if is_method { - if let Some(descr_get) = func.class().mro_find_map(|cls| cls.slots.descr_get.load()) { + if let Some(descr_get) = func.class().slots.descr_get.load() { let cls = target.class().to_owned().into(); descr_get(func, Some(target), Some(cls), vm)? } else { diff --git a/crates/vm/src/function/protocol.rs b/crates/vm/src/function/protocol.rs index 1e670b96389..7cee7041bf5 100644 --- a/crates/vm/src/function/protocol.rs +++ b/crates/vm/src/function/protocol.rs @@ -104,14 +104,11 @@ where T: TryFromObject, { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - let iter_fn = { - let cls = obj.class(); - let iter_fn = cls.mro_find_map(|x| x.slots.iter.load()); - if iter_fn.is_none() && !cls.has_attr(identifier!(vm, __getitem__)) { - return Err(vm.new_type_error(format!("'{}' object is not iterable", cls.name()))); - } - iter_fn - }; + let cls = obj.class(); + let iter_fn = cls.slots.iter.load(); + if iter_fn.is_none() && !cls.has_attr(identifier!(vm, __getitem__)) { + return Err(vm.new_type_error(format!("'{}' object is not iterable", cls.name()))); + } Ok(Self { iterable: obj, iter_fn, diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 60b623ef3ed..a092d7097be 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -810,7 +810,7 @@ impl PyObject { } // CPython-compatible drop implementation - let del = self.class().mro_find_map(|cls| cls.slots.del.load()); + let del = self.class().slots.del.load(); if let Some(slot_del) = del { call_slot_del(self, slot_del)?; } diff --git a/crates/vm/src/protocol/callable.rs b/crates/vm/src/protocol/callable.rs index 1444b6bf73a..5280e04e928 100644 --- a/crates/vm/src/protocol/callable.rs +++ b/crates/vm/src/protocol/callable.rs @@ -42,7 +42,7 @@ pub struct PyCallable<'a> { impl<'a> PyCallable<'a> { pub fn new(obj: &'a PyObject) -> Option { - let call = obj.class().mro_find_map(|cls| cls.slots.call.load())?; + let call = obj.class().slots.call.load()?; Some(PyCallable { obj, call }) } diff --git a/crates/vm/src/protocol/iter.rs b/crates/vm/src/protocol/iter.rs index 18f2b5243e2..f6146543de9 100644 --- a/crates/vm/src/protocol/iter.rs +++ b/crates/vm/src/protocol/iter.rs @@ -23,9 +23,7 @@ unsafe impl> Traverse for PyIter { impl PyIter { pub fn check(obj: &PyObject) -> bool { - obj.class() - .mro_find_map(|x| x.slots.iternext.load()) - .is_some() + obj.class().slots.iternext.load().is_some() } } @@ -37,18 +35,19 @@ where Self(obj) } pub fn next(&self, vm: &VirtualMachine) -> PyResult { - let iternext = { - self.0 - .borrow() - .class() - .mro_find_map(|x| x.slots.iternext.load()) - .ok_or_else(|| { - vm.new_type_error(format!( - "'{}' object is not an iterator", - self.0.borrow().class().name() - )) - })? - }; + let iternext = self + .0 + .borrow() + .class() + .slots + .iternext + .load() + .ok_or_else(|| { + vm.new_type_error(format!( + "'{}' object is not an iterator", + self.0.borrow().class().name() + )) + })?; iternext(self.0.borrow(), vm) } @@ -126,10 +125,7 @@ impl TryFromObject for PyIter { // in the vm when a for loop is entered. Next, it is used when the builtin // function 'iter' is called. fn try_from_object(vm: &VirtualMachine, iter_target: PyObjectRef) -> PyResult { - let get_iter = { - let cls = iter_target.class(); - cls.mro_find_map(|x| x.slots.iter.load()) - }; + let get_iter = iter_target.class().slots.iter.load(); if let Some(get_iter) = get_iter { let iter = get_iter(iter_target, vm)?; if Self::check(&iter) { diff --git a/crates/vm/src/protocol/number.rs b/crates/vm/src/protocol/number.rs index 1242ee52795..c53b7f1cf3b 100644 --- a/crates/vm/src/protocol/number.rs +++ b/crates/vm/src/protocol/number.rs @@ -441,27 +441,12 @@ impl<'a> PyNumber<'a> { self.0 } - // PyNumber_Check + // PyNumber_Check - slots are now inherited pub fn check(obj: &PyObject) -> bool { - let cls = &obj.class(); - // TODO: when we finally have a proper slot inheritance, mro_find_map can be removed - // methods.int.load().is_some() - // || methods.index.load().is_some() - // || methods.float.load().is_some() - // || obj.downcastable::() - let has_number = cls - .mro_find_map(|x| { - let methods = &x.slots.as_number; - if methods.int.load().is_some() - || methods.index.load().is_some() - || methods.float.load().is_some() - { - Some(()) - } else { - None - } - }) - .is_some(); + let methods = &obj.class().slots.as_number; + let has_number = methods.int.load().is_some() + || methods.index.load().is_some() + || methods.float.load().is_some(); has_number || obj.downcastable::() } } @@ -469,114 +454,106 @@ impl<'a> PyNumber<'a> { impl PyNumber<'_> { // PyIndex_Check pub fn is_index(self) -> bool { - self.class() - .mro_find_map(|x| x.slots.as_number.index.load()) - .is_some() + self.class().slots.as_number.index.load().is_some() } #[inline] pub fn int(self, vm: &VirtualMachine) -> Option> { - self.class() - .mro_find_map(|x| x.slots.as_number.int.load()) - .map(|f| { - let ret = f(self, vm)?; - - if let Some(ret) = ret.downcast_ref_if_exact::(vm) { - return Ok(ret.to_owned()); - } - - let ret_class = ret.class().to_owned(); - if let Some(ret) = ret.downcast_ref::() { - warnings::warn( - vm.ctx.exceptions.deprecation_warning, - format!( - "__int__ returned non-int (type {ret_class}). \ + self.class().slots.as_number.int.load().map(|f| { + let ret = f(self, vm)?; + + if let Some(ret) = ret.downcast_ref_if_exact::(vm) { + return Ok(ret.to_owned()); + } + + let ret_class = ret.class().to_owned(); + if let Some(ret) = ret.downcast_ref::() { + warnings::warn( + vm.ctx.exceptions.deprecation_warning, + format!( + "__int__ returned non-int (type {ret_class}). \ The ability to return an instance of a strict subclass of int \ is deprecated, and may be removed in a future version of Python." - ), - 1, - vm, - )?; - - Ok(ret.to_owned()) - } else { - Err(vm.new_type_error(format!( - "{}.__int__ returned non-int(type {})", - self.class(), - ret_class - ))) - } - }) + ), + 1, + vm, + )?; + + Ok(ret.to_owned()) + } else { + Err(vm.new_type_error(format!( + "{}.__int__ returned non-int(type {})", + self.class(), + ret_class + ))) + } + }) } #[inline] pub fn index(self, vm: &VirtualMachine) -> Option> { - self.class() - .mro_find_map(|x| x.slots.as_number.index.load()) - .map(|f| { - let ret = f(self, vm)?; - - if let Some(ret) = ret.downcast_ref_if_exact::(vm) { - return Ok(ret.to_owned()); - } - - let ret_class = ret.class().to_owned(); - if let Some(ret) = ret.downcast_ref::() { - warnings::warn( - vm.ctx.exceptions.deprecation_warning, - format!( - "__index__ returned non-int (type {ret_class}). \ + self.class().slots.as_number.index.load().map(|f| { + let ret = f(self, vm)?; + + if let Some(ret) = ret.downcast_ref_if_exact::(vm) { + return Ok(ret.to_owned()); + } + + let ret_class = ret.class().to_owned(); + if let Some(ret) = ret.downcast_ref::() { + warnings::warn( + vm.ctx.exceptions.deprecation_warning, + format!( + "__index__ returned non-int (type {ret_class}). \ The ability to return an instance of a strict subclass of int \ is deprecated, and may be removed in a future version of Python." - ), - 1, - vm, - )?; - - Ok(ret.to_owned()) - } else { - Err(vm.new_type_error(format!( - "{}.__index__ returned non-int(type {})", - self.class(), - ret_class - ))) - } - }) + ), + 1, + vm, + )?; + + Ok(ret.to_owned()) + } else { + Err(vm.new_type_error(format!( + "{}.__index__ returned non-int(type {})", + self.class(), + ret_class + ))) + } + }) } #[inline] pub fn float(self, vm: &VirtualMachine) -> Option>> { - self.class() - .mro_find_map(|x| x.slots.as_number.float.load()) - .map(|f| { - let ret = f(self, vm)?; - - if let Some(ret) = ret.downcast_ref_if_exact::(vm) { - return Ok(ret.to_owned()); - } - - let ret_class = ret.class().to_owned(); - if let Some(ret) = ret.downcast_ref::() { - warnings::warn( - vm.ctx.exceptions.deprecation_warning, - format!( - "__float__ returned non-float (type {ret_class}). \ + self.class().slots.as_number.float.load().map(|f| { + let ret = f(self, vm)?; + + if let Some(ret) = ret.downcast_ref_if_exact::(vm) { + return Ok(ret.to_owned()); + } + + let ret_class = ret.class().to_owned(); + if let Some(ret) = ret.downcast_ref::() { + warnings::warn( + vm.ctx.exceptions.deprecation_warning, + format!( + "__float__ returned non-float (type {ret_class}). \ The ability to return an instance of a strict subclass of float \ is deprecated, and may be removed in a future version of Python." - ), - 1, - vm, - )?; - - Ok(ret.to_owned()) - } else { - Err(vm.new_type_error(format!( - "{}.__float__ returned non-float(type {})", - self.class(), - ret_class - ))) - } - }) + ), + 1, + vm, + )?; + + Ok(ret.to_owned()) + } else { + Err(vm.new_type_error(format!( + "{}.__float__ returned non-float(type {})", + self.class(), + ret_class + ))) + } + }) } } diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index f2e52a94004..13e41cbec4c 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -136,10 +136,7 @@ impl PyObject { #[inline] pub(crate) fn get_attr_inner(&self, attr_name: &Py, vm: &VirtualMachine) -> PyResult { vm_trace!("object.__getattribute__: {:?} {:?}", self, attr_name); - let getattro = self - .class() - .mro_find_map(|cls| cls.slots.getattro.load()) - .unwrap(); + let getattro = self.class().slots.getattro.load().unwrap(); getattro(self, attr_name, vm).inspect_err(|exc| { vm.set_attribute_error_context(exc, self.to_owned(), attr_name.to_owned()); }) @@ -153,21 +150,20 @@ impl PyObject { ) -> PyResult<()> { let setattro = { let cls = self.class(); - cls.mro_find_map(|cls| cls.slots.setattro.load()) - .ok_or_else(|| { - let has_getattr = cls.mro_find_map(|cls| cls.slots.getattro.load()).is_some(); - vm.new_type_error(format!( - "'{}' object has {} attributes ({} {})", - cls.name(), - if has_getattr { "only read-only" } else { "no" }, - if attr_value.is_assign() { - "assign to" - } else { - "del" - }, - attr_name - )) - })? + cls.slots.setattro.load().ok_or_else(|| { + let has_getattr = cls.slots.getattro.load().is_some(); + vm.new_type_error(format!( + "'{}' object has {} attributes ({} {})", + cls.name(), + if has_getattr { "only read-only" } else { "no" }, + if attr_value.is_assign() { + "assign to" + } else { + "del" + }, + attr_name + )) + })? }; setattro(self, attr_name, attr_value, vm) } @@ -197,7 +193,7 @@ impl PyObject { .interned_str(attr_name) .and_then(|attr_name| self.get_class_attr(attr_name)) { - let descr_set = attr.class().mro_find_map(|cls| cls.slots.descr_set.load()); + let descr_set = attr.class().slots.descr_set.load(); if let Some(descriptor) = descr_set { return descriptor(&attr, self.to_owned(), value, vm); } @@ -239,11 +235,9 @@ impl PyObject { let cls_attr = match cls_attr_name.and_then(|name| obj_cls.get_attr(name)) { Some(descr) => { let descr_cls = descr.class(); - let descr_get = descr_cls.mro_find_map(|cls| cls.slots.descr_get.load()); + let descr_get = descr_cls.slots.descr_get.load(); if let Some(descr_get) = descr_get - && descr_cls - .mro_find_map(|cls| cls.slots.descr_set.load()) - .is_some() + && descr_cls.slots.descr_set.load().is_some() { let cls = obj_cls.to_owned().into(); return descr_get(descr, Some(self.to_owned()), Some(cls), vm).map(Some); @@ -293,10 +287,7 @@ impl PyObject { ) -> PyResult> { let swapped = op.swapped(); let call_cmp = |obj: &Self, other: &Self, op| { - let cmp = obj - .class() - .mro_find_map(|cls| cls.slots.richcompare.load()) - .unwrap(); + let cmp = obj.class().slots.richcompare.load().unwrap(); let r = match cmp(obj, other, op, vm)? { Either::A(obj) => PyArithmeticValue::from_object(vm, obj).map(Either::A), Either::B(arithmetic) => arithmetic.map(Either::B), @@ -353,18 +344,15 @@ impl PyObject { pub fn repr(&self, vm: &VirtualMachine) -> PyResult> { vm.with_recursion("while getting the repr of an object", || { - // TODO: RustPython does not implement type slots inheritance yet - self.class() - .mro_find_map(|cls| cls.slots.repr.load()) - .map_or_else( - || { - Err(vm.new_runtime_error(format!( + self.class().slots.repr.load().map_or_else( + || { + Err(vm.new_runtime_error(format!( "BUG: object of type '{}' has no __repr__ method. This is a bug in RustPython.", self.class().name() ))) - }, - |repr| repr(self, vm), - ) + }, + |repr| repr(self, vm), + ) }) } @@ -659,7 +647,7 @@ impl PyObject { } pub fn hash(&self, vm: &VirtualMachine) -> PyResult { - if let Some(hash) = self.class().mro_find_map(|cls| cls.slots.hash.load()) { + if let Some(hash) = self.class().slots.hash.load() { return hash(self, vm); } @@ -781,7 +769,7 @@ impl PyObject { let res = obj_cls.lookup_ref(attr, vm)?; // If it's a descriptor, call its __get__ method - let descr_get = res.class().mro_find_map(|cls| cls.slots.descr_get.load()); + let descr_get = res.class().slots.descr_get.load(); if let Some(descr_get) = descr_get { let obj_cls = obj_cls.to_owned().into(); // CPython ignores exceptions in _PyObject_LookupSpecial and returns NULL diff --git a/crates/vm/src/stdlib/ctypes/structure.rs b/crates/vm/src/stdlib/ctypes/structure.rs index 1ca428669d4..d5aca392c52 100644 --- a/crates/vm/src/stdlib/ctypes/structure.rs +++ b/crates/vm/src/stdlib/ctypes/structure.rs @@ -467,7 +467,7 @@ impl SetAttr for PyCStructType { // Check for data descriptor first if let Some(attr) = pytype.get_class_attr(attr_name_interned) { - let descr_set = attr.class().mro_find_map(|cls| cls.slots.descr_set.load()); + let descr_set = attr.class().slots.descr_set.load(); if let Some(descriptor) = descr_set { return descriptor(&attr, pytype.to_owned().into(), value, vm); } diff --git a/crates/vm/src/stdlib/ctypes/union.rs b/crates/vm/src/stdlib/ctypes/union.rs index 500aa8e6244..41bc7492a25 100644 --- a/crates/vm/src/stdlib/ctypes/union.rs +++ b/crates/vm/src/stdlib/ctypes/union.rs @@ -369,7 +369,7 @@ impl SetAttr for PyCUnionType { // 1. First, do PyType's setattro (PyType_Type.tp_setattro first) // Check for data descriptor first if let Some(attr) = pytype.get_class_attr(attr_name_interned) { - let descr_set = attr.class().mro_find_map(|cls| cls.slots.descr_set.load()); + let descr_set = attr.class().slots.descr_set.load(); if let Some(descriptor) = descr_set { descriptor(&attr, pytype.to_owned().into(), value.clone(), vm)?; // After successful setattro, check if _fields_ and call process_fields diff --git a/crates/vm/src/vm/method.rs b/crates/vm/src/vm/method.rs index 5df01c556ea..ba323391488 100644 --- a/crates/vm/src/vm/method.rs +++ b/crates/vm/src/vm/method.rs @@ -21,7 +21,7 @@ pub enum PyMethod { impl PyMethod { pub fn get(obj: PyObjectRef, name: &Py, vm: &VirtualMachine) -> PyResult { let cls = obj.class(); - let getattro = cls.mro_find_map(|cls| cls.slots.getattro.load()).unwrap(); + let getattro = cls.slots.getattro.load().unwrap(); if getattro as usize != PyBaseObject::getattro as usize { return obj.get_attr(name, vm).map(Self::Attribute); } @@ -41,11 +41,9 @@ impl PyMethod { is_method = true; None } else { - let descr_get = descr_cls.mro_find_map(|cls| cls.slots.descr_get.load()); + let descr_get = descr_cls.slots.descr_get.load(); if let Some(descr_get) = descr_get - && descr_cls - .mro_find_map(|cls| cls.slots.descr_set.load()) - .is_some() + && descr_cls.slots.descr_set.load().is_some() { let cls = cls.to_owned().into(); return descr_get(descr, Some(obj), Some(cls), vm).map(Self::Attribute); diff --git a/crates/vm/src/vm/vm_object.rs b/crates/vm/src/vm/vm_object.rs index e69301820d6..0d5b286148c 100644 --- a/crates/vm/src/vm/vm_object.rs +++ b/crates/vm/src/vm/vm_object.rs @@ -96,9 +96,7 @@ impl VirtualMachine { obj: Option, cls: Option, ) -> Option { - let descr_get = descr - .class() - .mro_find_map(|cls| cls.slots.descr_get.load())?; + let descr_get = descr.class().slots.descr_get.load()?; Some(descr_get(descr.to_owned(), obj, cls, self)) } diff --git a/crates/vm/src/vm/vm_ops.rs b/crates/vm/src/vm/vm_ops.rs index e30e19981a9..2a12b55357f 100644 --- a/crates/vm/src/vm/vm_ops.rs +++ b/crates/vm/src/vm/vm_ops.rs @@ -160,12 +160,12 @@ impl VirtualMachine { let class_a = a.class(); let class_b = b.class(); - // Look up number slots across MRO for inheritance - let slot_a = class_a.mro_find_map(|x| x.slots.as_number.left_binary_op(op_slot)); + // Number slots are inherited, direct access is O(1) + let slot_a = class_a.slots.as_number.left_binary_op(op_slot); let mut slot_b = None; if !class_a.is(class_b) { - let slot_bb = class_b.mro_find_map(|x| x.slots.as_number.right_binary_op(op_slot)); + let slot_bb = class_b.slots.as_number.right_binary_op(op_slot); if slot_bb.map(|x| x as usize) != slot_a.map(|x| x as usize) { slot_b = slot_bb; } @@ -231,10 +231,7 @@ impl VirtualMachine { iop_slot: PyNumberBinaryOp, op_slot: PyNumberBinaryOp, ) -> PyResult { - if let Some(slot) = a - .class() - .mro_find_map(|x| x.slots.as_number.left_binary_op(iop_slot)) - { + if let Some(slot) = a.class().slots.as_number.left_binary_op(iop_slot) { let x = slot(a, b, self)?; if !x.is(&self.ctx.not_implemented) { return Ok(x); @@ -270,12 +267,12 @@ impl VirtualMachine { let class_b = b.class(); let class_c = c.class(); - // Look up number slots across MRO for inheritance - let slot_a = class_a.mro_find_map(|x| x.slots.as_number.left_ternary_op(op_slot)); + // Number slots are inherited, direct access is O(1) + let slot_a = class_a.slots.as_number.left_ternary_op(op_slot); let mut slot_b = None; if !class_a.is(class_b) { - let slot_bb = class_b.mro_find_map(|x| x.slots.as_number.right_ternary_op(op_slot)); + let slot_bb = class_b.slots.as_number.right_ternary_op(op_slot); if slot_bb.map(|x| x as usize) != slot_a.map(|x| x as usize) { slot_b = slot_bb; } @@ -304,7 +301,7 @@ impl VirtualMachine { } } - if let Some(slot_c) = class_c.mro_find_map(|x| x.slots.as_number.left_ternary_op(op_slot)) + if let Some(slot_c) = class_c.slots.as_number.left_ternary_op(op_slot) && slot_a.is_some_and(|slot_a| !std::ptr::fn_addr_eq(slot_a, slot_c)) && slot_b.is_some_and(|slot_b| !std::ptr::fn_addr_eq(slot_b, slot_c)) { @@ -343,10 +340,7 @@ impl VirtualMachine { op_slot: PyNumberTernaryOp, op_str: &str, ) -> PyResult { - if let Some(slot) = a - .class() - .mro_find_map(|x| x.slots.as_number.left_ternary_op(iop_slot)) - { + if let Some(slot) = a.class().slots.as_number.left_ternary_op(iop_slot) { let x = slot(a, b, c, self)?; if !x.is(&self.ctx.not_implemented) { return Ok(x); From 71df458635443d35654044e88422b21b56d4ff1f Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 25 Dec 2025 09:37:33 +0900 Subject: [PATCH 3/9] toggle sbuslot --- crates/vm/src/types/slot.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 16b2b584fa7..79218d2aef2 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -501,17 +501,26 @@ impl PyType { macro_rules! toggle_slot { ($name:ident, $func:expr) => {{ - self.slots.$name.store(if ADD { Some($func) } else { None }); + if ADD { + self.slots.$name.store(Some($func)); + } else { + // When deleting, re-inherit from MRO (skip self) + let inherited = self.mro.read().iter().skip(1).find_map(|cls| cls.slots.$name.load()); + self.slots.$name.store(inherited); + } }}; } macro_rules! toggle_sub_slot { - ($group:ident, $name:ident, $func:expr) => { - self.slots - .$group - .$name - .store(if ADD { Some($func) } else { None }); - }; + ($group:ident, $name:ident, $func:expr) => {{ + if ADD { + self.slots.$group.$name.store(Some($func)); + } else { + // When deleting, re-inherit from MRO (skip self) + let inherited = self.mro.read().iter().skip(1).find_map(|cls| cls.slots.$group.$name.load()); + self.slots.$group.$name.store(inherited); + } + }}; } macro_rules! update_slot { From 099d9a43ea889d780b4e7b34b2f913356a159c88 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 25 Dec 2025 09:55:09 +0900 Subject: [PATCH 4/9] fix typing::init --- crates/vm/src/stdlib/typing.rs | 7 ++++++- crates/vm/src/types/slot.rs | 14 ++++++++++++-- crates/vm/src/types/zoo.rs | 1 + 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/vm/src/stdlib/typing.rs b/crates/vm/src/stdlib/typing.rs index afa8bd6eb90..469a75b010f 100644 --- a/crates/vm/src/stdlib/typing.rs +++ b/crates/vm/src/stdlib/typing.rs @@ -1,5 +1,5 @@ // spell-checker:ignore typevarobject funcobj -use crate::{PyPayload, PyRef, VirtualMachine, class::PyClassImpl, stdlib::PyModule}; +use crate::{Context, PyPayload, PyRef, VirtualMachine, class::PyClassImpl, stdlib::PyModule}; pub use crate::stdlib::typevar::{ Generic, ParamSpec, ParamSpecArgs, ParamSpecKwargs, TypeVar, TypeVarTuple, @@ -7,6 +7,11 @@ pub use crate::stdlib::typevar::{ }; pub use decl::*; +/// Initialize typing types (call extend_class) +pub fn init(ctx: &Context) { + NoDefault::extend_class(ctx, ctx.types.typing_no_default_type); +} + pub(crate) fn make_module(vm: &VirtualMachine) -> PyRef { let module = decl::make_module(vm); TypeVar::make_class(&vm.ctx); diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 79218d2aef2..b447ee5b43b 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -505,7 +505,12 @@ impl PyType { self.slots.$name.store(Some($func)); } else { // When deleting, re-inherit from MRO (skip self) - let inherited = self.mro.read().iter().skip(1).find_map(|cls| cls.slots.$name.load()); + let inherited = self + .mro + .read() + .iter() + .skip(1) + .find_map(|cls| cls.slots.$name.load()); self.slots.$name.store(inherited); } }}; @@ -517,7 +522,12 @@ impl PyType { self.slots.$group.$name.store(Some($func)); } else { // When deleting, re-inherit from MRO (skip self) - let inherited = self.mro.read().iter().skip(1).find_map(|cls| cls.slots.$group.$name.load()); + let inherited = self + .mro + .read() + .iter() + .skip(1) + .find_map(|cls| cls.slots.$group.$name.load()); self.slots.$group.$name.store(inherited); } }}; diff --git a/crates/vm/src/types/zoo.rs b/crates/vm/src/types/zoo.rs index dd4631bc767..0cd04a0ac17 100644 --- a/crates/vm/src/types/zoo.rs +++ b/crates/vm/src/types/zoo.rs @@ -242,5 +242,6 @@ impl TypeZoo { genericalias::init(context); union_::init(context); descriptor::init(context); + crate::stdlib::typing::init(context); } } From 97c49498253eee663263f52eb2c3d1c64107bb6b Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 25 Dec 2025 11:17:54 +0900 Subject: [PATCH 5/9] bool_wrapper --- Lib/test/test_typing.py | 1 - crates/vm/src/types/slot.rs | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index db0dc916f1a..74ab94eb5c3 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -10496,7 +10496,6 @@ class CustomerModel(ModelBase, init=False): class NoDefaultTests(BaseTestCase): - @unittest.expectedFailure # TODO: RUSTPYTHON def test_pickling(self): for proto in range(pickle.HIGHEST_PROTOCOL + 1): s = pickle.dumps(NoDefault, proto) diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index b447ee5b43b..dd157f07e53 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -436,6 +436,18 @@ fn iter_wrapper(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { vm.call_special_method(&zelf, identifier!(vm, __iter__), ()) } +fn bool_wrapper(num: PyNumber<'_>, vm: &VirtualMachine) -> PyResult { + let result = vm.call_special_method(num.obj(), identifier!(vm, __bool__), ())?; + // __bool__ must return exactly bool, not int subclass + if !result.class().is(vm.ctx.types.bool_type) { + return Err(vm.new_type_error(format!( + "__bool__ should return bool, returned {}", + result.class().name() + ))); + } + Ok(crate::builtins::bool_::get_value(&result)) +} + // PyObject_SelfIter in CPython const fn self_iter(zelf: PyObjectRef, _vm: &VirtualMachine) -> PyResult { Ok(zelf) @@ -656,6 +668,9 @@ impl PyType { _ if name == identifier!(ctx, __del__) => { toggle_slot!(del, del_wrapper); } + _ if name == identifier!(ctx, __bool__) => { + toggle_sub_slot!(as_number, boolean, bool_wrapper); + } _ if name == identifier!(ctx, __int__) => { toggle_sub_slot!(as_number, int, number_unary_op_wrapper!(__int__)); } From 0c70347765a286f9b443a0d6faa7e04b84fbc238 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 25 Dec 2025 11:04:46 +0900 Subject: [PATCH 6/9] apply pointerslot --- crates/vm/src/builtins/iter.rs | 18 +--- crates/vm/src/builtins/mappingproxy.rs | 8 +- crates/vm/src/builtins/type.rs | 51 +++++++++-- crates/vm/src/function/protocol.rs | 27 ++---- crates/vm/src/protocol/mapping.rs | 82 ++++++++++-------- crates/vm/src/protocol/mod.rs | 4 +- crates/vm/src/protocol/object.rs | 8 +- crates/vm/src/protocol/sequence.rs | 113 +++++++++++++++++-------- crates/vm/src/stdlib/builtins.rs | 2 +- crates/vm/src/types/slot.rs | 62 +++++--------- crates/vm/src/types/structseq.rs | 18 ++-- 11 files changed, 223 insertions(+), 170 deletions(-) diff --git a/crates/vm/src/builtins/iter.rs b/crates/vm/src/builtins/iter.rs index 56dfc14d164..edaa87a4360 100644 --- a/crates/vm/src/builtins/iter.rs +++ b/crates/vm/src/builtins/iter.rs @@ -8,7 +8,7 @@ use crate::{ class::PyClassImpl, function::ArgCallable, object::{Traverse, TraverseFn}, - protocol::{PyIterReturn, PySequence, PySequenceMethods}, + protocol::{PyIterReturn, PySequence}, types::{IterNext, Iterable, SelfIter}, }; use rustpython_common::{ @@ -177,9 +177,6 @@ pub fn builtins_reversed(vm: &VirtualMachine) -> &PyObject { #[pyclass(module = false, name = "iterator", traverse)] #[derive(Debug)] pub struct PySequenceIterator { - // cached sequence methods - #[pytraverse(skip)] - seq_methods: &'static PySequenceMethods, internal: PyMutex>, } @@ -193,9 +190,8 @@ impl PyPayload for PySequenceIterator { #[pyclass(with(IterNext, Iterable))] impl PySequenceIterator { pub fn new(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let seq = PySequence::try_protocol(&obj, vm)?; + let _seq = PySequence::try_protocol(&obj, vm)?; Ok(Self { - seq_methods: seq.methods, internal: PyMutex::new(PositionIterInternal::new(obj, 0)), }) } @@ -204,10 +200,7 @@ impl PySequenceIterator { fn __length_hint__(&self, vm: &VirtualMachine) -> PyObjectRef { let internal = self.internal.lock(); if let IterStatus::Active(obj) = &internal.status { - let seq = PySequence { - obj, - methods: self.seq_methods, - }; + let seq = obj.to_sequence(); seq.length(vm) .map(|x| PyInt::from(x).into_pyobject(vm)) .unwrap_or_else(|_| vm.ctx.not_implemented()) @@ -231,10 +224,7 @@ impl SelfIter for PySequenceIterator {} impl IterNext for PySequenceIterator { fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { zelf.internal.lock().next(|obj, pos| { - let seq = PySequence { - obj, - methods: zelf.seq_methods, - }; + let seq = obj.to_sequence(); PyIterReturn::from_getitem_result(seq.get_item(pos as isize, vm), vm) }) } diff --git a/crates/vm/src/builtins/mappingproxy.rs b/crates/vm/src/builtins/mappingproxy.rs index fb8ff5de9cc..f7c46ccca9f 100644 --- a/crates/vm/src/builtins/mappingproxy.rs +++ b/crates/vm/src/builtins/mappingproxy.rs @@ -6,7 +6,7 @@ use crate::{ convert::ToPyObject, function::{ArgMapping, OptionalArg, PyComparisonValue}, object::{Traverse, TraverseFn}, - protocol::{PyMapping, PyMappingMethods, PyNumberMethods, PySequenceMethods}, + protocol::{PyMappingMethods, PyNumberMethods, PySequenceMethods}, types::{ AsMapping, AsNumber, AsSequence, Comparable, Constructor, Iterable, PyComparisonOp, Representable, @@ -62,14 +62,12 @@ impl Constructor for PyMappingProxy { type Args = PyObjectRef; fn py_new(_cls: &Py, mapping: Self::Args, vm: &VirtualMachine) -> PyResult { - if let Some(methods) = PyMapping::find_methods(&mapping) + if mapping.to_mapping().check() && !mapping.downcastable::() && !mapping.downcastable::() { return Ok(Self { - mapping: MappingProxyInner::Mapping(ArgMapping::with_methods(mapping, unsafe { - methods.borrow_static() - })), + mapping: MappingProxyInner::Mapping(ArgMapping::new(mapping)), }); } Err(vm.new_type_error(format!( diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index f8a9cf53aa8..ce665bcc64f 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -23,7 +23,7 @@ use crate::{ convert::ToPyResult, function::{FuncArgs, KwArgs, OptionalArg, PyMethodDef, PySetterValue}, object::{Traverse, TraverseFn}, - protocol::{PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods}, + protocol::{PyIterReturn, PyNumberMethods}, types::{ AsNumber, Callable, Constructor, GetAttr, PyTypeFlags, PyTypeSlots, Representable, SetAttr, TypeDataRef, TypeDataRefMut, TypeDataSlot, @@ -64,8 +64,6 @@ pub struct HeapTypeExt { pub name: PyRwLock, pub qualname: PyRwLock, pub slots: Option>>, - pub sequence_methods: PySequenceMethods, - pub mapping_methods: PyMappingMethods, pub type_data: PyRwLock>, } @@ -206,8 +204,6 @@ impl PyType { name: PyRwLock::new(name.clone()), qualname: PyRwLock::new(name), slots: None, - sequence_methods: PySequenceMethods::default(), - mapping_methods: PyMappingMethods::default(), type_data: PyRwLock::new(None), }; let base = bases[0].clone(); @@ -493,8 +489,10 @@ impl PyType { copyslot!(del); // new is handled by set_new() - // Number slots + // Sub-slots (number, sequence, mapping) self.inherit_number_slots(base); + self.inherit_sequence_slots(base); + self.inherit_mapping_slots(base); } /// Inherit number sub-slots from base type @@ -571,6 +569,45 @@ impl PyType { copy_num_slot!(index); } + /// Inherit sequence sub-slots from base type + fn inherit_sequence_slots(&self, base: &Self) { + macro_rules! copy_seq_slot { + ($slot:ident) => { + if self.slots.as_sequence.$slot.load().is_none() { + if let Some(base_val) = base.slots.as_sequence.$slot.load() { + self.slots.as_sequence.$slot.store(Some(base_val)); + } + } + }; + } + + copy_seq_slot!(length); + copy_seq_slot!(concat); + copy_seq_slot!(repeat); + copy_seq_slot!(item); + copy_seq_slot!(ass_item); + copy_seq_slot!(contains); + copy_seq_slot!(inplace_concat); + copy_seq_slot!(inplace_repeat); + } + + /// Inherit mapping sub-slots from base type + fn inherit_mapping_slots(&self, base: &Self) { + macro_rules! copy_map_slot { + ($slot:ident) => { + if self.slots.as_mapping.$slot.load().is_none() { + if let Some(base_val) = base.slots.as_mapping.$slot.load() { + self.slots.as_mapping.$slot.store(Some(base_val)); + } + } + }; + } + + copy_map_slot!(length); + copy_map_slot!(subscript); + copy_map_slot!(ass_subscript); + } + // This is used for class initialization where the vm is not yet available. pub fn set_str_attr>( &self, @@ -1345,8 +1382,6 @@ impl Constructor for PyType { name: PyRwLock::new(name), qualname: PyRwLock::new(qualname), slots: heaptype_slots.clone(), - sequence_methods: PySequenceMethods::default(), - mapping_methods: PyMappingMethods::default(), type_data: PyRwLock::new(None), }; (slots, heaptype_ext) diff --git a/crates/vm/src/function/protocol.rs b/crates/vm/src/function/protocol.rs index 7cee7041bf5..498c403c6d1 100644 --- a/crates/vm/src/function/protocol.rs +++ b/crates/vm/src/function/protocol.rs @@ -1,11 +1,11 @@ use super::IntoFuncArgs; use crate::{ AsObject, PyObject, PyObjectRef, PyPayload, PyResult, TryFromObject, VirtualMachine, - builtins::{PyDict, PyDictRef, iter::PySequenceIterator}, + builtins::{PyDictRef, iter::PySequenceIterator}, convert::ToPyObject, object::{Traverse, TraverseFn}, - protocol::{PyIter, PyIterIter, PyMapping, PyMappingMethods}, - types::{AsMapping, GenericMethod}, + protocol::{PyIter, PyIterIter, PyMapping}, + types::GenericMethod, }; use std::{borrow::Borrow, marker::PhantomData, ops::Deref}; @@ -120,30 +120,22 @@ where #[derive(Debug, Clone, Traverse)] pub struct ArgMapping { obj: PyObjectRef, - #[pytraverse(skip)] - methods: &'static PyMappingMethods, } impl ArgMapping { #[inline] - pub const fn with_methods(obj: PyObjectRef, methods: &'static PyMappingMethods) -> Self { - Self { obj, methods } + pub const fn new(obj: PyObjectRef) -> Self { + Self { obj } } #[inline(always)] pub fn from_dict_exact(dict: PyDictRef) -> Self { - Self { - obj: dict.into(), - methods: PyDict::as_mapping(), - } + Self { obj: dict.into() } } #[inline(always)] pub fn mapping(&self) -> PyMapping<'_> { - PyMapping { - obj: &self.obj, - methods: self.methods, - } + self.obj.to_mapping() } } @@ -185,9 +177,8 @@ impl ToPyObject for ArgMapping { impl TryFromObject for ArgMapping { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - let mapping = PyMapping::try_protocol(&obj, vm)?; - let methods = mapping.methods; - Ok(Self { obj, methods }) + let _mapping = PyMapping::try_protocol(&obj, vm)?; + Ok(Self { obj }) } } diff --git a/crates/vm/src/protocol/mapping.rs b/crates/vm/src/protocol/mapping.rs index a942303dbb4..e5951cef8c6 100644 --- a/crates/vm/src/protocol/mapping.rs +++ b/crates/vm/src/protocol/mapping.rs @@ -3,7 +3,6 @@ use crate::{ builtins::{ PyDict, PyStrInterned, dict::{PyDictItems, PyDictKeys, PyDictValues}, - type_::PointerSlot, }, convert::ToPyResult, object::{Traverse, TraverseFn}, @@ -13,9 +12,38 @@ use crossbeam_utils::atomic::AtomicCell; // Mapping protocol // https://docs.python.org/3/c-api/mapping.html -impl PyObject { - pub fn to_mapping(&self) -> PyMapping<'_> { - PyMapping::from(self) +#[allow(clippy::type_complexity)] +#[derive(Default)] +pub struct PyMappingSlots { + pub length: AtomicCell, &VirtualMachine) -> PyResult>>, + pub subscript: AtomicCell, &PyObject, &VirtualMachine) -> PyResult>>, + pub ass_subscript: AtomicCell< + Option, &PyObject, Option, &VirtualMachine) -> PyResult<()>>, + >, +} + +impl std::fmt::Debug for PyMappingSlots { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("PyMappingSlots") + } +} + +impl PyMappingSlots { + pub fn has_subscript(&self) -> bool { + self.subscript.load().is_some() + } + + /// Copy from static PyMappingMethods + pub fn copy_from(&self, methods: &PyMappingMethods) { + if let Some(f) = methods.length.load() { + self.length.store(Some(f)); + } + if let Some(f) = methods.subscript.load() { + self.subscript.store(Some(f)); + } + if let Some(f) = methods.ass_subscript.load() { + self.ass_subscript.store(Some(f)); + } } } @@ -31,15 +59,11 @@ pub struct PyMappingMethods { impl std::fmt::Debug for PyMappingMethods { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "mapping methods") + f.write_str("PyMappingMethods") } } impl PyMappingMethods { - fn check(&self) -> bool { - self.subscript.load().is_some() - } - #[allow(clippy::declare_interior_mutable_const)] pub const NOT_IMPLEMENTED: Self = Self { length: AtomicCell::new(None), @@ -48,19 +72,15 @@ impl PyMappingMethods { }; } -impl<'a> From<&'a PyObject> for PyMapping<'a> { - fn from(obj: &'a PyObject) -> Self { - static GLOBAL_NOT_IMPLEMENTED: PyMappingMethods = PyMappingMethods::NOT_IMPLEMENTED; - let methods = Self::find_methods(obj) - .map_or(&GLOBAL_NOT_IMPLEMENTED, |x| unsafe { x.borrow_static() }); - Self { obj, methods } +impl PyObject { + pub fn to_mapping(&self) -> PyMapping<'_> { + PyMapping { obj: self } } } #[derive(Copy, Clone)] pub struct PyMapping<'a> { pub obj: &'a PyObject, - pub methods: &'static PyMappingMethods, } unsafe impl Traverse for PyMapping<'_> { @@ -78,32 +98,28 @@ impl AsRef for PyMapping<'_> { impl<'a> PyMapping<'a> { pub fn try_protocol(obj: &'a PyObject, vm: &VirtualMachine) -> PyResult { - if let Some(methods) = Self::find_methods(obj) - && methods.as_ref().check() - { - return Ok(Self { - obj, - methods: unsafe { methods.borrow_static() }, - }); + let mapping = obj.to_mapping(); + if mapping.check() { + Ok(mapping) + } else { + Err(vm.new_type_error(format!("{} is not a mapping object", obj.class()))) } - - Err(vm.new_type_error(format!("{} is not a mapping object", obj.class()))) } } impl PyMapping<'_> { - // PyMapping::Check #[inline] - pub fn check(obj: &PyObject) -> bool { - Self::find_methods(obj).is_some_and(|x| x.as_ref().check()) + pub fn slots(&self) -> &PyMappingSlots { + &self.obj.class().slots.as_mapping } - pub fn find_methods(obj: &PyObject) -> Option> { - obj.class().mro_find_map(|cls| cls.slots.as_mapping.load()) + #[inline] + pub fn check(&self) -> bool { + self.slots().has_subscript() } pub fn length_opt(self, vm: &VirtualMachine) -> Option> { - self.methods.length.load().map(|f| f(self, vm)) + self.slots().length.load().map(|f| f(self, vm)) } pub fn length(self, vm: &VirtualMachine) -> PyResult { @@ -130,7 +146,7 @@ impl PyMapping<'_> { fn _subscript(self, needle: &PyObject, vm: &VirtualMachine) -> PyResult { let f = - self.methods.subscript.load().ok_or_else(|| { + self.slots().subscript.load().ok_or_else(|| { vm.new_type_error(format!("{} is not a mapping", self.obj.class())) })?; f(self, needle, vm) @@ -142,7 +158,7 @@ impl PyMapping<'_> { value: Option, vm: &VirtualMachine, ) -> PyResult<()> { - let f = self.methods.ass_subscript.load().ok_or_else(|| { + let f = self.slots().ass_subscript.load().ok_or_else(|| { vm.new_type_error(format!( "'{}' object does not support item assignment", self.obj.class() diff --git a/crates/vm/src/protocol/mod.rs b/crates/vm/src/protocol/mod.rs index d5c7e239a24..e7be286c265 100644 --- a/crates/vm/src/protocol/mod.rs +++ b/crates/vm/src/protocol/mod.rs @@ -9,9 +9,9 @@ mod sequence; pub use buffer::{BufferDescriptor, BufferMethods, BufferResizeGuard, PyBuffer, VecBuffer}; pub use callable::PyCallable; pub use iter::{PyIter, PyIterIter, PyIterReturn}; -pub use mapping::{PyMapping, PyMappingMethods}; +pub use mapping::{PyMapping, PyMappingMethods, PyMappingSlots}; pub use number::{ PyNumber, PyNumberBinaryFunc, PyNumberBinaryOp, PyNumberMethods, PyNumberSlots, PyNumberTernaryOp, PyNumberUnaryFunc, handle_bytes_to_int_err, }; -pub use sequence::{PySequence, PySequenceMethods}; +pub use sequence::{PySequence, PySequenceMethods, PySequenceSlots}; diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index 13e41cbec4c..5d7f2d2004a 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -723,13 +723,13 @@ impl PyObject { } let mapping = self.to_mapping(); - if let Some(f) = mapping.methods.ass_subscript.load() { + if let Some(f) = mapping.slots().ass_subscript.load() { let needle = needle.to_pyobject(vm); return f(mapping, &needle, Some(value), vm); } let seq = self.to_sequence(); - if let Some(f) = seq.methods.ass_item.load() { + if let Some(f) = seq.slots().ass_item.load() { let i = needle.key_as_isize(vm)?; return f(seq, i, Some(value), vm); } @@ -746,12 +746,12 @@ impl PyObject { } let mapping = self.to_mapping(); - if let Some(f) = mapping.methods.ass_subscript.load() { + if let Some(f) = mapping.slots().ass_subscript.load() { let needle = needle.to_pyobject(vm); return f(mapping, &needle, None, vm); } let seq = self.to_sequence(); - if let Some(f) = seq.methods.ass_item.load() { + if let Some(f) = seq.slots().ass_item.load() { let i = needle.key_as_isize(vm)?; return f(seq, i, None, vm); } diff --git a/crates/vm/src/protocol/sequence.rs b/crates/vm/src/protocol/sequence.rs index fb71446a5a4..79627a62a31 100644 --- a/crates/vm/src/protocol/sequence.rs +++ b/crates/vm/src/protocol/sequence.rs @@ -1,6 +1,6 @@ use crate::{ PyObject, PyObjectRef, PyPayload, PyResult, VirtualMachine, - builtins::{PyList, PyListRef, PySlice, PyTuple, PyTupleRef, type_::PointerSlot}, + builtins::{PyList, PyListRef, PySlice, PyTuple, PyTupleRef}, convert::ToPyObject, function::PyArithmeticValue, object::{Traverse, TraverseFn}, @@ -8,19 +8,63 @@ use crate::{ }; use crossbeam_utils::atomic::AtomicCell; use itertools::Itertools; -use std::fmt::Debug; // Sequence Protocol // https://docs.python.org/3/c-api/sequence.html -impl PyObject { - #[inline] - pub fn to_sequence(&self) -> PySequence<'_> { - static GLOBAL_NOT_IMPLEMENTED: PySequenceMethods = PySequenceMethods::NOT_IMPLEMENTED; - PySequence { - obj: self, - methods: PySequence::find_methods(self) - .map_or(&GLOBAL_NOT_IMPLEMENTED, |x| unsafe { x.borrow_static() }), +#[allow(clippy::type_complexity)] +#[derive(Default)] +pub struct PySequenceSlots { + pub length: AtomicCell, &VirtualMachine) -> PyResult>>, + pub concat: AtomicCell, &PyObject, &VirtualMachine) -> PyResult>>, + pub repeat: AtomicCell, isize, &VirtualMachine) -> PyResult>>, + pub item: AtomicCell, isize, &VirtualMachine) -> PyResult>>, + pub ass_item: AtomicCell< + Option, isize, Option, &VirtualMachine) -> PyResult<()>>, + >, + pub contains: + AtomicCell, &PyObject, &VirtualMachine) -> PyResult>>, + pub inplace_concat: + AtomicCell, &PyObject, &VirtualMachine) -> PyResult>>, + pub inplace_repeat: AtomicCell, isize, &VirtualMachine) -> PyResult>>, +} + +impl std::fmt::Debug for PySequenceSlots { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("PySequenceSlots") + } +} + +impl PySequenceSlots { + pub fn has_item(&self) -> bool { + self.item.load().is_some() + } + + /// Copy from static PySequenceMethods + pub fn copy_from(&self, methods: &PySequenceMethods) { + if let Some(f) = methods.length.load() { + self.length.store(Some(f)); + } + if let Some(f) = methods.concat.load() { + self.concat.store(Some(f)); + } + if let Some(f) = methods.repeat.load() { + self.repeat.store(Some(f)); + } + if let Some(f) = methods.item.load() { + self.item.store(Some(f)); + } + if let Some(f) = methods.ass_item.load() { + self.ass_item.store(Some(f)); + } + if let Some(f) = methods.contains.load() { + self.contains.store(Some(f)); + } + if let Some(f) = methods.inplace_concat.load() { + self.inplace_concat.store(Some(f)); + } + if let Some(f) = methods.inplace_repeat.load() { + self.inplace_repeat.store(Some(f)); } } } @@ -42,9 +86,9 @@ pub struct PySequenceMethods { pub inplace_repeat: AtomicCell, isize, &VirtualMachine) -> PyResult>>, } -impl Debug for PySequenceMethods { +impl std::fmt::Debug for PySequenceMethods { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Sequence Methods") + f.write_str("PySequenceMethods") } } @@ -62,10 +106,16 @@ impl PySequenceMethods { }; } +impl PyObject { + #[inline] + pub fn to_sequence(&self) -> PySequence<'_> { + PySequence { obj: self } + } +} + #[derive(Copy, Clone)] pub struct PySequence<'a> { pub obj: &'a PyObject, - pub methods: &'static PySequenceMethods, } unsafe impl Traverse for PySequence<'_> { @@ -75,11 +125,6 @@ unsafe impl Traverse for PySequence<'_> { } impl<'a> PySequence<'a> { - #[inline] - pub const fn with_methods(obj: &'a PyObject, methods: &'static PySequenceMethods) -> Self { - Self { obj, methods } - } - pub fn try_protocol(obj: &'a PyObject, vm: &VirtualMachine) -> PyResult { let seq = obj.to_sequence(); if seq.check() { @@ -91,17 +136,17 @@ impl<'a> PySequence<'a> { } impl PySequence<'_> { - pub fn check(&self) -> bool { - self.methods.item.load().is_some() + #[inline] + pub fn slots(&self) -> &PySequenceSlots { + &self.obj.class().slots.as_sequence } - pub fn find_methods(obj: &PyObject) -> Option> { - let cls = obj.class(); - cls.mro_find_map(|x| x.slots.as_sequence.load()) + pub fn check(&self) -> bool { + self.slots().has_item() } pub fn length_opt(self, vm: &VirtualMachine) -> Option> { - self.methods.length.load().map(|f| f(self, vm)) + self.slots().length.load().map(|f| f(self, vm)) } pub fn length(self, vm: &VirtualMachine) -> PyResult { @@ -114,7 +159,7 @@ impl PySequence<'_> { } pub fn concat(self, other: &PyObject, vm: &VirtualMachine) -> PyResult { - if let Some(f) = self.methods.concat.load() { + if let Some(f) = self.slots().concat.load() { return f(self, other, vm); } @@ -133,7 +178,7 @@ impl PySequence<'_> { } pub fn repeat(self, n: isize, vm: &VirtualMachine) -> PyResult { - if let Some(f) = self.methods.repeat.load() { + if let Some(f) = self.slots().repeat.load() { return f(self, n, vm); } @@ -149,10 +194,10 @@ impl PySequence<'_> { } pub fn inplace_concat(self, other: &PyObject, vm: &VirtualMachine) -> PyResult { - if let Some(f) = self.methods.inplace_concat.load() { + if let Some(f) = self.slots().inplace_concat.load() { return f(self, other, vm); } - if let Some(f) = self.methods.concat.load() { + if let Some(f) = self.slots().concat.load() { return f(self, other, vm); } @@ -171,10 +216,10 @@ impl PySequence<'_> { } pub fn inplace_repeat(self, n: isize, vm: &VirtualMachine) -> PyResult { - if let Some(f) = self.methods.inplace_repeat.load() { + if let Some(f) = self.slots().inplace_repeat.load() { return f(self, n, vm); } - if let Some(f) = self.methods.repeat.load() { + if let Some(f) = self.slots().repeat.load() { return f(self, n, vm); } @@ -189,7 +234,7 @@ impl PySequence<'_> { } pub fn get_item(self, i: isize, vm: &VirtualMachine) -> PyResult { - if let Some(f) = self.methods.item.load() { + if let Some(f) = self.slots().item.load() { return f(self, i, vm); } Err(vm.new_type_error(format!( @@ -199,7 +244,7 @@ impl PySequence<'_> { } fn _ass_item(self, i: isize, value: Option, vm: &VirtualMachine) -> PyResult<()> { - if let Some(f) = self.methods.ass_item.load() { + if let Some(f) = self.slots().ass_item.load() { return f(self, i, value, vm); } Err(vm.new_type_error(format!( @@ -242,7 +287,7 @@ impl PySequence<'_> { vm: &VirtualMachine, ) -> PyResult<()> { let mapping = self.obj.to_mapping(); - if let Some(f) = mapping.methods.ass_subscript.load() { + if let Some(f) = mapping.slots().ass_subscript.load() { let slice = PySlice { start: Some(start.to_pyobject(vm)), stop: stop.to_pyobject(vm), @@ -355,7 +400,7 @@ impl PySequence<'_> { } pub fn contains(self, target: &PyObject, vm: &VirtualMachine) -> PyResult { - if let Some(f) = self.methods.contains.load() { + if let Some(f) = self.slots().contains.load() { return f(self, target, vm); } diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index 72d2c724159..41616c8f209 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -268,7 +268,7 @@ mod builtins { if !globals.fast_isinstance(vm.ctx.types.dict_type) { return Err(match func_name { "eval" => { - let is_mapping = crate::protocol::PyMapping::check(globals); + let is_mapping = globals.to_mapping().check(); vm.new_type_error(if is_mapping { "globals must be a real dict; try eval(expr, {}, mapping)" .to_owned() diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index dd157f07e53..643856d6b7b 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -3,7 +3,7 @@ use crate::common::lock::{ }; use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, - builtins::{PyInt, PyStr, PyStrInterned, PyStrRef, PyType, PyTypeRef, type_::PointerSlot}, + builtins::{PyInt, PyStr, PyStrInterned, PyStrRef, PyType, PyTypeRef}, bytecode::ComparisonOperator, common::hash::PyHash, convert::ToPyObject, @@ -11,8 +11,8 @@ use crate::{ Either, FromArgs, FuncArgs, OptionalArg, PyComparisonValue, PyMethodDef, PySetterValue, }, protocol::{ - PyBuffer, PyIterReturn, PyMapping, PyMappingMethods, PyNumber, PyNumberMethods, - PyNumberSlots, PySequence, PySequenceMethods, + PyBuffer, PyIterReturn, PyMapping, PyMappingMethods, PyMappingSlots, PyNumber, + PyNumberMethods, PyNumberSlots, PySequence, PySequenceMethods, PySequenceSlots, }, vm::Context, }; @@ -134,8 +134,8 @@ pub struct PyTypeSlots { // Method suites for standard classes pub as_number: PyNumberSlots, - pub as_sequence: AtomicCell>>, - pub as_mapping: AtomicCell>>, + pub as_sequence: PySequenceSlots, + pub as_mapping: PyMappingSlots, // More standard operations (here for binary compatibility) pub hash: AtomicCell>, @@ -551,62 +551,34 @@ impl PyType { }}; } - macro_rules! update_pointer_slot { - ($name:ident, $pointed:ident) => {{ - self.slots - .$name - .store(unsafe { PointerSlot::from_heaptype(self, |ext| &ext.$pointed) }); - }}; - } - - macro_rules! toggle_ext_func { - ($n1:ident, $n2:ident, $func:expr) => {{ - self.heaptype_ext.as_ref().unwrap().$n1.$n2.store(if ADD { - Some($func) - } else { - None - }); - }}; - } - match name { _ if name == identifier!(ctx, __len__) => { - // update_slot!(as_mapping, slot_as_mapping); - toggle_ext_func!(sequence_methods, length, |seq, vm| len_wrapper(seq.obj, vm)); - update_pointer_slot!(as_sequence, sequence_methods); - toggle_ext_func!(mapping_methods, length, |mapping, vm| len_wrapper( + toggle_sub_slot!(as_sequence, length, |seq, vm| len_wrapper(seq.obj, vm)); + toggle_sub_slot!(as_mapping, length, |mapping, vm| len_wrapper( mapping.obj, vm )); - update_pointer_slot!(as_mapping, mapping_methods); } _ if name == identifier!(ctx, __getitem__) => { - // update_slot!(as_mapping, slot_as_mapping); - toggle_ext_func!(sequence_methods, item, |seq, i, vm| getitem_wrapper( + toggle_sub_slot!(as_sequence, item, |seq, i, vm| getitem_wrapper( seq.obj, i, vm )); - update_pointer_slot!(as_sequence, sequence_methods); - toggle_ext_func!(mapping_methods, subscript, |mapping, key, vm| { + toggle_sub_slot!(as_mapping, subscript, |mapping, key, vm| { getitem_wrapper(mapping.obj, key, vm) }); - update_pointer_slot!(as_mapping, mapping_methods); } _ if name == identifier!(ctx, __setitem__) || name == identifier!(ctx, __delitem__) => { - // update_slot!(as_mapping, slot_as_mapping); - toggle_ext_func!(sequence_methods, ass_item, |seq, i, value, vm| { + toggle_sub_slot!(as_sequence, ass_item, |seq, i, value, vm| { setitem_wrapper(seq.obj, i, value, vm) }); - update_pointer_slot!(as_sequence, sequence_methods); - toggle_ext_func!(mapping_methods, ass_subscript, |mapping, key, value, vm| { + toggle_sub_slot!(as_mapping, ass_subscript, |mapping, key, value, vm| { setitem_wrapper(mapping.obj, key, value, vm) }); - update_pointer_slot!(as_mapping, mapping_methods); } _ if name == identifier!(ctx, __contains__) => { - toggle_ext_func!(sequence_methods, contains, |seq, needle, vm| { + toggle_sub_slot!(as_sequence, contains, |seq, needle, vm| { contains_wrapper(seq.obj, needle, vm) }); - update_pointer_slot!(as_sequence, sequence_methods); } _ if name == identifier!(ctx, __repr__) => { update_slot!(repr, repr_wrapper); @@ -1427,24 +1399,30 @@ pub trait AsBuffer: PyPayload { #[pyclass] pub trait AsMapping: PyPayload { - #[pyslot] fn as_mapping() -> &'static PyMappingMethods; #[inline] fn mapping_downcast(mapping: PyMapping<'_>) -> &Py { unsafe { mapping.obj.downcast_unchecked_ref() } } + + fn extend_slots(slots: &mut PyTypeSlots) { + slots.as_mapping.copy_from(Self::as_mapping()); + } } #[pyclass] pub trait AsSequence: PyPayload { - #[pyslot] fn as_sequence() -> &'static PySequenceMethods; #[inline] fn sequence_downcast(seq: PySequence<'_>) -> &Py { unsafe { seq.obj.downcast_unchecked_ref() } } + + fn extend_slots(slots: &mut PyTypeSlots) { + slots.as_sequence.copy_from(Self::as_sequence()); + } } #[pyclass] diff --git a/crates/vm/src/types/structseq.rs b/crates/vm/src/types/structseq.rs index 2b6a2530b02..721e3123893 100644 --- a/crates/vm/src/types/structseq.rs +++ b/crates/vm/src/types/structseq.rs @@ -1,8 +1,6 @@ use crate::{ AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, atomic_func, - builtins::{ - PyBaseExceptionRef, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef, type_::PointerSlot, - }, + builtins::{PyBaseExceptionRef, PyStrRef, PyTuple, PyTupleRef, PyType, PyTypeRef}, class::{PyClassImpl, StaticType}, function::{Either, PyComparisonValue}, iter::PyExactSizeIterator, @@ -306,12 +304,14 @@ pub trait PyStructSequence: StaticType + PyClassImpl + Sized + 'static { ); // Override as_sequence and as_mapping slots to use visible length - class.slots.as_sequence.store(Some(PointerSlot::from( - &*STRUCT_SEQUENCE_AS_SEQUENCE as &'static PySequenceMethods, - ))); - class.slots.as_mapping.store(Some(PointerSlot::from( - &*STRUCT_SEQUENCE_AS_MAPPING as &'static PyMappingMethods, - ))); + class + .slots + .as_sequence + .copy_from(&STRUCT_SEQUENCE_AS_SEQUENCE); + class + .slots + .as_mapping + .copy_from(&STRUCT_SEQUENCE_AS_MAPPING); // Override iter slot to return only visible elements class.slots.iter.store(Some(struct_sequence_iter)); From 6e6df1e7c14c305c509011678b8e1777849ee3ab Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 25 Dec 2025 15:32:29 +0900 Subject: [PATCH 7/9] align PyNumber --- crates/stdlib/src/sqlite.rs | 4 ++-- crates/vm/src/builtins/bool.rs | 2 +- crates/vm/src/builtins/dict.rs | 4 ++-- crates/vm/src/builtins/iter.rs | 8 +++---- crates/vm/src/builtins/list.rs | 6 ++++- crates/vm/src/builtins/mappingproxy.rs | 4 ++-- crates/vm/src/builtins/weakproxy.rs | 4 +++- crates/vm/src/function/protocol.rs | 4 ++-- crates/vm/src/protocol/mapping.rs | 22 ++++++++---------- crates/vm/src/protocol/number.rs | 26 +++++++++++---------- crates/vm/src/protocol/object.rs | 18 +++++++-------- crates/vm/src/protocol/sequence.rs | 32 ++++++++++++-------------- crates/vm/src/stdlib/builtins.rs | 2 +- crates/vm/src/types/slot.rs | 4 ++-- crates/vm/src/types/structseq.rs | 7 ++++-- crates/vm/src/vm/vm_ops.rs | 16 ++++++------- 16 files changed, 85 insertions(+), 78 deletions(-) diff --git a/crates/stdlib/src/sqlite.rs b/crates/stdlib/src/sqlite.rs index 2328f23430a..c760c2830d7 100644 --- a/crates/stdlib/src/sqlite.rs +++ b/crates/stdlib/src/sqlite.rs @@ -488,7 +488,7 @@ mod _sqlite { let text2 = vm.ctx.new_str(text2); let val = callable.call((text1, text2), vm)?; - let Some(val) = val.to_number().index(vm) else { + let Some(val) = val.number().index(vm) else { return Ok(0); }; @@ -2980,7 +2980,7 @@ mod _sqlite { fn bind_parameters(self, parameters: &PyObject, vm: &VirtualMachine) -> PyResult<()> { if let Some(dict) = parameters.downcast_ref::() { self.bind_parameters_name(dict, vm) - } else if let Ok(seq) = PySequence::try_protocol(parameters, vm) { + } else if let Ok(seq) = parameters.try_sequence(vm) { self.bind_parameters_sequence(seq, vm) } else { Err(new_programming_error( diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 6b3ddd8241a..8fee4af3834 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -43,7 +43,7 @@ impl PyObjectRef { return Ok(false); } let rs_bool = if let Some(nb_bool) = self.class().slots.as_number.boolean.load() { - nb_bool(self.as_object().to_number(), vm)? + nb_bool(self.as_object().number(), vm)? } else { // TODO: Fully implement AsNumber and remove this block match vm.get_method(self.clone(), identifier!(vm, __bool__)) { diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index aff7432d067..567e18d6419 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -1145,7 +1145,7 @@ impl ViewSetOps for PyDictKeys {} impl PyDictKeys { #[pymethod] fn __contains__(zelf: PyObjectRef, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { - zelf.to_sequence().contains(&key, vm) + zelf.sequence_unchecked().contains(&key, vm) } #[pygetset] @@ -1210,7 +1210,7 @@ impl ViewSetOps for PyDictItems {} impl PyDictItems { #[pymethod] fn __contains__(zelf: PyObjectRef, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - zelf.to_sequence().contains(&needle, vm) + zelf.sequence_unchecked().contains(&needle, vm) } #[pygetset] fn mapping(zelf: PyRef) -> PyMappingProxy { diff --git a/crates/vm/src/builtins/iter.rs b/crates/vm/src/builtins/iter.rs index edaa87a4360..736303e95ee 100644 --- a/crates/vm/src/builtins/iter.rs +++ b/crates/vm/src/builtins/iter.rs @@ -8,7 +8,7 @@ use crate::{ class::PyClassImpl, function::ArgCallable, object::{Traverse, TraverseFn}, - protocol::{PyIterReturn, PySequence}, + protocol::PyIterReturn, types::{IterNext, Iterable, SelfIter}, }; use rustpython_common::{ @@ -190,7 +190,7 @@ impl PyPayload for PySequenceIterator { #[pyclass(with(IterNext, Iterable))] impl PySequenceIterator { pub fn new(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let _seq = PySequence::try_protocol(&obj, vm)?; + let _seq = obj.try_sequence(vm)?; Ok(Self { internal: PyMutex::new(PositionIterInternal::new(obj, 0)), }) @@ -200,7 +200,7 @@ impl PySequenceIterator { fn __length_hint__(&self, vm: &VirtualMachine) -> PyObjectRef { let internal = self.internal.lock(); if let IterStatus::Active(obj) = &internal.status { - let seq = obj.to_sequence(); + let seq = obj.sequence_unchecked(); seq.length(vm) .map(|x| PyInt::from(x).into_pyobject(vm)) .unwrap_or_else(|_| vm.ctx.not_implemented()) @@ -224,7 +224,7 @@ impl SelfIter for PySequenceIterator {} impl IterNext for PySequenceIterator { fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { zelf.internal.lock().next(|obj, pos| { - let seq = obj.to_sequence(); + let seq = obj.sequence_unchecked(); PyIterReturn::from_getitem_result(seq.get_item(pos as isize, vm), vm) }) } diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index 13e8864cd1f..12cab27a750 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -354,7 +354,11 @@ where } else { let iter = obj.to_owned().get_iter(vm)?; let iter = iter.iter::(vm)?; - let len = obj.to_sequence().length_opt(vm).transpose()?.unwrap_or(0); + let len = obj + .sequence_unchecked() + .length_opt(vm) + .transpose()? + .unwrap_or(0); let mut v = Vec::with_capacity(len); for x in iter { v.push(f(x?)?); diff --git a/crates/vm/src/builtins/mappingproxy.rs b/crates/vm/src/builtins/mappingproxy.rs index f7c46ccca9f..475f36cb5a9 100644 --- a/crates/vm/src/builtins/mappingproxy.rs +++ b/crates/vm/src/builtins/mappingproxy.rs @@ -62,7 +62,7 @@ impl Constructor for PyMappingProxy { type Args = PyObjectRef; fn py_new(_cls: &Py, mapping: Self::Args, vm: &VirtualMachine) -> PyResult { - if mapping.to_mapping().check() + if mapping.mapping_unchecked().check() && !mapping.downcastable::() && !mapping.downcastable::() { @@ -122,7 +122,7 @@ impl PyMappingProxy { MappingProxyInner::Class(class) => Ok(key .as_interned_str(vm) .is_some_and(|key| class.attributes.read().contains_key(key))), - MappingProxyInner::Mapping(mapping) => mapping.to_sequence().contains(key, vm), + MappingProxyInner::Mapping(mapping) => mapping.sequence_unchecked().contains(key, vm), } } diff --git a/crates/vm/src/builtins/weakproxy.rs b/crates/vm/src/builtins/weakproxy.rs index 6e0e8308dbc..94c54b5459e 100644 --- a/crates/vm/src/builtins/weakproxy.rs +++ b/crates/vm/src/builtins/weakproxy.rs @@ -104,7 +104,9 @@ impl PyWeakProxy { } #[pymethod] fn __contains__(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.try_upgrade(vm)?.to_sequence().contains(&needle, vm) + self.try_upgrade(vm)? + .sequence_unchecked() + .contains(&needle, vm) } fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { diff --git a/crates/vm/src/function/protocol.rs b/crates/vm/src/function/protocol.rs index 498c403c6d1..a87ef339edd 100644 --- a/crates/vm/src/function/protocol.rs +++ b/crates/vm/src/function/protocol.rs @@ -135,7 +135,7 @@ impl ArgMapping { #[inline(always)] pub fn mapping(&self) -> PyMapping<'_> { - self.obj.to_mapping() + self.obj.mapping_unchecked() } } @@ -177,7 +177,7 @@ impl ToPyObject for ArgMapping { impl TryFromObject for ArgMapping { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - let _mapping = PyMapping::try_protocol(&obj, vm)?; + let _mapping = obj.try_mapping(vm)?; Ok(Self { obj }) } } diff --git a/crates/vm/src/protocol/mapping.rs b/crates/vm/src/protocol/mapping.rs index e5951cef8c6..36813bf1df8 100644 --- a/crates/vm/src/protocol/mapping.rs +++ b/crates/vm/src/protocol/mapping.rs @@ -73,9 +73,18 @@ impl PyMappingMethods { } impl PyObject { - pub fn to_mapping(&self) -> PyMapping<'_> { + pub fn mapping_unchecked(&self) -> PyMapping<'_> { PyMapping { obj: self } } + + pub fn try_mapping(&self, vm: &VirtualMachine) -> PyResult> { + let mapping = self.mapping_unchecked(); + if mapping.check() { + Ok(mapping) + } else { + Err(vm.new_type_error(format!("{} is not a mapping object", self.class()))) + } + } } #[derive(Copy, Clone)] @@ -96,17 +105,6 @@ impl AsRef for PyMapping<'_> { } } -impl<'a> PyMapping<'a> { - pub fn try_protocol(obj: &'a PyObject, vm: &VirtualMachine) -> PyResult { - let mapping = obj.to_mapping(); - if mapping.check() { - Ok(mapping) - } else { - Err(vm.new_type_error(format!("{} is not a mapping object", obj.class()))) - } - } -} - impl PyMapping<'_> { #[inline] pub fn slots(&self) -> &PyMappingSlots { diff --git a/crates/vm/src/protocol/number.rs b/crates/vm/src/protocol/number.rs index c53b7f1cf3b..a13323c33d2 100644 --- a/crates/vm/src/protocol/number.rs +++ b/crates/vm/src/protocol/number.rs @@ -20,8 +20,8 @@ pub type PyNumberTernaryFunc = fn(&PyObject, &PyObject, &PyObject, &VirtualMachi impl PyObject { #[inline] - pub const fn to_number(&self) -> PyNumber<'_> { - PyNumber(self) + pub const fn number(&self) -> PyNumber<'_> { + PyNumber { obj: self } } pub fn try_index_opt(&self, vm: &VirtualMachine) -> Option> { @@ -30,7 +30,7 @@ impl PyObject { } else if let Some(i) = self.downcast_ref::() { Some(Ok(vm.ctx.new_bigint(i.as_bigint()))) } else { - self.to_number().index(vm) + self.number().index(vm) } } @@ -56,7 +56,11 @@ impl PyObject { if let Some(i) = self.downcast_ref_if_exact::(vm) { Ok(i.to_owned()) - } else if let Some(i) = self.to_number().int(vm).or_else(|| self.try_index_opt(vm)) { + } else if let Some(i) = self + .number() + .int(vm) + .or_else(|| self.try_index_opt(vm)) + { i } else if let Ok(Some(f)) = vm.get_special_method(self, identifier!(vm, __trunc__)) { warnings::warn( @@ -92,7 +96,7 @@ impl PyObject { pub fn try_float_opt(&self, vm: &VirtualMachine) -> Option>> { if let Some(float) = self.downcast_ref_if_exact::(vm) { Some(Ok(float.to_owned())) - } else if let Some(f) = self.to_number().float(vm) { + } else if let Some(f) = self.number().float(vm) { Some(f) } else { self.try_index_opt(vm) @@ -420,11 +424,13 @@ impl PyNumberSlots { } } #[derive(Copy, Clone)] -pub struct PyNumber<'a>(&'a PyObject); +pub struct PyNumber<'a> { + pub obj: &'a PyObject, +} unsafe impl Traverse for PyNumber<'_> { fn traverse(&self, tracer_fn: &mut TraverseFn<'_>) { - self.0.traverse(tracer_fn) + self.obj.traverse(tracer_fn) } } @@ -432,15 +438,11 @@ impl Deref for PyNumber<'_> { type Target = PyObject; fn deref(&self) -> &Self::Target { - self.0 + self.obj } } impl<'a> PyNumber<'a> { - pub(crate) const fn obj(self) -> &'a PyObject { - self.0 - } - // PyNumber_Check - slots are now inherited pub fn check(obj: &PyObject) -> bool { let methods = &obj.class().slots.as_number; diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index 5d7f2d2004a..ec1a6f55969 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -12,7 +12,7 @@ use crate::{ dict_inner::DictKey, function::{Either, FuncArgs, PyArithmeticValue, PySetterValue}, object::PyPayload, - protocol::{PyIter, PyMapping, PySequence}, + protocol::PyIter, types::{Constructor, PyComparisonOp}, }; @@ -669,9 +669,9 @@ impl PyObject { } pub fn length_opt(&self, vm: &VirtualMachine) -> Option> { - self.to_sequence() + self.sequence_unchecked() .length_opt(vm) - .or_else(|| self.to_mapping().length_opt(vm)) + .or_else(|| self.mapping_unchecked().length_opt(vm)) } pub fn length(&self, vm: &VirtualMachine) -> PyResult { @@ -690,9 +690,9 @@ impl PyObject { let needle = needle.to_pyobject(vm); - if let Ok(mapping) = PyMapping::try_protocol(self, vm) { + if let Ok(mapping) = self.try_mapping(vm) { mapping.subscript(&needle, vm) - } else if let Ok(seq) = PySequence::try_protocol(self, vm) { + } else if let Ok(seq) = self.try_sequence(vm) { let i = needle.key_as_isize(vm)?; seq.get_item(i, vm) } else { @@ -722,13 +722,13 @@ impl PyObject { return dict.set_item(needle, value, vm); } - let mapping = self.to_mapping(); + let mapping = self.mapping_unchecked(); if let Some(f) = mapping.slots().ass_subscript.load() { let needle = needle.to_pyobject(vm); return f(mapping, &needle, Some(value), vm); } - let seq = self.to_sequence(); + let seq = self.sequence_unchecked(); if let Some(f) = seq.slots().ass_item.load() { let i = needle.key_as_isize(vm)?; return f(seq, i, Some(value), vm); @@ -745,12 +745,12 @@ impl PyObject { return dict.del_item(needle, vm); } - let mapping = self.to_mapping(); + let mapping = self.mapping_unchecked(); if let Some(f) = mapping.slots().ass_subscript.load() { let needle = needle.to_pyobject(vm); return f(mapping, &needle, None, vm); } - let seq = self.to_sequence(); + let seq = self.sequence_unchecked(); if let Some(f) = seq.slots().ass_item.load() { let i = needle.key_as_isize(vm)?; return f(seq, i, None, vm); diff --git a/crates/vm/src/protocol/sequence.rs b/crates/vm/src/protocol/sequence.rs index 79627a62a31..a7576e63efb 100644 --- a/crates/vm/src/protocol/sequence.rs +++ b/crates/vm/src/protocol/sequence.rs @@ -4,7 +4,7 @@ use crate::{ convert::ToPyObject, function::PyArithmeticValue, object::{Traverse, TraverseFn}, - protocol::{PyMapping, PyNumberBinaryOp}, + protocol::PyNumberBinaryOp, }; use crossbeam_utils::atomic::AtomicCell; use itertools::Itertools; @@ -108,9 +108,18 @@ impl PySequenceMethods { impl PyObject { #[inline] - pub fn to_sequence(&self) -> PySequence<'_> { + pub fn sequence_unchecked(&self) -> PySequence<'_> { PySequence { obj: self } } + + pub fn try_sequence(&self, vm: &VirtualMachine) -> PyResult> { + let seq = self.sequence_unchecked(); + if seq.check() { + Ok(seq) + } else { + Err(vm.new_type_error(format!("'{}' is not a sequence", self.class()))) + } + } } #[derive(Copy, Clone)] @@ -124,17 +133,6 @@ unsafe impl Traverse for PySequence<'_> { } } -impl<'a> PySequence<'a> { - pub fn try_protocol(obj: &'a PyObject, vm: &VirtualMachine) -> PyResult { - let seq = obj.to_sequence(); - if seq.check() { - Ok(seq) - } else { - Err(vm.new_type_error(format!("'{}' is not a sequence", obj.class()))) - } - } -} - impl PySequence<'_> { #[inline] pub fn slots(&self) -> &PySequenceSlots { @@ -164,7 +162,7 @@ impl PySequence<'_> { } // if both arguments appear to be sequences, try fallback to __add__ - if self.check() && other.to_sequence().check() { + if self.check() && other.sequence_unchecked().check() { let ret = vm.binary_op1(self.obj, other, PyNumberBinaryOp::Add)?; if let PyArithmeticValue::Implemented(ret) = PyArithmeticValue::from_object(vm, ret) { return Ok(ret); @@ -202,7 +200,7 @@ impl PySequence<'_> { } // if both arguments appear to be sequences, try fallback to __iadd__ - if self.check() && other.to_sequence().check() { + if self.check() && other.sequence_unchecked().check() { let ret = vm._iadd(self.obj, other)?; if let PyArithmeticValue::Implemented(ret) = PyArithmeticValue::from_object(vm, ret) { return Ok(ret); @@ -267,7 +265,7 @@ impl PySequence<'_> { } pub fn get_slice(&self, start: isize, stop: isize, vm: &VirtualMachine) -> PyResult { - if let Ok(mapping) = PyMapping::try_protocol(self.obj, vm) { + if let Ok(mapping) = self.obj.try_mapping(vm) { let slice = PySlice { start: Some(start.to_pyobject(vm)), stop: stop.to_pyobject(vm), @@ -286,7 +284,7 @@ impl PySequence<'_> { value: Option, vm: &VirtualMachine, ) -> PyResult<()> { - let mapping = self.obj.to_mapping(); + let mapping = self.obj.mapping_unchecked(); if let Some(f) = mapping.slots().ass_subscript.load() { let slice = PySlice { start: Some(start.to_pyobject(vm)), diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index 41616c8f209..19f85af55da 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -268,7 +268,7 @@ mod builtins { if !globals.fast_isinstance(vm.ctx.types.dict_type) { return Err(match func_name { "eval" => { - let is_mapping = globals.to_mapping().check(); + let is_mapping = globals.mapping_unchecked().check(); vm.new_type_error(if is_mapping { "globals must be a real dict; try eval(expr, {}, mapping)" .to_owned() diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 643856d6b7b..bfe6e047622 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -437,7 +437,7 @@ fn iter_wrapper(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { } fn bool_wrapper(num: PyNumber<'_>, vm: &VirtualMachine) -> PyResult { - let result = vm.call_special_method(num.obj(), identifier!(vm, __bool__), ())?; + let result = vm.call_special_method(num.obj, identifier!(vm, __bool__), ())?; // __bool__ must return exactly bool, not int subclass if !result.class().is(vm.ctx.types.bool_type) { return Err(vm.new_type_error(format!( @@ -1437,7 +1437,7 @@ pub trait AsNumber: PyPayload { #[inline] fn number_downcast(num: PyNumber<'_>) -> &Py { - unsafe { num.obj().downcast_unchecked_ref() } + unsafe { num.obj.downcast_unchecked_ref() } } #[inline] diff --git a/crates/vm/src/types/structseq.rs b/crates/vm/src/types/structseq.rs index 721e3123893..27315749e06 100644 --- a/crates/vm/src/types/structseq.rs +++ b/crates/vm/src/types/structseq.rs @@ -85,7 +85,10 @@ static STRUCT_SEQUENCE_AS_SEQUENCE: LazyLock = let visible: Vec<_> = tuple.iter().take(n_seq).cloned().collect(); let visible_tuple = PyTuple::new_ref(visible, &vm.ctx); // Use tuple's concat implementation - visible_tuple.as_object().to_sequence().concat(other, vm) + visible_tuple + .as_object() + .sequence_unchecked() + .concat(other, vm) }), repeat: atomic_func!(|seq, n, vm| { // Convert to visible-only tuple, then use regular tuple repeat @@ -94,7 +97,7 @@ static STRUCT_SEQUENCE_AS_SEQUENCE: LazyLock = let visible: Vec<_> = tuple.iter().take(n_seq).cloned().collect(); let visible_tuple = PyTuple::new_ref(visible, &vm.ctx); // Use tuple's repeat implementation - visible_tuple.as_object().to_sequence().repeat(n, vm) + visible_tuple.as_object().sequence_unchecked().repeat(n, vm) }), item: atomic_func!(|seq, i, vm| { let n_seq = get_visible_len(seq.obj, vm)?; diff --git a/crates/vm/src/vm/vm_ops.rs b/crates/vm/src/vm/vm_ops.rs index 2a12b55357f..635fa10e630 100644 --- a/crates/vm/src/vm/vm_ops.rs +++ b/crates/vm/src/vm/vm_ops.rs @@ -4,7 +4,7 @@ use crate::{ PyRef, builtins::{PyInt, PyStr, PyStrRef, PyUtf8Str}, object::{AsObject, PyObject, PyObjectRef, PyResult}, - protocol::{PyNumberBinaryOp, PyNumberTernaryOp, PySequence}, + protocol::{PyNumberBinaryOp, PyNumberTernaryOp}, types::PyComparisonOp, }; use num_traits::ToPrimitive; @@ -380,7 +380,7 @@ impl VirtualMachine { if !result.is(&self.ctx.not_implemented) { return Ok(result); } - if let Ok(seq_a) = PySequence::try_protocol(a, self) { + if let Ok(seq_a) = a.try_sequence(self) { let result = seq_a.concat(b, self)?; if !result.is(&self.ctx.not_implemented) { return Ok(result); @@ -394,7 +394,7 @@ impl VirtualMachine { if !result.is(&self.ctx.not_implemented) { return Ok(result); } - if let Ok(seq_a) = PySequence::try_protocol(a, self) { + if let Ok(seq_a) = a.try_sequence(self) { let result = seq_a.inplace_concat(b, self)?; if !result.is(&self.ctx.not_implemented) { return Ok(result); @@ -408,14 +408,14 @@ impl VirtualMachine { if !result.is(&self.ctx.not_implemented) { return Ok(result); } - if let Ok(seq_a) = PySequence::try_protocol(a, self) { + if let Ok(seq_a) = a.try_sequence(self) { let n = b .try_index(self)? .as_bigint() .to_isize() .ok_or_else(|| self.new_overflow_error("repeated bytes are too long"))?; return seq_a.repeat(n, self); - } else if let Ok(seq_b) = PySequence::try_protocol(b, self) { + } else if let Ok(seq_b) = b.try_sequence(self) { let n = a .try_index(self)? .as_bigint() @@ -436,14 +436,14 @@ impl VirtualMachine { if !result.is(&self.ctx.not_implemented) { return Ok(result); } - if let Ok(seq_a) = PySequence::try_protocol(a, self) { + if let Ok(seq_a) = a.try_sequence(self) { let n = b .try_index(self)? .as_bigint() .to_isize() .ok_or_else(|| self.new_overflow_error("repeated bytes are too long"))?; return seq_a.inplace_repeat(n, self); - } else if let Ok(seq_b) = PySequence::try_protocol(b, self) { + } else if let Ok(seq_b) = b.try_sequence(self) { let n = a .try_index(self)? .as_bigint() @@ -524,7 +524,7 @@ impl VirtualMachine { } pub fn _contains(&self, haystack: &PyObject, needle: &PyObject) -> PyResult { - let seq = haystack.to_sequence(); + let seq = haystack.sequence_unchecked(); seq.contains(needle, self) } } From 4448d7f43bbcb7392a8355b2e1cccf71e36ddd96 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 25 Dec 2025 11:38:16 +0900 Subject: [PATCH 8/9] apply buffer --- crates/vm/src/builtins/type.rs | 37 +++++++++++--------------------- crates/vm/src/protocol/buffer.rs | 3 +-- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index ce665bcc64f..c2373f26faf 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -98,17 +98,6 @@ impl AsRef for PointerSlot { } } -impl PointerSlot { - pub unsafe fn from_heaptype(typ: &PyType, f: F) -> Option - where - F: FnOnce(&HeapTypeExt) -> &T, - { - typ.heaptype_ext - .as_ref() - .map(|ext| Self(NonNull::from(f(ext)))) - } -} - pub type PyTypeRef = PyRef; cfg_if::cfg_if! { @@ -327,6 +316,8 @@ impl PyType { slots.basicsize = base.slots.basicsize; } + Self::inherit_readonly_slots(&mut slots, &base); + if let Some(qualname) = attrs.get(identifier!(ctx, __qualname__)) && !qualname.fast_isinstance(ctx.types.str_type) { @@ -383,6 +374,8 @@ impl PyType { slots.basicsize = base.slots.basicsize; } + Self::inherit_readonly_slots(&mut slots, &base); + let bases = PyRwLock::new(vec![base.clone()]); let mro = base.mro_map_collect(|x| x.to_owned()); @@ -459,6 +452,14 @@ impl PyType { } } + /// Inherit readonly slots from base type at creation time. + /// These slots are not AtomicCell and must be set before the type is used. + fn inherit_readonly_slots(slots: &mut PyTypeSlots, base: &Self) { + if slots.as_buffer.is_none() { + slots.as_buffer = base.slots.as_buffer; + } + } + /// Inherit slots from base type. typeobject.c: inherit_slots pub(crate) fn inherit_slots(&self, base: &Self) { macro_rules! copyslot { @@ -488,6 +489,7 @@ impl PyType { // TODO: implement proper init inheritance with object_init check copyslot!(del); // new is handled by set_new() + // as_buffer is inherited at type creation time (not AtomicCell) // Sub-slots (number, sequence, mapping) self.inherit_number_slots(base); @@ -817,19 +819,6 @@ impl Py { .collect() } - pub(crate) fn mro_find_map(&self, f: F) -> Option - where - F: Fn(&Self) -> Option, - { - // the hot path will be primitive types which usually hit the result from itself. - // try std::intrinsics::likely once it is stabilized - if let Some(r) = f(self) { - Some(r) - } else { - self.mro.read().iter().find_map(|cls| f(cls)) - } - } - pub fn iter_base_chain(&self) -> impl Iterator { std::iter::successors(Some(self), |cls| cls.base.as_deref()) } diff --git a/crates/vm/src/protocol/buffer.rs b/crates/vm/src/protocol/buffer.rs index 0a34af59080..88524a9a9ee 100644 --- a/crates/vm/src/protocol/buffer.rs +++ b/crates/vm/src/protocol/buffer.rs @@ -143,8 +143,7 @@ impl PyBuffer { impl<'a> TryFromBorrowedObject<'a> for PyBuffer { fn try_from_borrowed_object(vm: &VirtualMachine, obj: &'a PyObject) -> PyResult { let cls = obj.class(); - let as_buffer = cls.mro_find_map(|cls| cls.slots.as_buffer); - if let Some(f) = as_buffer { + if let Some(f) = cls.slots.as_buffer { return f(obj, vm); } Err(vm.new_type_error(format!( From c83101ffe577f6aef333bf95dae1f2439d867bd6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 25 Dec 2025 07:00:38 +0000 Subject: [PATCH 9/9] Auto-format: cargo fmt --all --- crates/vm/src/protocol/number.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/vm/src/protocol/number.rs b/crates/vm/src/protocol/number.rs index a13323c33d2..4f21a1e64f9 100644 --- a/crates/vm/src/protocol/number.rs +++ b/crates/vm/src/protocol/number.rs @@ -56,11 +56,7 @@ impl PyObject { if let Some(i) = self.downcast_ref_if_exact::(vm) { Ok(i.to_owned()) - } else if let Some(i) = self - .number() - .int(vm) - .or_else(|| self.try_index_opt(vm)) - { + } else if let Some(i) = self.number().int(vm).or_else(|| self.try_index_opt(vm)) { i } else if let Ok(Some(f)) = vm.get_special_method(self, identifier!(vm, __trunc__)) { warnings::warn(