From 0cb240cde8bc35f6cb6ec4f58db8356808d97bf7 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 31 Dec 2025 00:07:48 +0900 Subject: [PATCH] Update issubclass and make mro representation fit to CPython --- crates/vm/src/builtins/type.rs | 56 ++++++++++++++------------------ crates/vm/src/object/core.rs | 8 ++++- crates/vm/src/types/slot.rs | 7 ++-- crates/vm/src/types/slot_defs.rs | 5 +-- 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 3eca5edc478..178131c96d6 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -50,7 +50,8 @@ unsafe impl crate::object::Traverse for PyType { fn traverse(&self, tracer_fn: &mut crate::object::TraverseFn<'_>) { self.base.traverse(tracer_fn); self.bases.traverse(tracer_fn); - self.mro.traverse(tracer_fn); + // mro contains self as mro[0], so skip traversing to avoid circular reference + // self.mro.traverse(tracer_fn); self.subclasses.traverse(tracer_fn); self.attributes .read_recursive() @@ -341,6 +342,7 @@ impl PyType { metaclass, None, ); + new_type.mro.write().insert(0, new_type.clone()); new_type.init_slots(ctx); @@ -393,6 +395,7 @@ impl PyType { metaclass, None, ); + new_type.mro.write().insert(0, new_type.clone()); // Note: inherit_slots is called in PyClassImpl::init_class after // slots are fully initialized by make_slots() @@ -413,9 +416,8 @@ impl PyType { } pub(crate) fn init_slots(&self, ctx: &Context) { - // Inherit slots from MRO - // Note: self.mro does NOT include self, so we iterate all elements - let mro: Vec<_> = self.mro.read().iter().cloned().collect(); + // Inherit slots from MRO (mro[0] is self, so skip it) + let mro: Vec<_> = self.mro.read()[1..].to_vec(); for base in mro.iter() { self.inherit_slots(base); } @@ -424,7 +426,8 @@ impl PyType { #[allow(clippy::mutable_key_type)] let mut slot_name_set = std::collections::HashSet::new(); - for cls in self.mro.read().iter() { + // mro[0] is self, so skip it; self.attributes is checked separately below + for cls in self.mro.read()[1..].iter() { for &name in cls.attributes.read().keys() { if name.as_bytes().starts_with(b"__") && name.as_bytes().ends_with(b"__") { slot_name_set.insert(name); @@ -503,18 +506,12 @@ impl PyType { /// Equivalent to CPython's find_name_in_mro /// Look in tp_dict of types in MRO - bypasses descriptors and other attribute access machinery fn find_name_in_mro(&self, name: &'static PyStrInterned) -> Option { - // First check in our own dict - if let Some(value) = self.attributes.read().get(name) { - return Some(value.clone()); - } - - // Then check in MRO - for base in self.mro.read().iter() { - if let Some(value) = base.attributes.read().get(name) { + // mro[0] is self, so we just iterate through the entire MRO + for cls in self.mro.read().iter() { + if let Some(value) = cls.attributes.read().get(name) { return Some(value.clone()); } } - None } @@ -530,8 +527,7 @@ impl PyType { } pub fn get_super_attr(&self, attr_name: &'static PyStrInterned) -> Option { - self.mro - .read() + self.mro.read()[1..] .iter() .find_map(|class| class.attributes.read().get(attr_name).cloned()) } @@ -539,9 +535,7 @@ impl PyType { // This is the internal has_attr implementation for fast lookup on a class. pub fn has_attr(&self, attr_name: &'static PyStrInterned) -> bool { self.attributes.read().contains_key(attr_name) - || self - .mro - .read() + || self.mro.read()[1..] .iter() .any(|c| c.attributes.read().contains_key(attr_name)) } @@ -550,10 +544,8 @@ impl PyType { // Gather all members here: let mut attributes = PyAttributes::default(); - for bc in core::iter::once(self) - .chain(self.mro.read().iter().map(|cls| -> &Self { cls })) - .rev() - { + // mro[0] is self, so we iterate through the entire MRO in reverse + for bc in self.mro.read().iter().map(|cls| -> &Self { cls }).rev() { for (name, value) in bc.attributes.read().iter() { attributes.insert(name.to_owned(), value.clone()); } @@ -661,22 +653,21 @@ impl Py { /// so only use this if `cls` is known to have not overridden the base __subclasscheck__ magic /// method. pub fn fast_issubclass(&self, cls: &impl Borrow) -> bool { - self.as_object().is(cls.borrow()) || self.mro.read().iter().any(|c| c.is(cls.borrow())) + self.as_object().is(cls.borrow()) || self.mro.read()[1..].iter().any(|c| c.is(cls.borrow())) } pub fn mro_map_collect(&self, f: F) -> Vec where F: Fn(&Self) -> R, { - core::iter::once(self) - .chain(self.mro.read().iter().map(|x| x.deref())) - .map(f) - .collect() + self.mro.read().iter().map(|x| x.deref()).map(f).collect() } pub fn mro_collect(&self) -> Vec> { - core::iter::once(self) - .chain(self.mro.read().iter().map(|x| x.deref())) + self.mro + .read() + .iter() + .map(|x| x.deref()) .map(|x| x.to_owned()) .collect() } @@ -745,8 +736,11 @@ impl PyType { *zelf.bases.write() = bases; // Recursively update the mros of this class and all subclasses fn update_mro_recursively(cls: &PyType, vm: &VirtualMachine) -> PyResult<()> { - *cls.mro.write() = + let mut mro = PyType::resolve_mro(&cls.bases.read()).map_err(|msg| vm.new_type_error(msg))?; + // Preserve self (mro[0]) when updating MRO + mro.insert(0, cls.mro.read()[0].to_owned()); + *cls.mro.write() = mro; for subclass in cls.subclasses.write().iter() { let subclass = subclass.upgrade().unwrap(); let subclass: &Py = subclass.downcast_ref().unwrap(); diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index cee3fac266e..e904117b06a 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -1310,12 +1310,16 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) { ptr::write(&mut (*type_type_ptr).typ, PyAtomicRef::from(type_type)); let object_type = PyTypeRef::from_raw(object_type_ptr.cast()); + // object's mro is [object] + (*object_type_ptr).payload.mro = PyRwLock::new(vec![object_type.clone()]); - (*type_type_ptr).payload.mro = PyRwLock::new(vec![object_type.clone()]); (*type_type_ptr).payload.bases = PyRwLock::new(vec![object_type.clone()]); (*type_type_ptr).payload.base = Some(object_type.clone()); let type_type = PyTypeRef::from_raw(type_type_ptr.cast()); + // type's mro is [type, object] + (*type_type_ptr).payload.mro = + PyRwLock::new(vec![type_type.clone(), object_type.clone()]); (type_type, object_type) } @@ -1331,6 +1335,8 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) { heaptype_ext: None, }; let weakref_type = PyRef::new_ref(weakref_type, type_type.clone(), None); + // weakref's mro is [weakref, object] + weakref_type.mro.write().insert(0, weakref_type.clone()); object_type.subclasses.write().push( type_type diff --git a/crates/vm/src/types/slot.rs b/crates/vm/src/types/slot.rs index 9d7d09f2d18..085cfea5cee 100644 --- a/crates/vm/src/types/slot.rs +++ b/crates/vm/src/types/slot.rs @@ -760,8 +760,9 @@ impl PyType { let in_self = cmp_names.iter().any(|n| attrs.contains_key(*n)); drop(attrs); + // mro[0] is self, so skip it since we already checked self above in_self - || self.mro.read().iter().any(|cls| { + || self.mro.read()[1..].iter().any(|cls| { let attrs = cls.attributes.read(); cmp_names.iter().any(|n| { if let Some(attr) = attrs.get(*n) { @@ -1273,8 +1274,8 @@ impl PyType { return None; } - // Look up in MRO - for cls in self.mro.read().iter() { + // Look up in MRO (mro[0] is self, so skip it) + for cls in self.mro.read()[1..].iter() { if let Some(attr) = cls.attributes.read().get(name).cloned() { if let Some(func) = try_extract(&attr) { return Some(func); diff --git a/crates/vm/src/types/slot_defs.rs b/crates/vm/src/types/slot_defs.rs index 0d8dbf4b0cc..958adb2f27e 100644 --- a/crates/vm/src/types/slot_defs.rs +++ b/crates/vm/src/types/slot_defs.rs @@ -405,8 +405,9 @@ impl SlotAccessor { /// Inherit slot value from MRO pub fn inherit_from_mro(&self, typ: &crate::builtins::PyType) { - // Note: typ.mro does NOT include typ itself - let mro = typ.mro.read(); + // mro[0] is self, so skip it + let mro_guard = typ.mro.read(); + let mro = &mro_guard[1..]; macro_rules! inherit_main { ($slot:ident) => {{