diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index 70ff6d391..28d47cb04 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -6670,6 +6670,23 @@ impl Compiler { let mut have_dict = false; let mut elements: u32 = 0; + // Flush pending regular pairs as a BUILD_MAP, merging into the + // accumulator dict via DICT_UPDATE when one already exists. + macro_rules! flush_pending { + () => { + #[allow(unused_assignments)] + if elements > 0 { + emit!(self, Instruction::BuildMap { size: elements }); + if have_dict { + emit!(self, Instruction::DictUpdate { index: 1 }); + } else { + have_dict = true; + } + elements = 0; + } + }; + } + for item in items { if let Some(key) = &item.key { // Regular key: value pair @@ -6678,17 +6695,7 @@ impl Compiler { elements += 1; } else { // ** unpacking entry - if elements > 0 { - // Flush pending regular pairs - if !have_dict { - emit!(self, Instruction::BuildMap { size: elements }); - have_dict = true; - } else { - emit!(self, Instruction::BuildMap { size: elements }); - emit!(self, Instruction::DictUpdate { index: 1 }); - } - elements = 0; - } + flush_pending!(); if !have_dict { emit!(self, Instruction::BuildMap { size: 0 }); have_dict = true; @@ -6698,13 +6705,9 @@ impl Compiler { } } - if elements > 0 || !have_dict { - if !have_dict { - emit!(self, Instruction::BuildMap { size: elements }); - } else { - emit!(self, Instruction::BuildMap { size: elements }); - emit!(self, Instruction::DictUpdate { index: 1 }); - } + flush_pending!(); + if !have_dict { + emit!(self, Instruction::BuildMap { size: 0 }); } Ok(()) diff --git a/crates/vm/src/builtins/function.rs b/crates/vm/src/builtins/function.rs index 193655a98..9a6a6d49e 100644 --- a/crates/vm/src/builtins/function.rs +++ b/crates/vm/src/builtins/function.rs @@ -25,6 +25,37 @@ use itertools::Itertools; #[cfg(feature = "jit")] use rustpython_jit::CompiledCode; +fn format_missing_args( + qualname: impl core::fmt::Display, + kind: &str, + missing: &mut Vec, +) -> String { + let count = missing.len(); + let last = if missing.len() > 1 { + missing.pop() + } else { + None + }; + let (and, right): (&str, String) = if let Some(last) = last { + ( + if missing.len() == 1 { + "' and '" + } else { + "', and '" + }, + format!("{last}"), + ) + } else { + ("", String::new()) + }; + format!( + "{qualname}() missing {count} required {kind} argument{}: '{}{}{right}'", + if count == 1 { "" } else { "s" }, + missing.iter().join("', '"), + and, + ) +} + #[pyclass(module = false, name = "function", traverse = "manual")] #[derive(Debug)] pub struct PyFunction { @@ -343,36 +374,12 @@ impl PyFunction { } }) .collect(); - let missing_args_len = missing.len(); if !missing.is_empty() { - let last = if missing.len() > 1 { - missing.pop() - } else { - None - }; - - let (and, right) = if let Some(last) = last { - ( - if missing.len() == 1 { - "' and '" - } else { - "', and '" - }, - last.as_str(), - ) - } else { - ("", "") - }; - - return Err(vm.new_type_error(format!( - "{}() missing {} required positional argument{}: '{}{}{}'", + return Err(vm.new_type_error(format_missing_args( self.__qualname__(), - missing_args_len, - if missing_args_len == 1 { "" } else { "s" }, - missing.iter().join("', '"), - and, - right, + "positional", + &mut missing, ))); } @@ -413,34 +420,10 @@ impl PyFunction { } if !missing.is_empty() { - let missing_len = missing.len(); - let last = if missing.len() > 1 { - missing.pop() - } else { - None - }; - - let (and, right) = if let Some(last) = last { - ( - if missing.len() == 1 { - "' and '" - } else { - "', and '" - }, - last.as_str(), - ) - } else { - ("", "") - }; - - return Err(vm.new_type_error(format!( - "{}() missing {} required keyword-only argument{}: '{}{}{}'", + return Err(vm.new_type_error(format_missing_args( self.__qualname__(), - missing_len, - if missing_len == 1 { "" } else { "s" }, - missing.iter().join("', '"), - and, - right, + "keyword-only", + &mut missing, ))); } } diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index fd4863344..23137a3f2 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -2803,8 +2803,12 @@ impl ExecutingFrame<'_> { fn execute_unpack_ex(&mut self, vm: &VirtualMachine, before: u8, after: u8) -> FrameResult { let (before, after) = (before as usize, after as usize); let value = self.pop_value(); + let not_iterable = value.class().slots.iter.load().is_none() + && value + .get_class_attr(vm.ctx.intern_str("__getitem__")) + .is_none(); let elements: Vec<_> = value.try_to_value(vm).map_err(|e| { - if e.class().is(vm.ctx.exceptions.type_error) { + if not_iterable && e.class().is(vm.ctx.exceptions.type_error) { vm.new_type_error(format!( "cannot unpack non-iterable {} object", value.class().name() @@ -3013,8 +3017,12 @@ impl ExecutingFrame<'_> { fn unpack_sequence(&mut self, size: u32, vm: &VirtualMachine) -> FrameResult { let value = self.pop_value(); + let not_iterable = value.class().slots.iter.load().is_none() + && value + .get_class_attr(vm.ctx.intern_str("__getitem__")) + .is_none(); let elements: Vec<_> = value.try_to_value(vm).map_err(|e| { - if e.class().is(vm.ctx.exceptions.type_error) { + if not_iterable && e.class().is(vm.ctx.exceptions.type_error) { vm.new_type_error(format!( "cannot unpack non-iterable {} object", value.class().name()