https://github.com/python/cpython/commit/f5fb491341e566bbaf17d9bf3e4ec3af4a56bb3f
commit: f5fb491341e566bbaf17d9bf3e4ec3af4a56bb3f
branch: main
author: Thomas Kowalski <[email protected]>
committer: vstinner <[email protected]>
date: 2026-05-12T10:47:39Z
summary:

gh-149676: Fix hash(frozendict | frozendict) (#149675)

Fix new_dict_impl() to properly initialize ma_hash on frozendict.

Co-authored-by: Victor Stinner <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-05-11-14-48-56.gh-issue-149676.6aTrw1.rst
M Lib/test/test_dict.py
M Objects/dictobject.c

diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py
index b2f4363b23e748..4efb066d4fd01c 100644
--- a/Lib/test/test_dict.py
+++ b/Lib/test/test_dict.py
@@ -1868,6 +1868,11 @@ def test_merge(self):
         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
index 00000000000000..96f407cf5ad25a
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-11-14-48-56.gh-issue-149676.6aTrw1.rst
@@ -0,0 +1 @@
+Fix ``frozendict | frozendict`` hash.
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 09135e031e6fc7..b33a273dac3b95 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -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 *

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to