From f1d9358493f217beef28a6d8493cde00b405b462 Mon Sep 17 00:00:00 2001 From: alok-108 Date: Mon, 6 Apr 2026 23:03:10 +0530 Subject: [PATCH 01/13] Fix del obj.__dict__ to match CPython behavior (issue #5355) --- build_error.txt | Bin 0 -> 3926 bytes build_error_gnu.txt | Bin 0 -> 4186 bytes build_error_gnu_explicit.txt | Bin 0 -> 4424 bytes build_error_u8.txt | Bin 0 -> 3776 bytes crates/vm/src/builtins/object.rs | 24 +++++++++++++++++++++--- crates/vm/src/builtins/type.rs | 11 ++++++++--- crates/vm/src/object/core.rs | 20 ++++++++++---------- test.py | Bin 0 -> 126 bytes 8 files changed, 39 insertions(+), 16 deletions(-) create mode 100644 build_error.txt create mode 100644 build_error_gnu.txt create mode 100644 build_error_gnu_explicit.txt create mode 100644 build_error_u8.txt create mode 100644 test.py diff --git a/build_error.txt b/build_error.txt new file mode 100644 index 0000000000000000000000000000000000000000..8c8a1c488e271fa9d42386ca8cd3e16ac118cb89 GIT binary patch literal 3926 zcmd5;TTc^F5T0if|HD2RjMC78fEZ1bpy9=M3oph%X%|?b+qzvS7~@Y@zi(!zr`uh6 z@sgO$wtFr!^UZv7X3oza9cfD@r_z^5cJSJ>i*Zi|lE?+#6zh@Pl{=Wpu+ouLthVvb zQ44EV(v*g*;cen~Q};fX94jUtLw6ISj-07Ij;QZ}&IJTqkFo0<^Uv^d&#_!8j;21J9V~p~PvuJYl z?C!v34jwEs?!(f@!kR_)lRU)u9kBEiVJv&#s)O+Wa*6UXiHmV~36?ML+{jyewKZ?t zpS>~brr!|m!&8p~c}`bw4lHBk1`p{+HK0^+w+5QD67~L4MA-gx^(O6Tx z+$kg&n>Kjts$R2wrKe*Z8MQG-Lk7!3=%60^7KyPX(GO4D)_{#^BU=uc~nHVFC%DL)w7do4W*pnF|LB1x}0aNJ_bYXgUMJm z50#gd$Ey0dMj(=-@wkDRYrIE=7bzHK7hpxZ)C_P0-^M;Y#k%cdmx#bFyUMA?lUQ5_ zCChp^Gbzd1PCh-yBG|Ux!ZYc)+r=KsAjX|jjKtp>Ynun=i9DbLf(o=FV>M>*G7KiQk;qqEC7 zzaM!8Ihs)wcpg?JZZhV@5Z2j684Gq%Vy8A%#t5vMPccp~xn@@|xqy#8G4nDYvueN- z6mCpBd2Os1M~sO}kSnhro&!9qJO|8&&#-u`77Y#Mtco)i&k!xY9R)PBy?C_hy34O8H-Rd;X$BdkMOX}2C=o`>MX-8z<9)4)k~XH1E`qX*tvp%|Y4uk0Kce*hSH*T) z^ca&_r!_G9C(*0F5mr&s#%)Mvp)6x7kCx-Pd^@WmoJL1mi)7;8iR1K*_+R1s1xSl< AG5`Po literal 0 HcmV?d00001 diff --git a/build_error_gnu.txt b/build_error_gnu.txt new file mode 100644 index 0000000000000000000000000000000000000000..152146d731854f9ab54d97bf2b7959a3df8e13f0 GIT binary patch literal 4186 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 literal 0 HcmV?d00001 diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index 002b05d38f1..7ed511ceabe 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -554,10 +554,28 @@ impl PyBaseObject { } pub fn object_get_dict(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - obj.dict() - .ok_or_else(|| vm.new_attribute_error("This object has no __dict__")) + if let Some(dict) = obj.dict() { + Ok(dict) + } else { + match obj.instance_dict() { + Some(d) => { + let dict = vm.ctx.new_dict(); + d.set(Some(dict.clone())); + Ok(dict) + } + None => Err(vm.new_attribute_error("This object has no __dict__")), + } + } } -pub fn object_set_dict(obj: PyObjectRef, dict: PyDictRef, vm: &VirtualMachine) -> PyResult<()> { +pub fn object_set_dict( + obj: PyObjectRef, + value: PySetterValue, + vm: &VirtualMachine, +) -> PyResult<()> { + let dict = match value { + PySetterValue::Assign(dict) => Some(dict), + PySetterValue::Delete => None, + }; obj.set_dict(dict) .map_err(|_| vm.new_attribute_error("This object has no __dict__")) } diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 64801a1de5c..0b2024cc6b0 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -2622,7 +2622,11 @@ fn subtype_get_dict(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { } // = subtype_setdict -fn subtype_set_dict(obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { +fn subtype_set_dict( + obj: PyObjectRef, + value: PySetterValue, + vm: &VirtualMachine, +) -> PyResult<()> { let base = get_builtin_base_with_dict(obj.class(), vm); if let Some(base_type) = base { @@ -2634,13 +2638,14 @@ fn subtype_set_dict(obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) - .descr_set .load() .ok_or_else(|| raise_dict_descriptor_error(&obj, vm))?; - descr_set(&descr, obj, PySetterValue::Assign(value), vm) + descr_set(&descr, obj, value, vm) } else { Err(raise_dict_descriptor_error(&obj, vm)) } } else { // PyObject_GenericSetDict - object::object_set_dict(obj, value.try_into_value(vm)?, vm)?; + let value = value.map(|v| v.try_into_value(vm)).transpose()?; + object::object_set_dict(obj, value, vm)?; Ok(()) } } diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index a8d7c09da89..2086362e2f6 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -910,7 +910,7 @@ impl Py { #[derive(Debug)] pub(super) struct InstanceDict { - pub(super) d: PyRwLock, + pub(super) d: PyRwLock>, } impl From for InstanceDict { @@ -924,28 +924,28 @@ impl InstanceDict { #[inline] pub const fn new(d: PyDictRef) -> Self { Self { - d: PyRwLock::new(d), + d: PyRwLock::new(Some(d)), } } #[inline] - pub fn get(&self) -> PyDictRef { + pub fn get(&self) -> Option { self.d.read().clone() } #[inline] - pub fn set(&self, d: PyDictRef) { + pub fn set(&self, d: Option) { self.replace(d); } #[inline] - pub fn replace(&self, d: PyDictRef) -> PyDictRef { + pub fn replace(&self, d: Option) -> Option { core::mem::replace(&mut self.d.write(), d) } - /// Consume the InstanceDict and return the inner PyDictRef. + /// Consume the InstanceDict and return the inner Option. #[inline] - pub fn into_inner(self) -> PyDictRef { + pub fn into_inner(self) -> Option { self.d.into_inner() } } @@ -1451,18 +1451,18 @@ impl PyObject { } #[inline(always)] - fn instance_dict(&self) -> Option<&InstanceDict> { + pub(crate) fn instance_dict(&self) -> Option<&InstanceDict> { self.0.ext_ref().and_then(|ext| ext.dict.as_ref()) } #[inline(always)] pub fn dict(&self) -> Option { - self.instance_dict().map(|d| d.get()) + self.instance_dict().and_then(|d| d.get()) } /// Set the dict field. Returns `Err(dict)` if this object does not have a dict field /// in the first place. - pub fn set_dict(&self, dict: PyDictRef) -> Result<(), PyDictRef> { + pub fn set_dict(&self, dict: Option) -> Result<(), Option> { match self.instance_dict() { Some(d) => { d.set(dict); diff --git a/test.py b/test.py new file mode 100644 index 0000000000000000000000000000000000000000..0370b8a80cb5998039d406b0533f6112c45a75b5 GIT binary patch literal 126 zcmezWFPR~SA(5dN2o)II81jM83dkyeit#dVF{A Date: Tue, 7 Apr 2026 03:02:44 +0530 Subject: [PATCH 02/13] Address CodeRabbit concerns: fix GC clearing and improve thread safety of lazy __dict__ recreation --- crates/vm/src/builtins/object.rs | 6 +----- crates/vm/src/object/core.rs | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/crates/vm/src/builtins/object.rs b/crates/vm/src/builtins/object.rs index 7ed511ceabe..5599435706c 100644 --- a/crates/vm/src/builtins/object.rs +++ b/crates/vm/src/builtins/object.rs @@ -558,11 +558,7 @@ pub fn object_get_dict(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let dict = vm.ctx.new_dict(); - d.set(Some(dict.clone())); - Ok(dict) - } + Some(d) => Ok(d.get_or_insert(vm)), None => Err(vm.new_attribute_error("This object has no __dict__")), } } diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 2086362e2f6..0cffc0247de 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -909,8 +909,8 @@ impl Py { } #[derive(Debug)] -pub(super) struct InstanceDict { - pub(super) d: PyRwLock>, +pub(crate) struct InstanceDict { + pub(crate) d: PyRwLock>, } impl From for InstanceDict { @@ -948,6 +948,17 @@ impl InstanceDict { pub fn into_inner(self) -> Option { self.d.into_inner() } + + pub(crate) fn get_or_insert(&self, vm: &VirtualMachine) -> PyDictRef { + let mut d = self.d.write(); + if let Some(existing) = d.as_ref() { + existing.clone() + } else { + let dict = vm.ctx.new_dict(); + *d = Some(dict.clone()); + dict + } + } } impl PyInner { @@ -1776,8 +1787,9 @@ impl PyObject { let ext = unsafe { &mut *ext_ptr }; if let Some(old_dict) = ext.dict.take() { // Get the dict ref before dropping InstanceDict - let dict_ref = old_dict.into_inner(); - result.push(dict_ref.into()); + if let Some(dict_ref) = old_dict.into_inner() { + result.push(dict_ref.into()); + } } for slot in ext.slots.iter() { if let Some(val) = slot.write().take() { From d3cc9d436a56259e224c7807531703c585019fc0 Mon Sep 17 00:00:00 2001 From: alok-108 Date: Tue, 7 Apr 2026 13:23:57 +0530 Subject: [PATCH 03/13] Fix del obj.__dict__: improve GC safety, implement lazy re-creation in setattr, and enable passing CPython tests --- Lib/test/test_class.py | 6 +++--- crates/vm/src/object/core.rs | 28 ++++++++++++++++++++------- crates/vm/src/protocol/object.rs | 8 +++++--- extra_tests/snippets/test_del_dict.py | 23 ++++++++++++++++++++++ 4 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 extra_tests/snippets/test_del_dict.py diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 7420f289b16..a0194572558 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -910,7 +910,7 @@ def test_managed_dict_only_for_varsized_subclass(self): self.assertEqual(flags & Py_TPFLAGS_INLINE_VALUES, 0) self.assertFalse(has_inline_values(VarSizedSubclass())) - @unittest.expectedFailure # TODO: RUSTPYTHON + # TODO: RUSTPYTHON def test_has_inline_values(self): c = Plain() self.assertTrue(has_inline_values(c)) @@ -978,7 +978,7 @@ def __init__(self): obj.foo = None # Aborted here self.assertEqual(obj.__dict__, {"foo":None}) - @unittest.expectedFailure # TODO: RUSTPYTHON + # TODO: RUSTPYTHON def test_store_attr_deleted_dict(self): class Foo: pass @@ -988,7 +988,7 @@ class Foo: f.a = 3 self.assertEqual(f.a, 3) - @unittest.expectedFailure # TODO: RUSTPYTHON + # TODO: RUSTPYTHON def test_rematerialize_object_dict(self): # gh-121860: rematerializing an object's managed dictionary after it # had been deleted caused a crash. diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 0cffc0247de..0988bcc227d 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -307,9 +307,13 @@ pub(super) struct ObjExt { } impl ObjExt { - fn new(dict: Option, member_count: usize) -> Self { + fn new(dict: Option, member_count: usize, has_dict: bool) -> Self { Self { - dict: dict.map(InstanceDict::new), + dict: if has_dict { + Some(InstanceDict::from_opt(dict)) + } else { + None + }, slots: core::iter::repeat_with(|| PyRwLock::new(None)) .take(member_count) .collect_vec() @@ -928,6 +932,13 @@ impl InstanceDict { } } + #[inline] + pub const fn from_opt(d: Option) -> Self { + Self { + d: PyRwLock::new(d), + } + } + #[inline] pub fn get(&self) -> Option { self.d.read().clone() @@ -950,11 +961,14 @@ impl InstanceDict { } pub(crate) fn get_or_insert(&self, vm: &VirtualMachine) -> PyDictRef { + if let Some(existing) = self.d.read().as_ref() { + return existing.clone(); + } + let dict = vm.ctx.new_dict(); let mut d = self.d.write(); if let Some(existing) = d.as_ref() { existing.clone() } else { - let dict = vm.ctx.new_dict(); *d = Some(dict.clone()); dict } @@ -1071,7 +1085,8 @@ impl PyInner { unsafe { if let Some(offset) = ext_start { let ext_ptr = alloc_ptr.add(offset) as *mut ObjExt; - ext_ptr.write(ObjExt::new(dict, member_count)); + let has_dict = typ.slots.flags.has_feature(crate::types::PyTypeFlags::HAS_DICT); + ext_ptr.write(ObjExt::new(dict, member_count, has_dict)); } if let Some(offset) = weakref_start { @@ -1785,9 +1800,8 @@ impl PyObject { let ext_ptr = core::ptr::with_exposed_provenance_mut::(self_addr.wrapping_sub(offset)); let ext = unsafe { &mut *ext_ptr }; - if let Some(old_dict) = ext.dict.take() { - // Get the dict ref before dropping InstanceDict - if let Some(dict_ref) = old_dict.into_inner() { + if let Some(instance_dict) = &ext.dict { + if let Some(dict_ref) = instance_dict.replace(None) { result.push(dict_ref.into()); } } diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index e59a1f15a6f..5533a1c1762 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -199,10 +199,10 @@ impl PyObject { } } - if let Some(dict) = self.dict() { + if let Some(instance_dict) = self.instance_dict() { if let PySetterValue::Assign(value) = value { - dict.set_item(attr_name, value, vm)?; - } else { + instance_dict.get_or_insert(vm).set_item(attr_name, value, vm)?; + } else if let Some(dict) = instance_dict.get() { dict.del_item(attr_name, vm).map_err(|e| { if e.fast_isinstance(vm.ctx.exceptions.key_error) { vm.new_no_attribute_error(self.to_owned(), attr_name.to_owned()) @@ -210,6 +210,8 @@ impl PyObject { e } })?; + } else { + return Err(vm.new_no_attribute_error(self.to_owned(), attr_name.to_owned())); } Ok(()) } else { diff --git a/extra_tests/snippets/test_del_dict.py b/extra_tests/snippets/test_del_dict.py new file mode 100644 index 00000000000..7f0f1d594c2 --- /dev/null +++ b/extra_tests/snippets/test_del_dict.py @@ -0,0 +1,23 @@ +# Test that del obj.__dict__ works and lazy creation happens +class C: + pass + +obj = C() +obj.x = 42 + +# Delete __dict__ +del obj.__dict__ + +# After deletion, accessing __dict__ should return a new empty dict +d = obj.__dict__ +assert isinstance(d, dict) +assert len(d) == 0 + +# Old attribute should be gone +try: + obj.x + assert False, "AttributeError expected" +except AttributeError: + pass + +print("OK") From 0b085eef5674f8cafdfdc9b9c220926aee654d5b Mon Sep 17 00:00:00 2001 From: alok-108 Date: Tue, 7 Apr 2026 13:26:58 +0530 Subject: [PATCH 04/13] Restore expectedFailure for test_has_inline_values --- Lib/test/test_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index a0194572558..0bd6491954d 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -910,7 +910,7 @@ def test_managed_dict_only_for_varsized_subclass(self): self.assertEqual(flags & Py_TPFLAGS_INLINE_VALUES, 0) self.assertFalse(has_inline_values(VarSizedSubclass())) - # TODO: RUSTPYTHON + @unittest.expectedFailure # TODO: RUSTPYTHON def test_has_inline_values(self): c = Plain() self.assertTrue(has_inline_values(c)) From 477c9019be849dd405b6e55b9b068477300092df Mon Sep 17 00:00:00 2001 From: alok-108 Date: Tue, 7 Apr 2026 13:44:24 +0530 Subject: [PATCH 05/13] Fix ObjExt::new call site to include has_dict parameter --- crates/vm/src/object/core.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 0988bcc227d..665c51a5356 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -2470,7 +2470,7 @@ pub(crate) fn init_type_hierarchy() -> (PyTypeRef, PyTypeRef, PyTypeRef) { unsafe { let ext_ptr = alloc_ptr as *mut ObjExt; - ext_ptr.write(ObjExt::new(None, 0)); + ext_ptr.write(ObjExt::new(None, 0, true)); let weakref_ptr = alloc_ptr.add(weakref_offset) as *mut WeakRefList; weakref_ptr.write(WeakRefList::new()); From 4cc6d83e2bd6800250a4c65fb6d2ad6e245a1194 Mon Sep 17 00:00:00 2001 From: alok-108 Date: Tue, 7 Apr 2026 13:51:12 +0530 Subject: [PATCH 06/13] Remove stray test.py to avoid CI syntax errors --- test.py | Bin 126 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test.py diff --git a/test.py b/test.py deleted file mode 100644 index 0370b8a80cb5998039d406b0533f6112c45a75b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 126 zcmezWFPR~SA(5dN2o)II81jM83dkyeit#dVF{A Date: Tue, 7 Apr 2026 21:20:00 +0530 Subject: [PATCH 07/13] Remove debug txt files and clean test_class.py comments --- Lib/test/test_class.py | 2 -- build_error.txt | Bin 3926 -> 0 bytes build_error_gnu.txt | Bin 4186 -> 0 bytes build_error_gnu_explicit.txt | Bin 4424 -> 0 bytes build_error_u8.txt | Bin 3776 -> 0 bytes 5 files changed, 2 deletions(-) delete mode 100644 build_error.txt delete mode 100644 build_error_gnu.txt delete mode 100644 build_error_gnu_explicit.txt delete mode 100644 build_error_u8.txt diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 0bd6491954d..1aee6fb73d2 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -978,7 +978,6 @@ def __init__(self): obj.foo = None # Aborted here self.assertEqual(obj.__dict__, {"foo":None}) - # TODO: RUSTPYTHON def test_store_attr_deleted_dict(self): class Foo: pass @@ -988,7 +987,6 @@ class Foo: f.a = 3 self.assertEqual(f.a, 3) - # TODO: RUSTPYTHON def test_rematerialize_object_dict(self): # gh-121860: rematerializing an object's managed dictionary after it # had been deleted caused a crash. diff --git a/build_error.txt b/build_error.txt deleted file mode 100644 index 8c8a1c488e271fa9d42386ca8cd3e16ac118cb89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3926 zcmd5;TTc^F5T0if|HD2RjMC78fEZ1bpy9=M3oph%X%|?b+qzvS7~@Y@zi(!zr`uh6 z@sgO$wtFr!^UZv7X3oza9cfD@r_z^5cJSJ>i*Zi|lE?+#6zh@Pl{=Wpu+ouLthVvb zQ44EV(v*g*;cen~Q};fX94jUtLw6ISj-07Ij;QZ}&IJTqkFo0<^Uv^d&#_!8j;21J9V~p~PvuJYl z?C!v34jwEs?!(f@!kR_)lRU)u9kBEiVJv&#s)O+Wa*6UXiHmV~36?ML+{jyewKZ?t zpS>~brr!|m!&8p~c}`bw4lHBk1`p{+HK0^+w+5QD67~L4MA-gx^(O6Tx z+$kg&n>Kjts$R2wrKe*Z8MQG-Lk7!3=%60^7KyPX(GO4D)_{#^BU=uc~nHVFC%DL)w7do4W*pnF|LB1x}0aNJ_bYXgUMJm z50#gd$Ey0dMj(=-@wkDRYrIE=7bzHK7hpxZ)C_P0-^M;Y#k%cdmx#bFyUMA?lUQ5_ zCChp^Gbzd1PCh-yBG|Ux!ZYc)+r=KsAjX|jjKtp>Ynun=i9DbLf(o=FV>M>*G7KiQk;qqEC7 zzaM!8Ihs)wcpg?JZZhV@5Z2j684Gq%Vy8A%#t5vMPccp~xn@@|xqy#8G4nDYvueN- z6mCpBd2Os1M~sO}kSnhro&!9qJO|8&&#-u`77Y#Mtco)i&k!xY9R)PBy?C_hy34O8H-Rd;X$BdkMOX}2C=o`>MX-8z<9)4)k~XH1E`qX*tvp%|Y4uk0Kce*hSH*T) z^ca&_r!_G9C(*0F5mr&s#%)Mvp)6x7kCx-Pd^@WmoJL1mi)7;8iR1K*_+R1s1xSl< AG5`Po diff --git a/build_error_gnu.txt b/build_error_gnu.txt deleted file mode 100644 index 152146d731854f9ab54d97bf2b7959a3df8e13f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4186 zcmd6q+fNfw5XR@(#Q$L*F$$pt1u>dvgN7FqFX6=)DeY1#ZNY81#`veJ-|tLMx7#hf zs3c^wbkAjGzWL^I4!?gjtZtdL?8Fk=(d$o5ZqwXR=A zRjr*{#nxC@1fv;5f}Y5ZPTx&dKoyNXal<5S7CHP;KgM&ID!wIbQkb7k-JRS$U+ z{n;JG-1WQMo;-~_z+qcin1-l#Lw5GG?vm_}JixMl#~ET!&i3y@0lTSW^X%TV$0JSC zmX0Ho{0CWAqS3Q zTR1l6BB8pvss;C_t>1n@OI2Db_H+)8fozkZu5H{8Wa%2S{a_VVd%>T!{81dj2_ zJ*vo?HQSn_i*9r%pQ4m|;Qh+8YFaJ~)Ro%uJ?}ly3*^1)ZO^exzwGFx!@*=KFM4)e z;1f2yFUHEKjrw8i!Y-Rh!-<~GL`0kgXEaAkukb#69DGRZU^HIS%%$FgA(Ec7(zoeB zXCa4UbbgFHe6DrhM_*4AUCx14$a=VqI*=^tVP;$sW~s2Q{5TgkiER66Ko!MoYif^Y z5EW1NVBe%<(L*e8Zs5ywZ!csC3yY&Y6>Djb0y7!emxU~N03N!KXU2ZW7w=<9=$2>d z22?WV+Pb2=r7wDs?w4LV_eXm0F`metH%VRmvzoPxZvCa zJKCtmL{arxNR`XPnm+DwIX-+M^D-gxnx=yC8xECM*NVpoPND>{id`V)0JTaTkPqKv z@i16)G|0ghcKX_t-)C|(*j}8in|j!7a`m@Zr=4AQ5odnSD5IOqHX^!6#^szLk&l*( zcKK$=ZLo|I8h1`foxx3th>YnjnqgTii523$#W87)A#`>1p&%(X%gH~qR z8H7jL?dzAfUG1HRov;xGQS$emyW-)jle$x9Z zVVOjPd3dHs4fK90xk77YjF9^V56>0ru|8MfrJnjRs_xG-*|ON1ev^A4Pn`!iER@f= z_@oBN^HTdJ$$94imhIVBh(Wt<{uT<@%_Upc$$oge)YL6Ek_6nCee{~`^H^tl%FX?T z26TAsc*PDB$C2uRc&4_mM?4eaxopG2Oi#wMTlY%eRefEr=TnrYUrZIRs|5cO;onO< z=;Dq#p2R)?4}~zz6D_;avJ-x3;OOFEA{-N;IF>i?fE4z(8**LNPbG1rE-}|HJ&KBO z7OdC7#oi5CDtS&_nJ+GnB*h#4e0@C7sCzKHE)aw;9@;&-HIL;}$#57rfIMExus0XuyZ}NAsgEPNu3RVhOq; zR#F{(`YheXtF*>G&<=O8-;vZ|skJ&=ddemc)!Lj-Ix#+aT;SN}3Xc$ZQ}-$2dtXw8 z@Nypvg_)C;B(pd}V2QgXS0&S#=v7(6w#5LZ%ZPLoqgzh^WGp^dHsH5Q5I2vnXEmLh zEFmBMlC_V~nxR3Cn*}>mX+1IgTPB*F>UNk&k=QhLvhb%Dc`h|=ZE59FU#~8Y7c88~ zzq)GvxuWgWHn37xig$df()uVFMXh#8rXFI6 z$dmQm-d@NO7S>10oR~)`Fw@uPvS1ZRu26ZFQ*UEl*bI`zN#{&VlC#XF+sfvj^<>=> zq~j<{#K=lp|3kWMa?QdUtxa_byq=%PU1#S)2ieAOVY_YJ zf*%RlY`f=UX5N{1X3qKfqh+b(c4U1^Y+J8CI~sTG)H-&ex2N^QZrTmaPM9kZu%2nxvF0D^Wlz(F0Y}?D zXe|#mbA1o(zU&qDCD>Wh{#(-ea4hv)dkf*jlb*h^@SAAnr4SUdQ?EUdZk~wpyIt); z?)MU`##j@Boq#C|Jmhw6dqR6Cqs|Et`ed@cg(fOYg?kJhJcYuEwv0r5g}twkxh$8lOt86L^`#MI4@q)e}AE z_DWxA$e!rW?gjU*-x2P~(-;SM?I;(szZ|wQYmldA z`-=PdI0lY``dMFyq$ea@)s6ep(f^>LWldTd_Fw^yLbl0J*EZ<~vhFa!Asw6}1InTt0O(4qUFrV~&POA~hnml()8Cl#^aqyjtI z3TaQos{52Fm*JYu>vAPN?2>u8ka^7t`@;~Vl2iLF=4GP zyGkT~gKPHQ#$9a|B{rrb7?IYYtYWK~UGj From 6ca96310b9cfedacb4b4433671990968c6ab2130 Mon Sep 17 00:00:00 2001 From: Alok Pandey <166419621+alok-108@users.noreply.github.com> Date: Tue, 7 Apr 2026 21:51:27 +0530 Subject: [PATCH 08/13] Delete Lib/test/test_class.py --- Lib/test/test_class.py | 1061 ---------------------------------------- 1 file changed, 1061 deletions(-) delete mode 100644 Lib/test/test_class.py diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py deleted file mode 100644 index 1aee6fb73d2..00000000000 --- a/Lib/test/test_class.py +++ /dev/null @@ -1,1061 +0,0 @@ -"Test the functionality of Python classes implementing operators." - -import unittest -from test import support -from test.support import cpython_only, import_helper, script_helper - -testmeths = [ - -# Binary operations - "add", - "radd", - "sub", - "rsub", - "mul", - "rmul", - "matmul", - "rmatmul", - "truediv", - "rtruediv", - "floordiv", - "rfloordiv", - "mod", - "rmod", - "divmod", - "rdivmod", - "pow", - "rpow", - "rshift", - "rrshift", - "lshift", - "rlshift", - "and", - "rand", - "or", - "ror", - "xor", - "rxor", - -# List/dict operations - "contains", - "getitem", - "setitem", - "delitem", - -# Unary operations - "neg", - "pos", - "abs", - -# generic operations - "init", - ] - -# These need to return something other than None -# "hash", -# "str", -# "repr", -# "int", -# "float", - -# These are separate because they can influence the test of other methods. -# "getattr", -# "setattr", -# "delattr", - -callLst = [] -def trackCall(f): - def track(*args, **kwargs): - callLst.append((f.__name__, args)) - return f(*args, **kwargs) - return track - -statictests = """ -@trackCall -def __hash__(self, *args): - return hash(id(self)) - -@trackCall -def __str__(self, *args): - return "AllTests" - -@trackCall -def __repr__(self, *args): - return "AllTests" - -@trackCall -def __int__(self, *args): - return 1 - -@trackCall -def __index__(self, *args): - return 1 - -@trackCall -def __float__(self, *args): - return 1.0 - -@trackCall -def __eq__(self, *args): - return True - -@trackCall -def __ne__(self, *args): - return False - -@trackCall -def __lt__(self, *args): - return False - -@trackCall -def __le__(self, *args): - return True - -@trackCall -def __gt__(self, *args): - return False - -@trackCall -def __ge__(self, *args): - return True -""" - -# Synthesize all the other AllTests methods from the names in testmeths. - -method_template = """\ -@trackCall -def __%s__(self, *args): - pass -""" - -d = {} -exec(statictests, globals(), d) -for method in testmeths: - exec(method_template % method, globals(), d) -AllTests = type("AllTests", (object,), d) -del d, statictests, method, method_template - -@support.thread_unsafe("callLst is shared between threads") -class ClassTests(unittest.TestCase): - def setUp(self): - callLst[:] = [] - - def assertCallStack(self, expected_calls): - actualCallList = callLst[:] # need to copy because the comparison below will add - # additional calls to callLst - if expected_calls != actualCallList: - self.fail("Expected call list:\n %s\ndoes not match actual call list\n %s" % - (expected_calls, actualCallList)) - - def testInit(self): - foo = AllTests() - self.assertCallStack([("__init__", (foo,))]) - - def testBinaryOps(self): - testme = AllTests() - # Binary operations - - callLst[:] = [] - testme + 1 - self.assertCallStack([("__add__", (testme, 1))]) - - callLst[:] = [] - 1 + testme - self.assertCallStack([("__radd__", (testme, 1))]) - - callLst[:] = [] - testme - 1 - self.assertCallStack([("__sub__", (testme, 1))]) - - callLst[:] = [] - 1 - testme - self.assertCallStack([("__rsub__", (testme, 1))]) - - callLst[:] = [] - testme * 1 - self.assertCallStack([("__mul__", (testme, 1))]) - - callLst[:] = [] - 1 * testme - self.assertCallStack([("__rmul__", (testme, 1))]) - - callLst[:] = [] - testme @ 1 - self.assertCallStack([("__matmul__", (testme, 1))]) - - callLst[:] = [] - 1 @ testme - self.assertCallStack([("__rmatmul__", (testme, 1))]) - - callLst[:] = [] - testme / 1 - self.assertCallStack([("__truediv__", (testme, 1))]) - - - callLst[:] = [] - 1 / testme - self.assertCallStack([("__rtruediv__", (testme, 1))]) - - callLst[:] = [] - testme // 1 - self.assertCallStack([("__floordiv__", (testme, 1))]) - - - callLst[:] = [] - 1 // testme - self.assertCallStack([("__rfloordiv__", (testme, 1))]) - - callLst[:] = [] - testme % 1 - self.assertCallStack([("__mod__", (testme, 1))]) - - callLst[:] = [] - 1 % testme - self.assertCallStack([("__rmod__", (testme, 1))]) - - - callLst[:] = [] - divmod(testme,1) - self.assertCallStack([("__divmod__", (testme, 1))]) - - callLst[:] = [] - divmod(1, testme) - self.assertCallStack([("__rdivmod__", (testme, 1))]) - - callLst[:] = [] - testme ** 1 - self.assertCallStack([("__pow__", (testme, 1))]) - - callLst[:] = [] - 1 ** testme - self.assertCallStack([("__rpow__", (testme, 1))]) - - callLst[:] = [] - testme >> 1 - self.assertCallStack([("__rshift__", (testme, 1))]) - - callLst[:] = [] - 1 >> testme - self.assertCallStack([("__rrshift__", (testme, 1))]) - - callLst[:] = [] - testme << 1 - self.assertCallStack([("__lshift__", (testme, 1))]) - - callLst[:] = [] - 1 << testme - self.assertCallStack([("__rlshift__", (testme, 1))]) - - callLst[:] = [] - testme & 1 - self.assertCallStack([("__and__", (testme, 1))]) - - callLst[:] = [] - 1 & testme - self.assertCallStack([("__rand__", (testme, 1))]) - - callLst[:] = [] - testme | 1 - self.assertCallStack([("__or__", (testme, 1))]) - - callLst[:] = [] - 1 | testme - self.assertCallStack([("__ror__", (testme, 1))]) - - callLst[:] = [] - testme ^ 1 - self.assertCallStack([("__xor__", (testme, 1))]) - - callLst[:] = [] - 1 ^ testme - self.assertCallStack([("__rxor__", (testme, 1))]) - - def testListAndDictOps(self): - testme = AllTests() - - # List/dict operations - - class Empty: pass - - try: - 1 in Empty() - self.fail('failed, should have raised TypeError') - except TypeError: - pass - - callLst[:] = [] - 1 in testme - self.assertCallStack([('__contains__', (testme, 1))]) - - callLst[:] = [] - testme[1] - self.assertCallStack([('__getitem__', (testme, 1))]) - - callLst[:] = [] - testme[1] = 1 - self.assertCallStack([('__setitem__', (testme, 1, 1))]) - - callLst[:] = [] - del testme[1] - self.assertCallStack([('__delitem__', (testme, 1))]) - - callLst[:] = [] - testme[:42] - self.assertCallStack([('__getitem__', (testme, slice(None, 42)))]) - - callLst[:] = [] - testme[:42] = "The Answer" - self.assertCallStack([('__setitem__', (testme, slice(None, 42), - "The Answer"))]) - - callLst[:] = [] - del testme[:42] - self.assertCallStack([('__delitem__', (testme, slice(None, 42)))]) - - callLst[:] = [] - testme[2:1024:10] - self.assertCallStack([('__getitem__', (testme, slice(2, 1024, 10)))]) - - callLst[:] = [] - testme[2:1024:10] = "A lot" - self.assertCallStack([('__setitem__', (testme, slice(2, 1024, 10), - "A lot"))]) - callLst[:] = [] - del testme[2:1024:10] - self.assertCallStack([('__delitem__', (testme, slice(2, 1024, 10)))]) - - callLst[:] = [] - testme[:42, ..., :24:, 24, 100] - self.assertCallStack([('__getitem__', (testme, (slice(None, 42, None), - Ellipsis, - slice(None, 24, None), - 24, 100)))]) - callLst[:] = [] - testme[:42, ..., :24:, 24, 100] = "Strange" - self.assertCallStack([('__setitem__', (testme, (slice(None, 42, None), - Ellipsis, - slice(None, 24, None), - 24, 100), "Strange"))]) - callLst[:] = [] - del testme[:42, ..., :24:, 24, 100] - self.assertCallStack([('__delitem__', (testme, (slice(None, 42, None), - Ellipsis, - slice(None, 24, None), - 24, 100)))]) - - def testUnaryOps(self): - testme = AllTests() - - callLst[:] = [] - -testme - self.assertCallStack([('__neg__', (testme,))]) - callLst[:] = [] - +testme - self.assertCallStack([('__pos__', (testme,))]) - callLst[:] = [] - abs(testme) - self.assertCallStack([('__abs__', (testme,))]) - callLst[:] = [] - int(testme) - self.assertCallStack([('__int__', (testme,))]) - callLst[:] = [] - float(testme) - self.assertCallStack([('__float__', (testme,))]) - callLst[:] = [] - oct(testme) - self.assertCallStack([('__index__', (testme,))]) - callLst[:] = [] - hex(testme) - self.assertCallStack([('__index__', (testme,))]) - - - def testMisc(self): - testme = AllTests() - - callLst[:] = [] - hash(testme) - self.assertCallStack([('__hash__', (testme,))]) - - callLst[:] = [] - repr(testme) - self.assertCallStack([('__repr__', (testme,))]) - - callLst[:] = [] - str(testme) - self.assertCallStack([('__str__', (testme,))]) - - callLst[:] = [] - testme == 1 - self.assertCallStack([('__eq__', (testme, 1))]) - - callLst[:] = [] - testme < 1 - self.assertCallStack([('__lt__', (testme, 1))]) - - callLst[:] = [] - testme > 1 - self.assertCallStack([('__gt__', (testme, 1))]) - - callLst[:] = [] - testme != 1 - self.assertCallStack([('__ne__', (testme, 1))]) - - callLst[:] = [] - 1 == testme - self.assertCallStack([('__eq__', (1, testme))]) - - callLst[:] = [] - 1 < testme - self.assertCallStack([('__gt__', (1, testme))]) - - callLst[:] = [] - 1 > testme - self.assertCallStack([('__lt__', (1, testme))]) - - callLst[:] = [] - 1 != testme - self.assertCallStack([('__ne__', (1, testme))]) - - - def testGetSetAndDel(self): - # Interfering tests - class ExtraTests(AllTests): - @trackCall - def __getattr__(self, *args): - return "SomeVal" - - @trackCall - def __setattr__(self, *args): - pass - - @trackCall - def __delattr__(self, *args): - pass - - testme = ExtraTests() - - callLst[:] = [] - testme.spam - self.assertCallStack([('__getattr__', (testme, "spam"))]) - - callLst[:] = [] - testme.eggs = "spam, spam, spam and ham" - self.assertCallStack([('__setattr__', (testme, "eggs", - "spam, spam, spam and ham"))]) - - callLst[:] = [] - del testme.cardinal - self.assertCallStack([('__delattr__', (testme, "cardinal"))]) - - def testHasAttrString(self): - import sys - from test.support import import_helper - _testlimitedcapi = import_helper.import_module('_testlimitedcapi') - - class A: - def __init__(self): - self.attr = 1 - - a = A() - self.assertEqual(_testlimitedcapi.object_hasattrstring(a, b"attr"), 1) - self.assertEqual(_testlimitedcapi.object_hasattrstring(a, b"noattr"), 0) - self.assertIsNone(sys.exception()) - - def testDel(self): - x = [] - - class DelTest: - def __del__(self): - x.append("crab people, crab people") - testme = DelTest() - del testme - import gc - gc.collect() - self.assertEqual(["crab people, crab people"], x) - - def testBadTypeReturned(self): - # return values of some method are type-checked - class BadTypeClass: - def __int__(self): - return None - __float__ = __int__ - __complex__ = __int__ - __str__ = __int__ - __repr__ = __int__ - __bytes__ = __int__ - __bool__ = __int__ - __index__ = __int__ - def index(x): - return [][x] - - for f in [float, complex, str, repr, bytes, bin, oct, hex, bool, index]: - self.assertRaises(TypeError, f, BadTypeClass()) - - def testHashStuff(self): - # Test correct errors from hash() on objects with comparisons but - # no __hash__ - - class C0: - pass - - hash(C0()) # This should work; the next two should raise TypeError - - class C2: - def __eq__(self, other): return 1 - - self.assertRaises(TypeError, hash, C2()) - - def testPredefinedAttrs(self): - o = object() - - class Custom: - pass - - c = Custom() - - methods = ( - '__class__', '__delattr__', '__dir__', '__eq__', '__format__', - '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', - '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', - '__new__', '__reduce__', '__reduce_ex__', '__repr__', - '__setattr__', '__sizeof__', '__str__', '__subclasshook__' - ) - for name in methods: - with self.subTest(name): - self.assertTrue(callable(getattr(object, name, None))) - self.assertTrue(callable(getattr(o, name, None))) - self.assertTrue(callable(getattr(Custom, name, None))) - self.assertTrue(callable(getattr(c, name, None))) - - not_defined = [ - '__abs__', '__aenter__', '__aexit__', '__aiter__', '__anext__', - '__await__', '__bool__', '__bytes__', '__ceil__', - '__complex__', '__contains__', '__del__', '__delete__', - '__delitem__', '__divmod__', '__enter__', '__exit__', - '__float__', '__floor__', '__get__', '__getattr__', '__getitem__', - '__index__', '__int__', '__invert__', '__iter__', '__len__', - '__length_hint__', '__missing__', '__neg__', '__next__', - '__objclass__', '__pos__', '__rdivmod__', '__reversed__', - '__round__', '__set__', '__setitem__', '__trunc__' - ] - augment = ( - 'add', 'and', 'floordiv', 'lshift', 'matmul', 'mod', 'mul', 'pow', - 'rshift', 'sub', 'truediv', 'xor' - ) - not_defined.extend(map("__{}__".format, augment)) - not_defined.extend(map("__r{}__".format, augment)) - not_defined.extend(map("__i{}__".format, augment)) - for name in not_defined: - with self.subTest(name): - self.assertFalse(hasattr(object, name)) - self.assertFalse(hasattr(o, name)) - self.assertFalse(hasattr(Custom, name)) - self.assertFalse(hasattr(c, name)) - - # __call__() is defined on the metaclass but not the class - self.assertFalse(hasattr(o, "__call__")) - self.assertFalse(hasattr(c, "__call__")) - - @unittest.skip("TODO: RUSTPYTHON; segmentation fault") - @support.skip_emscripten_stack_overflow() - @support.skip_wasi_stack_overflow() - def testSFBug532646(self): - # Test for SF bug 532646 - - class A: - pass - A.__call__ = A() - a = A() - - try: - a() # This should not segfault - except RecursionError: - pass - else: - self.fail("Failed to raise RecursionError") - - @unittest.expectedFailure # TODO: RUSTPYTHON - def testForExceptionsRaisedInInstanceGetattr2(self): - # Tests for exceptions raised in instance_getattr2(). - - def booh(self): - raise AttributeError("booh") - - class A: - a = property(booh) - try: - A().a # Raised AttributeError: A instance has no attribute 'a' - except AttributeError as x: - if str(x) != "booh": - self.fail("attribute error for A().a got masked: %s" % x) - - class E: - __eq__ = property(booh) - E() == E() # In debug mode, caused a C-level assert() to fail - - class I: - __init__ = property(booh) - try: - # In debug mode, printed XXX undetected error and - # raises AttributeError - I() - except AttributeError: - pass - else: - self.fail("attribute error for I.__init__ got masked") - - def assertNotOrderable(self, a, b): - with self.assertRaises(TypeError): - a < b - with self.assertRaises(TypeError): - a > b - with self.assertRaises(TypeError): - a <= b - with self.assertRaises(TypeError): - a >= b - - def testHashComparisonOfMethods(self): - # Test comparison and hash of methods - class A: - def __init__(self, x): - self.x = x - def f(self): - pass - def g(self): - pass - def __eq__(self, other): - return True - def __hash__(self): - raise TypeError - class B(A): - pass - - a1 = A(1) - a2 = A(1) - self.assertTrue(a1.f == a1.f) - self.assertFalse(a1.f != a1.f) - self.assertFalse(a1.f == a2.f) - self.assertTrue(a1.f != a2.f) - self.assertFalse(a1.f == a1.g) - self.assertTrue(a1.f != a1.g) - self.assertNotOrderable(a1.f, a1.f) - self.assertEqual(hash(a1.f), hash(a1.f)) - - self.assertFalse(A.f == a1.f) - self.assertTrue(A.f != a1.f) - self.assertFalse(A.f == A.g) - self.assertTrue(A.f != A.g) - self.assertTrue(B.f == A.f) - self.assertFalse(B.f != A.f) - self.assertNotOrderable(A.f, A.f) - self.assertEqual(hash(B.f), hash(A.f)) - - # the following triggers a SystemError in 2.4 - a = A(hash(A.f)^(-1)) - hash(a.f) - - def testSetattrWrapperNameIntern(self): - # Issue #25794: __setattr__ should intern the attribute name - class A: - pass - - def add(self, other): - return 'summa' - - name = str(b'__add__', 'ascii') # shouldn't be optimized - self.assertIsNot(name, '__add__') # not interned - type.__setattr__(A, name, add) - self.assertEqual(A() + 1, 'summa') - - name2 = str(b'__add__', 'ascii') - self.assertIsNot(name2, '__add__') - self.assertIsNot(name2, name) - type.__delattr__(A, name2) - with self.assertRaises(TypeError): - A() + 1 - - def testSetattrNonStringName(self): - class A: - pass - - with self.assertRaises(TypeError): - type.__setattr__(A, b'x', None) - - def testTypeAttributeAccessErrorMessages(self): - class A: - pass - - error_msg = "type object 'A' has no attribute 'x'" - with self.assertRaisesRegex(AttributeError, error_msg): - A.x - with self.assertRaisesRegex(AttributeError, error_msg): - del A.x - - @unittest.expectedFailure # TODO: RUSTPYTHON - def testObjectAttributeAccessErrorMessages(self): - class A: - pass - class B: - y = 0 - __slots__ = ('z',) - class C: - __slots__ = ("y",) - - def __setattr__(self, name, value) -> None: - if name == "z": - super().__setattr__("y", 1) - else: - super().__setattr__(name, value) - - error_msg = "'A' object has no attribute 'x'" - with self.assertRaisesRegex(AttributeError, error_msg): - A().x - with self.assertRaisesRegex(AttributeError, error_msg): - del A().x - - error_msg = "'B' object has no attribute 'x'" - with self.assertRaisesRegex(AttributeError, error_msg): - B().x - with self.assertRaisesRegex(AttributeError, error_msg): - del B().x - with self.assertRaisesRegex( - AttributeError, - "'B' object has no attribute 'x' and no __dict__ for setting new attributes" - ): - B().x = 0 - with self.assertRaisesRegex( - AttributeError, - "'C' object has no attribute 'x'" - ): - C().x = 0 - - error_msg = "'B' object attribute 'y' is read-only" - with self.assertRaisesRegex(AttributeError, error_msg): - del B().y - with self.assertRaisesRegex(AttributeError, error_msg): - B().y = 0 - - error_msg = 'z' - with self.assertRaisesRegex(AttributeError, error_msg): - B().z - with self.assertRaisesRegex(AttributeError, error_msg): - del B().z - - def testConstructorErrorMessages(self): - # bpo-31506: Improves the error message logic for object_new & object_init - - # Class without any method overrides - class C: - pass - - error_msg = r'C.__init__\(\) takes exactly one argument \(the instance to initialize\)' - - with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'): - C(42) - - with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'): - C.__new__(C, 42) - - with self.assertRaisesRegex(TypeError, error_msg): - C().__init__(42) - - with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'): - object.__new__(C, 42) - - with self.assertRaisesRegex(TypeError, error_msg): - object.__init__(C(), 42) - - # Class with both `__init__` & `__new__` method overridden - class D: - def __new__(cls, *args, **kwargs): - super().__new__(cls, *args, **kwargs) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - error_msg = r'object.__new__\(\) takes exactly one argument \(the type to instantiate\)' - - with self.assertRaisesRegex(TypeError, error_msg): - D(42) - - with self.assertRaisesRegex(TypeError, error_msg): - D.__new__(D, 42) - - with self.assertRaisesRegex(TypeError, error_msg): - object.__new__(D, 42) - - # Class that only overrides __init__ - class E: - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - error_msg = r'object.__init__\(\) takes exactly one argument \(the instance to initialize\)' - - with self.assertRaisesRegex(TypeError, error_msg): - E().__init__(42) - - with self.assertRaisesRegex(TypeError, error_msg): - object.__init__(E(), 42) - - def testClassWithExtCall(self): - class Meta(int): - def __init__(*args, **kwargs): - pass - - def __new__(cls, name, bases, attrs, **kwargs): - return bases, kwargs - - d = {'metaclass': Meta} - - class A(**d): pass - self.assertEqual(A, ((), {})) - class A(0, 1, 2, 3, 4, 5, 6, 7, **d): pass - self.assertEqual(A, (tuple(range(8)), {})) - class A(0, *range(1, 8), **d, foo='bar'): pass - self.assertEqual(A, (tuple(range(8)), {'foo': 'bar'})) - - def testClassCallRecursionLimit(self): - class C: - def __init__(self): - self.c = C() - - with self.assertRaises(RecursionError): - C() - - def add_one_level(): - #Each call to C() consumes 2 levels, so offset by 1. - C() - - with self.assertRaises(RecursionError): - add_one_level() - - def testMetaclassCallOptimization(self): - calls = 0 - - class TypeMetaclass(type): - def __call__(cls, *args, **kwargs): - nonlocal calls - calls += 1 - return type.__call__(cls, *args, **kwargs) - - class Type(metaclass=TypeMetaclass): - def __init__(self, obj): - self._obj = obj - - for i in range(100): - Type(i) - self.assertEqual(calls, 100) - - def test_specialization_class_call_doesnt_crash(self): - # gh-123185 - - class Foo: - def __init__(self, arg): - pass - - for _ in range(8): - try: - Foo() - except: - pass - - -# from _testinternalcapi import has_inline_values # XXX: RUSTPYTHON - -Py_TPFLAGS_INLINE_VALUES = (1 << 2) -Py_TPFLAGS_MANAGED_DICT = (1 << 4) - -class NoManagedDict: - __slots__ = ('a',) - - -class Plain: - pass - - -class WithAttrs: - - def __init__(self): - self.a = 1 - self.b = 2 - self.c = 3 - self.d = 4 - - -class VarSizedSubclass(tuple): - pass - - -class TestInlineValues(unittest.TestCase): - - @unittest.expectedFailure # TODO: RUSTPYTHON; NameError: name 'has_inline_values' is not defined. - def test_no_flags_for_slots_class(self): - flags = NoManagedDict.__flags__ - self.assertEqual(flags & Py_TPFLAGS_MANAGED_DICT, 0) - self.assertEqual(flags & Py_TPFLAGS_INLINE_VALUES, 0) - self.assertFalse(has_inline_values(NoManagedDict())) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 4 - def test_both_flags_for_regular_class(self): - for cls in (Plain, WithAttrs): - with self.subTest(cls=cls.__name__): - flags = cls.__flags__ - self.assertEqual(flags & Py_TPFLAGS_MANAGED_DICT, Py_TPFLAGS_MANAGED_DICT) - self.assertEqual(flags & Py_TPFLAGS_INLINE_VALUES, Py_TPFLAGS_INLINE_VALUES) - self.assertTrue(has_inline_values(cls())) - - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 4 - def test_managed_dict_only_for_varsized_subclass(self): - flags = VarSizedSubclass.__flags__ - self.assertEqual(flags & Py_TPFLAGS_MANAGED_DICT, Py_TPFLAGS_MANAGED_DICT) - self.assertEqual(flags & Py_TPFLAGS_INLINE_VALUES, 0) - self.assertFalse(has_inline_values(VarSizedSubclass())) - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_has_inline_values(self): - c = Plain() - self.assertTrue(has_inline_values(c)) - del c.__dict__ - self.assertFalse(has_inline_values(c)) - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_instances(self): - self.assertTrue(has_inline_values(Plain())) - self.assertTrue(has_inline_values(WithAttrs())) - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_inspect_dict(self): - for cls in (Plain, WithAttrs): - c = cls() - c.__dict__ - self.assertTrue(has_inline_values(c)) - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_update_dict(self): - d = { "e": 5, "f": 6 } - for cls in (Plain, WithAttrs): - c = cls() - c.__dict__.update(d) - self.assertTrue(has_inline_values(c)) - - @staticmethod - def set_100(obj): - for i in range(100): - setattr(obj, f"a{i}", i) - - def check_100(self, obj): - for i in range(100): - self.assertEqual(getattr(obj, f"a{i}"), i) - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_many_attributes(self): - class C: pass - c = C() - self.assertTrue(has_inline_values(c)) - self.set_100(c) - self.assertFalse(has_inline_values(c)) - self.check_100(c) - c = C() - self.assertTrue(has_inline_values(c)) - - @unittest.expectedFailure # TODO: RUSTPYTHON - def test_many_attributes_with_dict(self): - class C: pass - c = C() - d = c.__dict__ - self.assertTrue(has_inline_values(c)) - self.set_100(c) - self.assertFalse(has_inline_values(c)) - self.check_100(c) - - def test_bug_117750(self): - "Aborted on 3.13a6" - class C: - def __init__(self): - self.__dict__.clear() - - obj = C() - self.assertEqual(obj.__dict__, {}) - obj.foo = None # Aborted here - self.assertEqual(obj.__dict__, {"foo":None}) - - def test_store_attr_deleted_dict(self): - class Foo: - pass - - f = Foo() - del f.__dict__ - f.a = 3 - self.assertEqual(f.a, 3) - - def test_rematerialize_object_dict(self): - # gh-121860: rematerializing an object's managed dictionary after it - # had been deleted caused a crash. - class Foo: pass - f = Foo() - f.__dict__["attr"] = 1 - del f.__dict__ - - # Using a str subclass is a way to trigger the re-materialization - class StrSubclass(str): pass - self.assertFalse(hasattr(f, StrSubclass("attr"))) - - # Changing the __class__ also triggers the re-materialization - class Bar: pass - f.__class__ = Bar - self.assertIsInstance(f, Bar) - self.assertEqual(f.__dict__, {}) - - @unittest.skip("TODO: RUSTPYTHON; unexpectedly long runtime") - def test_store_attr_type_cache(self): - """Verifies that the type cache doesn't provide a value which is - inconsistent from the dict.""" - class X: - def __del__(inner_self): - v = C.a - self.assertEqual(v, C.__dict__['a']) - - class C: - a = X() - - # prime the cache - C.a - C.a - - # destructor shouldn't be able to see inconsistent state - C.a = X() - C.a = X() - - @cpython_only - def test_detach_materialized_dict_no_memory(self): - # Skip test if _testcapi is not available: - import_helper.import_module('_testcapi') - - code = """if 1: - import test.support - import _testcapi - - class A: - def __init__(self): - self.a = 1 - self.b = 2 - a = A() - d = a.__dict__ - with test.support.catch_unraisable_exception() as ex: - _testcapi.set_nomemory(0, 1) - del a - assert ex.unraisable.exc_type is MemoryError - try: - d["a"] - except KeyError: - pass - else: - assert False, "KeyError not raised" - """ - rc, out, err = script_helper.assert_python_ok("-c", code) - self.assertEqual(rc, 0) - self.assertFalse(out, msg=out.decode('utf-8')) - self.assertFalse(err, msg=err.decode('utf-8')) - - -if __name__ == '__main__': - unittest.main() From 2564ed12b0525dfda2f1be72c5673377e40afc4e Mon Sep 17 00:00:00 2001 From: alok-108 Date: Tue, 7 Apr 2026 23:30:38 +0530 Subject: [PATCH 09/13] Restore test_class.py with correct changes (remove expectedFailure, no deletion) --- Lib/test/test_class.py | 1061 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1061 insertions(+) create mode 100644 Lib/test/test_class.py diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py new file mode 100644 index 00000000000..1aee6fb73d2 --- /dev/null +++ b/Lib/test/test_class.py @@ -0,0 +1,1061 @@ +"Test the functionality of Python classes implementing operators." + +import unittest +from test import support +from test.support import cpython_only, import_helper, script_helper + +testmeths = [ + +# Binary operations + "add", + "radd", + "sub", + "rsub", + "mul", + "rmul", + "matmul", + "rmatmul", + "truediv", + "rtruediv", + "floordiv", + "rfloordiv", + "mod", + "rmod", + "divmod", + "rdivmod", + "pow", + "rpow", + "rshift", + "rrshift", + "lshift", + "rlshift", + "and", + "rand", + "or", + "ror", + "xor", + "rxor", + +# List/dict operations + "contains", + "getitem", + "setitem", + "delitem", + +# Unary operations + "neg", + "pos", + "abs", + +# generic operations + "init", + ] + +# These need to return something other than None +# "hash", +# "str", +# "repr", +# "int", +# "float", + +# These are separate because they can influence the test of other methods. +# "getattr", +# "setattr", +# "delattr", + +callLst = [] +def trackCall(f): + def track(*args, **kwargs): + callLst.append((f.__name__, args)) + return f(*args, **kwargs) + return track + +statictests = """ +@trackCall +def __hash__(self, *args): + return hash(id(self)) + +@trackCall +def __str__(self, *args): + return "AllTests" + +@trackCall +def __repr__(self, *args): + return "AllTests" + +@trackCall +def __int__(self, *args): + return 1 + +@trackCall +def __index__(self, *args): + return 1 + +@trackCall +def __float__(self, *args): + return 1.0 + +@trackCall +def __eq__(self, *args): + return True + +@trackCall +def __ne__(self, *args): + return False + +@trackCall +def __lt__(self, *args): + return False + +@trackCall +def __le__(self, *args): + return True + +@trackCall +def __gt__(self, *args): + return False + +@trackCall +def __ge__(self, *args): + return True +""" + +# Synthesize all the other AllTests methods from the names in testmeths. + +method_template = """\ +@trackCall +def __%s__(self, *args): + pass +""" + +d = {} +exec(statictests, globals(), d) +for method in testmeths: + exec(method_template % method, globals(), d) +AllTests = type("AllTests", (object,), d) +del d, statictests, method, method_template + +@support.thread_unsafe("callLst is shared between threads") +class ClassTests(unittest.TestCase): + def setUp(self): + callLst[:] = [] + + def assertCallStack(self, expected_calls): + actualCallList = callLst[:] # need to copy because the comparison below will add + # additional calls to callLst + if expected_calls != actualCallList: + self.fail("Expected call list:\n %s\ndoes not match actual call list\n %s" % + (expected_calls, actualCallList)) + + def testInit(self): + foo = AllTests() + self.assertCallStack([("__init__", (foo,))]) + + def testBinaryOps(self): + testme = AllTests() + # Binary operations + + callLst[:] = [] + testme + 1 + self.assertCallStack([("__add__", (testme, 1))]) + + callLst[:] = [] + 1 + testme + self.assertCallStack([("__radd__", (testme, 1))]) + + callLst[:] = [] + testme - 1 + self.assertCallStack([("__sub__", (testme, 1))]) + + callLst[:] = [] + 1 - testme + self.assertCallStack([("__rsub__", (testme, 1))]) + + callLst[:] = [] + testme * 1 + self.assertCallStack([("__mul__", (testme, 1))]) + + callLst[:] = [] + 1 * testme + self.assertCallStack([("__rmul__", (testme, 1))]) + + callLst[:] = [] + testme @ 1 + self.assertCallStack([("__matmul__", (testme, 1))]) + + callLst[:] = [] + 1 @ testme + self.assertCallStack([("__rmatmul__", (testme, 1))]) + + callLst[:] = [] + testme / 1 + self.assertCallStack([("__truediv__", (testme, 1))]) + + + callLst[:] = [] + 1 / testme + self.assertCallStack([("__rtruediv__", (testme, 1))]) + + callLst[:] = [] + testme // 1 + self.assertCallStack([("__floordiv__", (testme, 1))]) + + + callLst[:] = [] + 1 // testme + self.assertCallStack([("__rfloordiv__", (testme, 1))]) + + callLst[:] = [] + testme % 1 + self.assertCallStack([("__mod__", (testme, 1))]) + + callLst[:] = [] + 1 % testme + self.assertCallStack([("__rmod__", (testme, 1))]) + + + callLst[:] = [] + divmod(testme,1) + self.assertCallStack([("__divmod__", (testme, 1))]) + + callLst[:] = [] + divmod(1, testme) + self.assertCallStack([("__rdivmod__", (testme, 1))]) + + callLst[:] = [] + testme ** 1 + self.assertCallStack([("__pow__", (testme, 1))]) + + callLst[:] = [] + 1 ** testme + self.assertCallStack([("__rpow__", (testme, 1))]) + + callLst[:] = [] + testme >> 1 + self.assertCallStack([("__rshift__", (testme, 1))]) + + callLst[:] = [] + 1 >> testme + self.assertCallStack([("__rrshift__", (testme, 1))]) + + callLst[:] = [] + testme << 1 + self.assertCallStack([("__lshift__", (testme, 1))]) + + callLst[:] = [] + 1 << testme + self.assertCallStack([("__rlshift__", (testme, 1))]) + + callLst[:] = [] + testme & 1 + self.assertCallStack([("__and__", (testme, 1))]) + + callLst[:] = [] + 1 & testme + self.assertCallStack([("__rand__", (testme, 1))]) + + callLst[:] = [] + testme | 1 + self.assertCallStack([("__or__", (testme, 1))]) + + callLst[:] = [] + 1 | testme + self.assertCallStack([("__ror__", (testme, 1))]) + + callLst[:] = [] + testme ^ 1 + self.assertCallStack([("__xor__", (testme, 1))]) + + callLst[:] = [] + 1 ^ testme + self.assertCallStack([("__rxor__", (testme, 1))]) + + def testListAndDictOps(self): + testme = AllTests() + + # List/dict operations + + class Empty: pass + + try: + 1 in Empty() + self.fail('failed, should have raised TypeError') + except TypeError: + pass + + callLst[:] = [] + 1 in testme + self.assertCallStack([('__contains__', (testme, 1))]) + + callLst[:] = [] + testme[1] + self.assertCallStack([('__getitem__', (testme, 1))]) + + callLst[:] = [] + testme[1] = 1 + self.assertCallStack([('__setitem__', (testme, 1, 1))]) + + callLst[:] = [] + del testme[1] + self.assertCallStack([('__delitem__', (testme, 1))]) + + callLst[:] = [] + testme[:42] + self.assertCallStack([('__getitem__', (testme, slice(None, 42)))]) + + callLst[:] = [] + testme[:42] = "The Answer" + self.assertCallStack([('__setitem__', (testme, slice(None, 42), + "The Answer"))]) + + callLst[:] = [] + del testme[:42] + self.assertCallStack([('__delitem__', (testme, slice(None, 42)))]) + + callLst[:] = [] + testme[2:1024:10] + self.assertCallStack([('__getitem__', (testme, slice(2, 1024, 10)))]) + + callLst[:] = [] + testme[2:1024:10] = "A lot" + self.assertCallStack([('__setitem__', (testme, slice(2, 1024, 10), + "A lot"))]) + callLst[:] = [] + del testme[2:1024:10] + self.assertCallStack([('__delitem__', (testme, slice(2, 1024, 10)))]) + + callLst[:] = [] + testme[:42, ..., :24:, 24, 100] + self.assertCallStack([('__getitem__', (testme, (slice(None, 42, None), + Ellipsis, + slice(None, 24, None), + 24, 100)))]) + callLst[:] = [] + testme[:42, ..., :24:, 24, 100] = "Strange" + self.assertCallStack([('__setitem__', (testme, (slice(None, 42, None), + Ellipsis, + slice(None, 24, None), + 24, 100), "Strange"))]) + callLst[:] = [] + del testme[:42, ..., :24:, 24, 100] + self.assertCallStack([('__delitem__', (testme, (slice(None, 42, None), + Ellipsis, + slice(None, 24, None), + 24, 100)))]) + + def testUnaryOps(self): + testme = AllTests() + + callLst[:] = [] + -testme + self.assertCallStack([('__neg__', (testme,))]) + callLst[:] = [] + +testme + self.assertCallStack([('__pos__', (testme,))]) + callLst[:] = [] + abs(testme) + self.assertCallStack([('__abs__', (testme,))]) + callLst[:] = [] + int(testme) + self.assertCallStack([('__int__', (testme,))]) + callLst[:] = [] + float(testme) + self.assertCallStack([('__float__', (testme,))]) + callLst[:] = [] + oct(testme) + self.assertCallStack([('__index__', (testme,))]) + callLst[:] = [] + hex(testme) + self.assertCallStack([('__index__', (testme,))]) + + + def testMisc(self): + testme = AllTests() + + callLst[:] = [] + hash(testme) + self.assertCallStack([('__hash__', (testme,))]) + + callLst[:] = [] + repr(testme) + self.assertCallStack([('__repr__', (testme,))]) + + callLst[:] = [] + str(testme) + self.assertCallStack([('__str__', (testme,))]) + + callLst[:] = [] + testme == 1 + self.assertCallStack([('__eq__', (testme, 1))]) + + callLst[:] = [] + testme < 1 + self.assertCallStack([('__lt__', (testme, 1))]) + + callLst[:] = [] + testme > 1 + self.assertCallStack([('__gt__', (testme, 1))]) + + callLst[:] = [] + testme != 1 + self.assertCallStack([('__ne__', (testme, 1))]) + + callLst[:] = [] + 1 == testme + self.assertCallStack([('__eq__', (1, testme))]) + + callLst[:] = [] + 1 < testme + self.assertCallStack([('__gt__', (1, testme))]) + + callLst[:] = [] + 1 > testme + self.assertCallStack([('__lt__', (1, testme))]) + + callLst[:] = [] + 1 != testme + self.assertCallStack([('__ne__', (1, testme))]) + + + def testGetSetAndDel(self): + # Interfering tests + class ExtraTests(AllTests): + @trackCall + def __getattr__(self, *args): + return "SomeVal" + + @trackCall + def __setattr__(self, *args): + pass + + @trackCall + def __delattr__(self, *args): + pass + + testme = ExtraTests() + + callLst[:] = [] + testme.spam + self.assertCallStack([('__getattr__', (testme, "spam"))]) + + callLst[:] = [] + testme.eggs = "spam, spam, spam and ham" + self.assertCallStack([('__setattr__', (testme, "eggs", + "spam, spam, spam and ham"))]) + + callLst[:] = [] + del testme.cardinal + self.assertCallStack([('__delattr__', (testme, "cardinal"))]) + + def testHasAttrString(self): + import sys + from test.support import import_helper + _testlimitedcapi = import_helper.import_module('_testlimitedcapi') + + class A: + def __init__(self): + self.attr = 1 + + a = A() + self.assertEqual(_testlimitedcapi.object_hasattrstring(a, b"attr"), 1) + self.assertEqual(_testlimitedcapi.object_hasattrstring(a, b"noattr"), 0) + self.assertIsNone(sys.exception()) + + def testDel(self): + x = [] + + class DelTest: + def __del__(self): + x.append("crab people, crab people") + testme = DelTest() + del testme + import gc + gc.collect() + self.assertEqual(["crab people, crab people"], x) + + def testBadTypeReturned(self): + # return values of some method are type-checked + class BadTypeClass: + def __int__(self): + return None + __float__ = __int__ + __complex__ = __int__ + __str__ = __int__ + __repr__ = __int__ + __bytes__ = __int__ + __bool__ = __int__ + __index__ = __int__ + def index(x): + return [][x] + + for f in [float, complex, str, repr, bytes, bin, oct, hex, bool, index]: + self.assertRaises(TypeError, f, BadTypeClass()) + + def testHashStuff(self): + # Test correct errors from hash() on objects with comparisons but + # no __hash__ + + class C0: + pass + + hash(C0()) # This should work; the next two should raise TypeError + + class C2: + def __eq__(self, other): return 1 + + self.assertRaises(TypeError, hash, C2()) + + def testPredefinedAttrs(self): + o = object() + + class Custom: + pass + + c = Custom() + + methods = ( + '__class__', '__delattr__', '__dir__', '__eq__', '__format__', + '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', + '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', + '__new__', '__reduce__', '__reduce_ex__', '__repr__', + '__setattr__', '__sizeof__', '__str__', '__subclasshook__' + ) + for name in methods: + with self.subTest(name): + self.assertTrue(callable(getattr(object, name, None))) + self.assertTrue(callable(getattr(o, name, None))) + self.assertTrue(callable(getattr(Custom, name, None))) + self.assertTrue(callable(getattr(c, name, None))) + + not_defined = [ + '__abs__', '__aenter__', '__aexit__', '__aiter__', '__anext__', + '__await__', '__bool__', '__bytes__', '__ceil__', + '__complex__', '__contains__', '__del__', '__delete__', + '__delitem__', '__divmod__', '__enter__', '__exit__', + '__float__', '__floor__', '__get__', '__getattr__', '__getitem__', + '__index__', '__int__', '__invert__', '__iter__', '__len__', + '__length_hint__', '__missing__', '__neg__', '__next__', + '__objclass__', '__pos__', '__rdivmod__', '__reversed__', + '__round__', '__set__', '__setitem__', '__trunc__' + ] + augment = ( + 'add', 'and', 'floordiv', 'lshift', 'matmul', 'mod', 'mul', 'pow', + 'rshift', 'sub', 'truediv', 'xor' + ) + not_defined.extend(map("__{}__".format, augment)) + not_defined.extend(map("__r{}__".format, augment)) + not_defined.extend(map("__i{}__".format, augment)) + for name in not_defined: + with self.subTest(name): + self.assertFalse(hasattr(object, name)) + self.assertFalse(hasattr(o, name)) + self.assertFalse(hasattr(Custom, name)) + self.assertFalse(hasattr(c, name)) + + # __call__() is defined on the metaclass but not the class + self.assertFalse(hasattr(o, "__call__")) + self.assertFalse(hasattr(c, "__call__")) + + @unittest.skip("TODO: RUSTPYTHON; segmentation fault") + @support.skip_emscripten_stack_overflow() + @support.skip_wasi_stack_overflow() + def testSFBug532646(self): + # Test for SF bug 532646 + + class A: + pass + A.__call__ = A() + a = A() + + try: + a() # This should not segfault + except RecursionError: + pass + else: + self.fail("Failed to raise RecursionError") + + @unittest.expectedFailure # TODO: RUSTPYTHON + def testForExceptionsRaisedInInstanceGetattr2(self): + # Tests for exceptions raised in instance_getattr2(). + + def booh(self): + raise AttributeError("booh") + + class A: + a = property(booh) + try: + A().a # Raised AttributeError: A instance has no attribute 'a' + except AttributeError as x: + if str(x) != "booh": + self.fail("attribute error for A().a got masked: %s" % x) + + class E: + __eq__ = property(booh) + E() == E() # In debug mode, caused a C-level assert() to fail + + class I: + __init__ = property(booh) + try: + # In debug mode, printed XXX undetected error and + # raises AttributeError + I() + except AttributeError: + pass + else: + self.fail("attribute error for I.__init__ got masked") + + def assertNotOrderable(self, a, b): + with self.assertRaises(TypeError): + a < b + with self.assertRaises(TypeError): + a > b + with self.assertRaises(TypeError): + a <= b + with self.assertRaises(TypeError): + a >= b + + def testHashComparisonOfMethods(self): + # Test comparison and hash of methods + class A: + def __init__(self, x): + self.x = x + def f(self): + pass + def g(self): + pass + def __eq__(self, other): + return True + def __hash__(self): + raise TypeError + class B(A): + pass + + a1 = A(1) + a2 = A(1) + self.assertTrue(a1.f == a1.f) + self.assertFalse(a1.f != a1.f) + self.assertFalse(a1.f == a2.f) + self.assertTrue(a1.f != a2.f) + self.assertFalse(a1.f == a1.g) + self.assertTrue(a1.f != a1.g) + self.assertNotOrderable(a1.f, a1.f) + self.assertEqual(hash(a1.f), hash(a1.f)) + + self.assertFalse(A.f == a1.f) + self.assertTrue(A.f != a1.f) + self.assertFalse(A.f == A.g) + self.assertTrue(A.f != A.g) + self.assertTrue(B.f == A.f) + self.assertFalse(B.f != A.f) + self.assertNotOrderable(A.f, A.f) + self.assertEqual(hash(B.f), hash(A.f)) + + # the following triggers a SystemError in 2.4 + a = A(hash(A.f)^(-1)) + hash(a.f) + + def testSetattrWrapperNameIntern(self): + # Issue #25794: __setattr__ should intern the attribute name + class A: + pass + + def add(self, other): + return 'summa' + + name = str(b'__add__', 'ascii') # shouldn't be optimized + self.assertIsNot(name, '__add__') # not interned + type.__setattr__(A, name, add) + self.assertEqual(A() + 1, 'summa') + + name2 = str(b'__add__', 'ascii') + self.assertIsNot(name2, '__add__') + self.assertIsNot(name2, name) + type.__delattr__(A, name2) + with self.assertRaises(TypeError): + A() + 1 + + def testSetattrNonStringName(self): + class A: + pass + + with self.assertRaises(TypeError): + type.__setattr__(A, b'x', None) + + def testTypeAttributeAccessErrorMessages(self): + class A: + pass + + error_msg = "type object 'A' has no attribute 'x'" + with self.assertRaisesRegex(AttributeError, error_msg): + A.x + with self.assertRaisesRegex(AttributeError, error_msg): + del A.x + + @unittest.expectedFailure # TODO: RUSTPYTHON + def testObjectAttributeAccessErrorMessages(self): + class A: + pass + class B: + y = 0 + __slots__ = ('z',) + class C: + __slots__ = ("y",) + + def __setattr__(self, name, value) -> None: + if name == "z": + super().__setattr__("y", 1) + else: + super().__setattr__(name, value) + + error_msg = "'A' object has no attribute 'x'" + with self.assertRaisesRegex(AttributeError, error_msg): + A().x + with self.assertRaisesRegex(AttributeError, error_msg): + del A().x + + error_msg = "'B' object has no attribute 'x'" + with self.assertRaisesRegex(AttributeError, error_msg): + B().x + with self.assertRaisesRegex(AttributeError, error_msg): + del B().x + with self.assertRaisesRegex( + AttributeError, + "'B' object has no attribute 'x' and no __dict__ for setting new attributes" + ): + B().x = 0 + with self.assertRaisesRegex( + AttributeError, + "'C' object has no attribute 'x'" + ): + C().x = 0 + + error_msg = "'B' object attribute 'y' is read-only" + with self.assertRaisesRegex(AttributeError, error_msg): + del B().y + with self.assertRaisesRegex(AttributeError, error_msg): + B().y = 0 + + error_msg = 'z' + with self.assertRaisesRegex(AttributeError, error_msg): + B().z + with self.assertRaisesRegex(AttributeError, error_msg): + del B().z + + def testConstructorErrorMessages(self): + # bpo-31506: Improves the error message logic for object_new & object_init + + # Class without any method overrides + class C: + pass + + error_msg = r'C.__init__\(\) takes exactly one argument \(the instance to initialize\)' + + with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'): + C(42) + + with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'): + C.__new__(C, 42) + + with self.assertRaisesRegex(TypeError, error_msg): + C().__init__(42) + + with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'): + object.__new__(C, 42) + + with self.assertRaisesRegex(TypeError, error_msg): + object.__init__(C(), 42) + + # Class with both `__init__` & `__new__` method overridden + class D: + def __new__(cls, *args, **kwargs): + super().__new__(cls, *args, **kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + error_msg = r'object.__new__\(\) takes exactly one argument \(the type to instantiate\)' + + with self.assertRaisesRegex(TypeError, error_msg): + D(42) + + with self.assertRaisesRegex(TypeError, error_msg): + D.__new__(D, 42) + + with self.assertRaisesRegex(TypeError, error_msg): + object.__new__(D, 42) + + # Class that only overrides __init__ + class E: + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + error_msg = r'object.__init__\(\) takes exactly one argument \(the instance to initialize\)' + + with self.assertRaisesRegex(TypeError, error_msg): + E().__init__(42) + + with self.assertRaisesRegex(TypeError, error_msg): + object.__init__(E(), 42) + + def testClassWithExtCall(self): + class Meta(int): + def __init__(*args, **kwargs): + pass + + def __new__(cls, name, bases, attrs, **kwargs): + return bases, kwargs + + d = {'metaclass': Meta} + + class A(**d): pass + self.assertEqual(A, ((), {})) + class A(0, 1, 2, 3, 4, 5, 6, 7, **d): pass + self.assertEqual(A, (tuple(range(8)), {})) + class A(0, *range(1, 8), **d, foo='bar'): pass + self.assertEqual(A, (tuple(range(8)), {'foo': 'bar'})) + + def testClassCallRecursionLimit(self): + class C: + def __init__(self): + self.c = C() + + with self.assertRaises(RecursionError): + C() + + def add_one_level(): + #Each call to C() consumes 2 levels, so offset by 1. + C() + + with self.assertRaises(RecursionError): + add_one_level() + + def testMetaclassCallOptimization(self): + calls = 0 + + class TypeMetaclass(type): + def __call__(cls, *args, **kwargs): + nonlocal calls + calls += 1 + return type.__call__(cls, *args, **kwargs) + + class Type(metaclass=TypeMetaclass): + def __init__(self, obj): + self._obj = obj + + for i in range(100): + Type(i) + self.assertEqual(calls, 100) + + def test_specialization_class_call_doesnt_crash(self): + # gh-123185 + + class Foo: + def __init__(self, arg): + pass + + for _ in range(8): + try: + Foo() + except: + pass + + +# from _testinternalcapi import has_inline_values # XXX: RUSTPYTHON + +Py_TPFLAGS_INLINE_VALUES = (1 << 2) +Py_TPFLAGS_MANAGED_DICT = (1 << 4) + +class NoManagedDict: + __slots__ = ('a',) + + +class Plain: + pass + + +class WithAttrs: + + def __init__(self): + self.a = 1 + self.b = 2 + self.c = 3 + self.d = 4 + + +class VarSizedSubclass(tuple): + pass + + +class TestInlineValues(unittest.TestCase): + + @unittest.expectedFailure # TODO: RUSTPYTHON; NameError: name 'has_inline_values' is not defined. + def test_no_flags_for_slots_class(self): + flags = NoManagedDict.__flags__ + self.assertEqual(flags & Py_TPFLAGS_MANAGED_DICT, 0) + self.assertEqual(flags & Py_TPFLAGS_INLINE_VALUES, 0) + self.assertFalse(has_inline_values(NoManagedDict())) + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 4 + def test_both_flags_for_regular_class(self): + for cls in (Plain, WithAttrs): + with self.subTest(cls=cls.__name__): + flags = cls.__flags__ + self.assertEqual(flags & Py_TPFLAGS_MANAGED_DICT, Py_TPFLAGS_MANAGED_DICT) + self.assertEqual(flags & Py_TPFLAGS_INLINE_VALUES, Py_TPFLAGS_INLINE_VALUES) + self.assertTrue(has_inline_values(cls())) + + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 0 != 4 + def test_managed_dict_only_for_varsized_subclass(self): + flags = VarSizedSubclass.__flags__ + self.assertEqual(flags & Py_TPFLAGS_MANAGED_DICT, Py_TPFLAGS_MANAGED_DICT) + self.assertEqual(flags & Py_TPFLAGS_INLINE_VALUES, 0) + self.assertFalse(has_inline_values(VarSizedSubclass())) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_has_inline_values(self): + c = Plain() + self.assertTrue(has_inline_values(c)) + del c.__dict__ + self.assertFalse(has_inline_values(c)) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_instances(self): + self.assertTrue(has_inline_values(Plain())) + self.assertTrue(has_inline_values(WithAttrs())) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_inspect_dict(self): + for cls in (Plain, WithAttrs): + c = cls() + c.__dict__ + self.assertTrue(has_inline_values(c)) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_update_dict(self): + d = { "e": 5, "f": 6 } + for cls in (Plain, WithAttrs): + c = cls() + c.__dict__.update(d) + self.assertTrue(has_inline_values(c)) + + @staticmethod + def set_100(obj): + for i in range(100): + setattr(obj, f"a{i}", i) + + def check_100(self, obj): + for i in range(100): + self.assertEqual(getattr(obj, f"a{i}"), i) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_many_attributes(self): + class C: pass + c = C() + self.assertTrue(has_inline_values(c)) + self.set_100(c) + self.assertFalse(has_inline_values(c)) + self.check_100(c) + c = C() + self.assertTrue(has_inline_values(c)) + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_many_attributes_with_dict(self): + class C: pass + c = C() + d = c.__dict__ + self.assertTrue(has_inline_values(c)) + self.set_100(c) + self.assertFalse(has_inline_values(c)) + self.check_100(c) + + def test_bug_117750(self): + "Aborted on 3.13a6" + class C: + def __init__(self): + self.__dict__.clear() + + obj = C() + self.assertEqual(obj.__dict__, {}) + obj.foo = None # Aborted here + self.assertEqual(obj.__dict__, {"foo":None}) + + def test_store_attr_deleted_dict(self): + class Foo: + pass + + f = Foo() + del f.__dict__ + f.a = 3 + self.assertEqual(f.a, 3) + + def test_rematerialize_object_dict(self): + # gh-121860: rematerializing an object's managed dictionary after it + # had been deleted caused a crash. + class Foo: pass + f = Foo() + f.__dict__["attr"] = 1 + del f.__dict__ + + # Using a str subclass is a way to trigger the re-materialization + class StrSubclass(str): pass + self.assertFalse(hasattr(f, StrSubclass("attr"))) + + # Changing the __class__ also triggers the re-materialization + class Bar: pass + f.__class__ = Bar + self.assertIsInstance(f, Bar) + self.assertEqual(f.__dict__, {}) + + @unittest.skip("TODO: RUSTPYTHON; unexpectedly long runtime") + def test_store_attr_type_cache(self): + """Verifies that the type cache doesn't provide a value which is + inconsistent from the dict.""" + class X: + def __del__(inner_self): + v = C.a + self.assertEqual(v, C.__dict__['a']) + + class C: + a = X() + + # prime the cache + C.a + C.a + + # destructor shouldn't be able to see inconsistent state + C.a = X() + C.a = X() + + @cpython_only + def test_detach_materialized_dict_no_memory(self): + # Skip test if _testcapi is not available: + import_helper.import_module('_testcapi') + + code = """if 1: + import test.support + import _testcapi + + class A: + def __init__(self): + self.a = 1 + self.b = 2 + a = A() + d = a.__dict__ + with test.support.catch_unraisable_exception() as ex: + _testcapi.set_nomemory(0, 1) + del a + assert ex.unraisable.exc_type is MemoryError + try: + d["a"] + except KeyError: + pass + else: + assert False, "KeyError not raised" + """ + rc, out, err = script_helper.assert_python_ok("-c", code) + self.assertEqual(rc, 0) + self.assertFalse(out, msg=out.decode('utf-8')) + self.assertFalse(err, msg=err.decode('utf-8')) + + +if __name__ == '__main__': + unittest.main() From 7651712c7336d919d6b7312bd07fb74d41f28c4c Mon Sep 17 00:00:00 2001 From: alok-108 Date: Wed, 8 Apr 2026 02:48:20 +0530 Subject: [PATCH 10/13] Fix clippy warnings: remove unused into_inner, collapse nested if-let --- crates/vm/src/object/core.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 665c51a5356..abbb454d830 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -954,11 +954,6 @@ impl InstanceDict { core::mem::replace(&mut self.d.write(), d) } - /// Consume the InstanceDict and return the inner Option. - #[inline] - pub fn into_inner(self) -> Option { - self.d.into_inner() - } pub(crate) fn get_or_insert(&self, vm: &VirtualMachine) -> PyDictRef { if let Some(existing) = self.d.read().as_ref() { @@ -1800,10 +1795,8 @@ impl PyObject { let ext_ptr = core::ptr::with_exposed_provenance_mut::(self_addr.wrapping_sub(offset)); let ext = unsafe { &mut *ext_ptr }; - if let Some(instance_dict) = &ext.dict { - if let Some(dict_ref) = instance_dict.replace(None) { - result.push(dict_ref.into()); - } + if let Some(dict_ref) = ext.dict.as_ref().and_then(|d| d.replace(None)) { + result.push(dict_ref.into()); } for slot in ext.slots.iter() { if let Some(val) = slot.write().take() { From 7882153ab39e9737fded3c3d0ffbb3f6e3e331cd Mon Sep 17 00:00:00 2001 From: alok-108 Date: Wed, 8 Apr 2026 12:20:44 +0530 Subject: [PATCH 11/13] Fix rustfmt formatting and ruff PEP 8 E302 blank line --- crates/vm/src/builtins/type.rs | 6 +----- crates/vm/src/object/core.rs | 5 ++++- crates/vm/src/protocol/object.rs | 4 +++- extra_tests/snippets/test_del_dict.py | 1 + 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index 0b2024cc6b0..75731a4e72f 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -2622,11 +2622,7 @@ fn subtype_get_dict(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { } // = subtype_setdict -fn subtype_set_dict( - obj: PyObjectRef, - value: PySetterValue, - vm: &VirtualMachine, -) -> PyResult<()> { +fn subtype_set_dict(obj: PyObjectRef, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> { let base = get_builtin_base_with_dict(obj.class(), vm); if let Some(base_type) = base { diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index abbb454d830..ab433e4c9c6 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -1080,7 +1080,10 @@ impl PyInner { unsafe { if let Some(offset) = ext_start { let ext_ptr = alloc_ptr.add(offset) as *mut ObjExt; - let has_dict = typ.slots.flags.has_feature(crate::types::PyTypeFlags::HAS_DICT); + let has_dict = typ + .slots + .flags + .has_feature(crate::types::PyTypeFlags::HAS_DICT); ext_ptr.write(ObjExt::new(dict, member_count, has_dict)); } diff --git a/crates/vm/src/protocol/object.rs b/crates/vm/src/protocol/object.rs index 5533a1c1762..79458d50a54 100644 --- a/crates/vm/src/protocol/object.rs +++ b/crates/vm/src/protocol/object.rs @@ -201,7 +201,9 @@ impl PyObject { if let Some(instance_dict) = self.instance_dict() { if let PySetterValue::Assign(value) = value { - instance_dict.get_or_insert(vm).set_item(attr_name, value, vm)?; + instance_dict + .get_or_insert(vm) + .set_item(attr_name, value, vm)?; } else if let Some(dict) = instance_dict.get() { dict.del_item(attr_name, vm).map_err(|e| { if e.fast_isinstance(vm.ctx.exceptions.key_error) { diff --git a/extra_tests/snippets/test_del_dict.py b/extra_tests/snippets/test_del_dict.py index 7f0f1d594c2..16eb4ca6982 100644 --- a/extra_tests/snippets/test_del_dict.py +++ b/extra_tests/snippets/test_del_dict.py @@ -2,6 +2,7 @@ class C: pass + obj = C() obj.x = 42 From 143a92cc5c3a860dea8a8047615d5849dbea82ac Mon Sep 17 00:00:00 2001 From: alok-108 Date: Wed, 8 Apr 2026 21:46:15 +0530 Subject: [PATCH 12/13] Align __dict__ error messages and ensure safety for function/partial objects --- crates/vm/src/builtins/function.rs | 31 +++++++++++++++++++++++++-- crates/vm/src/object/core.rs | 1 - crates/vm/src/stdlib/_functools.rs | 30 ++++++++++++++++++++++++-- extra_tests/snippets/test_del_dict.py | 17 +++++++++++++++ 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/crates/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs index 782daf0b9a6..d6af63663fd 100644 --- a/crates/vm/src/builtins/function.rs +++ b/crates/vm/src/builtins/function.rs @@ -2,8 +2,8 @@ mod jit; use super::{ - PyAsyncGen, PyCode, PyCoroutine, PyDictRef, PyGenerator, PyModule, PyStr, PyStrRef, PyTuple, - PyTupleRef, PyType, + PyAsyncGen, PyCode, PyCoroutine, PyDict, PyDictRef, PyGenerator, PyModule, PyStr, PyStrRef, + PyTuple, PyTupleRef, PyType, }; use crate::common::hash::PyHash; use crate::common::lock::PyMutex; @@ -954,6 +954,33 @@ impl PyFunction { Ok(()) } + #[pygetset] + fn __dict__(&self, vm: &VirtualMachine) -> PyDictRef { + self.as_object() + .instance_dict() + .map(|d| d.get_or_insert(vm)) + .unwrap_or_else(|| vm.ctx.new_dict()) + } + + #[pygetset(setter)] + fn set___dict__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> { + match value { + PySetterValue::Assign(obj) => { + let dict = obj.downcast::().map_err(|_| { + vm.new_type_error(format!( + "__dict__ must be set to a dictionary, not a '{}'", + obj.class().name() + )) + })?; + self.as_object().set_dict(Some(dict)).map_err(|_| { + vm.new_attribute_error("function object has no __dict__") + }) + } + PySetterValue::Delete => Err(vm.new_type_error("cannot delete __dict__")), + } + } + + #[pygetset] fn __annotate__(&self, vm: &VirtualMachine) -> PyObjectRef { self.annotate diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index ab433e4c9c6..bee32f543a2 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -954,7 +954,6 @@ impl InstanceDict { core::mem::replace(&mut self.d.write(), d) } - pub(crate) fn get_or_insert(&self, vm: &VirtualMachine) -> PyDictRef { if let Some(existing) = self.d.read().as_ref() { return existing.clone(); diff --git a/crates/vm/src/stdlib/_functools.rs b/crates/vm/src/stdlib/_functools.rs index 494f0e7fd83..a7127fa19ff 100644 --- a/crates/vm/src/stdlib/_functools.rs +++ b/crates/vm/src/stdlib/_functools.rs @@ -4,9 +4,9 @@ pub(crate) use _functools::module_def; mod _functools { use crate::{ Py, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine, - builtins::{PyBoundMethod, PyDict, PyGenericAlias, PyTuple, PyType, PyTypeRef}, + builtins::{PyBoundMethod, PyDict, PyDictRef, PyGenericAlias, PyTuple, PyType, PyTypeRef}, common::lock::PyRwLock, - function::{FuncArgs, KwArgs, OptionalOption}, + function::{FuncArgs, KwArgs, OptionalOption, PySetterValue}, object::AsObject, protocol::PyIter, pyclass, @@ -158,6 +158,32 @@ mod _functools { self.inner.read().keywords.clone() } + #[pygetset] + fn __dict__(&self, vm: &VirtualMachine) -> PyDictRef { + self.as_object() + .instance_dict() + .map(|d| d.get_or_insert(vm)) + .unwrap_or_else(|| vm.ctx.new_dict()) + } + + #[pygetset(setter)] + fn set___dict__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> { + match value { + PySetterValue::Assign(obj) => { + let dict = obj.downcast::().map_err(|_| { + vm.new_type_error(format!( + "__dict__ must be set to a dictionary, not a '{}'", + obj.class().name() + )) + })?; + self.as_object().set_dict(Some(dict)).map_err(|_| { + vm.new_attribute_error("partial object has no __dict__") + }) + } + PySetterValue::Delete => Err(vm.new_type_error("cannot delete __dict__")), + } + } + #[pymethod] fn __reduce__(zelf: &Py, vm: &VirtualMachine) -> PyResult { let inner = zelf.inner.read(); diff --git a/extra_tests/snippets/test_del_dict.py b/extra_tests/snippets/test_del_dict.py index 16eb4ca6982..965699b6054 100644 --- a/extra_tests/snippets/test_del_dict.py +++ b/extra_tests/snippets/test_del_dict.py @@ -21,4 +21,21 @@ class C: except AttributeError: pass +# Function __dict__ deletion should fail +def f(): pass +try: + del f.__dict__ + assert False, "TypeError expected for function dict deletion" +except TypeError: + pass + +# functools.partial __dict__ deletion should fail +import functools +p = functools.partial(f) +try: + del p.__dict__ + assert False, "TypeError expected for partial dict deletion" +except TypeError: + pass + print("OK") From c46e482bfeeacba63bdb647f290656c011921d03 Mon Sep 17 00:00:00 2001 From: alok-108 Date: Thu, 9 Apr 2026 00:28:02 +0530 Subject: [PATCH 13/13] Fix compilation errors: change &self to &Py in __dict__ methods --- crates/vm/src/builtins/function.rs | 10 +++++----- crates/vm/src/stdlib/_functools.rs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs index d6af63663fd..55137c0de36 100644 --- a/crates/vm/src/builtins/function.rs +++ b/crates/vm/src/builtins/function.rs @@ -955,15 +955,14 @@ impl PyFunction { } #[pygetset] - fn __dict__(&self, vm: &VirtualMachine) -> PyDictRef { - self.as_object() - .instance_dict() + fn __dict__(zelf: &Py, vm: &VirtualMachine) -> PyDictRef { + zelf.instance_dict() .map(|d| d.get_or_insert(vm)) .unwrap_or_else(|| vm.ctx.new_dict()) } #[pygetset(setter)] - fn set___dict__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> { + fn set___dict__(zelf: &Py, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> { match value { PySetterValue::Assign(obj) => { let dict = obj.downcast::().map_err(|_| { @@ -972,7 +971,7 @@ impl PyFunction { obj.class().name() )) })?; - self.as_object().set_dict(Some(dict)).map_err(|_| { + zelf.set_dict(Some(dict)).map_err(|_| { vm.new_attribute_error("function object has no __dict__") }) } @@ -981,6 +980,7 @@ impl PyFunction { } + #[pygetset] fn __annotate__(&self, vm: &VirtualMachine) -> PyObjectRef { self.annotate diff --git a/crates/vm/src/stdlib/_functools.rs b/crates/vm/src/stdlib/_functools.rs index a7127fa19ff..d6837489e1a 100644 --- a/crates/vm/src/stdlib/_functools.rs +++ b/crates/vm/src/stdlib/_functools.rs @@ -159,15 +159,14 @@ mod _functools { } #[pygetset] - fn __dict__(&self, vm: &VirtualMachine) -> PyDictRef { - self.as_object() - .instance_dict() + fn __dict__(zelf: &Py, vm: &VirtualMachine) -> PyDictRef { + zelf.instance_dict() .map(|d| d.get_or_insert(vm)) .unwrap_or_else(|| vm.ctx.new_dict()) } #[pygetset(setter)] - fn set___dict__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> { + fn set___dict__(zelf: &Py, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> { match value { PySetterValue::Assign(obj) => { let dict = obj.downcast::().map_err(|_| { @@ -176,7 +175,7 @@ mod _functools { obj.class().name() )) })?; - self.as_object().set_dict(Some(dict)).map_err(|_| { + zelf.set_dict(Some(dict)).map_err(|_| { vm.new_attribute_error("partial object has no __dict__") }) } @@ -184,6 +183,7 @@ mod _functools { } } + #[pymethod] fn __reduce__(zelf: &Py, vm: &VirtualMachine) -> PyResult { let inner = zelf.inner.read();