From 330b18f2fe4693c08931ec9295904a036b7456d0 Mon Sep 17 00:00:00 2001 From: Changjoon Date: Wed, 29 Apr 2026 18:14:32 +0900 Subject: [PATCH] Preserve recursively-set value in defaultdict.__missing__ (#7718) CPython's defaultdict.__missing__ (Modules/_collectionsmodule.c::defdict_missing) calls default_factory() first; if the factory's recursion already populated self[key] while running, the existing value is preserved instead of being overwritten. RustPython ships a Python fallback at Lib/collections/_defaultdict.py (the C _collections.defaultdict is not available). That fallback unconditionally executed self[key] = val after the factory returned, overwriting any value the recursive call had already stored. Add a 'if key in self: return self[key]' guard before the assignment. dict.__contains__ does not invoke __missing__, so there's no recursion risk; in the common non-reentrant case the check is False and behavior is unchanged. Unmasks test_defaultdict.TestDefaultDict.test_factory_conflict_with_set_value. --- Lib/collections/_defaultdict.py | 4 ++++ Lib/test/test_defaultdict.py | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/collections/_defaultdict.py b/Lib/collections/_defaultdict.py index b9c6c4961..cb9d403c8 100644 --- a/Lib/collections/_defaultdict.py +++ b/Lib/collections/_defaultdict.py @@ -17,6 +17,10 @@ class defaultdict(dict): val = self.default_factory() else: raise KeyError(key) + # CPython parity: a recursive __missing__ via factory() may have + # already populated key; preserve that value instead of overwriting. + if key in self: + return self[key] self[key] = val return val diff --git a/Lib/test/test_defaultdict.py b/Lib/test/test_defaultdict.py index f10603fb4..fbd7354a9 100644 --- a/Lib/test/test_defaultdict.py +++ b/Lib/test/test_defaultdict.py @@ -186,7 +186,6 @@ class TestDefaultDict(unittest.TestCase): with self.assertRaises(TypeError): i |= None - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 1 != 2 def test_factory_conflict_with_set_value(self): key = "conflict_test" count = 0