Skip to content

Commit 019e754

Browse files
authored
Fix __set_name__ error handling to match Python 3.12+ (RustPython#6937)
Changed type.rs to add notes to original exceptions instead of wrapping them in RuntimeError, following PEP 678 (gh-77757). This allows enum.py's exception handling to work correctly when super().__new__() is misused in Enum subclasses, enabling the proper TypeError to propagate instead of being hidden behind a RuntimeError wrapper. Fixes test_bad_new_super test case.
1 parent d767b5f commit 019e754

4 files changed

Lines changed: 32 additions & 31 deletions

File tree

Lib/test/test_enum.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,6 @@ def spam(cls):
445445
with self.assertRaises(AttributeError):
446446
del Season.SPRING.name
447447

448-
@unittest.expectedFailure # TODO: RUSTPYTHON; RuntimeError: Error calling __set_name__ on '_proto_member' instance failed in 'BadSuper'
449448
def test_bad_new_super(self):
450449
with self.assertRaisesRegex(
451450
TypeError,
@@ -1903,7 +1902,6 @@ def test_wrong_inheritance_order(self):
19031902
class Wrong(Enum, str):
19041903
NotHere = 'error before this point'
19051904

1906-
@unittest.expectedFailure # TODO: RUSTPYTHON; RuntimeError: Error calling __set_name__ on '_proto_member' instance INVALID in 'RgbColor'
19071905
def test_raise_custom_error_on_creation(self):
19081906
class InvalidRgbColorError(ValueError):
19091907
def __init__(self, r, g, b):
@@ -2591,7 +2589,6 @@ class Test(Base2):
25912589
self.assertEqual(Test.flash.flash, 'flashy dynamic')
25922590
self.assertEqual(Test.flash.value, 1)
25932591

2594-
@unittest.expectedFailure # TODO: RUSTPYTHON; RuntimeError: Error calling __set_name__ on '_proto_member' instance grene in 'Color'
25952592
def test_no_duplicates(self):
25962593
class UniqueEnum(Enum):
25972594
def __init__(self, *args):
@@ -2977,7 +2974,6 @@ def test_empty_globals(self):
29772974
local_ls = {}
29782975
exec(code, global_ns, local_ls)
29792976

2980-
@unittest.expectedFailure # TODO: RUSTPYTHON; RuntimeError: Error calling __set_name__ on '_proto_member' instance one in 'FirstFailedStrEnum'
29812977
def test_strenum(self):
29822978
class GoodStrEnum(StrEnum):
29832979
one = '1'
@@ -3102,7 +3098,6 @@ class ThirdFailedStrEnum(CustomStrEnum):
31023098
one = '1'
31033099
two = b'2', 'ascii', 9
31043100

3105-
@unittest.expectedFailure # TODO: RUSTPYTHON; RuntimeError: Error calling __set_name__ on '_proto_member' instance key_type in 'Combined'
31063101
def test_missing_value_error(self):
31073102
with self.assertRaisesRegex(TypeError, "_value_ not set in __new__"):
31083103
class Combined(str, Enum):
@@ -3389,7 +3384,6 @@ def __new__(cls, c):
33893384
self.assertEqual(FlagFromChar.a, 158456325028528675187087900672)
33903385
self.assertEqual(FlagFromChar.a|1, 158456325028528675187087900673)
33913386

3392-
@unittest.expectedFailure # TODO: RUSTPYTHON; RuntimeError: Error calling __set_name__ on '_proto_member' instance A in 'MyEnum'
33933387
def test_init_exception(self):
33943388
class Base:
33953389
def __new__(cls, *args):

Lib/test/test_functools.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,39 +1192,47 @@ def test_disallow_instantiation(self):
11921192
self, type(c_functools.cmp_to_key(None))
11931193
)
11941194

1195-
@unittest.expectedFailure # TODO: RUSTPYTHON
1195+
@unittest.expectedFailure # TODO: RUSTPYTHON; + (mycmp)
1196+
def test_cmp_to_signature(self):
1197+
return super().test_cmp_to_signature()
1198+
1199+
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: cmp_to_key() got multiple values for argument 'mycmp'
1200+
def test_cmp_to_key_arguments(self):
1201+
return super().test_cmp_to_key_arguments()
1202+
1203+
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: cmp_to_key() got multiple values for argument 'mycmp'
1204+
def test_obj_field(self):
1205+
return super().test_obj_field()
1206+
1207+
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: cmp_to_key() takes 1 positional argument but 2 were given
11961208
def test_bad_cmp(self):
11971209
return super().test_bad_cmp()
11981210

1199-
@unittest.expectedFailure # TODO: RUSTPYTHON
1211+
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: cmp_to_key() takes 1 positional argument but 2 were given
12001212
def test_cmp_to_key(self):
12011213
return super().test_cmp_to_key()
12021214

1203-
@unittest.expectedFailure # TODO: RUSTPYTHON
1204-
def test_cmp_to_key_arguments(self):
1205-
return super().test_cmp_to_key_arguments()
1206-
1207-
@unittest.expectedFailure # TODO: RUSTPYTHON
1208-
def test_cmp_to_signature(self):
1209-
return super().test_cmp_to_signature()
1210-
1211-
@unittest.expectedFailure # TODO: RUSTPYTHON
1215+
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: cmp_to_key() takes 1 positional argument but 2 were given
12121216
def test_hash(self):
12131217
return super().test_hash()
12141218

1215-
@unittest.expectedFailure # TODO: RUSTPYTHON
1216-
def test_obj_field(self):
1217-
return super().test_obj_field()
1218-
1219-
@unittest.expectedFailure # TODO: RUSTPYTHON
1219+
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: cmp_to_key() takes 1 positional argument but 2 were given
12201220
def test_sort_int(self):
12211221
return super().test_sort_int()
12221222

1223-
@unittest.expectedFailure # TODO: RUSTPYTHON
1223+
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: cmp_to_key() takes 1 positional argument but 2 were given
12241224
def test_sort_int_str(self):
12251225
return super().test_sort_int_str()
12261226

12271227

1228+
1229+
1230+
1231+
1232+
1233+
1234+
1235+
12281236
class TestCmpToKeyPy(TestCmpToKey, unittest.TestCase):
12291237
cmp_to_key = staticmethod(py_functools.cmp_to_key)
12301238

@@ -3592,7 +3600,6 @@ class MyClass(metaclass=MyMeta):
35923600
):
35933601
MyClass.prop
35943602

3595-
@unittest.expectedFailure # TODO: RUSTPYTHON
35963603
def test_reuse_different_names(self):
35973604
"""Disallow this case because decorated function a would not be cached."""
35983605
with self.assertRaises(TypeError) as ctx:

Lib/test/test_subclassinit.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@ class A(metaclass=Meta):
129129
d = Descriptor()
130130
self.assertEqual(A, 0)
131131

132-
@unittest.expectedFailure # TODO: RUSTPYTHON; ZeroDivisionError: division by zero
133132
def test_set_name_error(self):
134133
class Descriptor:
135134
def __set_name__(self, owner, name):
@@ -144,7 +143,6 @@ class NotGoingToWork:
144143
self.assertRegex(str(notes), r'\battr\b')
145144
self.assertRegex(str(notes), r'\bDescriptor\b')
146145

147-
@unittest.expectedFailure # TODO: RUSTPYTHON; RuntimeError: Error calling __set_name__ on 'Descriptor' instance attr in 'NotGoingToWork'
148146
def test_set_name_wrong(self):
149147
class Descriptor:
150148
def __set_name__(self):

crates/vm/src/builtins/type.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,15 +1554,17 @@ impl Constructor for PyType {
15541554
})
15551555
.collect::<PyResult<Vec<_>>>()?;
15561556
for (obj, name, set_name) in attributes {
1557-
set_name.call((typ.clone(), name), vm).map_err(|e| {
1558-
let err = vm.new_runtime_error(format!(
1557+
set_name.call((typ.clone(), name), vm).inspect_err(|e| {
1558+
// PEP 678: Add a note to the original exception instead of wrapping it
1559+
// (Python 3.12+, gh-77757)
1560+
let note = format!(
15591561
"Error calling __set_name__ on '{}' instance {} in '{}'",
15601562
obj.class().name(),
15611563
name,
15621564
typ.name()
1563-
));
1564-
err.set___cause__(Some(e));
1565-
err
1565+
);
1566+
// Ignore result - adding a note is best-effort, the original exception is what matters
1567+
drop(vm.call_method(e.as_object(), "add_note", (vm.ctx.new_str(note.as_str()),)));
15661568
})?;
15671569
}
15681570

0 commit comments

Comments
 (0)