Skip to content

Commit 9467632

Browse files
committed
Fix dict unpacking order preservation
1 parent 829388b commit 9467632

2 files changed

Lines changed: 29 additions & 14 deletions

File tree

‎Lib/test/test_call.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313

1414
class FunctionCalls(unittest.TestCase):
1515

16-
# TODO: RUSTPYTHON
17-
@unittest.expectedFailure
1816
def test_kwargs_order(self):
1917
# bpo-34320: **kwargs should preserve order of passed OrderedDict
2018
od = collections.OrderedDict([('a', 1), ('b', 2)])

‎vm/src/frame.rs‎

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,11 +1527,20 @@ impl ExecutingFrame<'_> {
15271527
let size = size as usize;
15281528
let map_obj = vm.ctx.new_dict();
15291529
for obj in self.pop_multiple(size) {
1530-
// Take all key-value pairs from the dict:
1531-
let dict: PyDictRef = obj.downcast().map_err(|obj| {
1532-
vm.new_type_error(format!("'{}' object is not a mapping", obj.class().name()))
1533-
})?;
1534-
for (key, value) in dict {
1530+
// Use keys() method for all mapping objects to preserve order
1531+
let Some(keys_method) = vm.get_method(obj.clone(), vm.ctx.intern_str("keys")) else {
1532+
return Err(
1533+
vm.new_type_error(format!("'{}' object is not a mapping", obj.class().name()))
1534+
);
1535+
};
1536+
1537+
let keys = keys_method?.call((), vm)?.get_iter(vm)?;
1538+
while let PyIterReturn::Return(key) = keys.next(vm)? {
1539+
// Check for keyword argument restrictions
1540+
if key.downcast_ref::<PyStr>().is_none() {
1541+
return Err(vm.new_type_error("keywords must be strings".to_owned()));
1542+
}
1543+
15351544
if map_obj.contains_key(&*key, vm) {
15361545
let key_repr = &key.repr(vm)?;
15371546
let msg = format!(
@@ -1540,6 +1549,8 @@ impl ExecutingFrame<'_> {
15401549
);
15411550
return Err(vm.new_type_error(msg));
15421551
}
1552+
1553+
let value = obj.get_item(&*key, vm)?;
15431554
map_obj.set_item(&*key, value, vm)?;
15441555
}
15451556
}
@@ -1586,16 +1597,22 @@ impl ExecutingFrame<'_> {
15861597

15871598
fn collect_ex_args(&mut self, vm: &VirtualMachine, has_kwargs: bool) -> PyResult<FuncArgs> {
15881599
let kwargs = if has_kwargs {
1589-
let kw_dict: PyDictRef = self.pop_value().downcast().map_err(|_| {
1590-
// TODO: check collections.abc.Mapping
1591-
vm.new_type_error("Kwargs must be a dict.".to_owned())
1592-
})?;
1600+
let kw_obj = self.pop_value();
15931601
let mut kwargs = IndexMap::new();
1594-
for (key, value) in kw_dict.into_iter() {
1595-
let key = key
1602+
1603+
// Use keys() method for all mapping objects to preserve order
1604+
let Some(keys_method) = vm.get_method(kw_obj.clone(), vm.ctx.intern_str("keys")) else {
1605+
return Err(vm.new_type_error("argument after ** must be a mapping".to_owned()));
1606+
};
1607+
1608+
// Handle custom mapping objects like OrderedDict using keys() method
1609+
let keys = keys_method?.call((), vm)?.get_iter(vm)?;
1610+
while let PyIterReturn::Return(key) = keys.next(vm)? {
1611+
let key_str = key
15961612
.payload_if_subclass::<PyStr>(vm)
15971613
.ok_or_else(|| vm.new_type_error("keywords must be strings".to_owned()))?;
1598-
kwargs.insert(key.as_str().to_owned(), value);
1614+
let value = kw_obj.get_item(&*key, vm)?;
1615+
kwargs.insert(key_str.as_str().to_owned(), value);
15991616
}
16001617
kwargs
16011618
} else {

0 commit comments

Comments
 (0)