Skip to content

Update issubclass and make mro representation fit to CPython #5866

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions vm/src/builtins/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,9 +537,9 @@ impl<T: TransmuteFromObject> AsRef<[T]> for PyTupleTyped<T> {
}

impl<T: TransmuteFromObject> PyTupleTyped<T> {
pub fn empty(vm: &VirtualMachine) -> Self {
pub fn empty(ctx: &Context) -> Self {
Self {
tuple: vm.ctx.empty_tuple.clone(),
tuple: ctx.empty_tuple.clone(),
_marker: PhantomData,
}
}
Expand Down
68 changes: 44 additions & 24 deletions vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use std::{borrow::Borrow, collections::HashSet, ops::Deref, pin::Pin, ptr::NonNu
pub struct PyType {
pub base: Option<PyTypeRef>,
pub bases: PyRwLock<Vec<PyTypeRef>>,
pub mro: PyRwLock<Vec<PyTypeRef>>,
pub mro: PyRwLock<Vec<PyTypeRef>>, // TODO: PyTypedTuple<PyTypeRef>
pub subclasses: PyRwLock<Vec<PyRef<PyWeak>>>,
pub attributes: PyRwLock<PyAttributes>,
pub slots: PyTypeSlots,
Expand All @@ -48,7 +48,7 @@ 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);
// self.mro.traverse(tracer_fn);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Uncommenting MRO traversal may be necessary for proper garbage collection.

The MRO field contains references to types that should be traversed. Commenting this out could lead to memory leaks or incorrect reference cycle detection.

-        // self.mro.traverse(tracer_fn);
+        self.mro.traverse(tracer_fn);
🤖 Prompt for AI Agents
In vm/src/builtins/type.rs at line 51, the call to traverse the MRO field is
commented out, which may cause memory leaks or incorrect reference cycle
detection. Uncomment the line that calls self.mro.traverse(tracer_fn) to ensure
proper traversal of references in the MRO field during garbage collection.

self.subclasses.traverse(tracer_fn);
self.attributes
.read_recursive()
Expand Down Expand Up @@ -158,6 +158,15 @@ fn downcast_qualname(value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyRef<
}
}

fn is_subtype_with_mro(a_mro: &[PyTypeRef], _a: &Py<PyType>, b: &Py<PyType>) -> bool {
for item in a_mro {
if item.is(b) {
return true;
}
}
false
}

impl PyType {
pub fn new_simple_heap(
name: &str,
Expand Down Expand Up @@ -197,6 +206,12 @@ impl PyType {
Self::new_heap_inner(base, bases, attrs, slots, heaptype_ext, metaclass, ctx)
}

/// Equivalent to CPython's PyType_Check macro
/// Checks if obj is an instance of type (or its subclass)
pub(crate) fn check(obj: &PyObject) -> Option<&Py<Self>> {
obj.downcast_ref::<Self>()
}

fn resolve_mro(bases: &[PyRef<Self>]) -> Result<Vec<PyTypeRef>, String> {
// Check for duplicates in bases.
let mut unique_bases = HashSet::new();
Expand All @@ -223,8 +238,6 @@ impl PyType {
metaclass: PyRef<Self>,
ctx: &Context,
) -> Result<PyRef<Self>, String> {
let mro = Self::resolve_mro(&bases)?;

if base.slots.flags.has_feature(PyTypeFlags::HAS_DICT) {
slots.flags |= PyTypeFlags::HAS_DICT
}
Expand All @@ -241,6 +254,7 @@ impl PyType {
}
}

let mro = Self::resolve_mro(&bases)?;
let new_type = PyRef::new_ref(
PyType {
base: Some(base),
Expand All @@ -254,6 +268,7 @@ impl PyType {
metaclass,
None,
);
new_type.mro.write().insert(0, new_type.clone());

new_type.init_slots(ctx);

Expand Down Expand Up @@ -285,7 +300,6 @@ impl PyType {

let bases = PyRwLock::new(vec![base.clone()]);
let mro = base.mro_map_collect(|x| x.to_owned());

let new_type = PyRef::new_ref(
PyType {
base: Some(base),
Expand All @@ -299,6 +313,7 @@ impl PyType {
metaclass,
None,
);
new_type.mro.write().insert(0, new_type.clone());

let weakref_type = super::PyWeak::static_type();
for base in new_type.bases.read().iter() {
Expand All @@ -317,7 +332,7 @@ impl PyType {
#[allow(clippy::mutable_key_type)]
let mut slot_name_set = std::collections::HashSet::new();

for cls in self.mro.read().iter() {
for cls in self.mro.read()[1..].iter() {
for &name in cls.attributes.read().keys() {
if name == identifier!(ctx, __new__) {
continue;
Expand Down Expand Up @@ -366,18 +381,15 @@ impl PyType {
}

pub fn get_super_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> {
self.mro
.read()
self.mro.read()[1..]
.iter()
.find_map(|class| class.attributes.read().get(attr_name).cloned())
}

// 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))
}
Expand All @@ -386,10 +398,7 @@ impl PyType {
// Gather all members here:
let mut attributes = PyAttributes::default();

for bc in std::iter::once(self)
.chain(self.mro.read().iter().map(|cls| -> &PyType { cls }))
.rev()
{
for bc in self.mro.read().iter().map(|cls| -> &PyType { cls }).rev() {
for (name, value) in bc.attributes.read().iter() {
attributes.insert(name.to_owned(), value.clone());
}
Expand Down Expand Up @@ -439,26 +448,35 @@ impl PyType {
}

impl Py<PyType> {
pub(crate) fn is_subtype(&self, other: &Py<PyType>) -> bool {
is_subtype_with_mro(&self.mro.read(), self, other)
}

/// Equivalent to CPython's PyType_CheckExact macro
/// Checks if obj is exactly a type (not a subclass)
pub fn check_exact<'a>(obj: &'a PyObject, vm: &VirtualMachine) -> Option<&'a Py<PyType>> {
obj.downcast_ref_if_exact::<PyType>(vm)
}

/// Determines if `subclass` is actually a subclass of `cls`, this doesn't call __subclasscheck__,
/// 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<PyObject>) -> 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<F, R>(&self, f: F) -> Vec<R>
where
F: Fn(&Self) -> R,
{
std::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<PyRef<PyType>> {
std::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()
}
Expand All @@ -472,7 +490,7 @@ impl Py<PyType> {
if let Some(r) = f(self) {
Some(r)
} else {
self.mro.read().iter().find_map(|cls| f(cls))
self.mro.read()[1..].iter().find_map(|cls| f(cls))
}
}

Expand Down Expand Up @@ -531,8 +549,10 @@ 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))?;
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: &PyType = subclass.payload().unwrap();
Expand Down
2 changes: 1 addition & 1 deletion vm/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1384,7 +1384,7 @@ impl ExecutingFrame<'_> {
fn import(&mut self, vm: &VirtualMachine, module_name: Option<&Py<PyStr>>) -> PyResult<()> {
let module_name = module_name.unwrap_or(vm.ctx.empty_str);
let from_list = <Option<PyTupleTyped<PyStrRef>>>::try_from_object(vm, self.pop_value())?
.unwrap_or_else(|| PyTupleTyped::empty(vm));
.unwrap_or_else(|| PyTupleTyped::empty(&vm.ctx));
let level = usize::try_from_object(vm, self.pop_value())?;

let module = vm.import_from(module_name, from_list, level)?;
Expand Down
5 changes: 4 additions & 1 deletion vm/src/object/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1252,12 +1252,14 @@ 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_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_type_ptr).payload.mro =
PyRwLock::new(vec![type_type.clone(), object_type.clone()]);

(type_type, object_type)
}
Expand All @@ -1273,6 +1275,7 @@ 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_type.mro.write().insert(0, weakref_type.clone());

object_type.subclasses.write().push(
type_type
Expand Down
Loading
Loading