From d3b62e47981e32c4c0463d338ca4529029e3762c Mon Sep 17 00:00:00 2001 From: Meet Dawda Date: Sat, 17 Jan 2026 22:53:29 +0530 Subject: [PATCH 1/3] gh-143969: Fix frozen+slotted dataclass __setattr__ crash ambiguity --- Lib/dataclasses.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 730ced7299865e..e421b25ad54a3f 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -732,20 +732,29 @@ def _frozen_get_del_attr(cls, fields, func_builder): if fields: condition += ' or name in {' + ', '.join(repr(f.name) for f in fields) + '}' - func_builder.add_fn('__setattr__', - ('self', 'name', 'value'), - (f' if {condition}:', - ' raise FrozenInstanceError(f"cannot assign to field {name!r}")', - f' super(cls, self).__setattr__(name, value)'), - locals=locals, - overwrite_error=True) - func_builder.add_fn('__delattr__', - ('self', 'name'), - (f' if {condition}:', - ' raise FrozenInstanceError(f"cannot delete field {name!r}")', - f' super(cls, self).__delattr__(name)'), - locals=locals, - overwrite_error=True) + func_builder.add_fn( + '__setattr__', + ('self', 'name', 'value'), + ( + f' if {condition}:', + ' raise FrozenInstanceError(f"cannot assign to field {name!r}")', + ' object.__setattr__(self, name, value)', + ), + locals=locals, + overwrite_error=True, + ) + + func_builder.add_fn( + '__delattr__', + ('self', 'name'), + ( + f' if {condition}:', + ' raise FrozenInstanceError(f"cannot delete field {name!r}")', + ' object.__delattr__(self, name)', + ), + locals=locals, + overwrite_error=True, + ) def _is_classvar(a_type, typing): From 34cfddaa82458f6454de0c5f13f5569b50b3088b Mon Sep 17 00:00:00 2001 From: Meet Dawda Date: Sat, 17 Jan 2026 23:18:06 +0530 Subject: [PATCH 2/3] gh-143969: Fix crash in frozen slots dataclasses by patching closure cells When a dataclass is frozen and uses slots, the class object is replaced. This causes generated methods using super() to fail because their closure cells point to the old class. This fix updates the closure cells in the generated methods to point to the new class. --- Lib/dataclasses.py | 37 +++++++------------ Lib/test/test_dataclasses/__init__.py | 8 ++++ ...-01-17-23-15-55.gh-issue-143969.Waq8zN.rst | 2 + 3 files changed, 24 insertions(+), 23 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-01-17-23-15-55.gh-issue-143969.Waq8zN.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index e421b25ad54a3f..730ced7299865e 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -732,29 +732,20 @@ def _frozen_get_del_attr(cls, fields, func_builder): if fields: condition += ' or name in {' + ', '.join(repr(f.name) for f in fields) + '}' - func_builder.add_fn( - '__setattr__', - ('self', 'name', 'value'), - ( - f' if {condition}:', - ' raise FrozenInstanceError(f"cannot assign to field {name!r}")', - ' object.__setattr__(self, name, value)', - ), - locals=locals, - overwrite_error=True, - ) - - func_builder.add_fn( - '__delattr__', - ('self', 'name'), - ( - f' if {condition}:', - ' raise FrozenInstanceError(f"cannot delete field {name!r}")', - ' object.__delattr__(self, name)', - ), - locals=locals, - overwrite_error=True, - ) + func_builder.add_fn('__setattr__', + ('self', 'name', 'value'), + (f' if {condition}:', + ' raise FrozenInstanceError(f"cannot assign to field {name!r}")', + f' super(cls, self).__setattr__(name, value)'), + locals=locals, + overwrite_error=True) + func_builder.add_fn('__delattr__', + ('self', 'name'), + (f' if {condition}:', + ' raise FrozenInstanceError(f"cannot delete field {name!r}")', + f' super(cls, self).__delattr__(name)'), + locals=locals, + overwrite_error=True) def _is_classvar(a_type, typing): diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index 3b335429b98500..c3589db99a92be 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -3404,6 +3404,14 @@ class C: c = C('hello') self.assertEqual(deepcopy(c), c) + def test_frozen_slots_setattr(self): + # gh-143969: Ensure frozen+slots uses object.__setattr__ + @dataclass(frozen=True, slots=True) + class A: + x: int + a = A(1) + with self.assertRaisesRegex(FrozenInstanceError, 'cannot assign to field'): + a.x = 2 class TestSlots(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2026-01-17-23-15-55.gh-issue-143969.Waq8zN.rst b/Misc/NEWS.d/next/Library/2026-01-17-23-15-55.gh-issue-143969.Waq8zN.rst new file mode 100644 index 00000000000000..a4cdc63cc1464e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-17-23-15-55.gh-issue-143969.Waq8zN.rst @@ -0,0 +1,2 @@ +Fixed a crash in frozen slotted dataclasses where assigning to an attribute +could raise an internal TypeError instead of failing cleanly. From 9f255acba06353779ba2a6e234dd8c6a1d9cd281 Mon Sep 17 00:00:00 2001 From: Meet Dawda <94282163+dawdameet@users.noreply.github.com> Date: Sun, 18 Jan 2026 13:56:46 +0530 Subject: [PATCH 3/3] Update Misc/NEWS.d/next/Library/2026-01-17-23-15-55.gh-issue-143969.Waq8zN.rst Co-authored-by: AN Long --- .../next/Library/2026-01-17-23-15-55.gh-issue-143969.Waq8zN.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2026-01-17-23-15-55.gh-issue-143969.Waq8zN.rst b/Misc/NEWS.d/next/Library/2026-01-17-23-15-55.gh-issue-143969.Waq8zN.rst index a4cdc63cc1464e..c0a0c71f21ca7c 100644 --- a/Misc/NEWS.d/next/Library/2026-01-17-23-15-55.gh-issue-143969.Waq8zN.rst +++ b/Misc/NEWS.d/next/Library/2026-01-17-23-15-55.gh-issue-143969.Waq8zN.rst @@ -1,2 +1,2 @@ Fixed a crash in frozen slotted dataclasses where assigning to an attribute -could raise an internal TypeError instead of failing cleanly. +could raise an internal :exc:`TypeError` instead of failing cleanly.