From 86cdbcc5600af27a813861fcd92a05d7c275a114 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 18 Jun 2025 14:29:16 +0900 Subject: [PATCH] Impl PyAttributeError args --- Lib/test/test_exceptions.py | 4 ---- vm/src/builtins/descriptor.rs | 6 +----- vm/src/exceptions.rs | 25 ++++++++++++++++++++++++- vm/src/protocol/object.rs | 21 ++++----------------- vm/src/vm/method.rs | 8 +------- vm/src/vm/vm_new.rs | 14 ++++++++++++++ 6 files changed, 44 insertions(+), 34 deletions(-) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 8be8122507..b3f5e0650d 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -2074,8 +2074,6 @@ def f(): class AttributeErrorTests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_attributes(self): # Setting 'attr' should not be a problem. exc = AttributeError('Ouch!') @@ -2087,8 +2085,6 @@ def test_attributes(self): self.assertEqual(exc.name, 'carry') self.assertIs(exc.obj, sentinel) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_getattr_has_name_and_obj(self): class A: blech = None diff --git a/vm/src/builtins/descriptor.rs b/vm/src/builtins/descriptor.rs index 9da4e1d87a..3c294db096 100644 --- a/vm/src/builtins/descriptor.rs +++ b/vm/src/builtins/descriptor.rs @@ -287,11 +287,7 @@ fn get_slot_from_object( .get_slot(offset) .unwrap_or_else(|| vm.ctx.new_bool(false).into()), MemberKind::ObjectEx => obj.get_slot(offset).ok_or_else(|| { - vm.new_attribute_error(format!( - "'{}' object has no attribute '{}'", - obj.class().name(), - member.name - )) + vm.new_no_attribute_error(obj.clone(), vm.ctx.new_str(member.name.clone())) })?, }; Ok(slot) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 42bd8c75da..e28121f94b 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1260,10 +1260,33 @@ pub(super) mod types { #[derive(Debug)] pub struct PyAssertionError {} - #[pyexception(name, base = "PyException", ctx = "attribute_error", impl)] + #[pyexception(name, base = "PyException", ctx = "attribute_error")] #[derive(Debug)] pub struct PyAttributeError {} + #[pyexception] + impl PyAttributeError { + #[pyslot] + #[pymethod(name = "__init__")] + pub(crate) fn slot_init( + zelf: PyObjectRef, + args: ::rustpython_vm::function::FuncArgs, + vm: &::rustpython_vm::VirtualMachine, + ) -> ::rustpython_vm::PyResult<()> { + zelf.set_attr( + "name", + vm.unwrap_or_none(args.kwargs.get("name").cloned()), + vm, + )?; + zelf.set_attr( + "obj", + vm.unwrap_or_none(args.kwargs.get("obj").cloned()), + vm, + )?; + Ok(()) + } + } + #[pyexception(name, base = "PyException", ctx = "buffer_error", impl)] #[derive(Debug)] pub struct PyBufferError {} diff --git a/vm/src/protocol/object.rs b/vm/src/protocol/object.rs index eab24f82d0..4f0d816b8a 100644 --- a/vm/src/protocol/object.rs +++ b/vm/src/protocol/object.rs @@ -187,11 +187,7 @@ impl PyObject { } else { dict.del_item(attr_name, vm).map_err(|e| { if e.fast_isinstance(vm.ctx.exceptions.key_error) { - vm.new_attribute_error(format!( - "'{}' object has no attribute '{}'", - self.class().name(), - attr_name.as_str(), - )) + vm.new_no_attribute_error(self.to_owned(), attr_name.to_owned()) } else { e } @@ -199,22 +195,13 @@ impl PyObject { } Ok(()) } else { - Err(vm.new_attribute_error(format!( - "'{}' object has no attribute '{}'", - self.class().name(), - attr_name.as_str(), - ))) + Err(vm.new_no_attribute_error(self.to_owned(), attr_name.to_owned())) } } pub fn generic_getattr(&self, name: &Py, vm: &VirtualMachine) -> PyResult { - self.generic_getattr_opt(name, None, vm)?.ok_or_else(|| { - vm.new_attribute_error(format!( - "'{}' object has no attribute '{}'", - self.class().name(), - name - )) - }) + self.generic_getattr_opt(name, None, vm)? + .ok_or_else(|| vm.new_no_attribute_error(self.to_owned(), name.to_owned())) } /// CPython _PyObject_GenericGetAttrWithDict diff --git a/vm/src/vm/method.rs b/vm/src/vm/method.rs index 258b1e9473..099d5bb9fb 100644 --- a/vm/src/vm/method.rs +++ b/vm/src/vm/method.rs @@ -79,13 +79,7 @@ impl PyMethod { } else if let Some(getter) = cls.get_attr(identifier!(vm, __getattr__)) { getter.call((obj, name.to_owned()), vm).map(Self::Attribute) } else { - let exc = vm.new_attribute_error(format!( - "'{}' object has no attribute '{}'", - cls.name(), - name - )); - vm.set_attribute_error_context(&exc, obj.clone(), name.to_owned()); - Err(exc) + Err(vm.new_no_attribute_error(obj.clone(), name.to_owned())) } } diff --git a/vm/src/vm/vm_new.rs b/vm/src/vm/vm_new.rs index 978b694fc4..e72acc6d69 100644 --- a/vm/src/vm/vm_new.rs +++ b/vm/src/vm/vm_new.rs @@ -135,6 +135,20 @@ impl VirtualMachine { self.new_exception_msg(attribute_error, msg) } + pub fn new_no_attribute_error(&self, obj: PyObjectRef, name: PyStrRef) -> PyBaseExceptionRef { + let msg = format!( + "'{}' object has no attribute '{}'", + obj.class().name(), + name + ); + let attribute_error = self.new_attribute_error(msg); + + // Use existing set_attribute_error_context function + self.set_attribute_error_context(&attribute_error, obj, name); + + attribute_error + } + pub fn new_type_error(&self, msg: String) -> PyBaseExceptionRef { let type_error = self.ctx.exceptions.type_error.to_owned(); self.new_exception_msg(type_error, msg)