From 057010b0ee2eff0adf4ecc32cbead108204ea0fa Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 24 Sep 2026 23:43:25 +0900 Subject: [PATCH 01/13] Add payload views and Py handle shortcuts PyTuple::payload_slice, PyBytes::payload_bytes, and PyStr::payload_wtf8 expose the payload data. Common payload methods are forwarded from Py. Assisted-by: Grok CLI:grok-4.7 --- crates/vm/src/builtins/bytes.rs | 11 +++++++++++ crates/vm/src/builtins/dict.rs | 10 ++++++++++ crates/vm/src/builtins/float.rs | 7 +++++++ crates/vm/src/builtins/int.rs | 15 +++++++++++++++ crates/vm/src/builtins/list.rs | 12 ++++++++++++ crates/vm/src/builtins/str.rs | 31 +++++++++++++++++++++++++++++++ crates/vm/src/builtins/tuple.rs | 28 ++++++++++++++++++++++++++++ crates/vm/src/builtins/type.rs | 25 +++++++++++++++++++++++++ 8 files changed, 139 insertions(+) diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index 11e6956ad13..1bcbcf56d57 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -550,6 +550,17 @@ impl PyBytes { #[pyclass] impl Py { + /// Borrowed bytes of the payload. + #[inline] + pub fn payload_bytes(&self) -> &[u8] { + self.payload().as_bytes() + } + + #[inline] + pub fn as_bytes(&self) -> &[u8] { + self.payload_bytes() + } + #[pymethod] fn __reduce_ex__( &self, diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index d22b97b165a..c158f4f45ed 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -750,6 +750,16 @@ impl Representable for PyDict { } impl Py { + #[inline] + pub fn is_empty(&self) -> bool { + self.payload().is_empty() + } + + #[inline] + pub fn size(&self) -> dict_inner::DictSize { + self.payload().size() + } + #[inline] fn exact_dict(&self, vm: &VirtualMachine) -> bool { self.class().is(vm.ctx.types.dict_type) diff --git a/crates/vm/src/builtins/float.rs b/crates/vm/src/builtins/float.rs index 154e19e63cb..36e05e8efaa 100644 --- a/crates/vm/src/builtins/float.rs +++ b/crates/vm/src/builtins/float.rs @@ -35,6 +35,13 @@ impl PyFloat { } } +impl Py { + #[inline] + pub fn to_f64(&self) -> f64 { + self.payload().to_f64() + } +} + thread_local! { static FLOAT_FREELIST: Cell> = const { Cell::new(crate::object::FreeList::new()) }; } diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index 31a497fd0cb..6e1a4cbfe89 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -747,6 +747,21 @@ impl AsNumber for PyInt { } } +impl Py { + #[inline] + pub fn as_bigint(&self) -> &BigInt { + self.payload().as_bigint() + } + + #[inline] + pub fn try_to_primitive<'a, I>(&'a self, vm: &VirtualMachine) -> PyResult + where + I: PrimInt + TryFrom<&'a BigInt>, + { + self.payload().try_to_primitive(vm) + } +} + impl PyInt { pub(super) const AS_NUMBER: PyNumberMethods = PyNumberMethods { add: Some(|a, b, vm| Self::number_op(a, b, |a, b, _vm| a + b, vm)), diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index c20c8db378e..b87140158a3 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -128,6 +128,18 @@ impl ToPyObject for Vec { } } +impl Py { + #[inline] + pub fn borrow_vec(&self) -> PyMappedRwLockReadGuard<'_, [PyObjectRef]> { + self.payload().borrow_vec() + } + + #[inline] + pub fn borrow_vec_mut(&self) -> PyRwLockWriteGuard<'_, Vec> { + self.payload().borrow_vec_mut() + } +} + impl PyList { #[deprecated(note = "use PyList::from(...).into_ref() instead")] pub fn new_ref(elements: Vec, ctx: &Context) -> PyRef { diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 98bcd217ca2..71d20e46bfd 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -629,6 +629,37 @@ impl PyStr { } impl Py { + /// Borrowed text of the payload. + #[inline] + pub fn payload_wtf8(&self) -> &Wtf8 { + self.payload().as_wtf8() + } + + #[inline] + pub fn as_wtf8(&self) -> &Wtf8 { + self.payload_wtf8() + } + + #[inline] + pub fn as_bytes(&self) -> &[u8] { + self.payload_wtf8().as_bytes() + } + + #[inline] + pub fn to_str(&self) -> Option<&str> { + self.payload().to_str() + } + + #[inline] + pub fn byte_len(&self) -> usize { + self.payload().byte_len() + } + + #[inline] + pub fn char_len(&self) -> usize { + self.payload().char_len() + } + pub fn as_utf8(&self) -> Option<&Py> { if self.is_utf8() { // SAFETY: is_utf8() guarantees the PyUtf8Str invariant. diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index b4edba7d996..390c7442f94 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -386,7 +386,35 @@ impl PyTuple { } } +impl Py> { + /// Element slice of the tuple payload. + #[inline] + pub fn payload_slice(&self) -> &[R] { + (&*self).as_slice() + } +} + impl Py { + #[inline] + pub fn as_slice(&self) -> &[PyObjectRef] { + self.payload_slice() + } + + #[inline] + pub fn len(&self) -> usize { + self.payload_slice().len() + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.payload_slice().is_empty() + } + + #[inline] + pub fn iter(&self) -> core::slice::Iter<'_, PyObjectRef> { + self.payload_slice().iter() + } + pub fn extract_tuple<'a, T: FromPyTuple<'a>>(&'a self, vm: &VirtualMachine) -> PyResult { T::from_pytuple(self, vm) } diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 63bcb4afbc0..eca3a3cd2b9 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1651,6 +1651,31 @@ impl PyType { } impl Py { + #[inline] + pub fn name(&self) -> BorrowedValue<'_, str> { + self.payload().name() + } + + #[inline] + pub fn slot_name(&self) -> BorrowedValue<'_, str> { + self.payload().slot_name() + } + + #[inline] + pub fn set_attr(&self, attr_name: &'static PyStrInterned, value: PyObjectRef) { + self.payload().set_attr(attr_name, value) + } + + #[inline] + pub fn get_attr(&self, attr_name: &'static PyStrInterned) -> Option { + self.payload().get_attr(attr_name) + } + + #[inline] + pub fn get_direct_attr(&self, attr_name: &'static PyStrInterned) -> Option { + self.payload().get_direct_attr(attr_name) + } + pub fn is_subtype(&self, other: &Self) -> bool { is_subtype_with_mro(&self.mro.read(), self, other) } From 3bcbf3635b1c7a332f89543ada3e186350998899 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 25 Sep 2026 00:23:58 +0900 Subject: [PATCH 02/13] Call PyTuple::as_slice from payload_slice `&*self` tripped clippy borrow_deref_ref and needless_borrow. Assisted-by: Grok CLI:grok-4.7 --- crates/vm/src/builtins/tuple.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index 390c7442f94..e9b0b375e65 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -390,7 +390,7 @@ impl Py> { /// Element slice of the tuple payload. #[inline] pub fn payload_slice(&self) -> &[R] { - (&*self).as_slice() + PyTuple::as_slice(self) } } From f2a2cb33bac00deb3c25f372179b27ab4c1e038b Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 25 Sep 2026 00:42:02 +0900 Subject: [PATCH 03/13] Use Element.is_empty in the truth-value check len() != 0 trips clippy::len_zero once is_empty is available. Assisted-by: Grok CLI:grok-4.7 --- crates/stdlib/src/elementtree.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/stdlib/src/elementtree.rs b/crates/stdlib/src/elementtree.rs index f72dd2a61be..d9c9362663e 100644 --- a/crates/stdlib/src/elementtree.rs +++ b/crates/stdlib/src/elementtree.rs @@ -239,6 +239,10 @@ pub(crate) mod _elementtree { self.inner.read().children.len() } + fn is_empty(&self) -> bool { + self.len() == 0 + } + fn child(&self, index: usize) -> Option { self.inner.read().children.get(index).cloned() } @@ -1209,7 +1213,7 @@ pub(crate) mod _elementtree { None, vm, )?; - Ok(zelf.len() != 0) + Ok(!zelf.is_empty()) }), ..PyNumberMethods::NOT_IMPLEMENTED }; From da310a5aeed840679b402f8f00cda6b7e63fe699 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 25 Sep 2026 08:45:18 +0900 Subject: [PATCH 04/13] Drop redundant payload view aliases as_bytes, as_wtf8, and as_slice already forward to the payload methods. Assisted-by: Grok CLI:grok-4.7 --- crates/vm/src/builtins/bytes.rs | 8 +------- crates/vm/src/builtins/str.rs | 10 ++-------- crates/vm/src/builtins/tuple.rs | 16 ++++------------ 3 files changed, 7 insertions(+), 27 deletions(-) diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index 1bcbcf56d57..c314b8500aa 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -550,15 +550,9 @@ impl PyBytes { #[pyclass] impl Py { - /// Borrowed bytes of the payload. - #[inline] - pub fn payload_bytes(&self) -> &[u8] { - self.payload().as_bytes() - } - #[inline] pub fn as_bytes(&self) -> &[u8] { - self.payload_bytes() + self.payload().as_bytes() } #[pymethod] diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 71d20e46bfd..3a0652b1883 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -629,20 +629,14 @@ impl PyStr { } impl Py { - /// Borrowed text of the payload. - #[inline] - pub fn payload_wtf8(&self) -> &Wtf8 { - self.payload().as_wtf8() - } - #[inline] pub fn as_wtf8(&self) -> &Wtf8 { - self.payload_wtf8() + self.payload().as_wtf8() } #[inline] pub fn as_bytes(&self) -> &[u8] { - self.payload_wtf8().as_bytes() + self.payload().as_bytes() } #[inline] diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index e9b0b375e65..dc471eb9347 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -386,33 +386,25 @@ impl PyTuple { } } -impl Py> { - /// Element slice of the tuple payload. - #[inline] - pub fn payload_slice(&self) -> &[R] { - PyTuple::as_slice(self) - } -} - impl Py { #[inline] pub fn as_slice(&self) -> &[PyObjectRef] { - self.payload_slice() + self.payload().as_slice() } #[inline] pub fn len(&self) -> usize { - self.payload_slice().len() + self.as_slice().len() } #[inline] pub fn is_empty(&self) -> bool { - self.payload_slice().is_empty() + self.as_slice().is_empty() } #[inline] pub fn iter(&self) -> core::slice::Iter<'_, PyObjectRef> { - self.payload_slice().iter() + self.as_slice().iter() } pub fn extract_tuple<'a, T: FromPyTuple<'a>>(&'a self, vm: &VirtualMachine) -> PyResult { From 09d43b63dbd7b92084494da11e3c6c61f52aadfe Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 25 Sep 2026 09:58:39 +0900 Subject: [PATCH 05/13] Forward more payload methods from Py handles Frame iframe, bytearray buffer, set add, exception args and traceback, and length or lookup helpers are available on Py. Assisted-by: Grok CLI:grok-4.7 --- crates/vm/src/builtins/bytearray.rs | 5 +++++ crates/vm/src/builtins/bytes.rs | 5 +++++ crates/vm/src/builtins/dict.rs | 10 ++++++++++ crates/vm/src/builtins/list.rs | 5 +++++ crates/vm/src/builtins/set.rs | 7 +++++++ crates/vm/src/builtins/str.rs | 5 +++++ crates/vm/src/builtins/type.rs | 5 +++++ crates/vm/src/exceptions.rs | 15 +++++++++++++++ crates/vm/src/frame.rs | 11 +++++++++++ 9 files changed, 68 insertions(+) diff --git a/crates/vm/src/builtins/bytearray.rs b/crates/vm/src/builtins/bytearray.rs index ba6847c8e8d..75459303bc3 100644 --- a/crates/vm/src/builtins/bytearray.rs +++ b/crates/vm/src/builtins/bytearray.rs @@ -569,6 +569,11 @@ impl PyByteArray { #[pyclass] impl Py { + #[inline] + pub fn borrow_buf(&self) -> PyMappedDetachingRwLockReadGuard<'_, [u8]> { + self.payload().borrow_buf() + } + fn __setitem__( &self, needle: &PyObject, diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index c314b8500aa..3e2bcc44679 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -555,6 +555,11 @@ impl Py { self.payload().as_bytes() } + #[inline] + pub fn len(&self) -> usize { + self.payload().__len__() + } + #[pymethod] fn __reduce_ex__( &self, diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index c158f4f45ed..f1198836b4a 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -760,6 +760,16 @@ impl Py { self.payload().size() } + #[inline] + pub fn __len__(&self) -> usize { + self.payload().__len__() + } + + #[inline] + pub fn contains_key(&self, key: &K, vm: &VirtualMachine) -> bool { + self.payload().contains_key(key, vm) + } + #[inline] fn exact_dict(&self, vm: &VirtualMachine) -> bool { self.class().is(vm.ctx.types.dict_type) diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index b87140158a3..ee7bedbace4 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -138,6 +138,11 @@ impl Py { pub fn borrow_vec_mut(&self) -> PyRwLockWriteGuard<'_, Vec> { self.payload().borrow_vec_mut() } + + #[inline] + pub fn __len__(&self) -> usize { + self.payload().__len__() + } } impl PyList { diff --git a/crates/vm/src/builtins/set.rs b/crates/vm/src/builtins/set.rs index ae4ddccdd49..e8dad07334b 100644 --- a/crates/vm/src/builtins/set.rs +++ b/crates/vm/src/builtins/set.rs @@ -1069,6 +1069,13 @@ impl Representable for PySet { } } +impl Py { + #[inline] + pub fn add(&self, object: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + self.payload().add(object, vm) + } +} + impl Constructor for PyFrozenSet { type Args = OptionalArg; diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 3a0652b1883..2eb83a6fa8c 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -654,6 +654,11 @@ impl Py { self.payload().char_len() } + #[inline] + pub fn to_string_lossy(&self) -> Cow<'_, str> { + self.payload().to_string_lossy() + } + pub fn as_utf8(&self) -> Option<&Py> { if self.is_utf8() { // SAFETY: is_utf8() guarantees the PyUtf8Str invariant. diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index eca3a3cd2b9..3ed065c837d 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1676,6 +1676,11 @@ impl Py { self.payload().get_direct_attr(attr_name) } + #[inline] + pub fn has_attr(&self, attr_name: &'static PyStrInterned) -> bool { + self.payload().has_attr(attr_name) + } + pub fn is_subtype(&self, other: &Self) -> bool { is_subtype_with_mro(&self.mro.read(), self, other) } diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 56a87980b5f..f1cec32fb1e 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -777,6 +777,21 @@ impl PyBaseException { #[pyclass] impl Py { + #[inline] + pub fn args(&self) -> PyTupleRef { + self.payload().args() + } + + #[inline] + pub fn __traceback__(&self) -> Option { + self.payload().__traceback__() + } + + #[inline] + pub fn set_traceback(&self, traceback: Option) { + self.payload().set_traceback(traceback) + } + #[pymethod] pub(super) fn __str__(&self, vm: &VirtualMachine) -> PyStrRef { let str_args = vm.exception_args_as_string(&self.args(), true); diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 7b94b38dde3..d29521f779d 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -2458,6 +2458,17 @@ impl FrameObject { } impl Py { + #[inline(always)] + pub fn iframe(&self) -> &InterpreterFrame { + self.payload().iframe() + } + + #[inline(always)] + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn iframe_mut(&self) -> &mut InterpreterFrame { + unsafe { self.payload().iframe_mut() } + } + #[inline(always)] fn with_exec(&self, vm: &VirtualMachine, f: impl FnOnce(ExecutingFrame<'_>) -> R) -> R { // SAFETY: FrameObject execution is single-threaded. Only one thread at a time From d1fd977b53f9a0bb39ee94138a60d2b53a3530e8 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 25 Sep 2026 10:00:23 +0900 Subject: [PATCH 06/13] Read type slots through payload in macros Generated subclass checks and MRO slot inheritance no longer use Deref to reach PyType.slots or attributes. Assisted-by: Grok CLI:grok-4.7 --- crates/derive-impl/src/pyclass.rs | 2 +- crates/derive-impl/src/pymodule.rs | 4 ++-- crates/vm/src/types/slot_defs.rs | 31 ++++++++++++++++++++---------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/crates/derive-impl/src/pyclass.rs b/crates/derive-impl/src/pyclass.rs index ccedcc59455..69698d3a5bf 100644 --- a/crates/derive-impl/src/pyclass.rs +++ b/crates/derive-impl/src/pyclass.rs @@ -764,7 +764,7 @@ pub(crate) fn impl_pyclass(attr: PunctuatedNestedMeta, item: Item) -> Result bool { - ::BASICSIZE <= obj.class().slots.basicsize && obj.class().fast_issubclass(::static_type()) + ::BASICSIZE <= obj.class().payload().slots.basicsize && obj.class().fast_issubclass(::static_type()) } fn class(ctx: &::rustpython_vm::vm::Context) -> &'static ::rustpython_vm::Py<::rustpython_vm::builtins::PyType> { diff --git a/crates/derive-impl/src/pymodule.rs b/crates/derive-impl/src/pymodule.rs index d0cf5657e33..398a551e44c 100644 --- a/crates/derive-impl/src/pymodule.rs +++ b/crates/derive-impl/src/pymodule.rs @@ -755,7 +755,7 @@ impl ModuleItem for ClassItem { // module resolution, e.g. TypeAliasType) { let module_key = rustpython_vm::identifier!(ctx, __module__); - let has_module_getset = new_class.attributes + let has_module_getset = new_class.payload().attributes .get(module_key) .is_some_and(|v| v.downcastable::()); if !has_module_getset { @@ -849,7 +849,7 @@ impl ModuleItem for StructSequenceItem { let new_class = <#pytype_ident as ::rustpython_vm::class::PyClassImpl>::make_static_type(); { let module_key = rustpython_vm::identifier!(ctx, __module__); - let has_module_getset = new_class.attributes + let has_module_getset = new_class.payload().attributes .get(module_key) .is_some_and(|v| v.downcastable::()); if !has_module_getset { diff --git a/crates/vm/src/types/slot_defs.rs b/crates/vm/src/types/slot_defs.rs index 9d1f2dd2141..ec393fb2b4b 100644 --- a/crates/vm/src/types/slot_defs.rs +++ b/crates/vm/src/types/slot_defs.rs @@ -427,14 +427,16 @@ impl SlotAccessor { macro_rules! inherit_main { ($slot:ident) => {{ - let inherited = mro.iter().find_map(|cls| cls.slots.$slot.load()); + let inherited = mro.iter().find_map(|cls| cls.payload().slots.$slot.load()); typ.slots.$slot.store(inherited); }}; } macro_rules! inherit_number { ($slot:ident) => {{ - let inherited = mro.iter().find_map(|cls| cls.slots.as_number.$slot.load()); + let inherited = mro + .iter() + .find_map(|cls| cls.payload().slots.as_number.$slot.load()); typ.slots.as_number.$slot.store(inherited); }}; } @@ -443,14 +445,16 @@ impl SlotAccessor { ($slot:ident) => {{ let inherited = mro .iter() - .find_map(|cls| cls.slots.as_sequence.$slot.load()); + .find_map(|cls| cls.payload().slots.as_sequence.$slot.load()); typ.slots.as_sequence.$slot.store(inherited); }}; } macro_rules! inherit_mapping { ($slot:ident) => {{ - let inherited = mro.iter().find_map(|cls| cls.slots.as_mapping.$slot.load()); + let inherited = mro + .iter() + .find_map(|cls| cls.payload().slots.as_mapping.$slot.load()); typ.slots.as_mapping.$slot.store(inherited); }}; } @@ -469,8 +473,8 @@ impl SlotAccessor { // vectorcall as a constructor fast path (call=None). // See vectorcall_type() in type.rs for the dual-use design rationale. let inherited_vc = mro.iter().find_map(|cls| { - if cls.slots.call.load().is_some() { - cls.slots.vectorcall.load() + if cls.payload().slots.call.load().is_some() { + cls.payload().slots.vectorcall.load() } else { None } @@ -543,13 +547,19 @@ impl SlotAccessor { // Buffer protocol Self::BfGetBuffer => { - let inherited = mro.iter().find_map(|cls| cls.slots.as_buffer.load()); + let inherited = mro + .iter() + .find_map(|cls| cls.payload().slots.as_buffer.load()); typ.slots.as_buffer.store(inherited); } Self::BfReleaseBuffer => { - let has_release = mro.iter().any(|cls| cls.slots.has_release_buffer.load()); + let has_release = mro + .iter() + .any(|cls| cls.payload().slots.has_release_buffer.load()); typ.slots.has_release_buffer.store(has_release); - let py_release = mro.iter().any(|cls| cls.slots.python_release_buffer.load()); + let py_release = mro + .iter() + .any(|cls| cls.payload().slots.python_release_buffer.load()); typ.slots.python_release_buffer.store(py_release); } @@ -623,7 +633,8 @@ impl SlotAccessor { && let Some(base_val) = base.slots.init.load() { let slot_defined = base.base.deref().is_none_or(|bb| { - bb.slots.init.load().map(|v| fn_addr(v)) != Some(fn_addr(base_val)) + bb.payload().slots.init.load().map(|v| fn_addr(v)) + != Some(fn_addr(base_val)) }); if slot_defined { typ.slots.init.store(Some(base_val)); From 669ae7ddb334a0054257d2731e10b906db8da292 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 25 Sep 2026 10:06:30 +0900 Subject: [PATCH 07/13] Index a few tuples through as_slice These Py indexes no longer rely on Deref to [T]. Assisted-by: Grok CLI:grok-4.7 --- crates/vm/src/builtins/genericalias.rs | 2 +- crates/vm/src/builtins/type.rs | 2 +- crates/vm/src/builtins/union.rs | 4 ++-- crates/vm/src/exceptions.rs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/vm/src/builtins/genericalias.rs b/crates/vm/src/builtins/genericalias.rs index 7ccdf4eff12..78dab27462d 100644 --- a/crates/vm/src/builtins/genericalias.rs +++ b/crates/vm/src/builtins/genericalias.rs @@ -369,7 +369,7 @@ fn subs_tvars( for arg in sub_params.iter() { if let Some(idx) = tuple_index(params.as_slice(), arg) { - let param = ¶ms[idx]; + let param = ¶ms.as_slice()[idx]; let substituted_arg = &arg_items[idx]; // Check if this is a TypeVarTuple (has tp_iter) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 3ed065c837d..6c817430861 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -797,7 +797,7 @@ impl PyType { interpreter_id: HeapTypeExt::creating_interpreter_id(), }; let bases = PyTuple::new_ref_typed(bases, ctx); - let base = bases[0].clone(); + let base = bases.as_slice()[0].clone(); Self::new_heap_inner( base, diff --git a/crates/vm/src/builtins/union.rs b/crates/vm/src/builtins/union.rs index a30c87f2cd7..e56421eaea3 100644 --- a/crates/vm/src/builtins/union.rs +++ b/crates/vm/src/builtins/union.rs @@ -365,7 +365,7 @@ fn dedup_and_flatten_args(args: &Py, vm: &VirtualMachine) -> PyResult, vm: &VirtualMachine) -> PyResult { let result = dedup_and_flatten_args(args, vm)?; Ok(match result.args.len() { - 1 => result.args[0].to_owned(), + 1 => result.args.as_slice()[0].to_owned(), _ => PyUnion::from_components(result, vm)?.to_pyobject(vm), }) } @@ -383,7 +383,7 @@ impl PyUnion { Ok(if new_args.is_empty() { make_union(&new_args, vm)? } else { - let mut tmp = new_args[0].to_owned(); + let mut tmp = new_args.as_slice()[0].to_owned(); for arg in new_args.iter().skip(1) { tmp = vm._or(&tmp, arg)?; } diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index f1cec32fb1e..0315a09d2ac 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -358,11 +358,11 @@ impl VirtualMachine { 0 => vec![], 1 => { let args0_repr = if str_single { - varargs[0] + varargs.as_slice()[0] .str(vm) .unwrap_or_else(|_| PyStr::from("").into_ref(&vm.ctx)) } else { - varargs[0].repr(vm).unwrap_or_else(|_| { + varargs.as_slice()[0].repr(vm).unwrap_or_else(|_| { PyStr::from("").into_ref(&vm.ctx) }) }; From 72d08a722baa2d254e70f248a893579e7a7db98b Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 25 Sep 2026 11:44:44 +0900 Subject: [PATCH 08/13] Read type slots through Py::slots obj.class().slots and builtin type slots now call slots() instead of the payload field. Assisted-by: Grok CLI:grok-4.7 --- crates/vm/src/builtins/bool.rs | 2 +- crates/vm/src/builtins/float.rs | 2 +- crates/vm/src/builtins/genericalias.rs | 2 +- crates/vm/src/builtins/list.rs | 2 +- crates/vm/src/builtins/memory.rs | 7 ++++++- crates/vm/src/builtins/object.rs | 20 ++++++++++++-------- crates/vm/src/builtins/type.rs | 18 ++++++++++++++---- crates/vm/src/builtins/weakproxy.rs | 4 ++-- crates/vm/src/class.rs | 2 +- crates/vm/src/frame.rs | 14 +++++++------- crates/vm/src/object/core.rs | 4 ++-- crates/vm/src/protocol/buffer.rs | 4 ++-- crates/vm/src/protocol/callable.rs | 2 +- crates/vm/src/protocol/iter.rs | 4 ++-- crates/vm/src/protocol/mapping.rs | 2 +- crates/vm/src/protocol/number.rs | 10 +++++----- crates/vm/src/protocol/object.rs | 16 ++++++++-------- crates/vm/src/protocol/sequence.rs | 2 +- crates/vm/src/stdlib/_ctypes/structure.rs | 2 +- crates/vm/src/stdlib/_ctypes/union.rs | 2 +- crates/vm/src/vm/mod.rs | 2 +- crates/vm/src/vm/vm_object.rs | 2 +- crates/vm/src/vm/vm_ops.rs | 4 ++-- 23 files changed, 74 insertions(+), 55 deletions(-) diff --git a/crates/vm/src/builtins/bool.rs b/crates/vm/src/builtins/bool.rs index 6101fd62fd4..53e96998279 100644 --- a/crates/vm/src/builtins/bool.rs +++ b/crates/vm/src/builtins/bool.rs @@ -52,7 +52,7 @@ impl PyObject { #[cold] #[inline(never)] fn try_to_bool_slow(&self, vm: &VirtualMachine) -> PyResult { - let slots = &self.class().slots; + let slots = self.class().slots(); // 1. Try nb_bool slot first if let Some(nb_bool) = slots.as_number.boolean.load() { diff --git a/crates/vm/src/builtins/float.rs b/crates/vm/src/builtins/float.rs index 36e05e8efaa..2cb52ad6bd6 100644 --- a/crates/vm/src/builtins/float.rs +++ b/crates/vm/src/builtins/float.rs @@ -567,5 +567,5 @@ fn vectorcall_float( #[rustfmt::skip] // to avoid line splitting pub(crate) fn init(context: &'static Context) { PyFloat::extend_class(context, context.types.float_type); - context.types.float_type.slots.vectorcall.store(Some(vectorcall_float)); + context.types.float_type.slots().vectorcall.store(Some(vectorcall_float)); } diff --git a/crates/vm/src/builtins/genericalias.rs b/crates/vm/src/builtins/genericalias.rs index 78dab27462d..b0780405431 100644 --- a/crates/vm/src/builtins/genericalias.rs +++ b/crates/vm/src/builtins/genericalias.rs @@ -373,7 +373,7 @@ fn subs_tvars( let substituted_arg = &arg_items[idx]; // Check if this is a TypeVarTuple (has tp_iter) - if param.class().slots.iter.load().is_some() + if param.class().slots().iter.load().is_some() && substituted_arg.try_to_ref::(vm).is_ok() { // TypeVarTuple case - extend with tuple elements diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index ee7bedbace4..a417d658196 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -809,7 +809,7 @@ fn object_lt( vm: &VirtualMachine, ) -> PyResult { #[allow(unpredictable_function_pointer_comparisons)] - if a.class().slots.richcompare.load() != Some(cmp) { + if a.class().slots().richcompare.load() != Some(cmp) { return a.rich_compare_bool(b, PyComparisonOp::Lt, vm); } match cmp(a, b, PyComparisonOp::Lt, vm)? { diff --git a/crates/vm/src/builtins/memory.rs b/crates/vm/src/builtins/memory.rs index cb53dbdc95e..cd567255e2a 100644 --- a/crates/vm/src/builtins/memory.rs +++ b/crates/vm/src/builtins/memory.rs @@ -1474,7 +1474,12 @@ static BUFFER_WRAPPER_METHODS: BufferMethods = BufferMethods { // A native release runs when the memoryview itself is torn down; only a // Python-level hook on a foreign exporter has to be called here. if !mv.buffer.obj.is(&wrapper.exporter) - && wrapper.exporter.class().slots.python_release_buffer.load() + && wrapper + .exporter + .class() + .slots() + .python_release_buffer + .load() { call_python_release_buffer(&wrapper.exporter, mv.clone()); } diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index 01b56b4e70a..bc29e530b3a 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -186,7 +186,7 @@ fn type_slot_names(typ: &Py, vm: &VirtualMachine) -> PyResult PyResult { // Check itemsize - if required && obj.class().slots.itemsize > 0 { + if required && obj.class().slots().itemsize > 0 { return Err(vm.new_type_error(format!("cannot pickle {:.200} objects", obj.class().name()))); } @@ -205,10 +205,10 @@ fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine) if required { // Start with PyBaseObject_Type's basicsize - let mut basicsize = vm.ctx.types.object_type.slots.basicsize; + let mut basicsize = vm.ctx.types.object_type.slots().basicsize; // Add __dict__ size if type has dict - if obj.class().slots.flags.has_feature(PyTypeFlags::HAS_DICT) { + if obj.class().slots().flags.has_feature(PyTypeFlags::HAS_DICT) { basicsize += core::mem::size_of::(); } @@ -220,7 +220,7 @@ fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine) } } else { let weakref_name = vm.ctx.intern_str("__weakref__"); - obj.class().attributes.contains(weakref_name) + obj.class().attributes().contains(weakref_name) }; if has_weakref { basicsize += core::mem::size_of::(); @@ -232,7 +232,7 @@ fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine) } // Fail if actual type's basicsize > expected basicsize - if obj.class().slots.basicsize > basicsize { + if obj.class().slots().basicsize > basicsize { return Err(vm.new_type_error(format!("cannot pickle '{}' object", obj.class().name()))); } } @@ -326,7 +326,7 @@ impl PyBaseObject { } } PyComparisonOp::Ne => { - let cmp = zelf.class().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)) @@ -524,7 +524,7 @@ impl PyBaseObject { #[pymethod] fn __sizeof__(zelf: PyObjectRef) -> usize { - zelf.class().slots.basicsize + zelf.class().slots().basicsize } } @@ -580,7 +580,11 @@ pub fn object_generic_set_dict( pub(crate) fn init(ctx: &'static Context) { // Manually set alloc/init slots - derive macro doesn't generate extend_slots // for trait impl that overrides #[pyslot] method - ctx.types.object_type.slots.alloc.store(Some(generic_alloc)); + ctx.types + .object_type + .slots() + .alloc + .store(Some(generic_alloc)); ctx.types .object_type .slots diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 6c817430861..81328f54230 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1681,6 +1681,16 @@ impl Py { self.payload().has_attr(attr_name) } + #[inline] + pub fn slots(&self) -> &PyTypeSlots { + &self.payload().slots + } + + #[inline] + pub fn attributes(&self) -> &TypeNamespace { + &self.payload().attributes + } + pub fn is_subtype(&self, other: &Self) -> bool { is_subtype_with_mro(&self.mro.read(), self, other) } @@ -3045,7 +3055,7 @@ impl GetAttr for PyType { let zelf_attr = zelf.get_attr(name); if let Some(attr) = zelf_attr { - let descr_get = attr.class().slots.descr_get.load(); + let descr_get = attr.class().slots().descr_get.load(); if let Some(descr_get) = descr_get { descr_get(attr.as_object(), None, Some(zelf.as_object()), vm) } else { @@ -3088,7 +3098,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().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.as_object(), None, Some(self.as_object()), vm) } else { @@ -3173,7 +3183,7 @@ impl SetAttr for PyType { ))); } if let Some(attr) = zelf.get_class_attr(attr_name) { - let descr_set = attr.class().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); } @@ -3269,7 +3279,7 @@ impl Callable for PyType { return Ok(obj); } - if let Some(init_method) = obj.class().slots.init.load() { + if let Some(init_method) = obj.class().slots().init.load() { init_method(&obj, init_args, vm)?; } Ok(obj) diff --git a/crates/vm/src/builtins/weakproxy.rs b/crates/vm/src/builtins/weakproxy.rs index 0a959e4c9f6..0006674a9fb 100644 --- a/crates/vm/src/builtins/weakproxy.rs +++ b/crates/vm/src/builtins/weakproxy.rs @@ -23,7 +23,7 @@ impl PyPayload for PyWeakProxy { #[inline] unsafe fn validate_downcastable_from(obj: &PyObject) -> bool { - ::BASICSIZE <= obj.class().slots.basicsize + ::BASICSIZE <= obj.class().slots().basicsize && obj .class() .fast_issubclass(::static_type()) @@ -170,7 +170,7 @@ impl Iterable for PyWeakProxy { impl IterNext for PyWeakProxy { fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { let obj = zelf.try_upgrade(vm)?; - if obj.class().slots.iternext.load().is_none() { + if obj.class().slots().iternext.load().is_none() { return Err(vm.new_type_error("Weakref proxy referenced a non-iterator")); } PyIter::new(obj).next(vm) diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index c9d341e9337..f69075696e5 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -203,7 +203,7 @@ pub trait PyClassImpl: PyClassDef { // (Python doesn't add __new__ to __dict__ for inherited slots) // Exception: object itself should have __new__ in its dict if let Some(slot_new) = class.slots.new.load() { - let object_new = ctx.types.object_type.slots.new.load(); + let object_new = ctx.types.object_type.slots().new.load(); let is_object_itself = core::ptr::eq(class, ctx.types.object_type); let is_inherited_from_object = !is_object_itself && object_new.is_some_and(|obj_new| fn_addr(slot_new) == fn_addr(obj_new)); diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index d29521f779d..d1e0c43c5bf 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -4918,7 +4918,7 @@ impl ExecutingFrame<'_> { // Only rewrite the error if the type is truly not iterable // (no __iter__ and no __getitem__). Preserve original TypeError // from custom iterables that raise during iteration. - let not_iterable = iterable.class().slots.iter.load().is_none() + let not_iterable = iterable.class().slots().iter.load().is_none() && iterable .get_class_attr(vm.ctx.intern_str("__getitem__")) .is_none(); @@ -8786,7 +8786,7 @@ impl ExecutingFrame<'_> { // Stack: [callable, self_or_null] let callable = self.nth_value(1); let func_str = Self::object_function_str(callable, vm); - let not_iterable = args_obj.class().slots.iter.load().is_none() + let not_iterable = args_obj.class().slots().iter.load().is_none() && args_obj .get_class_attr(vm.ctx.intern_str("__getitem__")) .is_none(); @@ -9168,7 +9168,7 @@ impl ExecutingFrame<'_> { fn execute_unpack_ex(&mut self, vm: &VirtualMachine, before: u8, after: u32) -> FrameResult { let (before, after) = (before as usize, after as usize); let value = self.pop_value(); - let not_iterable = value.class().slots.iter.load().is_none() + let not_iterable = value.class().slots().iter.load().is_none() && value .get_class_attr(vm.ctx.intern_str("__getitem__")) .is_none(); @@ -9697,7 +9697,7 @@ impl ExecutingFrame<'_> { // General path — iterate up to `size + 1` elements to avoid // consuming the entire iterator (fixes hang on infinite sequences). - let not_iterable = value.class().slots.iter.load().is_none() + let not_iterable = value.class().slots().iter.load().is_none() && value .get_class_attr(vm.ctx.intern_str("__getitem__")) .is_none(); @@ -10202,7 +10202,7 @@ impl ExecutingFrame<'_> { }); let has_descr_get = cls_attr .as_ref() - .is_some_and(|descr| descr.class().slots.descr_get.load().is_some()); + .is_some_and(|descr| descr.class().slots().descr_get.load().is_some()); if has_data_descr { // Check for member descriptor (slot access) @@ -11106,9 +11106,9 @@ impl ExecutingFrame<'_> { // concurrently installed __new__ invalidates the version this // specialization is cached against. let type_version = cls.version_for_specialization(vm); - let object_new = vm.ctx.types.object_type.slots.new.load(); + let object_new = vm.ctx.types.object_type.slots().new.load(); let cls_new = cls.slots.new.load(); - let object_alloc = vm.ctx.types.object_type.slots.alloc.load(); + let object_alloc = vm.ctx.types.object_type.slots().alloc.load(); let cls_alloc = cls.slots.alloc.load(); if let (Some(cls_new_fn), Some(obj_new_fn), Some(cls_alloc_fn), Some(obj_alloc_fn)) = (cls_new, object_new, cls_alloc, object_alloc) diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 8feaeb5b81c..280f3bf917e 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -2038,7 +2038,7 @@ impl PyObject { // __del__ should only be called once (like _PyGC_FINALIZED check in GIL_DISABLED) // We call __del__ BEFORE clearing weakrefs to allow the finalizer to access // the object's weak references if needed. - let del = self.class().slots.del.load(); + let del = self.class().slots().del.load(); if let Some(slot_del) = del && !self.gc_finalized() { @@ -2148,7 +2148,7 @@ impl PyObject { /// This allows proper resurrection detection. /// PyObject_CallFinalizerFromDealloc pub fn try_call_finalizer(&self) { - let del = self.class().slots.del.load(); + let del = self.class().slots().del.load(); if let Some(slot_del) = del && !self.gc_finalized() && self diff --git a/crates/vm/src/protocol/buffer.rs b/crates/vm/src/protocol/buffer.rs index 9a184ee853f..500ea5992f7 100644 --- a/crates/vm/src/protocol/buffer.rs +++ b/crates/vm/src/protocol/buffer.rs @@ -312,7 +312,7 @@ impl PyBuffer { } // slot_bf_releasebuffer: a Python-level `__release_buffer__` runs first, // then the exporter's own release so export counts stay balanced. - if self.obj.class().slots.python_release_buffer.load() { + if self.obj.class().slots().python_release_buffer.load() { crate::builtins::memory::release_buffer_call_python(self); } (self.methods.release)(self) @@ -368,7 +368,7 @@ impl PyObject { /// `__buffer__` is not mistaken for "not a buffer". #[must_use] pub fn check_buffer(&self) -> bool { - self.class().slots.as_buffer.load().is_some() + self.class().slots().as_buffer.load().is_some() } } diff --git a/crates/vm/src/protocol/callable.rs b/crates/vm/src/protocol/callable.rs index 0bcd756d154..49172a15222 100644 --- a/crates/vm/src/protocol/callable.rs +++ b/crates/vm/src/protocol/callable.rs @@ -66,7 +66,7 @@ pub struct PyCallable<'a> { impl<'a> PyCallable<'a> { pub fn new(obj: &'a PyObject) -> Option { - let slots = &obj.class().slots; + let slots = obj.class().slots(); let call = slots.call.load()?; let vectorcall = slots.vectorcall.load(); Some(PyCallable { diff --git a/crates/vm/src/protocol/iter.rs b/crates/vm/src/protocol/iter.rs index d2001e96699..9322890f68e 100644 --- a/crates/vm/src/protocol/iter.rs +++ b/crates/vm/src/protocol/iter.rs @@ -26,7 +26,7 @@ unsafe impl> Traverse for PyIter { impl PyIter { pub fn check(obj: &PyObject) -> bool { - obj.class().slots.iternext.load().is_some() + obj.class().slots().iternext.load().is_some() } } @@ -136,7 +136,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 = iter_target.class().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/mapping.rs b/crates/vm/src/protocol/mapping.rs index 01db553ccbf..e791757901e 100644 --- a/crates/vm/src/protocol/mapping.rs +++ b/crates/vm/src/protocol/mapping.rs @@ -113,7 +113,7 @@ impl PyMapping<'_> { #[inline] #[must_use] pub fn slots(&self) -> &PyMappingSlots { - &self.obj.class().slots.as_mapping + &self.obj.class().slots().as_mapping } #[inline] diff --git a/crates/vm/src/protocol/number.rs b/crates/vm/src/protocol/number.rs index 2bf9d896b77..f3bd82da7a5 100644 --- a/crates/vm/src/protocol/number.rs +++ b/crates/vm/src/protocol/number.rs @@ -661,12 +661,12 @@ impl PyNumber<'_> { // PyIndex_Check #[must_use] pub fn is_index(self) -> bool { - self.class().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().slots.as_number.int.load().map(|f| { + self.class().slots().as_number.int.load().map(|f| { let ret = f(self, vm)?; if let Some(ret) = ret.downcast_ref_if_exact::(vm) { @@ -695,7 +695,7 @@ and may be removed in a future version of Python.", #[inline] pub fn index(self, vm: &VirtualMachine) -> Option> { - self.class().slots.as_number.index.load().map(|f| { + self.class().slots().as_number.index.load().map(|f| { let ret = f(self, vm)?; if let Some(ret) = ret.downcast_ref_if_exact::(vm) { @@ -724,7 +724,7 @@ and may be removed in a future version of Python.", #[inline] pub fn float(self, vm: &VirtualMachine) -> Option>> { - self.class().slots.as_number.float.load().map(|f| { + self.class().slots().as_number.float.load().map(|f| { let ret = f(self, vm)?; if let Some(ret) = ret.downcast_ref_if_exact::(vm) { @@ -756,7 +756,7 @@ and may be removed in a future version of Python.", // PyNumber_Check - slots are now inherited #[must_use] pub fn check(obj: &PyObject) -> bool { - let methods = &obj.class().slots.as_number; + 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(); diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index c3662b321e4..f7236ad3930 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -134,7 +134,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().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()); }) @@ -191,7 +191,7 @@ impl PyObject { .interned_str(attr_name) .and_then(|attr_name| self.get_class_attr(attr_name)); if let Some(attr) = &descr - && let Some(descriptor) = attr.class().slots.descr_set.load() + && let Some(descriptor) = attr.class().slots().descr_set.load() { return descriptor(attr, self.to_owned(), value, vm); } @@ -205,7 +205,7 @@ impl PyObject { } // Only a type that left __setattr__ alone can be told about the // missing __dict__, since overriding it is what hides the slot. - let generic_setattro = self.class().slots.setattro.load().is_some_and(|f| { + let generic_setattro = self.class().slots().setattro.load().is_some_and(|f| { crate::types::fn_addr(f) == crate::types::fn_addr( PyBaseObject::slot_setattro as crate::types::SetattroFunc, @@ -338,7 +338,7 @@ impl PyObject { ) -> PyResult> { let swapped = op.swapped(); let call_cmp = |obj: &Self, other: &Self, op| { - let Some(cmp) = obj.class().slots.richcompare.load() else { + let Some(cmp) = obj.class().slots().richcompare.load() else { return Ok(PyArithmeticValue::NotImplemented); }; let r = match cmp(obj, other, op, vm)? { @@ -420,7 +420,7 @@ impl PyObject { pub fn repr(&self, vm: &VirtualMachine) -> PyResult> { vm.with_recursion("while getting the repr of an object", || { - self.class().slots.repr.load().map_or_else( + 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.", @@ -460,7 +460,7 @@ impl PyObject { Err(obj) => obj, }; - // TODO: replace to obj.class().slots.str + // TODO: replace to obj.class().slots().str let Some(str_method) = vm.get_special_method(&obj, identifier!(vm, __str__))? else { return obj.repr(vm); }; @@ -720,7 +720,7 @@ impl PyObject { } pub fn hash(&self, vm: &VirtualMachine) -> PyResult { - if let Some(hash) = self.class().slots.hash.load() { + if let Some(hash) = self.class().slots().hash.load() { return vm.with_recursion("while hashing", || hash(self, vm)); } @@ -859,7 +859,7 @@ impl PyObject { return Ok(None); }; - let descr_get = res.class().slots.descr_get.load(); + let descr_get = res.class().slots().descr_get.load(); if let Some(descr_get) = descr_get { descr_get(res.as_object(), Some(self), Some(obj_cls.as_object()), vm).map(Some) } else { diff --git a/crates/vm/src/protocol/sequence.rs b/crates/vm/src/protocol/sequence.rs index 0c0bb503718..241f71398d6 100644 --- a/crates/vm/src/protocol/sequence.rs +++ b/crates/vm/src/protocol/sequence.rs @@ -153,7 +153,7 @@ impl PySequence<'_> { #[inline] #[must_use] pub fn slots(&self) -> &PySequenceSlots { - &self.obj.class().slots.as_sequence + &self.obj.class().slots().as_sequence } #[must_use] diff --git a/crates/vm/src/stdlib/_ctypes/structure.rs b/crates/vm/src/stdlib/_ctypes/structure.rs index 41c7af418aa..8539c0caa16 100644 --- a/crates/vm/src/stdlib/_ctypes/structure.rs +++ b/crates/vm/src/stdlib/_ctypes/structure.rs @@ -634,7 +634,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().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 c1d49374e3f..f02aef3049d 100644 --- a/crates/vm/src/stdlib/_ctypes/union.rs +++ b/crates/vm/src/stdlib/_ctypes/union.rs @@ -487,7 +487,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().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/mod.rs b/crates/vm/src/vm/mod.rs index c0750c7b871..1d68a1be0a8 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -3439,7 +3439,7 @@ impl VirtualMachine { attr_name: impl AsPyStr<'a>, ) -> PyResult> { let attr_name = attr_name.as_pystr(&self.ctx); - let getattro = obj.class().slots.getattro.load().unwrap(); + let getattro = obj.class().slots().getattro.load().unwrap(); let result = if fn_addr(getattro) == fn_addr(PyBaseObject::getattro as GetattroFunc) { obj.generic_getattr_opt(attr_name, None, self) } else { diff --git a/crates/vm/src/vm/vm_object.rs b/crates/vm/src/vm/vm_object.rs index 789979addcd..baa0c8095c6 100644 --- a/crates/vm/src/vm/vm_object.rs +++ b/crates/vm/src/vm/vm_object.rs @@ -109,7 +109,7 @@ impl VirtualMachine { obj: Option<&PyObject>, cls: Option<&PyObject>, ) -> Option { - let descr_get = descr.class().slots.descr_get.load()?; + let descr_get = descr.class().slots().descr_get.load()?; Some(descr_get(descr, obj, cls, self)) } diff --git a/crates/vm/src/vm/vm_ops.rs b/crates/vm/src/vm/vm_ops.rs index 958ba65ff4c..2e2c11f81ab 100644 --- a/crates/vm/src/vm/vm_ops.rs +++ b/crates/vm/src/vm/vm_ops.rs @@ -292,7 +292,7 @@ impl VirtualMachine { iop_slot: PyNumberBinaryOp, op_slot: PyNumberBinaryOp, ) -> PyResult { - if let Some(slot) = a.class().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); @@ -419,7 +419,7 @@ impl VirtualMachine { op_slot: PyNumberTernaryOp, op_str: &str, ) -> PyResult { - if let Some(slot) = a.class().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 de5e7e4e30b58f17df4fe6569d9cf0b36f5e7780 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 25 Sep 2026 11:50:55 +0900 Subject: [PATCH 09/13] Read type version and heap layout through Py Inline-cache checks use tp_version_tag(). Heap-type weakref checks use heaptype_ext(). Assisted-by: Grok CLI:grok-4.7 --- crates/vm/src/builtins/object.rs | 2 +- crates/vm/src/builtins/type.rs | 10 ++++++++ crates/vm/src/frame.rs | 39 ++++++++++++++++++-------------- crates/vm/src/protocol/object.rs | 2 +- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index bc29e530b3a..d947389d695 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -213,7 +213,7 @@ fn object_getstate_default(obj: &PyObject, required: bool, vm: &VirtualMachine) } // Add __weakref__ size if type has weakref support - let has_weakref = if let Some(ref ext) = obj.class().heaptype_ext { + let has_weakref = if let Some(ext) = obj.class().heaptype_ext() { match &ext.slots { None => true, // Heap type without __slots__ has automatic weakref Some(slots) => slots.iter().any(|s| s.as_bytes() == b"__weakref__"), diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 81328f54230..86a9933eb81 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1691,6 +1691,16 @@ impl Py { &self.payload().attributes } + #[inline] + pub fn tp_version_tag(&self) -> &AtomicU32 { + &self.payload().tp_version_tag + } + + #[inline] + pub fn heaptype_ext(&self) -> Option<&HeapTypeExt> { + self.payload().heaptype_ext.as_deref() + } + pub fn is_subtype(&self, other: &Self) -> bool { is_subtype_with_mro(&self.mro.read(), self, other) } diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index d1e0c43c5bf..9969fd19c88 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -6015,7 +6015,7 @@ impl ExecutingFrame<'_> { let type_version = self.code.instructions.read_cache_u32(cache_base + 1); if type_version != 0 - && owner.class().tp_version_tag.load(Acquire) == type_version + && owner.class().tp_version_tag().load(Acquire) == type_version && let Some(func) = self.try_read_cached_descriptor(cache_base, type_version) { let owner = self.pop_stackref(); @@ -6034,7 +6034,7 @@ impl ExecutingFrame<'_> { let type_version = self.code.instructions.read_cache_u32(cache_base + 1); if type_version != 0 - && owner.class().tp_version_tag.load(Acquire) == type_version + && owner.class().tp_version_tag().load(Acquire) == type_version && !owner.has_instance_dict() && let Some(func) = self.try_read_cached_descriptor(cache_base, type_version) { @@ -6054,7 +6054,8 @@ impl ExecutingFrame<'_> { let owner = self.top_value(); let type_version = self.code.instructions.read_cache_u32(cache_base + 1); - if type_version != 0 && owner.class().tp_version_tag.load(Acquire) == type_version { + if type_version != 0 && owner.class().tp_version_tag().load(Acquire) == type_version + { // Check instance dict doesn't shadow the method. let shadowed = match self.shadowing_instance_attr(cache_base, attr_name, vm) { Ok(shadowed) => shadowed.is_some(), @@ -6082,7 +6083,8 @@ impl ExecutingFrame<'_> { let owner = self.top_value(); let type_version = self.code.instructions.read_cache_u32(cache_base + 1); - if type_version != 0 && owner.class().tp_version_tag.load(Acquire) == type_version { + if type_version != 0 && owner.class().tp_version_tag().load(Acquire) == type_version + { // Type version matches — no data descriptor for this attr. // Try direct dict lookup, skipping full descriptor protocol. if let Some(dict) = owner.dict() @@ -6105,7 +6107,7 @@ impl ExecutingFrame<'_> { let type_version = self.code.instructions.read_cache_u32(cache_base + 1); if type_version != 0 - && owner.class().tp_version_tag.load(Acquire) == type_version + && owner.class().tp_version_tag().load(Acquire) == type_version && let Some(dict) = owner.dict() { // Try the cached entry index first; a hit is an identity @@ -6143,7 +6145,7 @@ impl ExecutingFrame<'_> { let type_version = self.code.instructions.read_cache_u32(cache_base + 1); if type_version != 0 - && owner.class().tp_version_tag.load(Acquire) == type_version + && owner.class().tp_version_tag().load(Acquire) == type_version && let Some(module) = owner.downcast_ref_if_exact::(vm) && let Ok(value) = module.get_attr(attr_name, vm) { @@ -6166,7 +6168,7 @@ impl ExecutingFrame<'_> { let type_version = self.code.instructions.read_cache_u32(cache_base + 1); if type_version != 0 - && owner.class().tp_version_tag.load(Acquire) == type_version + && owner.class().tp_version_tag().load(Acquire) == type_version && let Some(attr) = self.try_read_cached_descriptor(cache_base, type_version) { self.pop_stackref(); @@ -6188,7 +6190,8 @@ impl ExecutingFrame<'_> { let owner = self.top_value(); let type_version = self.code.instructions.read_cache_u32(cache_base + 1); - if type_version != 0 && owner.class().tp_version_tag.load(Acquire) == type_version { + if type_version != 0 && owner.class().tp_version_tag().load(Acquire) == type_version + { // Instance dict has priority — check if attr is shadowed if let Some(value) = self.shadowing_instance_attr(cache_base, attr_name, vm)? { self.pop_stackref(); @@ -6251,7 +6254,7 @@ impl ExecutingFrame<'_> { && metaclass_version != 0 && let Some(owner_type) = owner.downcast_ref::() && owner_type.tp_version_tag.load(Acquire) == type_version - && owner.class().tp_version_tag.load(Acquire) == metaclass_version + && owner.class().tp_version_tag().load(Acquire) == metaclass_version && let Some(attr) = self.try_read_cached_descriptor(cache_base, type_version) { self.pop_stackref(); @@ -6276,7 +6279,7 @@ impl ExecutingFrame<'_> { && !self.specialization_eval_frame_active(vm) && type_version != 0 && func_version != 0 - && owner.class().tp_version_tag.load(Acquire) == type_version + && owner.class().tp_version_tag().load(Acquire) == type_version && let Some(func_obj) = self.try_read_cached_descriptor(cache_base, type_version) && let Some(func) = func_obj.downcast_ref_if_exact::(vm) @@ -6300,7 +6303,8 @@ impl ExecutingFrame<'_> { let owner = self.top_value(); let type_version = self.code.instructions.read_cache_u32(cache_base + 1); - if type_version != 0 && owner.class().tp_version_tag.load(Acquire) == type_version { + if type_version != 0 && owner.class().tp_version_tag().load(Acquire) == type_version + { let slot_offset = self.code.instructions.read_cache_u32(cache_base + 3) as usize; if let Some(value) = owner.get_slot(slot_offset) { @@ -6326,7 +6330,7 @@ impl ExecutingFrame<'_> { if type_version != 0 && !self.specialization_eval_frame_active(vm) - && owner.class().tp_version_tag.load(Acquire) == type_version + && owner.class().tp_version_tag().load(Acquire) == type_version && let Some(fget_obj) = self.try_read_cached_descriptor(cache_base, type_version) && let Some(func) = fget_obj.downcast_ref_if_exact::(vm) @@ -6349,7 +6353,7 @@ impl ExecutingFrame<'_> { let type_version = self.code.instructions.read_cache_u32(cache_base + 1); if type_version != 0 - && owner.class().tp_version_tag.load(Acquire) == type_version + && owner.class().tp_version_tag().load(Acquire) == type_version && let Some(dict) = owner.dict() { self.pop_stackref(); // owner @@ -6371,7 +6375,7 @@ impl ExecutingFrame<'_> { let type_version = self.code.instructions.read_cache_u32(cache_base + 1); if type_version != 0 - && owner.class().tp_version_tag.load(Acquire) == type_version + && owner.class().tp_version_tag().load(Acquire) == type_version && let Some(dict) = owner.dict() { self.pop_stackref(); // owner @@ -6387,7 +6391,7 @@ impl ExecutingFrame<'_> { let type_version = self.code.instructions.read_cache_u32(cache_base + 1); let version_match = type_version != 0 && { let owner = self.top_value(); - owner.class().tp_version_tag.load(Acquire) == type_version + owner.class().tp_version_tag().load(Acquire) == type_version }; if version_match { @@ -6473,7 +6477,7 @@ impl ExecutingFrame<'_> { let owner = self.nth_value(1); if !self.specialization_eval_frame_active(vm) && type_version != 0 - && owner.class().tp_version_tag.load(Acquire) == type_version + && owner.class().tp_version_tag().load(Acquire) == type_version && let Some((func, func_version)) = owner.class().get_cached_getitem_for_specialization() && func.func_version() == func_version @@ -7706,7 +7710,8 @@ impl ExecutingFrame<'_> { let cache_base = instr_idx + 1; let obj = self.top_value(); let cached_version = self.code.instructions.read_cache_u32(cache_base + 1); - if cached_version != 0 && obj.class().tp_version_tag.load(Acquire) == cached_version + if cached_version != 0 + && obj.class().tp_version_tag().load(Acquire) == cached_version { self.pop_stackref(); self.push_bool_or_fused_jump(instruction.cache_entries(), true, vm); diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index f7236ad3930..e9abad45ff1 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -837,7 +837,7 @@ impl PyObject { // A type carrying a sequence table turns the deletion down in // PySequence_DelItem's words instead; every heap type carries one. let name = self.class().slot_name(); - let msg = if seq.slots().has_any() || self.class().heaptype_ext.is_some() { + let msg = if seq.slots().has_any() || self.class().heaptype_ext().is_some() { format!("'{name}' object doesn't support item deletion") } else { format!("'{name}' object does not support item deletion") From 1b505986f9ccfb95805786d41590db1af10b67df Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 25 Sep 2026 12:12:53 +0900 Subject: [PATCH 10/13] Call slots() on Py type handles Type creation, type methods, and frame specialization read slots through Py instead of the payload field. Assisted-by: Grok CLI:grok-4.7 --- crates/vm/src/builtins/type.rs | 20 ++++++++------- crates/vm/src/byte.rs | 2 +- crates/vm/src/class.rs | 16 ++++++------ crates/vm/src/frame.rs | 45 +++++++++++++++++----------------- 4 files changed, 43 insertions(+), 40 deletions(-) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 86a9933eb81..83e05ab17e7 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1772,7 +1772,7 @@ impl PyType { fn set_bases(zelf: &Py, bases_tuple: PyTupleRef, vm: &VirtualMachine) -> PyResult<()> { // TODO: Assigning to __bases__ is only used in typing.NamedTupleMeta.__new__ // Rather than correctly re-initializing the class, we are skipping a few steps for now - if zelf.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) { + if zelf.slots().flags.has_feature(PyTypeFlags::IMMUTABLETYPE) { return Err(vm.new_type_error(format!( "cannot set '__bases__' attribute of immutable type '{}'", zelf.name() @@ -3185,7 +3185,7 @@ impl SetAttr for PyType { vm: &VirtualMachine, ) -> PyResult<()> { let attr_name = vm.ctx.intern_str(attr_name.as_wtf8()); - if zelf.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) { + if zelf.slots().flags.has_feature(PyTypeFlags::IMMUTABLETYPE) { return Err(vm.new_type_error(format!( "cannot set '{}' attribute of immutable type '{}'", attr_name, @@ -3210,9 +3210,9 @@ impl SetAttr for PyType { zelf.modified_inner(); let prev_value = if let PySetterValue::Assign(value) = value { - zelf.attributes.insert(attr_name, value) + zelf.attributes().insert(attr_name, value) } else { - let prev_value = zelf.attributes.remove(attr_name); + let prev_value = zelf.attributes().remove(attr_name); if prev_value.is_none() { return Err(vm.new_attribute_error(format!( "type object '{}' has no attribute '{}'", @@ -3253,8 +3253,10 @@ impl Callable for PyType { } } - let Some(slot_new) = zelf.slots.new.load() else { - return Err(vm.new_type_error(format!("cannot create '{}' instances", zelf.slots.name))); + let Some(slot_new) = zelf.slots().new.load() else { + return Err( + vm.new_type_error(format!("cannot create '{}' instances", zelf.slots().name)) + ); }; // Both the new and init slots consume args, so the init call gets a @@ -3273,7 +3275,7 @@ impl Callable for PyType { // so a mismatch is conservative: if it ever compared unequal for the // wrapper it would only take the slower cloning path, never the fast // path incorrectly. - if zelf.slots.init.load().is_none() + if zelf.slots().init.load().is_none() && !zelf.is(vm.ctx.types.type_type) && crate::types::fn_addr(slot_new) != crate::types::fn_addr(crate::types::new_wrapper as crate::types::NewFunc) @@ -3456,10 +3458,10 @@ fn vectorcall_type( if nargs == 1 && no_kwargs { return Ok(args[0].obj_type()); } - } else if zelf.slots.call.load().is_none() && zelf.slots.new.load().is_some() { + } else if zelf.slots().call.load().is_none() && zelf.slots().new.load().is_some() { // Per-type constructor vectorcall for non-callable types (dict, list, int, etc.) // Also guard on slots.new to avoid dispatching for DISALLOW_INSTANTIATION types. - if let Some(type_vc) = zelf.slots.vectorcall.load() { + if let Some(type_vc) = zelf.slots().vectorcall.load() { return type_vc(zelf_obj, args, nargs, kwnames, vm); } } diff --git a/crates/vm/src/byte.rs b/crates/vm/src/byte.rs index d6ed983e8aa..aadbc518403 100644 --- a/crates/vm/src/byte.rs +++ b/crates/vm/src/byte.rs @@ -57,7 +57,7 @@ fn collect_bytes( // which knows what it was being asked to do, rather than by the // iteration protocol saying the object is not iterable. let cls = obj.class(); - if cls.slots.iter.load().is_none() && !cls.has_attr(identifier!(vm, __getitem__)) { + if cls.slots().iter.load().is_none() && !cls.has_attr(identifier!(vm, __getitem__)) { return Err(vm.new_type_error(unusable(&cls.name()))); } let value = |x: PyObjectRef| value_from_object_with(vm, &x, element); diff --git a/crates/vm/src/class.rs b/crates/vm/src/class.rs index f69075696e5..620bf813731 100644 --- a/crates/vm/src/class.rs +++ b/crates/vm/src/class.rs @@ -24,7 +24,7 @@ pub fn add_operators(class: &'static Py, ctx: &Context) { // Special handling for __hash__ = None if def.name == "__hash__" - && class.slots.hash.load().is_some_and(|h| { + && class.slots().hash.load().is_some_and(|h| { fn_addr(h) == fn_addr(hash_not_implemented as crate::types::HashFunc) }) { @@ -40,13 +40,13 @@ pub fn add_operators(class: &'static Py, ctx: &Context) { } // Get the slot function wrapped in SlotFunc - let Some(slot_func) = def.accessor.get_slot_func_with_op(&class.slots, def.op) else { + let Some(slot_func) = def.accessor.get_slot_func_with_op(class.slots(), def.op) else { continue; }; // Check if attribute already exists in dict let attr_name = ctx.intern_str(def.name); - if class.attributes.contains(attr_name) { + if class.attributes().contains(attr_name) { continue; } @@ -157,7 +157,7 @@ pub trait PyClassImpl: PyClassDef { { // NOTE: `is_created_with_flags` if only available when debug_assertions is true #[cfg(debug_assertions)] - debug_assert!(class.slots.flags.is_created_with_flags()); + debug_assert!(class.slots().flags.is_created_with_flags()); let _ = ctx.intern_str(Self::NAME); // intern type name @@ -181,7 +181,7 @@ pub trait PyClassImpl: PyClassDef { // Only set __doc__ if it doesn't already exist (e.g., as a member descriptor) // This matches CPython's behavior in type_dict_set_doc let doc_attr_name = identifier!(ctx, __doc__); - if class.attributes.get(doc_attr_name).is_none() { + if class.attributes().get(doc_attr_name).is_none() { class.set_attr(doc_attr_name, ctx.new_str(doc).into()); } } @@ -191,7 +191,7 @@ pub trait PyClassImpl: PyClassDef { // Don't overwrite a getset descriptor for __module__ (e.g. TypeAliasType // has an instance-level __module__ getset that should not be replaced) let has_getset = class - .attributes + .attributes() .get(module_key) .is_some_and(|v| v.downcastable::()); if !has_getset { @@ -202,7 +202,7 @@ pub trait PyClassImpl: PyClassDef { // Don't add __new__ attribute if slot_new is inherited from object // (Python doesn't add __new__ to __dict__ for inherited slots) // Exception: object itself should have __new__ in its dict - if let Some(slot_new) = class.slots.new.load() { + if let Some(slot_new) = class.slots().new.load() { let object_new = ctx.types.object_type.slots().new.load(); let is_object_itself = core::ptr::eq(class, ctx.types.object_type); let is_inherited_from_object = !is_object_itself @@ -224,7 +224,7 @@ pub trait PyClassImpl: PyClassDef { class.inherit_slots(base); } - class.extend_methods(class.slots.methods, ctx); + class.extend_methods(class.slots().methods, ctx); } #[must_use] diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 9969fd19c88..d26a8457b0a 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -7143,7 +7143,7 @@ impl ExecutingFrame<'_> { let nargs: u32 = arg.into(); let callable = self.nth_value(nargs + 1); if let Some(cls) = callable.downcast_ref::() - && cls.slots.vectorcall.load().is_some() + && cls.slots().vectorcall.load().is_some() { let (callable, args_vec) = self.take_call_args(nargs as usize); let effective_nargs = args_vec.len(); @@ -7168,12 +7168,12 @@ impl ExecutingFrame<'_> { && !self_or_null_is_some && cached_version != 0 && let Some(cls) = callable.downcast_ref::() - && cls.tp_version_tag.load(Acquire) == cached_version + && cls.tp_version_tag().load(Acquire) == cached_version && let Some((init_func, init_func_version)) = cls.get_cached_init_for_specialization(cached_version) && init_func.func_version() == init_func_version && init_func.has_exact_argcount(nargs + 1) - && let Some(cls_alloc) = cls.slots.alloc.load() + && let Some(cls_alloc) = cls.slots().alloc.load() { // The specialization runs `__init__` directly with no // interpreter-visible trampoline frame. Deopt when the @@ -7525,14 +7525,14 @@ impl ExecutingFrame<'_> { if let Some(descr) = cls.get_direct_attr(attr_name) { let descr_cls = descr.class(); if descr_cls - .slots + .slots() .flags .has_feature(PyTypeFlags::METHOD_DESCRIPTOR) { // Method descriptor: push unbound func + self // CALL will prepend self as first positional arg found = Some((descr, true)); - } else if let Some(descr_get) = descr_cls.slots.descr_get.load() { + } else if let Some(descr_get) = descr_cls.slots().descr_get.load() { // Has __get__ but not METHOD_DESCRIPTOR: bind it let bound = descr_get( &descr, @@ -10089,7 +10089,7 @@ impl ExecutingFrame<'_> { } // Only specialize if getattro is the default (PyBaseObject::getattro) - let is_default_getattro = cls.slots.getattro.load().is_some_and(|f| { + let is_default_getattro = cls.slots().getattro.load().is_some_and(|f| { crate::types::fn_addr(f) == crate::types::fn_addr(PyBaseObject::getattro as crate::types::GetattroFunc) }); @@ -10163,7 +10163,7 @@ impl ExecutingFrame<'_> { } let cls_attr = cls.get_attr(attr_name); - let class_has_dict = cls.slots.flags.has_feature(PyTypeFlags::HAS_DICT); + let class_has_dict = cls.slots().flags.has_feature(PyTypeFlags::HAS_DICT); if oparg.is_method() { // Method specialization @@ -10202,8 +10202,8 @@ impl ExecutingFrame<'_> { // Regular attribute access let has_data_descr = cls_attr.as_ref().is_some_and(|descr| { let descr_cls = descr.class(); - descr_cls.slots.descr_get.load().is_some() - && descr_cls.slots.descr_set.load().is_some() + descr_cls.slots().descr_get.load().is_some() + && descr_cls.slots().descr_set.load().is_some() }); let has_descr_get = cls_attr .as_ref() @@ -10348,7 +10348,7 @@ impl ExecutingFrame<'_> { let (mcl_attr, mut metaclass_version) = mcl.lookup_ref_and_version_interned(attr_name, _vm); if let Some(ref attr) = mcl_attr { let attr_class = attr.class(); - if attr_class.slots.descr_set.load().is_some() { + if attr_class.slots().descr_set.load().is_some() { // Data descriptor on metaclass — can't specialize unsafe { self.code.instructions.write_adaptive_counter( @@ -10565,7 +10565,7 @@ impl ExecutingFrame<'_> { let cls = a.class(); // Check the cheap gates before the __getitem__ lookup, which // takes the global type lock and may allocate a version tag. - if cls.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) + if cls.slots().flags.has_feature(PyTypeFlags::HEAPTYPE) && !self.specialization_eval_frame_active(vm) { let (getitem, type_version) = @@ -11074,7 +11074,7 @@ impl ExecutingFrame<'_> { // type/str/tuple(x) and class-call specializations if let Some(cls) = callable.downcast_ref::() { - if cls.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) { + if cls.slots().flags.has_feature(PyTypeFlags::IMMUTABLETYPE) { if !self_or_null_is_some && nargs == 1 { let new_op = if callable.is(&vm.ctx.types.type_type.as_object()) { Some(Instruction::CallType1) @@ -11090,7 +11090,7 @@ impl ExecutingFrame<'_> { return; } } - if cls.slots.vectorcall.load().is_some() { + if cls.slots().vectorcall.load().is_some() { self.specialize_at(instr_idx, cache_base, Instruction::CallBuiltinClass); return; } @@ -11106,15 +11106,15 @@ impl ExecutingFrame<'_> { } // CallAllocAndEnterInit: heap type with default __new__ - if !self_or_null_is_some && cls.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) { + if !self_or_null_is_some && cls.slots().flags.has_feature(PyTypeFlags::HEAPTYPE) { // Capture the version before inspecting tp_new/tp_alloc so a // concurrently installed __new__ invalidates the version this // specialization is cached against. let type_version = cls.version_for_specialization(vm); let object_new = vm.ctx.types.object_type.slots().new.load(); - let cls_new = cls.slots.new.load(); + let cls_new = cls.slots().new.load(); let object_alloc = vm.ctx.types.object_type.slots().alloc.load(); - let cls_alloc = cls.slots.alloc.load(); + let cls_alloc = cls.slots().alloc.load(); if let (Some(cls_new_fn), Some(obj_new_fn), Some(cls_alloc_fn), Some(obj_alloc_fn)) = (cls_new, object_new, cls_alloc, object_alloc) && crate::types::fn_addr(cls_new_fn) == crate::types::fn_addr(obj_new_fn) @@ -11518,14 +11518,14 @@ impl ExecutingFrame<'_> { Some(Instruction::ToBoolList) } else if cls.is(PyStr::class(&vm.ctx)) { Some(Instruction::ToBoolStr) - } else if cls.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) { + } else if cls.slots().flags.has_feature(PyTypeFlags::HEAPTYPE) { // Capture the version before inspecting the bool/len slots so a // concurrently installed __bool__/__len__ invalidates the version // the ToBoolAlwaysTrue guard is cached against. let type_version = cls.version_for_specialization(vm); - let has_bool_or_len = cls.slots.as_number.boolean.load().is_some() - || cls.slots.as_mapping.length.load().is_some() - || cls.slots.as_sequence.length.load().is_some(); + let has_bool_or_len = cls.slots().as_number.boolean.load().is_some() + || cls.slots().as_mapping.length.load().is_some() + || cls.slots().as_sequence.length.load().is_some(); if !has_bool_or_len { if type_version != 0 { unsafe { @@ -11993,7 +11993,7 @@ impl ExecutingFrame<'_> { } // Only specialize if setattr is the default (generic_setattr) - let is_default_setattr = cls.slots.setattro.load().is_some_and(|f| { + let is_default_setattr = cls.slots().setattro.load().is_some_and(|f| { crate::types::fn_addr(f) == crate::types::fn_addr(PyBaseObject::slot_setattro as crate::types::SetattroFunc) }); @@ -12013,7 +12013,8 @@ impl ExecutingFrame<'_> { let cls_attr = cls.get_attr(attr_name); let has_data_descr = cls_attr.as_ref().is_some_and(|descr| { let descr_cls = descr.class(); - descr_cls.slots.descr_get.load().is_some() && descr_cls.slots.descr_set.load().is_some() + descr_cls.slots().descr_get.load().is_some() + && descr_cls.slots().descr_set.load().is_some() }); if has_data_descr { From d82a950b45aa40015d573e966046c82c940d9723 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 25 Sep 2026 14:00:11 +0900 Subject: [PATCH 11/13] Use as_bytes, as_slice, and traceback accessors Bytes lengths go through as_bytes(). Tuple len, emptiness, and iteration go through as_slice(). Exception handles use traceback(). Assisted-by: Grok CLI:grok-4.7 --- crates/capi/src/pyerrors.rs | 2 +- crates/stdlib/src/_asyncio.rs | 4 ++-- crates/vm/src/builtins/bytes.rs | 17 +++++++---------- crates/vm/src/builtins/tuple.rs | 20 ++++++++++---------- crates/vm/src/cformat.rs | 4 ++-- crates/vm/src/coroutine.rs | 2 +- crates/vm/src/exceptions.rs | 8 ++++---- crates/vm/src/frame.rs | 18 +++++++++--------- crates/vm/src/import.rs | 2 +- crates/vm/src/stdlib/_codecs.rs | 2 +- crates/vm/src/stdlib/_ctypes/simple.rs | 4 ++-- crates/vm/src/stdlib/_io.rs | 2 +- crates/vm/src/suggestion.rs | 2 +- crates/vm/src/vm/mod.rs | 2 +- 14 files changed, 43 insertions(+), 46 deletions(-) diff --git a/crates/capi/src/pyerrors.rs b/crates/capi/src/pyerrors.rs index 034c21cffee..6650b52c50c 100644 --- a/crates/capi/src/pyerrors.rs +++ b/crates/capi/src/pyerrors.rs @@ -260,7 +260,7 @@ pub unsafe extern "C" fn PyException_GetTraceback(exc: *mut PyObject) -> *mut Py with_vm(|vm| { let exc = unsafe { &*exc }.try_downcast_ref::(vm)?; let tb = exc - .__traceback__() + .traceback() .map(|tb| tb.into_object().into_raw().as_ptr()) .unwrap_or_default(); Ok(tb) diff --git a/crates/stdlib/src/_asyncio.rs b/crates/stdlib/src/_asyncio.rs index fc758ffdda9..769774c9d97 100644 --- a/crates/stdlib/src/_asyncio.rs +++ b/crates/stdlib/src/_asyncio.rs @@ -322,7 +322,7 @@ pub(crate) mod _asyncio { // Save the original traceback for later restoration if let Ok(exc_ref) = exc.clone().downcast::() { - let tb = exc_ref.__traceback__().map(|tb| tb.into()); + let tb = exc_ref.traceback().map(|tb| tb.into()); *zelf.fut_exception_tb.write() = tb; } @@ -2296,7 +2296,7 @@ pub(crate) mod _asyncio { } else { task.base.fut_state.store(FutureState::Finished); // Save the original traceback for later restoration - let tb = exc.__traceback__().map(|tb| tb.into()); + let tb = exc.traceback().map(|tb| tb.into()); *task.base.fut_exception_tb.write() = tb; *task.base.fut_exception.write() = Some(exc.clone().into()); task.base.fut_log_tb.store(true, Ordering::Relaxed); diff --git a/crates/vm/src/builtins/bytes.rs b/crates/vm/src/builtins/bytes.rs index 3e2bcc44679..2a52705ded2 100644 --- a/crates/vm/src/builtins/bytes.rs +++ b/crates/vm/src/builtins/bytes.rs @@ -555,11 +555,6 @@ impl Py { self.payload().as_bytes() } - #[inline] - pub fn len(&self) -> usize { - self.payload().__len__() - } - #[pymethod] fn __reduce_ex__( &self, @@ -647,7 +642,7 @@ impl AsBuffer for PyBytes { fn as_buffer(zelf: &Py, _vm: &VirtualMachine) -> PyResult { let buf = PyBuffer::new( zelf.to_owned().into(), - BufferDescriptor::simple(zelf.len(), true), + BufferDescriptor::simple(zelf.as_bytes().len(), true), &BUFFER_METHODS, ); Ok(buf) @@ -657,7 +652,9 @@ impl AsBuffer for PyBytes { impl AsMapping for PyBytes { fn as_mapping() -> &'static PyMappingMethods { static AS_MAPPING: LazyLock = LazyLock::new(|| PyMappingMethods { - length: atomic_func!(|mapping, _vm| Ok(PyBytes::mapping_downcast(mapping).len())), + length: atomic_func!(|mapping, _vm| { + Ok(PyBytes::mapping_downcast(mapping).as_bytes().len()) + }), subscript: atomic_func!( |mapping, needle, vm| PyBytes::mapping_downcast(mapping)._getitem(needle, vm) ), @@ -670,7 +667,7 @@ impl AsMapping for PyBytes { impl AsSequence for PyBytes { fn as_sequence() -> &'static PySequenceMethods { static AS_SEQUENCE: LazyLock = LazyLock::new(|| PySequenceMethods { - length: atomic_func!(|seq, _vm| Ok(PyBytes::sequence_downcast(seq).len())), + length: atomic_func!(|seq, _vm| Ok(PyBytes::sequence_downcast(seq).as_bytes().len())), concat: atomic_func!(|seq, other, vm| { PyBytes::sequence_downcast(seq) .inner @@ -779,7 +776,7 @@ impl PyPayload for PyBytesIterator { impl PyBytesIterator { #[pymethod] fn __length_hint__(&self) -> usize { - self.internal.lock().length_hint(|obj| obj.len()) + self.internal.lock().length_hint(|obj| obj.as_bytes().len()) } #[pymethod] @@ -797,7 +794,7 @@ impl PyBytesIterator { fn __setstate__(&self, state: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { self.internal .lock() - .set_state(&state, |obj, pos| pos.min(obj.len()), vm) + .set_state(&state, |obj, pos| pos.min(obj.as_bytes().len()), vm) } } diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index dc471eb9347..1fa684e8190 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -317,18 +317,18 @@ impl PyTuple { #[inline] #[must_use] pub fn len(&self) -> usize { - self.elements.len() + self.as_slice().len() } #[inline] #[must_use] pub fn is_empty(&self) -> bool { - self.elements.is_empty() + self.as_slice().is_empty() } #[inline] pub fn iter(&self) -> core::slice::Iter<'_, R> { - self.elements.iter() + self.as_slice().iter() } } @@ -367,7 +367,7 @@ impl PyTuple { } fn repeat(zelf: PyRef, value: isize, vm: &VirtualMachine) -> PyResult> { - Ok(if zelf.elements.is_empty() || value == 0 { + Ok(if zelf.is_empty() || value == 0 { vm.ctx.empty_tuple.clone() } else if value == 1 && zelf.class().is(vm.ctx.types.tuple_type) { // Special case: when some `tuple` is multiplied by `1`, @@ -453,9 +453,9 @@ impl PyTuple { vm: &VirtualMachine, ) -> PyArithmeticValue> { let added = other.downcast::().map(|other| { - if other.elements.is_empty() && zelf.class().is(vm.ctx.types.tuple_type) { + if other.is_empty() && zelf.class().is(vm.ctx.types.tuple_type) { zelf - } else if zelf.elements.is_empty() && other.class().is(vm.ctx.types.tuple_type) { + } else if zelf.is_empty() && other.class().is(vm.ctx.types.tuple_type) { other } else { let elements = zelf @@ -486,7 +486,7 @@ impl PyTuple { #[inline] #[must_use] pub fn __len__(&self) -> usize { - self.elements.len() + self.len() } fn __mul__(zelf: PyRef, value: ArgSize, vm: &VirtualMachine) -> PyResult> { @@ -521,7 +521,7 @@ impl PyTuple { vm: &VirtualMachine, ) -> PyResult { let (start, stop) = range.saturate(self.len(), vm)?; - for (index, element) in self.elements.iter().enumerate().take(stop).skip(start) { + for (index, element) in self.iter().enumerate().take(stop).skip(start) { if vm.identical_or_equal(element, &needle)? { return Ok(index); } @@ -615,7 +615,7 @@ impl AsNumber for PyTuple { static AS_NUMBER: PyNumberMethods = PyNumberMethods { boolean: Some(|number, _vm| { let zelf = number.obj.downcast_ref::().unwrap(); - Ok(!zelf.elements.is_empty()) + Ok(!zelf.is_empty()) }), ..PyNumberMethods::NOT_IMPLEMENTED }; @@ -666,7 +666,7 @@ impl Representable for PyTuple { let s = if zelf.len() == 1 { wtf8_concat!("(", zelf.elements[0].repr(vm)?.as_wtf8(), ",)") } else { - collection_repr(None, "(", ")", "()", zelf.elements.iter().map(|o| &**o), vm)? + collection_repr(None, "(", ")", "()", zelf.iter().map(|o| &**o), vm)? }; vm.ctx.new_str(s) } else { diff --git a/crates/vm/src/cformat.rs b/crates/vm/src/cformat.rs index d5ce89a669b..a88eba9174b 100644 --- a/crates/vm/src/cformat.rs +++ b/crates/vm/src/cformat.rs @@ -138,7 +138,7 @@ fn spec_format_bytes( // CPython parity: bytes `%c` accepts a single byte or any object // with `__index__` in range(256). if let Some(b) = obj.downcast_ref::() { - if b.len() == 1 { + if b.as_bytes().len() == 1 { return Ok(spec.format_char(b.as_bytes()[0])); } } else if let Some(ba) = obj.downcast_ref::() { @@ -155,7 +155,7 @@ fn spec_format_bytes( // A bytes-like argument that is not one byte long is named by // its length rather than by its type. let what = if let Some(b) = obj.downcast_ref::() { - format!("a bytes object of length {}", b.len()) + format!("a bytes object of length {}", b.as_bytes().len()) } else if let Some(ba) = obj.downcast_ref::() { format!("a bytearray object of length {}", ba.borrow_buf().len()) } else { diff --git a/crates/vm/src/coroutine.rs b/crates/vm/src/coroutine.rs index ade2890cf69..1b7dbfe958a 100644 --- a/crates/vm/src/coroutine.rs +++ b/crates/vm/src/coroutine.rs @@ -655,7 +655,7 @@ pub(crate) fn unraisable_while_closing( ) { // Explicit close() leaves the traceback to the caller frame. // Finalize has no caller frame, so attach the generator site here. - if e.__traceback__().is_none() + if e.traceback().is_none() && let Some(frame) = coro.frame_opt() { let lasti = frame.lasti().saturating_mul(2); diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 0315a09d2ac..8c9854710ae 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -383,7 +383,7 @@ impl VirtualMachine { &self, exc: PyBaseExceptionRef, ) -> (PyObjectRef, PyObjectRef, PyObjectRef) { - let tb = exc.__traceback__().to_pyobject(self); + let tb = exc.traceback().to_pyobject(self); let class = exc.class().to_owned(); (class.into(), exc.into(), tb) } @@ -783,7 +783,7 @@ impl Py { } #[inline] - pub fn __traceback__(&self) -> Option { + pub fn traceback(&self) -> Option { self.payload().__traceback__() } @@ -1264,7 +1264,7 @@ impl serde::Serialize for SerializeException<'_, '_> { s.end() } } - self.exc.__traceback__().map(Tracebacks) + self.exc.traceback().map(Tracebacks) }; struc.serialize_field("traceback", &tbs)?; struc.serialize_field( @@ -3428,7 +3428,7 @@ pub fn exception_group_match( let wrapped = eg_type.call((vm.ctx.new_str(""), excs), vm)?; // Copy traceback from original exception if let Ok(exc) = exc_value.to_owned().downcast::() - && let Some(tb) = exc.__traceback__() + && let Some(tb) = exc.traceback() && let Ok(wrapped_exc) = wrapped.clone().downcast::() { wrapped_exc.set_traceback(Some(tb)); diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index d26a8457b0a..4348f2d8e69 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -2681,7 +2681,7 @@ pub(crate) fn trampoline_handle_exception( // Add traceback entry at the call site. if let Some((loc, _end_loc)) = exec.code.locations.get(idx) { - let next = exception.__traceback__(); + let next = exception.traceback(); let new_traceback = PyTraceback::new(next, exec.frame_object(vm), idx as u32 * 2, loc.line); exception.set_traceback(Some(new_traceback.into_ref(&vm.ctx))); } @@ -3524,7 +3524,7 @@ impl ExecutingFrame<'_> { let exc_type: PyObjectRef = exc.class().to_owned().into(); let exc_value: PyObjectRef = exc.to_owned().into(); let exc_tb: PyObjectRef = exc - .__traceback__() + .traceback() .map_or_else(|| vm.ctx.none(), |tb| -> PyObjectRef { tb.into() }); let tuple = vm.ctx.new_tuple(vec![exc_type, exc_value, exc_tb]).into(); vm.trace_event(crate::protocol::TraceEvent::Exception, Some(tuple))?; @@ -3599,7 +3599,7 @@ impl ExecutingFrame<'_> { Ok(_) => {} Err(exception) => { if let Some((loc, _end_loc)) = self.code.locations.get(idx) { - let next = exception.__traceback__(); + let next = exception.traceback(); let new_traceback = PyTraceback::new( next, self.frame_object(vm), @@ -3708,7 +3708,7 @@ impl ExecutingFrame<'_> { vm: &VirtualMachine, ) -> FrameResult { if let Some((loc, _end_loc)) = frame.code.locations.get(idx) { - let next = exception.__traceback__(); + let next = exception.traceback(); let new_traceback = PyTraceback::new( next, frame.frame_object(vm), @@ -3791,14 +3791,14 @@ impl ExecutingFrame<'_> { // Check if the exception already has traceback entries before // we add ours. If it does, it was propagated from a callee // function and we should not re-contextualize it. - let had_prior_traceback = exception.__traceback__().is_some(); + let had_prior_traceback = exception.traceback().is_some(); // PyTraceBack_Here always adds a new entry without // checking for duplicates. Each time an exception passes through // a frame (e.g., in a loop with repeated raise statements), // a new traceback entry is added. if let Some((loc, _end_loc)) = frame.code.locations.get(idx) { - let next = exception.__traceback__(); + let next = exception.traceback(); let new_traceback = PyTraceback::new( next, @@ -4010,7 +4010,7 @@ impl ExecutingFrame<'_> { let idx = self.lasti().saturating_sub(1) as usize; if idx < self.code.locations.len() { let (loc, _end_loc) = self.code.locations[idx]; - let next = err.__traceback__(); + let next = err.traceback(); let new_traceback = PyTraceback::new(next, self.frame_object(vm), idx as u32 * 2, loc.line); err.set_traceback(Some(new_traceback.into_ref(&vm.ctx))); @@ -4068,7 +4068,7 @@ impl ExecutingFrame<'_> { let idx = self.lasti().saturating_sub(1) as usize; if idx < self.code.locations.len() { let (loc, _end_loc) = self.code.locations[idx]; - let next = err.__traceback__(); + let next = err.traceback(); let new_traceback = PyTraceback::new( next, self.frame_object(vm), @@ -4117,7 +4117,7 @@ impl ExecutingFrame<'_> { let idx = self.lasti().saturating_sub(1) as usize; if idx < self.code.locations.len() { let (loc, _end_loc) = self.code.locations[idx]; - let next = exception.__traceback__(); + let next = exception.traceback(); let new_traceback = PyTraceback::new(next, self.frame_object(vm), idx as u32 * 2, loc.line); exception.set_traceback(Some(new_traceback.into_ref(&vm.ctx))); diff --git a/crates/vm/src/import.rs b/crates/vm/src/import.rs index ba2be436615..3fb6e5fdc2e 100644 --- a/crates/vm/src/import.rs +++ b/crates/vm/src/import.rs @@ -271,7 +271,7 @@ pub fn remove_importlib_frames(vm: &VirtualMachine, exc: &Py) { let always_trim = exc.fast_isinstance(vm.ctx.exceptions.import_error); - if let Some(tb) = exc.__traceback__() { + if let Some(tb) = exc.traceback() { let trimmed_tb = remove_importlib_frames_inner(vm, Some(tb), always_trim).0; exc.set_traceback(trimmed_tb); } diff --git a/crates/vm/src/stdlib/_codecs.rs b/crates/vm/src/stdlib/_codecs.rs index af57a433c2f..3a12bd502f1 100644 --- a/crates/vm/src/stdlib/_codecs.rs +++ b/crates/vm/src/stdlib/_codecs.rs @@ -287,7 +287,7 @@ mod _codecs { #[pyfunction] fn escape_encode(args: EscapeEncodeArgs, _vm: &VirtualMachine) -> (Vec, usize) { let encoded = encodings::escape::encode(args.data.as_bytes()); - (encoded, args.data.len()) + (encoded, args.data.as_bytes().len()) } #[derive(FromArgs)] diff --git a/crates/vm/src/stdlib/_ctypes/simple.rs b/crates/vm/src/stdlib/_ctypes/simple.rs index 0ade154e36c..5b8dfb94244 100644 --- a/crates/vm/src/stdlib/_ctypes/simple.rs +++ b/crates/vm/src/stdlib/_ctypes/simple.rs @@ -90,7 +90,7 @@ fn set_primitive(_type_: &str, value: &PyObject, vm: &VirtualMachine) -> PyResul // c_set: accepts bytes(len=1), bytearray(len=1), or int(0-255) if value .downcast_ref_if_exact::(vm) - .is_some_and(|v| v.len() == 1) + .is_some_and(|v| v.as_bytes().len() == 1) || value .downcast_ref_if_exact::(vm) .is_some_and(|v| v.borrow_buf().len() == 1) @@ -303,7 +303,7 @@ impl PyCSimpleType { // c_char: 1 byte character Some("c") => { if let Some(bytes) = value.downcast_ref::() - && bytes.len() == 1 + && bytes.as_bytes().len() == 1 { return create_simple_with_value("c", &value); } diff --git a/crates/vm/src/stdlib/_io.rs b/crates/vm/src/stdlib/_io.rs index fe98d5f43d6..6e911a32895 100644 --- a/crates/vm/src/stdlib/_io.rs +++ b/crates/vm/src/stdlib/_io.rs @@ -3431,7 +3431,7 @@ mod _io { num_to_skip -= n_decoded; break; } - skip_bytes -= dec_buffer.len() as isize; + skip_bytes -= dec_buffer.as_bytes().len() as isize; skip_back = 1; } else { skip_bytes -= skip_back; diff --git a/crates/vm/src/suggestion.rs b/crates/vm/src/suggestion.rs index d300d4de8ff..7f3077b9e7a 100644 --- a/crates/vm/src/suggestion.rs +++ b/crates/vm/src/suggestion.rs @@ -71,7 +71,7 @@ pub fn offer_suggestions(exc: &Py, vm: &VirtualMachine) -> Opti if vm.is_none(&name) { return None; } - let tb = exc.__traceback__()?; + let tb = exc.traceback()?; let tb = tb.iter().last().unwrap_or(tb); let varnames = tb.frame.iframe().code().to_owned().co_varnames(vm); diff --git a/crates/vm/src/vm/mod.rs b/crates/vm/src/vm/mod.rs index 1d68a1be0a8..d1a676bec88 100644 --- a/crates/vm/src/vm/mod.rs +++ b/crates/vm/src/vm/mod.rs @@ -1637,7 +1637,7 @@ impl VirtualMachine { let unraisablehook = sys_module.get_attr("unraisablehook", self).unwrap(); let exc_type = e.class().to_owned(); - let exc_traceback = e.__traceback__().to_pyobject(self); // TODO: actual traceback + let exc_traceback = e.traceback().to_pyobject(self); // TODO: actual traceback let exc_value = e.into(); let args = stdlib::sys::UnraisableHookArgsData { exc_type, From 38ee0218a02639292f5217e76f451042ffbf6fbc Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 25 Sep 2026 14:56:59 +0900 Subject: [PATCH 12/13] Drop shortcuts that only forward payload methods Tuple length and iteration go through as_slice. Dict and list expose len. Set add stays on the payload. Assisted-by: Grok CLI:grok-4.7 --- crates/vm/src/builtins/bytearray.rs | 5 -- crates/vm/src/builtins/dict.rs | 17 +------ crates/vm/src/builtins/float.rs | 7 --- crates/vm/src/builtins/int.rs | 15 ------ crates/vm/src/builtins/list.rs | 2 +- crates/vm/src/builtins/set.rs | 7 --- crates/vm/src/builtins/str.rs | 20 -------- crates/vm/src/builtins/tuple.rs | 79 +++++++++++------------------ crates/vm/src/builtins/type.rs | 30 ----------- crates/vm/src/exceptions.rs | 10 ---- crates/vm/src/frame.rs | 11 ---- 11 files changed, 31 insertions(+), 172 deletions(-) diff --git a/crates/vm/src/builtins/bytearray.rs b/crates/vm/src/builtins/bytearray.rs index 75459303bc3..ba6847c8e8d 100644 --- a/crates/vm/src/builtins/bytearray.rs +++ b/crates/vm/src/builtins/bytearray.rs @@ -569,11 +569,6 @@ impl PyByteArray { #[pyclass] impl Py { - #[inline] - pub fn borrow_buf(&self) -> PyMappedDetachingRwLockReadGuard<'_, [u8]> { - self.payload().borrow_buf() - } - fn __setitem__( &self, needle: &PyObject, diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index f1198836b4a..2a06c7dedbe 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -751,25 +751,10 @@ impl Representable for PyDict { impl Py { #[inline] - pub fn is_empty(&self) -> bool { - self.payload().is_empty() - } - - #[inline] - pub fn size(&self) -> dict_inner::DictSize { - self.payload().size() - } - - #[inline] - pub fn __len__(&self) -> usize { + pub fn len(&self) -> usize { self.payload().__len__() } - #[inline] - pub fn contains_key(&self, key: &K, vm: &VirtualMachine) -> bool { - self.payload().contains_key(key, vm) - } - #[inline] fn exact_dict(&self, vm: &VirtualMachine) -> bool { self.class().is(vm.ctx.types.dict_type) diff --git a/crates/vm/src/builtins/float.rs b/crates/vm/src/builtins/float.rs index 2cb52ad6bd6..04a2bba704b 100644 --- a/crates/vm/src/builtins/float.rs +++ b/crates/vm/src/builtins/float.rs @@ -35,13 +35,6 @@ impl PyFloat { } } -impl Py { - #[inline] - pub fn to_f64(&self) -> f64 { - self.payload().to_f64() - } -} - thread_local! { static FLOAT_FREELIST: Cell> = const { Cell::new(crate::object::FreeList::new()) }; } diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index 6e1a4cbfe89..31a497fd0cb 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -747,21 +747,6 @@ impl AsNumber for PyInt { } } -impl Py { - #[inline] - pub fn as_bigint(&self) -> &BigInt { - self.payload().as_bigint() - } - - #[inline] - pub fn try_to_primitive<'a, I>(&'a self, vm: &VirtualMachine) -> PyResult - where - I: PrimInt + TryFrom<&'a BigInt>, - { - self.payload().try_to_primitive(vm) - } -} - impl PyInt { pub(super) const AS_NUMBER: PyNumberMethods = PyNumberMethods { add: Some(|a, b, vm| Self::number_op(a, b, |a, b, _vm| a + b, vm)), diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index a417d658196..f2d260db7e5 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -140,7 +140,7 @@ impl Py { } #[inline] - pub fn __len__(&self) -> usize { + pub fn len(&self) -> usize { self.payload().__len__() } } diff --git a/crates/vm/src/builtins/set.rs b/crates/vm/src/builtins/set.rs index e8dad07334b..ae4ddccdd49 100644 --- a/crates/vm/src/builtins/set.rs +++ b/crates/vm/src/builtins/set.rs @@ -1069,13 +1069,6 @@ impl Representable for PySet { } } -impl Py { - #[inline] - pub fn add(&self, object: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { - self.payload().add(object, vm) - } -} - impl Constructor for PyFrozenSet { type Args = OptionalArg; diff --git a/crates/vm/src/builtins/str.rs b/crates/vm/src/builtins/str.rs index 2eb83a6fa8c..76bf7f6cdf7 100644 --- a/crates/vm/src/builtins/str.rs +++ b/crates/vm/src/builtins/str.rs @@ -639,26 +639,6 @@ impl Py { self.payload().as_bytes() } - #[inline] - pub fn to_str(&self) -> Option<&str> { - self.payload().to_str() - } - - #[inline] - pub fn byte_len(&self) -> usize { - self.payload().byte_len() - } - - #[inline] - pub fn char_len(&self) -> usize { - self.payload().char_len() - } - - #[inline] - pub fn to_string_lossy(&self) -> Cow<'_, str> { - self.payload().to_string_lossy() - } - pub fn as_utf8(&self) -> Option<&Py> { if self.is_utf8() { // SAFETY: is_utf8() guarantees the PyUtf8Str invariant. diff --git a/crates/vm/src/builtins/tuple.rs b/crates/vm/src/builtins/tuple.rs index 1fa684e8190..7c4110dbf88 100644 --- a/crates/vm/src/builtins/tuple.rs +++ b/crates/vm/src/builtins/tuple.rs @@ -295,7 +295,7 @@ impl<'a, R> core::iter::IntoIterator for &'a PyTuple { type IntoIter = core::slice::Iter<'a, R>; fn into_iter(self) -> Self::IntoIter { - self.iter() + self.as_slice().iter() } } @@ -304,7 +304,7 @@ impl<'a, R> core::iter::IntoIterator for &'a Py> { type IntoIter = core::slice::Iter<'a, R>; fn into_iter(self) -> Self::IntoIter { - self.iter() + PyTuple::as_slice(self).iter() } } @@ -313,23 +313,6 @@ impl PyTuple { pub fn as_slice(&self) -> &[R] { &self.elements } - - #[inline] - #[must_use] - pub fn len(&self) -> usize { - self.as_slice().len() - } - - #[inline] - #[must_use] - pub fn is_empty(&self) -> bool { - self.as_slice().is_empty() - } - - #[inline] - pub fn iter(&self) -> core::slice::Iter<'_, R> { - self.as_slice().iter() - } } impl PyTuple { @@ -367,7 +350,7 @@ impl PyTuple { } fn repeat(zelf: PyRef, value: isize, vm: &VirtualMachine) -> PyResult> { - Ok(if zelf.is_empty() || value == 0 { + Ok(if zelf.as_slice().is_empty() || value == 0 { vm.ctx.empty_tuple.clone() } else if value == 1 && zelf.class().is(vm.ctx.types.tuple_type) { // Special case: when some `tuple` is multiplied by `1`, @@ -392,21 +375,6 @@ impl Py { self.payload().as_slice() } - #[inline] - pub fn len(&self) -> usize { - self.as_slice().len() - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.as_slice().is_empty() - } - - #[inline] - pub fn iter(&self) -> core::slice::Iter<'_, PyObjectRef> { - self.as_slice().iter() - } - pub fn extract_tuple<'a, T: FromPyTuple<'a>>(&'a self, vm: &VirtualMachine) -> PyResult { T::from_pytuple(self, vm) } @@ -453,12 +421,13 @@ impl PyTuple { vm: &VirtualMachine, ) -> PyArithmeticValue> { let added = other.downcast::().map(|other| { - if other.is_empty() && zelf.class().is(vm.ctx.types.tuple_type) { + if other.as_slice().is_empty() && zelf.class().is(vm.ctx.types.tuple_type) { zelf - } else if zelf.is_empty() && other.class().is(vm.ctx.types.tuple_type) { + } else if zelf.as_slice().is_empty() && other.class().is(vm.ctx.types.tuple_type) { other } else { let elements = zelf + .as_slice() .iter() .chain(other.as_slice()) .cloned() @@ -486,7 +455,7 @@ impl PyTuple { #[inline] #[must_use] pub fn __len__(&self) -> usize { - self.len() + self.as_slice().len() } fn __mul__(zelf: PyRef, value: ArgSize, vm: &VirtualMachine) -> PyResult> { @@ -520,8 +489,8 @@ impl PyTuple { range: OptionalRangeArgs, vm: &VirtualMachine, ) -> PyResult { - let (start, stop) = range.saturate(self.len(), vm)?; - for (index, element) in self.iter().enumerate().take(stop).skip(start) { + let (start, stop) = range.saturate(self.as_slice().len(), vm)?; + for (index, element) in self.as_slice().iter().enumerate().take(stop).skip(start) { if vm.identical_or_equal(element, &needle)? { return Ok(index); } @@ -568,7 +537,9 @@ impl PyTuple { impl AsMapping for PyTuple { fn as_mapping() -> &'static PyMappingMethods { static AS_MAPPING: LazyLock = LazyLock::new(|| PyMappingMethods { - length: atomic_func!(|mapping, _vm| Ok(PyTuple::mapping_downcast(mapping).len())), + length: atomic_func!(|mapping, _vm| { + Ok(PyTuple::mapping_downcast(mapping).as_slice().len()) + }), subscript: atomic_func!( |mapping, needle, vm| PyTuple::mapping_downcast(mapping)._getitem(needle, vm) ), @@ -615,7 +586,7 @@ impl AsNumber for PyTuple { static AS_NUMBER: PyNumberMethods = PyNumberMethods { boolean: Some(|number, _vm| { let zelf = number.obj.downcast_ref::().unwrap(); - Ok(!zelf.is_empty()) + Ok(!zelf.as_slice().is_empty()) }), ..PyNumberMethods::NOT_IMPLEMENTED }; @@ -641,9 +612,10 @@ impl Comparable for PyTuple { return Ok(res.into()); } let other = class_or_notimplemented!(Self, other); - zelf.iter() + zelf.as_slice() + .iter() .map(|o| &**o) - .richcompare(other.iter().map(|o| &**o), op, vm) + .richcompare(other.as_slice().iter().map(|o| &**o), op, vm) .map(PyComparisonValue::Implemented) } } @@ -660,13 +632,20 @@ impl Iterable for PyTuple { impl Representable for PyTuple { #[inline] fn repr(zelf: &Py, vm: &VirtualMachine) -> PyResult { - let s = if zelf.is_empty() { + let s = if zelf.as_slice().is_empty() { vm.ctx.intern_str("()").to_owned() } else if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) { - let s = if zelf.len() == 1 { - wtf8_concat!("(", zelf.elements[0].repr(vm)?.as_wtf8(), ",)") + let s = if zelf.as_slice().len() == 1 { + wtf8_concat!("(", zelf.as_slice()[0].repr(vm)?.as_wtf8(), ",)") } else { - collection_repr(None, "(", ")", "()", zelf.iter().map(|o| &**o), vm)? + collection_repr( + None, + "(", + ")", + "()", + zelf.as_slice().iter().map(|o| &**o), + vm, + )? }; vm.ctx.new_str(s) } else { @@ -733,14 +712,14 @@ impl PyPayload for PyTupleIterator { impl PyTupleIterator { #[pymethod] fn __length_hint__(&self) -> usize { - self.internal.lock().length_hint(|obj| obj.len()) + self.internal.lock().length_hint(|obj| obj.as_slice().len()) } #[pymethod] fn __setstate__(&self, state: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { self.internal .lock() - .set_state(&state, |obj, pos| pos.min(obj.len()), vm) + .set_state(&state, |obj, pos| pos.min(obj.as_slice().len()), vm) } #[pymethod] diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 83e05ab17e7..f82ac6d42c4 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -1651,36 +1651,6 @@ impl PyType { } impl Py { - #[inline] - pub fn name(&self) -> BorrowedValue<'_, str> { - self.payload().name() - } - - #[inline] - pub fn slot_name(&self) -> BorrowedValue<'_, str> { - self.payload().slot_name() - } - - #[inline] - pub fn set_attr(&self, attr_name: &'static PyStrInterned, value: PyObjectRef) { - self.payload().set_attr(attr_name, value) - } - - #[inline] - pub fn get_attr(&self, attr_name: &'static PyStrInterned) -> Option { - self.payload().get_attr(attr_name) - } - - #[inline] - pub fn get_direct_attr(&self, attr_name: &'static PyStrInterned) -> Option { - self.payload().get_direct_attr(attr_name) - } - - #[inline] - pub fn has_attr(&self, attr_name: &'static PyStrInterned) -> bool { - self.payload().has_attr(attr_name) - } - #[inline] pub fn slots(&self) -> &PyTypeSlots { &self.payload().slots diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 8c9854710ae..f01f8b249c9 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -777,21 +777,11 @@ impl PyBaseException { #[pyclass] impl Py { - #[inline] - pub fn args(&self) -> PyTupleRef { - self.payload().args() - } - #[inline] pub fn traceback(&self) -> Option { self.payload().__traceback__() } - #[inline] - pub fn set_traceback(&self, traceback: Option) { - self.payload().set_traceback(traceback) - } - #[pymethod] pub(super) fn __str__(&self, vm: &VirtualMachine) -> PyStrRef { let str_args = vm.exception_args_as_string(&self.args(), true); diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index 4348f2d8e69..e9af2edde42 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -2458,17 +2458,6 @@ impl FrameObject { } impl Py { - #[inline(always)] - pub fn iframe(&self) -> &InterpreterFrame { - self.payload().iframe() - } - - #[inline(always)] - #[allow(clippy::mut_from_ref)] - pub(crate) unsafe fn iframe_mut(&self) -> &mut InterpreterFrame { - unsafe { self.payload().iframe_mut() } - } - #[inline(always)] fn with_exec(&self, vm: &VirtualMachine, f: impl FnOnce(ExecutingFrame<'_>) -> R) -> R { // SAFETY: FrameObject execution is single-threaded. Only one thread at a time From a6cd8b94d76a1d132e5f00812e1aaadbede284f3 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 25 Sep 2026 15:42:53 +0900 Subject: [PATCH 13/13] Drop dict and list len shortcuts len without is_empty fails clippy. Tuple element lookup uses as_slice().get instead of iter().nth. Assisted-by: Grok CLI:grok-4.7 --- crates/vm/src/builtins/dict.rs | 5 ----- crates/vm/src/builtins/list.rs | 5 ----- crates/vm/src/stdlib/_ctypes/function.rs | 4 ++-- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index 2a06c7dedbe..d22b97b165a 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -750,11 +750,6 @@ impl Representable for PyDict { } impl Py { - #[inline] - pub fn len(&self) -> usize { - self.payload().__len__() - } - #[inline] fn exact_dict(&self, vm: &VirtualMachine) -> bool { self.class().is(vm.ctx.types.dict_type) diff --git a/crates/vm/src/builtins/list.rs b/crates/vm/src/builtins/list.rs index f2d260db7e5..2fd40f721d4 100644 --- a/crates/vm/src/builtins/list.rs +++ b/crates/vm/src/builtins/list.rs @@ -138,11 +138,6 @@ impl Py { pub fn borrow_vec_mut(&self) -> PyRwLockWriteGuard<'_, Vec> { self.payload().borrow_vec_mut() } - - #[inline] - pub fn len(&self) -> usize { - self.payload().__len__() - } } impl PyList { diff --git a/crates/vm/src/stdlib/_ctypes/function.rs b/crates/vm/src/stdlib/_ctypes/function.rs index b2667190a28..0770880161d 100644 --- a/crates/vm/src/stdlib/_ctypes/function.rs +++ b/crates/vm/src/stdlib/_ctypes/function.rs @@ -751,8 +751,8 @@ impl Constructor for PyCFuncPtr { .ok_or_else(|| vm.new_type_error("Expected a string"))? .to_string(); let dll = tuple - .iter() - .nth(1) + .as_slice() + .get(1) .ok_or_else(|| vm.new_type_error("Expected a tuple with at least 2 elements"))? .clone();