]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.15] gh-149676: Fix hash(frozendict | frozendict) (GH-149675) (#149717)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Tue, 12 May 2026 13:57:02 +0000 (15:57 +0200)
committerGitHub <noreply@github.com>
Tue, 12 May 2026 13:57:02 +0000 (15:57 +0200)
gh-149676: Fix hash(frozendict | frozendict) (GH-149675)

Fix new_dict_impl() to properly initialize ma_hash on frozendict.
(cherry picked from commit f5fb491341e566bbaf17d9bf3e4ec3af4a56bb3f)

Co-authored-by: Thomas Kowalski <thom.kowa@gmail.com>
Co-authored-by: Victor Stinner <vstinner@python.org>
Lib/test/test_dict.py
Misc/NEWS.d/next/Core_and_Builtins/2026-05-11-14-48-56.gh-issue-149676.6aTrw1.rst [new file with mode: 0644]
Objects/dictobject.c

index b2f4363b23e74802714ff59a2497d4ce3803df0a..4efb066d4fd01ca0266b638fc482bd5c4160698c 100644 (file)
@@ -1868,6 +1868,11 @@ class FrozenDictTests(unittest.TestCase):
         self.assertEqual(fd | {}, fd)
         self.assertEqual(frozendict() | fd, fd)
 
+        # gh-149676: Test hash(frozendict | frozendict)
+        a = frozendict({"a": 1})
+        b = frozendict({"b": 2})
+        self.assertEqual(hash(a | b), hash(frozendict({"a": 1, "b": 2})))
+
     def test_update(self):
         # test "a |= b" operator
         d = frozendict(x=1)
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-11-14-48-56.gh-issue-149676.6aTrw1.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-11-14-48-56.gh-issue-149676.6aTrw1.rst
new file mode 100644 (file)
index 0000000..96f407c
--- /dev/null
@@ -0,0 +1 @@
+Fix ``frozendict | frozendict`` hash.
index 09135e031e6fc73a604a99e550113a266544cb56..b33a273dac3b95b904ad03aa2004b53321be673d 100644 (file)
@@ -900,7 +900,7 @@ free_values(PyDictValues *values, bool use_qsbr)
 static inline PyObject *
 new_dict_impl(PyDictObject *mp, PyDictKeysObject *keys,
               PyDictValues *values, Py_ssize_t used,
-              int free_values_on_failure)
+              int free_values_on_failure, int frozendict)
 {
     assert(keys != NULL);
     if (mp == NULL) {
@@ -915,6 +915,9 @@ new_dict_impl(PyDictObject *mp, PyDictKeysObject *keys,
     mp->ma_values = values;
     mp->ma_used = used;
     mp->_ma_watcher_tag = 0;
+    if (frozendict) {
+        ((PyFrozenDictObject *)mp)->ma_hash = -1;
+    }
     ASSERT_CONSISTENT(mp);
     _PyObject_GC_TRACK(mp);
     return (PyObject *)mp;
@@ -931,7 +934,7 @@ new_dict(PyDictKeysObject *keys, PyDictValues *values,
     }
     assert(mp == NULL || Py_IS_TYPE(mp, &PyDict_Type));
 
-    return new_dict_impl(mp, keys, values, used, free_values_on_failure);
+    return new_dict_impl(mp, keys, values, used, free_values_on_failure, 0);
 }
 
 /* Consumes a reference to the keys object */
@@ -940,7 +943,7 @@ new_frozendict(PyDictKeysObject *keys, PyDictValues *values,
                Py_ssize_t used, int free_values_on_failure)
 {
     PyDictObject *mp = PyObject_GC_New(PyDictObject, &PyFrozenDict_Type);
-    return new_dict_impl(mp, keys, values, used, free_values_on_failure);
+    return new_dict_impl(mp, keys, values, used, free_values_on_failure, 1);
 }
 
 static PyObject *