From 324ecaa59b399e90be903f2cb04af9606837e3a5 Mon Sep 17 00:00:00 2001 From: Sanghun Lee Date: Wed, 12 Aug 2026 22:15:33 +0900 Subject: [PATCH 1/2] Reuse stored hashes in dict.fromkeys() Closes #8490. Co-Authored-By: Claude Opus 5 (1M context) --- Lib/test/test_set.py | 1 - crates/vm/src/builtins/dict.rs | 30 ++++++++++++++++++++++++++---- crates/vm/src/builtins/set.rs | 17 +++++++++++++++++ 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_set.py b/Lib/test/test_set.py index 4d062b42ded..42f11c9eb28 100644 --- a/Lib/test/test_set.py +++ b/Lib/test/test_set.py @@ -330,7 +330,6 @@ def test_cyclical_repr(self): name = repr(s).partition('(')[0] # strip class name self.assertEqual(repr(s), '%s({%s(...)})' % (name, name)) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_do_not_rehash_dict_keys(self): n = 10 d = dict.fromkeys(map(HashCountingInt, range(n))) diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index 5db071e1d8f..980f200071c 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -1,6 +1,6 @@ use super::{ IterStatus, PositionIterInternal, PyBaseExceptionRef, PyGenericAlias, PyMappingProxy, PySet, - PyStr, PyStrRef, PyTupleRef, PyType, PyTypeRef, set::PySetInner, + PyStr, PyStrRef, PyTupleRef, PyType, PyTypeRef, set, set::PySetInner, }; use crate::common::lock::LazyLock; use crate::object::{Traverse, TraverseFn}; @@ -9,7 +9,7 @@ use crate::{ TryFromObject, atomic_func, builtins::{PyList, PyTuple, iter::builtins_iter, type_::PyAttributes}, class::{PyClassDef, PyClassImpl}, - common::ascii, + common::{ascii, hash::PyHash}, dict_inner::{self, DictKey}, function::{ArgIterable, FuncArgs, KwArgs, OptionalArg, PyArithmeticValue, PyComparisonValue}, iter::PyExactSizeIterator, @@ -356,6 +356,20 @@ impl PyDict { } } +/// Keys of `obj` with their stored hashes, or `None` if it must be iterated +/// generically. Only exact dicts and sets qualify, as in CPython's +/// `_PyDict_FromKeys`: a subclass may override `__iter__`. +fn fromkeys_known_hashes( + obj: &PyObject, + vm: &VirtualMachine, +) -> Option> { + if let Some(dict) = obj.downcast_ref_if_exact::(vm) { + Some(dict.entries.keys_with_hashes()) + } else { + set::exact_set_keys_with_hashes(obj, vm) + } +} + // Python dict methods: #[pyclass( with( @@ -384,8 +398,16 @@ impl PyDict { let d = PyType::call(&class, ().into(), vm)?; match d.downcast_exact::(vm) { Ok(pydict) => { - for key in iterable.iter(vm)? { - pydict.__setitem__(key?, value.clone(), vm)?; + if let Some(keys) = fromkeys_known_hashes(iterable.as_object(), vm) { + for (key, hash) in keys { + pydict + .entries + .insert_known_hash(vm, &*key, hash, value.clone())?; + } + } else { + for key in iterable.iter(vm)? { + pydict.__setitem__(key?, value.clone(), vm)?; + } } Ok(pydict.into_pyref().into()) } diff --git a/crates/vm/src/builtins/set.rs b/crates/vm/src/builtins/set.rs index 62bdb0f0da5..6961040c792 100644 --- a/crates/vm/src/builtins/set.rs +++ b/crates/vm/src/builtins/set.rs @@ -605,6 +605,23 @@ fn extract_set(obj: &PyObject) -> Option<&PySetInner> { }) } +/// Elements of `obj` with their stored hashes, or `None` unless `obj` is exactly +/// a `set` or `frozenset` — `PyAnySet_CheckExact`, where [`extract_set`] is the +/// subclass-inclusive `PyAnySet_Check`. +pub(super) fn exact_set_keys_with_hashes( + obj: &PyObject, + vm: &VirtualMachine, +) -> Option> { + let inner = obj + .downcast_ref_if_exact::(vm) + .map(|set| &set.inner) + .or_else(|| { + obj.downcast_ref_if_exact::(vm) + .map(|frozen| &frozen.inner) + })?; + Some(inner.content.keys_with_hashes()) +} + fn reduce_set(zelf: &PyObject, vm: &VirtualMachine) -> (PyTypeRef, PyTupleRef, Option) { ( zelf.class().to_owned(), From b13fd86a4c31d37b249cced5d161bb133053d278 Mon Sep 17 00:00:00 2001 From: Sanghun Lee Date: Thu, 13 Aug 2026 01:18:26 +0900 Subject: [PATCH 2/2] Make fromkeys_known_hashes a PyDict associated fn --- crates/vm/src/builtins/dict.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index 980f200071c..fbc23a0dde7 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -354,19 +354,19 @@ impl PyDict { ) -> PyResult> { self.entries.get(vm, key) } -} -/// Keys of `obj` with their stored hashes, or `None` if it must be iterated -/// generically. Only exact dicts and sets qualify, as in CPython's -/// `_PyDict_FromKeys`: a subclass may override `__iter__`. -fn fromkeys_known_hashes( - obj: &PyObject, - vm: &VirtualMachine, -) -> Option> { - if let Some(dict) = obj.downcast_ref_if_exact::(vm) { - Some(dict.entries.keys_with_hashes()) - } else { - set::exact_set_keys_with_hashes(obj, vm) + /// Keys of `obj` with their stored hashes, or `None` if it must be iterated + /// generically. Only exact dicts and sets qualify, as in CPython's + /// `_PyDict_FromKeys`: a subclass may override `__iter__`. + fn fromkeys_known_hashes( + obj: &PyObject, + vm: &VirtualMachine, + ) -> Option> { + if let Some(dict) = obj.downcast_ref_if_exact::(vm) { + Some(dict.entries.keys_with_hashes()) + } else { + set::exact_set_keys_with_hashes(obj, vm) + } } } @@ -398,7 +398,7 @@ impl PyDict { let d = PyType::call(&class, ().into(), vm)?; match d.downcast_exact::(vm) { Ok(pydict) => { - if let Some(keys) = fromkeys_known_hashes(iterable.as_object(), vm) { + if let Some(keys) = Self::fromkeys_known_hashes(iterable.as_object(), vm) { for (key, hash) in keys { pydict .entries