diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 37f86a55dc..94b4645489 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -702,7 +702,10 @@ impl ExceptionZoo { extend_exception!(PyZeroDivisionError, ctx, &excs.zero_division_error); extend_exception!(PyAssertionError, ctx, &excs.assertion_error); - extend_exception!(PyAttributeError, ctx, &excs.attribute_error); + extend_exception!(PyAttributeError, ctx, &excs.attribute_error, { + "name" => ctx.none(), + "obj" => ctx.none(), + }); extend_exception!(PyBufferError, ctx, &excs.buffer_error); extend_exception!(PyEOFError, ctx, &excs.eof_error); diff --git a/vm/src/protocol/object.rs b/vm/src/protocol/object.rs index 7bcd081ca2..6b0580cd1f 100644 --- a/vm/src/protocol/object.rs +++ b/vm/src/protocol/object.rs @@ -32,7 +32,10 @@ impl PyObjectRef { .class() .mro_find_map(|cls| cls.slots.getattro.load()) .unwrap(); - getattro(self, attr_name, vm) + getattro(self.clone(), attr_name.clone(), vm).map_err(|exc| { + vm.set_attribute_error_context(&exc, self, attr_name); + exc + }) } pub fn call_set_attr( diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 12b182dcc9..d6dcf08806 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1103,11 +1103,13 @@ impl PyMethod { drop(cls); vm.invoke(&getter, (obj, name)).map(Self::Attribute) } else { - Err(vm.new_attribute_error(format!( + let exc = vm.new_attribute_error(format!( "'{}' object has no attribute '{}'", cls.name(), name - ))) + )); + vm.set_attribute_error_context(&exc, obj.clone(), name); + Err(exc) } } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 6f11bea8f2..219492fbcd 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -1413,6 +1413,19 @@ impl VirtualMachine { } } + pub fn set_attribute_error_context( + &self, + exc: &PyBaseExceptionRef, + obj: PyObjectRef, + name: PyStrRef, + ) { + if exc.class().is(&self.ctx.exceptions.attribute_error) { + let exc = exc.as_object(); + exc.set_attr("name", name, self).unwrap(); + exc.set_attr("obj", obj, self).unwrap(); + } + } + // get_method should be used for internal access to magic methods (by-passing // the full getattribute look-up. pub fn get_method_or_type_error(