Skip to content

Commit fbc87f4

Browse files
committed
Fix property error messages
1 parent 6e4e9e9 commit fbc87f4

2 files changed

Lines changed: 67 additions & 27 deletions

File tree

Lib/test/test_property.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -340,20 +340,14 @@ def _format_exc_msg(self, msg):
340340
def setUpClass(cls):
341341
cls.obj = cls.cls()
342342

343-
# TODO: RUSTPYTHON
344-
@unittest.expectedFailure
345343
def test_get_property(self):
346344
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no getter")):
347345
self.obj.foo
348346

349-
# TODO: RUSTPYTHON
350-
@unittest.expectedFailure
351347
def test_set_property(self):
352348
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no setter")):
353349
self.obj.foo = None
354350

355-
# TODO: RUSTPYTHON
356-
@unittest.expectedFailure
357351
def test_del_property(self):
358352
with self.assertRaisesRegex(AttributeError, self._format_exc_msg("has no deleter")):
359353
del self.obj.foo

vm/src/builtins/property.rs

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,31 @@ impl GetDescriptor for PyProperty {
5757
} else if let Some(getter) = zelf.getter.read().as_ref() {
5858
getter.call((obj,), vm)
5959
} else {
60-
Err(vm.new_attribute_error("property has no getter".to_string()))
60+
let error_msg = zelf.format_property_error(&obj, "getter", vm)?;
61+
Err(vm.new_attribute_error(error_msg))
6162
}
6263
}
6364
}
6465

6566
#[pyclass(with(Constructor, Initializer, GetDescriptor), flags(BASETYPE))]
6667
impl PyProperty {
68+
// Helper method to get property name
69+
fn get_property_name(&self, vm: &VirtualMachine) -> Option<PyObjectRef> {
70+
// First check if name was set via __set_name__
71+
if let Some(name) = self.name.read().as_ref() {
72+
return Some(name.clone());
73+
}
74+
75+
// Otherwise try to get __name__ from getter
76+
if let Some(getter) = self.getter.read().as_ref() {
77+
if let Ok(name) = getter.get_attr("__name__", vm) {
78+
return Some(name);
79+
}
80+
}
81+
82+
None
83+
}
84+
6785
// Descriptor methods
6886

6987
#[pyslot]
@@ -77,16 +95,18 @@ impl PyProperty {
7795
match value {
7896
PySetterValue::Assign(value) => {
7997
if let Some(setter) = zelf.setter.read().as_ref() {
80-
setter.call((obj, value), vm).map(drop)
98+
setter.call((obj.clone(), value), vm).map(drop)
8199
} else {
82-
Err(vm.new_attribute_error("property has no setter".to_owned()))
100+
let error_msg = zelf.format_property_error(&obj, "setter", vm)?;
101+
Err(vm.new_attribute_error(error_msg))
83102
}
84103
}
85104
PySetterValue::Delete => {
86105
if let Some(deleter) = zelf.deleter.read().as_ref() {
87-
deleter.call((obj,), vm).map(drop)
106+
deleter.call((obj.clone(),), vm).map(drop)
88107
} else {
89-
Err(vm.new_attribute_error("property has no deleter".to_owned()))
108+
let error_msg = zelf.format_property_error(&obj, "deleter", vm)?;
109+
Err(vm.new_attribute_error(error_msg))
90110
}
91111
}
92112
}
@@ -256,33 +276,32 @@ impl PyProperty {
256276

257277
#[pygetset(magic)]
258278
fn isabstractmethod(&self, vm: &VirtualMachine) -> PyResult {
259-
// Check getter first
279+
// Helper to check if a method is abstract
280+
let is_abstract = |method: &PyObjectRef| -> PyResult<bool> {
281+
match method.get_attr("__isabstractmethod__", vm) {
282+
Ok(isabstract) => isabstract.try_to_bool(vm),
283+
Err(_) => Ok(false),
284+
}
285+
};
286+
287+
// Check getter
260288
if let Some(getter) = self.getter.read().as_ref() {
261-
if let Ok(isabstract) = getter.get_attr("__isabstractmethod__", vm) {
262-
let is_true = isabstract.try_to_bool(vm)?;
263-
if is_true {
264-
return Ok(vm.ctx.new_bool(true).into());
265-
}
289+
if is_abstract(getter)? {
290+
return Ok(vm.ctx.new_bool(true).into());
266291
}
267292
}
268293

269294
// Check setter
270295
if let Some(setter) = self.setter.read().as_ref() {
271-
if let Ok(isabstract) = setter.get_attr("__isabstractmethod__", vm) {
272-
let is_true = isabstract.try_to_bool(vm)?;
273-
if is_true {
274-
return Ok(vm.ctx.new_bool(true).into());
275-
}
296+
if is_abstract(setter)? {
297+
return Ok(vm.ctx.new_bool(true).into());
276298
}
277299
}
278300

279301
// Check deleter
280302
if let Some(deleter) = self.deleter.read().as_ref() {
281-
if let Ok(isabstract) = deleter.get_attr("__isabstractmethod__", vm) {
282-
let is_true = isabstract.try_to_bool(vm)?;
283-
if is_true {
284-
return Ok(vm.ctx.new_bool(true).into());
285-
}
303+
if is_abstract(deleter)? {
304+
return Ok(vm.ctx.new_bool(true).into());
286305
}
287306
}
288307

@@ -296,6 +315,33 @@ impl PyProperty {
296315
}
297316
Ok(())
298317
}
318+
319+
// Helper method to format property error messages
320+
#[cold]
321+
fn format_property_error(
322+
&self,
323+
obj: &PyObjectRef,
324+
error_type: &str,
325+
vm: &VirtualMachine,
326+
) -> PyResult<String> {
327+
let prop_name = self.get_property_name(vm);
328+
let obj_type = obj.class();
329+
let qualname = obj_type.qualname(vm);
330+
331+
match prop_name {
332+
Some(name) => Ok(format!(
333+
"property {} of {} object has no {}",
334+
name.repr(vm)?,
335+
qualname.repr(vm)?,
336+
error_type
337+
)),
338+
None => Ok(format!(
339+
"property of {} object has no {}",
340+
qualname.repr(vm)?,
341+
error_type
342+
)),
343+
}
344+
}
299345
}
300346

301347
impl Constructor for PyProperty {

0 commit comments

Comments
 (0)