From 0aed904dd70efc1fb2ce63508812d44082261cca Mon Sep 17 00:00:00 2001 From: 2jiyong Date: Sat, 18 Jul 2026 23:58:29 +0900 Subject: [PATCH 1/4] fix dict update type error --- Lib/test/test_dict.py | 1 - crates/vm/src/builtins/dict.rs | 78 +++++++++++++++++++++++++++------- 2 files changed, 63 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 79c975946f7..e2a73773cc2 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -275,7 +275,6 @@ def __next__(self): self.assertRaises(ValueError, {}.update, [(1, 2, 3)]) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_update_type_error(self): with self.assertRaises(TypeError) as cm: {}.update([object() for _ in range(3)]) diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index 06791410edf..c05884af8bf 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -8,7 +8,7 @@ use crate::{ AsObject, Context, Py, PyExact, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult, TryFromObject, atomic_func, builtins::{ - PyTuple, + PyList, PyTuple, iter::{builtins_iter, builtins_reversed}, type_::PyAttributes, }, @@ -187,6 +187,64 @@ impl PyDict { self.merge_object_with_override(other, false, vm) } + fn add_update_sequence_note(exc: &PyBaseExceptionRef, index: usize, vm: &VirtualMachine) { + if exc.fast_isinstance(vm.ctx.exceptions.type_error) { + let note = + format!("Cannot convert dictionary update sequence element #{index} to a sequence"); + let _ = vm.call_method(exc.as_object(), "add_note", (vm.ctx.new_str(note),)); + } + } + + fn update_sequence_pair_from_slice( + elements: &[PyObjectRef], + index: usize, + vm: &VirtualMachine, + ) -> PyResult<[PyObjectRef; 2]> { + let [key, value] = elements else { + return Err(vm.new_value_error(format!( + "dictionary update sequence element #{index} has length {}; 2 is required", + elements.len() + ))); + }; + Ok([key.clone(), value.clone()]) + } + + fn update_sequence_pair( + element: PyObjectRef, + index: usize, + vm: &VirtualMachine, + ) -> PyResult<[PyObjectRef; 2]> { + let element = match element.downcast_exact::(vm) { + Ok(list) => { + let elements = list.borrow_vec(); + return Self::update_sequence_pair_from_slice(&elements, index, vm); + } + Err(element) => element, + }; + let element = match element.downcast_exact::(vm) { + Ok(tuple) => { + return Self::update_sequence_pair_from_slice(tuple.as_slice(), index, vm); + } + Err(element) => element, + }; + + let elements = (|| { + let elem_iter = element.get_iter(vm).map_err(|exc| { + if exc.fast_isinstance(vm.ctx.exceptions.type_error) { + vm.new_type_error("object is not iterable") + } else { + exc + } + })?; + elem_iter + .into_iter::(vm)? + .collect::>>() + })() + .inspect_err(|exc| Self::add_update_sequence_note(exc, index, vm))?; + + Self::update_sequence_pair_from_slice(&elements, index, vm) + } + pub fn merge_from_seq2( &self, seq2: PyObjectRef, @@ -195,20 +253,10 @@ impl PyDict { ) -> PyResult<()> { let iter = seq2.get_iter(vm)?; let dict = &self.entries; - loop { - fn err(vm: &VirtualMachine) -> PyBaseExceptionRef { - vm.new_value_error("Iterator must have exactly two elements") - } - let element = match iter.next(vm)? { - PyIterReturn::Return(obj) => obj, - PyIterReturn::StopIteration(_) => break, - }; - let elem_iter = element.get_iter(vm)?; - let key = elem_iter.next(vm)?.into_result().map_err(|_| err(vm))?; - let value = elem_iter.next(vm)?.into_result().map_err(|_| err(vm))?; - if matches!(elem_iter.next(vm)?, PyIterReturn::Return(_)) { - return Err(err(vm)); - } + + for (index, element) in iter.iter_without_hint::(vm)?.enumerate() { + let [key, value] = Self::update_sequence_pair(element?, index, vm)?; + if !override_existing && dict.contains(vm, &*key)? { continue; } From 73891b1b9b31453749abcd38145ac00961d7047b Mon Sep 17 00:00:00 2001 From: 2jiyong Date: Sun, 26 Jul 2026 01:20:46 +0900 Subject: [PATCH 2/4] Use tuple for dictionary update sequence pairs --- crates/vm/src/builtins/dict.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index c05884af8bf..8ae2dc899d3 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -199,21 +199,21 @@ impl PyDict { elements: &[PyObjectRef], index: usize, vm: &VirtualMachine, - ) -> PyResult<[PyObjectRef; 2]> { + ) -> PyResult<(PyObjectRef, PyObjectRef)> { let [key, value] = elements else { return Err(vm.new_value_error(format!( "dictionary update sequence element #{index} has length {}; 2 is required", elements.len() ))); }; - Ok([key.clone(), value.clone()]) + Ok((key.clone(), value.clone())) } fn update_sequence_pair( element: PyObjectRef, index: usize, vm: &VirtualMachine, - ) -> PyResult<[PyObjectRef; 2]> { + ) -> PyResult<(PyObjectRef, PyObjectRef)> { let element = match element.downcast_exact::(vm) { Ok(list) => { let elements = list.borrow_vec(); @@ -255,7 +255,7 @@ impl PyDict { let dict = &self.entries; for (index, element) in iter.iter_without_hint::(vm)?.enumerate() { - let [key, value] = Self::update_sequence_pair(element?, index, vm)?; + let (key, value) = Self::update_sequence_pair(element?, index, vm)?; if !override_existing && dict.contains(vm, &*key)? { continue; From a0adb664a8f9d95a3a3b0683aec68b76ec995a74 Mon Sep 17 00:00:00 2001 From: 2jiyong Date: Wed, 29 Jul 2026 15:51:25 +0900 Subject: [PATCH 3/4] Propagate errors while adding dict update sequence notes --- crates/vm/src/builtins/dict.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/crates/vm/src/builtins/dict.rs b/crates/vm/src/builtins/dict.rs index 8ae2dc899d3..f66985179cf 100644 --- a/crates/vm/src/builtins/dict.rs +++ b/crates/vm/src/builtins/dict.rs @@ -187,11 +187,23 @@ impl PyDict { self.merge_object_with_override(other, false, vm) } - fn add_update_sequence_note(exc: &PyBaseExceptionRef, index: usize, vm: &VirtualMachine) { - if exc.fast_isinstance(vm.ctx.exceptions.type_error) { - let note = - format!("Cannot convert dictionary update sequence element #{index} to a sequence"); - let _ = vm.call_method(exc.as_object(), "add_note", (vm.ctx.new_str(note),)); + fn add_update_sequence_note( + exc: PyBaseExceptionRef, + index: usize, + vm: &VirtualMachine, + ) -> PyBaseExceptionRef { + if !exc.fast_isinstance(vm.ctx.exceptions.type_error) { + return exc; + } + + let note = + format!("Cannot convert dictionary update sequence element #{index} to a sequence"); + match vm.call_method(exc.as_object(), "add_note", (vm.ctx.new_str(note),)) { + Ok(_) => exc, + Err(note_err) => { + note_err.set___context__(Some(exc)); + note_err + } } } @@ -240,7 +252,7 @@ impl PyDict { .into_iter::(vm)? .collect::>>() })() - .inspect_err(|exc| Self::add_update_sequence_note(exc, index, vm))?; + .map_err(|exc| Self::add_update_sequence_note(exc, index, vm))?; Self::update_sequence_pair_from_slice(&elements, index, vm) } From a0817f9be2da4e0a7a07efef405e47a1762663ef Mon Sep 17 00:00:00 2001 From: 2jiyong Date: Wed, 29 Jul 2026 15:51:39 +0900 Subject: [PATCH 4/4] Match BaseException.add_note error message with CPython --- crates/vm/src/exceptions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index cd1eb1adbcb..c2c82f67e14 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -703,7 +703,7 @@ impl PyRef { let notes = notes .downcast::() - .map_err(|_| vm.new_type_error("__notes__ must be a list"))?; + .map_err(|_| vm.new_type_error("Cannot add note: __notes__ is not a list"))?; notes.borrow_vec_mut().push(note.into()); Ok(())