summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBluenix <bluenixdev@gmail.com>2022-06-08 00:53:08 (GMT)
committerGitHub <noreply@github.com>2022-06-08 00:53:08 (GMT)
commit5849af7a80166e9e82040e082f22772bd7cf3061 (patch)
treefa94a6b81bbdf006040f1275942a40cb91918b4c
parentffc58a9710172b2d716a810a9f303828f3ebf108 (diff)
downloadcpython-5849af7a80166e9e82040e082f22772bd7cf3061.zip
cpython-5849af7a80166e9e82040e082f22772bd7cf3061.tar.gz
cpython-5849af7a80166e9e82040e082f22772bd7cf3061.tar.bz2
GH-93521: For dataclasses, filter out `__weakref__` slot if present in bases (GH-93535)
-rw-r--r--Lib/dataclasses.py13
-rw-r--r--Lib/test/test_dataclasses.py48
-rw-r--r--Misc/NEWS.d/next/Library/2022-06-06-13-19-43.gh-issue-93521._vE8m9.rst4
3 files changed, 61 insertions, 4 deletions
diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index 4645ebf..18ab690 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -1156,11 +1156,16 @@ def _add_slots(cls, is_frozen, weakref_slot):
itertools.chain.from_iterable(map(_get_slots, cls.__mro__[1:-1]))
)
# The slots for our class. Remove slots from our base classes. Add
- # '__weakref__' if weakref_slot was given.
+ # '__weakref__' if weakref_slot was given, unless it is already present.
cls_dict["__slots__"] = tuple(
- itertools.chain(
- itertools.filterfalse(inherited_slots.__contains__, field_names),
- ("__weakref__",) if weakref_slot else ())
+ itertools.filterfalse(
+ inherited_slots.__contains__,
+ itertools.chain(
+ # gh-93521: '__weakref__' also needs to be filtered out if
+ # already present in inherited_slots
+ field_names, ('__weakref__',) if weakref_slot else ()
+ )
+ ),
)
for field_name in field_names:
diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py
index cf29cd0..98dfab4 100644
--- a/Lib/test/test_dataclasses.py
+++ b/Lib/test/test_dataclasses.py
@@ -3109,6 +3109,54 @@ class TestSlots(unittest.TestCase):
"weakref_slot is True but slots is False"):
B = make_dataclass('B', [('a', int),], weakref_slot=True)
+ def test_weakref_slot_subclass_weakref_slot(self):
+ @dataclass(slots=True, weakref_slot=True)
+ class Base:
+ field: int
+
+ # A *can* also specify weakref_slot=True if it wants to (gh-93521)
+ @dataclass(slots=True, weakref_slot=True)
+ class A(Base):
+ ...
+
+ # __weakref__ is in the base class, not A. But an instance of A
+ # is still weakref-able.
+ self.assertIn("__weakref__", Base.__slots__)
+ self.assertNotIn("__weakref__", A.__slots__)
+ a = A(1)
+ weakref.ref(a)
+
+ def test_weakref_slot_subclass_no_weakref_slot(self):
+ @dataclass(slots=True, weakref_slot=True)
+ class Base:
+ field: int
+
+ @dataclass(slots=True)
+ class A(Base):
+ ...
+
+ # __weakref__ is in the base class, not A. Even though A doesn't
+ # specify weakref_slot, it should still be weakref-able.
+ self.assertIn("__weakref__", Base.__slots__)
+ self.assertNotIn("__weakref__", A.__slots__)
+ a = A(1)
+ weakref.ref(a)
+
+ def test_weakref_slot_normal_base_weakref_slot(self):
+ class Base:
+ __slots__ = ('__weakref__',)
+
+ @dataclass(slots=True, weakref_slot=True)
+ class A(Base):
+ field: int
+
+ # __weakref__ is in the base class, not A. But an instance of
+ # A is still weakref-able.
+ self.assertIn("__weakref__", Base.__slots__)
+ self.assertNotIn("__weakref__", A.__slots__)
+ a = A(1)
+ weakref.ref(a)
+
class TestDescriptors(unittest.TestCase):
def test_set_name(self):
diff --git a/Misc/NEWS.d/next/Library/2022-06-06-13-19-43.gh-issue-93521._vE8m9.rst b/Misc/NEWS.d/next/Library/2022-06-06-13-19-43.gh-issue-93521._vE8m9.rst
new file mode 100644
index 0000000..3a3ff47
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-06-06-13-19-43.gh-issue-93521._vE8m9.rst
@@ -0,0 +1,4 @@
+Fixed a case where dataclasses would try to add ``__weakref__`` into the
+``__slots__`` for a dataclass that specified ``weakref_slot=True`` when it was
+already defined in one of its bases. This resulted in a ``TypeError`` upon the
+new class being created.