]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-148947: dataclasses: fix error on empty __class__ cell (GH-148948) (#148995)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Sat, 25 Apr 2026 15:57:33 +0000 (17:57 +0200)
committerGitHub <noreply@github.com>
Sat, 25 Apr 2026 15:57:33 +0000 (15:57 +0000)
gh-148947: dataclasses: fix error on empty __class__ cell  (GH-148948)

Also add a test demonstrating the need for the existing "is oldcls" check.
(cherry picked from commit 6d7bbee1d5714a345dca5a7e4089de3c2fc0fb59)

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
Lib/dataclasses.py
Lib/test/test_dataclasses/__init__.py
Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst [new file with mode: 0644]

index 6f0539b740a83fd183815d65e27f48f4c4cf7654..f9c1113b1e367f47cd710367c1a2ec3619a94df3 100644 (file)
@@ -1292,10 +1292,18 @@ def _update_func_cell_for__class__(f, oldcls, newcls):
         # This function doesn't reference __class__, so nothing to do.
         return False
     # Fix the cell to point to the new class, if it's already pointing
-    # at the old class.  I'm not convinced that the "is oldcls" test
-    # is needed, but other than performance can't hurt.
+    # at the old class.
     closure = f.__closure__[idx]
-    if closure.cell_contents is oldcls:
+
+    try:
+        contents = closure.cell_contents
+    except ValueError:
+        # Cell is empty
+        return False
+
+    # This check makes it so we avoid updating an incorrect cell if the
+    # class body contains a function that was defined in a different class.
+    if contents is oldcls:
         closure.cell_contents = newcls
         return True
     return False
index 10e040976ae83b3451dd975e828a7d65fd64a7df..fed32260c792707dbe9f43b7da4b99dd8ebcea2d 100644 (file)
@@ -5361,5 +5361,51 @@ class TestZeroArgumentSuperWithSlots(unittest.TestCase):
         # one will be keeping a reference to the underlying class A.
         self.assertIs(A().cls(), B)
 
+    def test_empty_class_cell(self):
+        # gh-148947: Make sure that we explicitly handle the empty class cell.
+        def maker():
+            if False:
+                __class__ = 42
+
+            def method(self):
+                return __class__
+            return method
+
+        from dataclasses import dataclass
+
+        @dataclass(slots=True)
+        class X:
+            a: int
+
+            meth = maker()
+
+        with self.assertRaisesRegex(NameError, '__class__'):
+            X(1).meth()
+
+    def test_class_cell_from_other_class(self):
+        # This test fails without the "is oldcls" check in
+        # _update_func_cell_for__class__.
+        class Base:
+            def meth(self):
+                return "Base"
+
+        class Child(Base):
+            def meth(self):
+                return super().meth() + " Child"
+
+        @dataclass(slots=True)
+        class DC(Child):
+            a: int
+
+            meth = Child.meth
+
+        closure = DC.meth.__closure__
+        self.assertEqual(len(closure), 1)
+        self.assertIs(closure[0].cell_contents, Child)
+
+        self.assertEqual(DC(1).meth(), "Base Child")
+
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst b/Misc/NEWS.d/next/Library/2026-04-23-21-47-49.gh-issue-148947.W4V2lG.rst
new file mode 100644 (file)
index 0000000..f978326
--- /dev/null
@@ -0,0 +1,2 @@
+Fix crash in :deco:`dataclasses.dataclass` with ``slots=True`` that occurred
+when a function found within the class had an empty ``__class__`` cell.