8000 gh-109409: Fix inheritance of frozen dataclass from non-frozen datacl… · python/cpython@b6000d2 · GitHub
[go: up one dir, main page]

Skip to content

Commit b6000d2

Browse files
authored
gh-109409: Fix inheritance of frozen dataclass from non-frozen dataclass mixins (gh-109437)
Fix inheritance of frozen dataclass from non-frozen dataclass mixins
1 parent 7dd3c2b commit b6000d2

File tree

3 files changed

+107
-4
lines changed

3 files changed

+107
-4
lines changed

Lib/dataclasses.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -944,8 +944,11 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
944944
# Find our base classes in reverse MRO order, and exclude
945945
# ourselves. In reversed order so that more derived classes
946946
# override earlier field definitions in base classes. As long as
947-
# we're iterating over them, see if any are frozen.
947+
# we're iterating over them, see if all or any of them are frozen.
948948
any_frozen_base = False
949+
# By default `all_frozen_bases` is `None` to represent a case,
950+
# where some dataclasses does not have any bases with `_FIELDS`
951+
all_frozen_bases = None
949952
has_dataclass_bases = False
950953
for b in cls.__mro__[-1:0:-1]:
951954
# Only process classes that have been processed by our
@@ -955,8 +958,11 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
955958
has_dataclass_bases = True
956959
for f in base_fields.values():
957960
fields[f.name] = f
958-
if getattr(b, _PARAMS).frozen:
959-
any_frozen_base = True
961+
if all_frozen_bases is None:
962+
all_frozen_bases = True
963+
current_frozen = getattr(b, _PARAMS).frozen
964+
all_frozen_bases = all_frozen_bases and current_frozen
965+
any_frozen_base = any_frozen_base or current_frozen
960966

961967
# Annotations defined specifically in this class (not in base classes).
962968
#
@@ -1025,7 +1031,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
10251031
'frozen one')
10261032

10271033
# Raise an exception if we're frozen, but none of our bases are.
1028-
if not any_frozen_base and frozen:
1034+
if all_frozen_bases is False and frozen:
10291035
raise TypeError('cannot inherit frozen dataclass from a '
10301036
'non-frozen one')
10311037

Lib/test/test_dataclasses/__init__.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2863,6 +2863,101 @@ class C:
28632863
class D(C):
28642864
j: int
28652865

2866+
def test_inherit_frozen_mutliple_inheritance(self):
2867+
@dataclass
2868+
class NotFrozen:
2869+
pass
2870+
2871+
@dataclass(frozen=True)
2872+
class Frozen:
2873+
pass
2874+
2875+
class NotDataclass:
2876+
pass
2877+
2878+
for bases in (
2879+
(NotFrozen, Frozen),
2880+
(Frozen, NotFrozen),
2881+
(Frozen, NotDataclass),
2882+
(NotDataclass, Frozen),
2883+
):
2884+
with self.subTest(bases=bases):
2885+
with self.assertRaisesRegex(
2886+
TypeError,
2887+
'cannot inherit non-frozen dataclass from a frozen one',
2888+
):
2889+
@dataclass
2890+
class NotFrozenChild(*bases):
2891+
pass
2892+
2893+
for bases in (
2894+
(NotFrozen, Frozen),
2895+
(Frozen, NotFrozen),
2896+
(NotFrozen, NotDataclass),
2897+
(NotDataclass, NotFrozen),
2898+
):
2899+
with self.subTest(bases=bases):
2900+
with self.assertRaisesRegex(
2901+
TypeError,
2902+
'cannot inherit frozen dataclass from a non-frozen one',
2903+
):
2904+
@dataclass(frozen=True)
2905+
class FrozenChild(*bases):
2906+
pass
2907+
2908+
def test_inherit_frozen_mutliple_inheritance_regular_mixins(self):
2909+
@dataclass(frozen=True)
2910+
class Frozen:
2911+
pass
2912+
2913+
class NotDataclass:
2914+
pass
2915+
2916+
class C1(Frozen, NotDataclass):
2917+
pass
2918+
self.assertEqual(C1.__mro__, (C1, Frozen, NotDataclass, object))
2919+
2920+
class C2(NotDataclass, Frozen):
2921+
pass
2922+
self.assertEqual(C2.__mro__, (C2, NotDataclass, Frozen, object))
2923+
2924+
@dataclass(frozen=True)
2925+
class C3(Frozen, NotDataclass):
2926+
pass
2927+
self.assertEqual(C3.__mro__, (C3, Frozen, NotDataclass, object))
2928+
2929+
@dataclass(frozen=True)
2930+
class C4(NotDataclass, Frozen):
2931+
pass
2932+
self.assertEqual(C4.__mro__, (C4, NotDataclass, Frozen, object))
2933+
2934+
def test_multiple_frozen_dataclasses_inheritance(self):
2935+
@dataclass(frozen=True)
2936+
class FrozenA:
2937+
pass
2938+
2939+
@dataclass(frozen=True)
2940+
class FrozenB:
2941+
pass
2942+
2943+
class C1(FrozenA, FrozenB):
2944+
pass
2945+
self.assertEqual(C1.__mro__, (C1, FrozenA, FrozenB, object))
2946+
2947+
class C2(FrozenB, FrozenA):
2948+
pass
2949+
self.assertEqual(C2.__mro__, (C2, FrozenB, FrozenA, object))
2950+
2951+
@dataclass(frozen=True)
2952+
class C3(FrozenA, FrozenB):
2953+
pass
2954+
self.assertEqual(C3.__mro__, (C3, FrozenA, FrozenB, object))
2955+
2956+
@dataclass(frozen=True)
2957+
class C4(FrozenB, FrozenA):
2958+
pass
2959+
self.assertEqual(C4.__mro__, (C4, FrozenB, FrozenA, object))
2960+
28662961
def test_inherit_nonfrozen_from_empty(self):
28672962
@dataclass
28682963
class C:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix error when it was possible to inherit a frozen dataclass from multiple
2+
parents some of which were possibly not frozen.

0 commit comments

Comments
 (0)
0