Skip to content

Commit f1d9358

Browse files
committed
Fix del obj.__dict__ to match CPython behavior (issue #5355)
1 parent eed618d commit f1d9358

File tree

8 files changed

+39
-16
lines changed

8 files changed

+39
-16
lines changed

build_error.txt

3.83 KB
Binary file not shown.

build_error_gnu.txt

4.09 KB
Binary file not shown.

build_error_gnu_explicit.txt

4.32 KB
Binary file not shown.

build_error_u8.txt

3.69 KB
Binary file not shown.

crates/vm/src/builtins/object.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -554,10 +554,28 @@ impl PyBaseObject {
554554
}
555555

556556
pub fn object_get_dict(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyDictRef> {
557-
obj.dict()
558-
.ok_or_else(|| vm.new_attribute_error("This object has no __dict__"))
557+
if let Some(dict) = obj.dict() {
558+
Ok(dict)
559+
} else {
560+
match obj.instance_dict() {
561+
Some(d) => {
562+
let dict = vm.ctx.new_dict();
563+
d.set(Some(dict.clone()));
564+
Ok(dict)
565+
}
566+
None => Err(vm.new_attribute_error("This object has no __dict__")),
567+
}
568+
}
559569
}
560-
pub fn object_set_dict(obj: PyObjectRef, dict: PyDictRef, vm: &VirtualMachine) -> PyResult<()> {
570+
pub fn object_set_dict(
571+
obj: PyObjectRef,
572+
value: PySetterValue<PyDictRef>,
573+
vm: &VirtualMachine,
574+
) -> PyResult<()> {
575+
let dict = match value {
576+
PySetterValue::Assign(dict) => Some(dict),
577+
PySetterValue::Delete => None,
578+
};
561579
obj.set_dict(dict)
562580
.map_err(|_| vm.new_attribute_error("This object has no __dict__"))
563581
}

crates/vm/src/builtins/type.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2622,7 +2622,11 @@ fn subtype_get_dict(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
26222622
}
26232623

26242624
// = subtype_setdict
2625-
fn subtype_set_dict(obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
2625+
fn subtype_set_dict(
2626+
obj: PyObjectRef,
2627+
value: PySetterValue,
2628+
vm: &VirtualMachine,
2629+
) -> PyResult<()> {
26262630
let base = get_builtin_base_with_dict(obj.class(), vm);
26272631

26282632
if let Some(base_type) = base {
@@ -2634,13 +2638,14 @@ fn subtype_set_dict(obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -
26342638
.descr_set
26352639
.load()
26362640
.ok_or_else(|| raise_dict_descriptor_error(&obj, vm))?;
2637-
descr_set(&descr, obj, PySetterValue::Assign(value), vm)
2641+
descr_set(&descr, obj, value, vm)
26382642
} else {
26392643
Err(raise_dict_descriptor_error(&obj, vm))
26402644
}
26412645
} else {
26422646
// PyObject_GenericSetDict
2643-
object::object_set_dict(obj, value.try_into_value(vm)?, vm)?;
2647+
let value = value.map(|v| v.try_into_value(vm)).transpose()?;
2648+
object::object_set_dict(obj, value, vm)?;
26442649
Ok(())
26452650
}
26462651
}

crates/vm/src/object/core.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -910,7 +910,7 @@ impl Py<PyWeak> {
910910

911911
#[derive(Debug)]
912912
pub(super) struct InstanceDict {
913-
pub(super) d: PyRwLock<PyDictRef>,
913+
pub(super) d: PyRwLock<Option<PyDictRef>>,
914914
}
915915

916916
impl From<PyDictRef> for InstanceDict {
@@ -924,28 +924,28 @@ impl InstanceDict {
924924
#[inline]
925925
pub const fn new(d: PyDictRef) -> Self {
926926
Self {
927-
d: PyRwLock::new(d),
927+
d: PyRwLock::new(Some(d)),
928928
}
929929
}
930930

931931
#[inline]
932-
pub fn get(&self) -> PyDictRef {
932+
pub fn get(&self) -> Option<PyDictRef> {
933933
self.d.read().clone()
934934
}
935935

936936
#[inline]
937-
pub fn set(&self, d: PyDictRef) {
937+
pub fn set(&self, d: Option<PyDictRef>) {
938938
self.replace(d);
939939
}
940940

941941
#[inline]
942-
pub fn replace(&self, d: PyDictRef) -> PyDictRef {
942+
pub fn replace(&self, d: Option<PyDictRef>) -> Option<PyDictRef> {
943943
core::mem::replace(&mut self.d.write(), d)
944944
}
945945

946-
/// Consume the InstanceDict and return the inner PyDictRef.
946+
/// Consume the InstanceDict and return the inner Option<PyDictRef>.
947947
#[inline]
948-
pub fn into_inner(self) -> PyDictRef {
948+
pub fn into_inner(self) -> Option<PyDictRef> {
949949
self.d.into_inner()
950950
}
951951
}
@@ -1451,18 +1451,18 @@ impl PyObject {
14511451
}
14521452

14531453
#[inline(always)]
1454-
fn instance_dict(&self) -> Option<&InstanceDict> {
1454+
pub(crate) fn instance_dict(&self) -> Option<&InstanceDict> {
14551455
self.0.ext_ref().and_then(|ext| ext.dict.as_ref())
14561456
}
14571457

14581458
#[inline(always)]
14591459
pub fn dict(&self) -> Option<PyDictRef> {
1460-
self.instance_dict().map(|d| d.get())
1460+
self.instance_dict().and_then(|d| d.get())
14611461
}
14621462

14631463
/// Set the dict field. Returns `Err(dict)` if this object does not have a dict field
14641464
/// in the first place.
1465-
pub fn set_dict(&self, dict: PyDictRef) -> Result<(), PyDictRef> {
1465+
pub fn set_dict(&self, dict: Option<PyDictRef>) -> Result<(), Option<PyDictRef>> {
14661466
match self.instance_dict() {
14671467
Some(d) => {
14681468
d.set(dict);

test.py

126 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)