Skip to content

Commit

Permalink
pythongh-109409: Fix inheritance of frozen dataclass from non-frozen …
Browse files Browse the repository at this point in the history
…dataclass mixins (pythongh-109437)

Fix inheritance of frozen dataclass from non-frozen dataclass mixins
  • Loading branch information
sobolevn authored and Glyphack committed Jan 27, 2024
1 parent 9264ed7 commit 6491d39
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 4 deletions.
14 changes: 10 additions & 4 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -944,8 +944,11 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
# Find our base classes in reverse MRO order, and exclude
# ourselves. In reversed order so that more derived classes
# override earlier field definitions in base classes. As long as
# we're iterating over them, see if any are frozen.
# we're iterating over them, see if all or any of them are frozen.
any_frozen_base = False
# By default `all_frozen_bases` is `None` to represent a case,
# where some dataclasses does not have any bases with `_FIELDS`
all_frozen_bases = None
has_dataclass_bases = False
for b in cls.__mro__[-1:0:-1]:
# Only process classes that have been processed by our
Expand All @@ -955,8 +958,11 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
has_dataclass_bases = True
for f in base_fields.values():
fields[f.name] = f
if getattr(b, _PARAMS).frozen:
any_frozen_base = True
if all_frozen_bases is None:
all_frozen_bases = True
current_frozen = getattr(b, _PARAMS).frozen
all_frozen_bases = all_frozen_bases and current_frozen
any_frozen_base = any_frozen_base or current_frozen

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

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

Expand Down
95 changes: 95 additions & 0 deletions Lib/test/test_dataclasses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2863,6 +2863,101 @@ class C:
class D(C):
j: int

def test_inherit_frozen_mutliple_inheritance(self):
@dataclass
class NotFrozen:
pass

@dataclass(frozen=True)
class Frozen:
pass

class NotDataclass:
pass

for bases in (
(NotFrozen, Frozen),
(Frozen, NotFrozen),
(Frozen, NotDataclass),
(NotDataclass, Frozen),
):
with self.subTest(bases=bases):
with self.assertRaisesRegex(
TypeError,
'cannot inherit non-frozen dataclass from a frozen one',
):
@dataclass
class NotFrozenChild(*bases):
pass

for bases in (
(NotFrozen, Frozen),
(Frozen, NotFrozen),
(NotFrozen, NotDataclass),
(NotDataclass, NotFrozen),
):
with self.subTest(bases=bases):
with self.assertRaisesRegex(
TypeError,
'cannot inherit frozen dataclass from a non-frozen one',
):
@dataclass(frozen=True)
class FrozenChild(*bases):
pass

def test_inherit_frozen_mutliple_inheritance_regular_mixins(self):
@dataclass(frozen=True)
class Frozen:
pass

class NotDataclass:
pass

class C1(Frozen, NotDataclass):
pass
self.assertEqual(C1.__mro__, (C1, Frozen, NotDataclass, object))

class C2(NotDataclass, Frozen):
pass
self.assertEqual(C2.__mro__, (C2, NotDataclass, Frozen, object))

@dataclass(frozen=True)
class C3(Frozen, NotDataclass):
pass
self.assertEqual(C3.__mro__, (C3, Frozen, NotDataclass, object))

@dataclass(frozen=True)
class C4(NotDataclass, Frozen):
pass
self.assertEqual(C4.__mro__, (C4, NotDataclass, Frozen, object))

def test_multiple_frozen_dataclasses_inheritance(self):
@dataclass(frozen=True)
class FrozenA:
pass

@dataclass(frozen=True)
class FrozenB:
pass

class C1(FrozenA, FrozenB):
pass
self.assertEqual(C1.__mro__, (C1, FrozenA, FrozenB, object))

class C2(FrozenB, FrozenA):
pass
self.assertEqual(C2.__mro__, (C2, FrozenB, FrozenA, object))

@dataclass(frozen=True)
class C3(FrozenA, FrozenB):
pass
self.assertEqual(C3.__mro__, (C3, FrozenA, FrozenB, object))

@dataclass(frozen=True)
class C4(FrozenB, FrozenA):
pass
self.assertEqual(C4.__mro__, (C4, FrozenB, FrozenA, object))

def test_inherit_nonfrozen_from_empty(self):
@dataclass
class C:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix error when it was possible to inherit a frozen dataclass from multiple
parents some of which were possibly not frozen.

0 comments on commit 6491d39

Please sign in to comment.