From efb3d43064903503fc357b31b91400041e0c1855 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 27 Jun 2025 18:12:47 +0900 Subject: [PATCH] heaptype __qualname__ --- Lib/test/test_descr.py | 4 +-- Lib/test/test_typing.py | 4 --- vm/src/builtins/dict.rs | 14 +++++++++ vm/src/builtins/type.rs | 65 +++++++++++++++++++++++------------------ vm/src/dict_inner.rs | 30 +++++++++++++++---- 5 files changed, 78 insertions(+), 39 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index af0c3acb61..d1c83cc337 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -4987,8 +4987,6 @@ class Sub(Base): self.assertIn("__dict__", Base.__dict__) self.assertNotIn("__dict__", Sub.__dict__) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_bound_method_repr(self): class Foo: def method(self): @@ -5126,6 +5124,8 @@ def test_iter_keys(self): self.assertEqual(keys, ['__dict__', '__doc__', '__module__', '__weakref__', 'meth']) + # TODO: RUSTPYTHON + @unittest.expectedFailure @unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(), 'trace function introduces __local__') def test_iter_values(self): diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index d850d26ca4..72ecc037a8 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2960,8 +2960,6 @@ def __init__(self, y): self.assertNotIsInstance(Capybara('a'), HasX) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_everything_implements_empty_protocol(self): @runtime_checkable class Empty(Protocol): @@ -9238,8 +9236,6 @@ def test_no_isinstance(self): class SpecialAttrsTests(BaseTestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_special_attrs(self): cls_to_check = { # ABC classes diff --git a/vm/src/builtins/dict.rs b/vm/src/builtins/dict.rs index f5f5f2b401..108b05d27c 100644 --- a/vm/src/builtins/dict.rs +++ b/vm/src/builtins/dict.rs @@ -616,6 +616,20 @@ impl Py { } } + pub fn pop_item( + &self, + key: &K, + vm: &VirtualMachine, + ) -> PyResult> { + if self.exact_dict(vm) { + self.entries.remove_if_exists(vm, key) + } else { + let value = self.as_object().get_item(key, vm)?; + self.as_object().del_item(key, vm)?; + Ok(Some(value)) + } + } + pub fn get_chain( &self, other: &Self, diff --git a/vm/src/builtins/type.rs b/vm/src/builtins/type.rs index 8b92cfb1b8..e7c84191b6 100644 --- a/vm/src/builtins/type.rs +++ b/vm/src/builtins/type.rs @@ -58,8 +58,10 @@ unsafe impl crate::object::Traverse for PyType { } } +// PyHeapTypeObject in CPython pub struct HeapTypeExt { pub name: PyRwLock, + pub qualname: PyRwLock, pub slots: Option>, pub sequence_methods: PySequenceMethods, pub mapping_methods: PyMappingMethods, @@ -143,6 +145,16 @@ impl PyPayload for PyType { } } +fn downcast_qualname(value: PyObjectRef, vm: &VirtualMachine) -> PyResult> { + match value.downcast::() { + Ok(value) => Ok(value), + Err(value) => Err(vm.new_type_error(format!( + "can only assign string to __qualname__, not '{}'", + value.class().name() + ))), + } +} + impl PyType { pub fn new_simple_heap( name: &str, @@ -171,7 +183,8 @@ impl PyType { let name = ctx.new_str(name); let heaptype_ext = HeapTypeExt { - name: PyRwLock::new(name), + name: PyRwLock::new(name.clone()), + qualname: PyRwLock::new(name), slots: None, sequence_methods: PySequenceMethods::default(), mapping_methods: PyMappingMethods::default(), @@ -577,19 +590,12 @@ impl PyType { #[pygetset] pub fn __qualname__(&self, vm: &VirtualMachine) -> PyObjectRef { - self.attributes - .read() - .get(identifier!(vm, __qualname__)) - .cloned() - // We need to exclude this method from going into recursion: - .and_then(|found| { - if found.fast_isinstance(vm.ctx.types.getset_type) { - None - } else { - Some(found) - } - }) - .unwrap_or_else(|| vm.ctx.new_str(self.name().deref()).into()) + if let Some(ref heap_type) = self.heaptype_ext { + heap_type.qualname.read().clone().into() + } else { + // For static types, return the name + vm.ctx.new_str(self.name().deref()).into() + } } #[pygetset(setter)] @@ -607,16 +613,14 @@ impl PyType { self.name() )) })?; - if !value.class().fast_issubclass(vm.ctx.types.str_type) { - return Err(vm.new_type_error(format!( - "can only assign string to {}.__qualname__, not '{}'", - self.name(), - value.class().name() - ))); - } - self.attributes - .write() - .insert(identifier!(vm, __qualname__), value); + + let str_value = downcast_qualname(value, vm)?; + + let heap_type = self + .heaptype_ext + .as_ref() + .expect("HEAPTYPE should have heaptype_ext"); + *heap_type.qualname.write() = str_value; Ok(()) } @@ -856,6 +860,14 @@ impl Constructor for PyType { (metatype, base.to_owned(), bases) }; + let qualname = dict + .pop_item(identifier!(vm, __qualname__).as_object(), vm)? + .map(|obj| downcast_qualname(obj, vm)) + .transpose()? + .unwrap_or_else(|| { + // If __qualname__ is not provided, we can use the name as default + name.clone() + }); let mut attributes = dict.to_attributes(vm); if let Some(f) = attributes.get_mut(identifier!(vm, __init_subclass__)) { @@ -882,10 +894,6 @@ impl Constructor for PyType { } } - attributes - .entry(identifier!(vm, __qualname__)) - .or_insert_with(|| name.clone().into()); - if attributes.get(identifier!(vm, __eq__)).is_some() && attributes.get(identifier!(vm, __hash__)).is_none() { @@ -952,6 +960,7 @@ impl Constructor for PyType { }; let heaptype_ext = HeapTypeExt { name: PyRwLock::new(name), + qualname: PyRwLock::new(qualname), slots: heaptype_slots.to_owned(), sequence_methods: PySequenceMethods::default(), mapping_methods: PyMappingMethods::default(), diff --git a/vm/src/dict_inner.rs b/vm/src/dict_inner.rs index 98d955ec8c..3938fe7c93 100644 --- a/vm/src/dict_inner.rs +++ b/vm/src/dict_inner.rs @@ -366,7 +366,7 @@ impl Dict { where K: DictKey + ?Sized, { - if self.delete_if_exists(vm, key)? { + if self.remove_if_exists(vm, key)?.is_some() { Ok(()) } else { Err(vm.new_key_error(key.to_pyobject(vm))) @@ -377,25 +377,45 @@ impl Dict { where K: DictKey + ?Sized, { - self.delete_if(vm, key, |_| Ok(true)) + self.remove_if_exists(vm, key).map(|opt| opt.is_some()) + } + + pub fn delete_if(&self, vm: &VirtualMachine, key: &K, pred: F) -> PyResult + where + K: DictKey + ?Sized, + F: Fn(&T) -> PyResult, + { + self.remove_if(vm, key, pred).map(|opt| opt.is_some()) + } + + pub fn remove_if_exists(&self, vm: &VirtualMachine, key: &K) -> PyResult> + where + K: DictKey + ?Sized, + { + self.remove_if(vm, key, |_| Ok(true)) } /// pred should be VERY CAREFUL about what it does as it is called while /// the dict's internal mutex is held - pub(crate) fn delete_if(&self, vm: &VirtualMachine, key: &K, pred: F) -> PyResult + pub(crate) fn remove_if( + &self, + vm: &VirtualMachine, + key: &K, + pred: F, + ) -> PyResult> where K: DictKey + ?Sized, F: Fn(&T) -> PyResult, { let hash = key.key_hash(vm)?; - let deleted = loop { + let removed = loop { let lookup = self.lookup(vm, key, hash, None)?; match self.pop_inner_if(lookup, &pred)? { ControlFlow::Break(entry) => break entry, ControlFlow::Continue(()) => continue, } }; - Ok(deleted.is_some()) + Ok(removed.map(|entry| entry.value)) } pub fn delete_or_insert(&self, vm: &VirtualMachine, key: &PyObject, value: T) -> PyResult<()> {