From f4543f5f51c3e07bfb15817906f08998df1015e2 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 26 Aug 2025 17:18:05 +0900 Subject: [PATCH] Fix defaultdict --- Lib/test/test_patma.py | 3 --- vm/src/frame.rs | 47 +++++++++++++++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_patma.py b/Lib/test/test_patma.py index 269e5f87e..847ca001e 100644 --- a/Lib/test/test_patma.py +++ b/Lib/test/test_patma.py @@ -1337,7 +1337,6 @@ class TestPatma(unittest.TestCase): self.assertEqual(x, [0, 1, 2]) self.assertEqual(y, 0) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_133(self): x = collections.defaultdict(int, {0: 1}) match x: @@ -1350,7 +1349,6 @@ class TestPatma(unittest.TestCase): self.assertEqual(x, {0: 1}) self.assertEqual(y, 2) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_134(self): x = collections.defaultdict(int, {0: 1}) match x: @@ -1364,7 +1362,6 @@ class TestPatma(unittest.TestCase): self.assertEqual(y, 2) self.assertEqual(z, {0: 1}) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_patma_135(self): x = collections.defaultdict(int, {0: 1}) match x: diff --git a/vm/src/frame.rs b/vm/src/frame.rs index ec03eef22..ba9abffc6 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1332,14 +1332,47 @@ impl ExecutingFrame<'_> { let mut values = Vec::new(); let mut all_match = true; - for key in keys { - match subject.get_item(key.as_object(), vm) { - Ok(value) => values.push(value), - Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => { - all_match = false; - break; + // We use the two argument form of map.get(key, default) for two reasons: + // - Atomically check for a key and get its value without error handling. + // - Don't cause key creation or resizing in dict subclasses like + // collections.defaultdict that define __missing__ (or similar). + // See CPython's _PyEval_MatchKeys + + if let Some(get_method) = vm + .get_method(subject.to_owned(), vm.ctx.intern_str("get")) + .transpose()? + { + // dummy = object() + // CPython: dummy = _PyObject_CallNoArgs((PyObject *)&PyBaseObject_Type); + let dummy = vm + .ctx + .new_base_object(vm.ctx.types.object_type.to_owned(), None); + + for key in keys { + // value = map.get(key, dummy) + match get_method.call((key.as_object(), dummy.clone()), vm) { + Ok(value) => { + // if value == dummy: key not in map! + if value.is(&dummy) { + all_match = false; + break; + } + values.push(value); + } + Err(e) => return Err(e), + } + } + } else { + // Fallback if .get() method is not available (shouldn't happen for mappings) + for key in keys { + match subject.get_item(key.as_object(), vm) { + Ok(value) => values.push(value), + Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => { + all_match = false; + break; + } + Err(e) => return Err(e), } - Err(e) => return Err(e), } }