Skip to content

Commit fe81249

Browse files
committed
heaptype __qualname__
1 parent f0c7cb2 commit fe81249

File tree

4 files changed

+73
-29
lines changed

4 files changed

+73
-29
lines changed

Lib/test/test_typing.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2960,8 +2960,6 @@ def __init__(self, y):
29602960
self.assertNotIsInstance(Capybara('a'), HasX)
29612961

29622962

2963-
# TODO: RUSTPYTHON
2964-
@unittest.expectedFailure
29652963
def test_everything_implements_empty_protocol(self):
29662964
@runtime_checkable
29672965
class Empty(Protocol):
@@ -9238,8 +9236,6 @@ def test_no_isinstance(self):
92389236

92399237

92409238
class SpecialAttrsTests(BaseTestCase):
9241-
# TODO: RUSTPYTHON
9242-
@unittest.expectedFailure
92439239
def test_special_attrs(self):
92449240
cls_to_check = {
92459241
# ABC classes

vm/src/builtins/dict.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,20 @@ impl Py<PyDict> {
616616
}
617617
}
618618

619+
pub fn pop_item<K: DictKey + ?Sized>(
620+
&self,
621+
key: &K,
622+
vm: &VirtualMachine,
623+
) -> PyResult<Option<PyObjectRef>> {
624+
if self.exact_dict(vm) {
625+
self.entries.remove_if_exists(vm, key)
626+
} else {
627+
let value = self.as_object().get_item(key, vm)?;
628+
self.as_object().del_item(key, vm)?;
629+
Ok(Some(value))
630+
}
631+
}
632+
619633
pub fn get_chain<K: DictKey + ?Sized>(
620634
&self,
621635
other: &Self,

vm/src/builtins/type.rs

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ unsafe impl crate::object::Traverse for PyType {
5858
}
5959
}
6060

61+
// PyHeapTypeObject in CPython
6162
pub struct HeapTypeExt {
6263
pub name: PyRwLock<PyStrRef>,
64+
pub qualname: PyRwLock<Option<PyStrRef>>,
6365
pub slots: Option<PyTupleTyped<PyStrRef>>,
6466
pub sequence_methods: PySequenceMethods,
6567
pub mapping_methods: PyMappingMethods,
@@ -172,6 +174,7 @@ impl PyType {
172174
let name = ctx.new_str(name);
173175
let heaptype_ext = HeapTypeExt {
174176
name: PyRwLock::new(name),
177+
qualname: PyRwLock::new(None),
175178
slots: None,
176179
sequence_methods: PySequenceMethods::default(),
177180
mapping_methods: PyMappingMethods::default(),
@@ -577,19 +580,17 @@ impl PyType {
577580

578581
#[pygetset]
579582
pub fn __qualname__(&self, vm: &VirtualMachine) -> PyObjectRef {
580-
self.attributes
581-
.read()
582-
.get(identifier!(vm, __qualname__))
583-
.cloned()
584-
// We need to exclude this method from going into recursion:
585-
.and_then(|found| {
586-
if found.fast_isinstance(vm.ctx.types.getset_type) {
587-
None
588-
} else {
589-
Some(found)
590-
}
591-
})
592-
.unwrap_or_else(|| vm.ctx.new_str(self.name().deref()).into())
583+
if let Some(ref heap_type) = self.heaptype_ext {
584+
heap_type
585+
.qualname
586+
.read()
587+
.clone()
588+
.map(|s| s.into())
589+
.unwrap_or_else(|| vm.ctx.new_str(self.name().deref()).into())
590+
} else {
591+
// For static types, return the name
592+
vm.ctx.new_str(self.name().deref()).into()
593+
}
593594
}
594595

595596
#[pygetset(setter)]
@@ -614,9 +615,18 @@ impl PyType {
614615
value.class().name()
615616
)));
616617
}
617-
self.attributes
618-
.write()
619-
.insert(identifier!(vm, __qualname__), value);
618+
// Store in HeapTypeExt instead of attributes (matching CPython)
619+
if let Some(ref heap_type) = self.heaptype_ext {
620+
let str_value = value
621+
.downcast::<PyStr>()
622+
.map_err(|_| vm.new_type_error("__qualname__ must be a string"))?;
623+
*heap_type.qualname.write() = Some(str_value);
624+
} else {
625+
// This shouldn't happen for HEAPTYPE, but fallback to attributes
626+
self.attributes
627+
.write()
628+
.insert(identifier!(vm, __qualname__), value);
629+
}
620630
Ok(())
621631
}
622632

@@ -856,6 +866,13 @@ impl Constructor for PyType {
856866
(metatype, base.to_owned(), bases)
857867
};
858868

869+
let qualname = dict
870+
.pop_item(identifier!(vm, __qualname__).as_object(), vm)?
871+
.and_then(|obj| obj.downcast_ref::<PyStr>().map(|s| s.to_owned()))
872+
.or_else(|| {
873+
// If __qualname__ is not provided, we can use the name as default
874+
Some(name.clone())
875+
});
859876
let mut attributes = dict.to_attributes(vm);
860877

861878
if let Some(f) = attributes.get_mut(identifier!(vm, __init_subclass__)) {
@@ -882,10 +899,6 @@ impl Constructor for PyType {
882899
}
883900
}
884901

885-
attributes
886-
.entry(identifier!(vm, __qualname__))
887-
.or_insert_with(|| name.clone().into());
888-
889902
if attributes.get(identifier!(vm, __eq__)).is_some()
890903
&& attributes.get(identifier!(vm, __hash__)).is_none()
891904
{
@@ -952,6 +965,7 @@ impl Constructor for PyType {
952965
};
953966
let heaptype_ext = HeapTypeExt {
954967
name: PyRwLock::new(name),
968+
qualname: PyRwLock::new(qualname),
955969
slots: heaptype_slots.to_owned(),
956970
sequence_methods: PySequenceMethods::default(),
957971
mapping_methods: PyMappingMethods::default(),

vm/src/dict_inner.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ impl<T: Clone> Dict<T> {
366366
where
367367
K: DictKey + ?Sized,
368368
{
369-
if self.delete_if_exists(vm, key)? {
369+
if self.remove_if_exists(vm, key)?.is_some() {
370370
Ok(())
371371
} else {
372372
Err(vm.new_key_error(key.to_pyobject(vm)))
@@ -377,25 +377,45 @@ impl<T: Clone> Dict<T> {
377377
where
378378
K: DictKey + ?Sized,
379379
{
380-
self.delete_if(vm, key, |_| Ok(true))
380+
self.remove_if_exists(vm, key).map(|opt| opt.is_some())
381+
}
382+
383+
pub fn delete_if<K, F>(&self, vm: &VirtualMachine, key: &K, pred: F) -> PyResult<bool>
384+
where
385+
K: DictKey + ?Sized,
386+
F: Fn(&T) -> PyResult<bool>,
387+
{
388+
self.remove_if(vm, key, pred).map(|opt| opt.is_some())
389+
}
390+
391+
pub fn remove_if_exists<K>(&self, vm: &VirtualMachine, key: &K) -> PyResult<Option<T>>
392+
where
393+
K: DictKey + ?Sized,
394+
{
395+
self.remove_if(vm, key, |_| Ok(true))
381396
}
382397

383398
/// pred should be VERY CAREFUL about what it does as it is called while
384399
/// the dict's internal mutex is held
385-
pub(crate) fn delete_if<K, F>(&self, vm: &VirtualMachine, key: &K, pred: F) -> PyResult<bool>
400+
pub(crate) fn remove_if<K, F>(
401+
&self,
402+
vm: &VirtualMachine,
403+
key: &K,
404+
pred: F,
405+
) -> PyResult<Option<T>>
386406
where
387407
K: DictKey + ?Sized,
388408
F: Fn(&T) -> PyResult<bool>,
389409
{
390410
let hash = key.key_hash(vm)?;
391-
let deleted = loop {
411+
let removed = loop {
392412
let lookup = self.lookup(vm, key, hash, None)?;
393413
match self.pop_inner_if(lookup, &pred)? {
394414
ControlFlow::Break(entry) => break entry,
395415
ControlFlow::Continue(()) => continue,
396416
}
397417
};
398-
Ok(deleted.is_some())
418+
Ok(removed.map(|entry| entry.value))
399419
}
400420

401421
pub fn delete_or_insert(&self, vm: &VirtualMachine, key: &PyObject, value: T) -> PyResult<()> {

0 commit comments

Comments
 (0)