Skip to content

Commit 5504f6d

Browse files
authored
heaptype __qualname__ (#5848)
1 parent f0c7cb2 commit 5504f6d

File tree

5 files changed

+78
-39
lines changed

5 files changed

+78
-39
lines changed

Lib/test/test_descr.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4987,8 +4987,6 @@ class Sub(Base):
49874987
self.assertIn("__dict__", Base.__dict__)
49884988
self.assertNotIn("__dict__", Sub.__dict__)
49894989

4990-
# TODO: RUSTPYTHON
4991-
@unittest.expectedFailure
49924990
def test_bound_method_repr(self):
49934991
class Foo:
49944992
def method(self):
@@ -5126,6 +5124,8 @@ def test_iter_keys(self):
51265124
self.assertEqual(keys, ['__dict__', '__doc__', '__module__',
51275125
'__weakref__', 'meth'])
51285126

5127+
# TODO: RUSTPYTHON
5128+
@unittest.expectedFailure
51295129
@unittest.skipIf(hasattr(sys, 'gettrace') and sys.gettrace(),
51305130
'trace function introduces __local__')
51315131
def test_iter_values(self):

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: 37 additions & 28 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<PyStrRef>,
6365
pub slots: Option<PyTupleTyped<PyStrRef>>,
6466
pub sequence_methods: PySequenceMethods,
6567
pub mapping_methods: PyMappingMethods,
@@ -143,6 +145,16 @@ impl PyPayload for PyType {
143145
}
144146
}
145147

148+
fn downcast_qualname(value: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
149+
match value.downcast::<PyStr>() {
150+
Ok(value) => Ok(value),
151+
Err(value) => Err(vm.new_type_error(format!(
152+
"can only assign string to __qualname__, not '{}'",
153+
value.class().name()
154+
))),
155+
}
156+
}
157+
146158
impl PyType {
147159
pub fn new_simple_heap(
148160
name: &str,
@@ -171,7 +183,8 @@ impl PyType {
171183

172184
let name = ctx.new_str(name);
173185
let heaptype_ext = HeapTypeExt {
174-
name: PyRwLock::new(name),
186+
name: PyRwLock::new(name.clone()),
187+
qualname: PyRwLock::new(name),
175188
slots: None,
176189
sequence_methods: PySequenceMethods::default(),
177190
mapping_methods: PyMappingMethods::default(),
@@ -577,19 +590,12 @@ impl PyType {
577590

578591
#[pygetset]
579592
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())
593+
if let Some(ref heap_type) = self.heaptype_ext {
594+
heap_type.qualname.read().clone().into()
595+
} else {
596+
// For static types, return the name
597+
vm.ctx.new_str(self.name().deref()).into()
598+
}
593599
}
594600

595601
#[pygetset(setter)]
@@ -607,16 +613,14 @@ impl PyType {
607613
self.name()
608614
))
609615
})?;
610-
if !value.class().fast_issubclass(vm.ctx.types.str_type) {
611-
return Err(vm.new_type_error(format!(
612-
"can only assign string to {}.__qualname__, not '{}'",
613-
self.name(),
614-
value.class().name()
615-
)));
616-
}
617-
self.attributes
618-
.write()
619-
.insert(identifier!(vm, __qualname__), value);
616+
617+
let str_value = downcast_qualname(value, vm)?;
618+
619+
let heap_type = self
620+
.heaptype_ext
621+
.as_ref()
622+
.expect("HEAPTYPE should have heaptype_ext");
623+
*heap_type.qualname.write() = str_value;
620624
Ok(())
621625
}
622626

@@ -856,6 +860,14 @@ impl Constructor for PyType {
856860
(metatype, base.to_owned(), bases)
857861
};
858862

863+
let qualname = dict
864+
.pop_item(identifier!(vm, __qualname__).as_object(), vm)?
865+
.map(|obj| downcast_qualname(obj, vm))
866+
.transpose()?
867+
.unwrap_or_else(|| {
868+
// If __qualname__ is not provided, we can use the name as default
869+
name.clone()
870+
});
859871
let mut attributes = dict.to_attributes(vm);
860872

861873
if let Some(f) = attributes.get_mut(identifier!(vm, __init_subclass__)) {
@@ -882,10 +894,6 @@ impl Constructor for PyType {
882894
}
883895
}
884896

885-
attributes
886-
.entry(identifier!(vm, __qualname__))
887-
.or_insert_with(|| name.clone().into());
888-
889897
if attributes.get(identifier!(vm, __eq__)).is_some()
890898
&& attributes.get(identifier!(vm, __hash__)).is_none()
891899
{
@@ -952,6 +960,7 @@ impl Constructor for PyType {
952960
};
953961
let heaptype_ext = HeapTypeExt {
954962
name: PyRwLock::new(name),
963+
qualname: PyRwLock::new(qualname),
955964
slots: heaptype_slots.to_owned(),
956965
sequence_methods: PySequenceMethods::default(),
957966
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)