diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index c39c11892f9..be7807ec411 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -4423,11 +4423,12 @@ impl Compiler { } // Build kwargs if needed - let has_kwargs = arguments.is_some_and(|args| !args.keywords.is_empty()); - if has_kwargs { + if arguments.is_some_and(|args| !args.keywords.is_empty()) { self.compile_keywords(&arguments.unwrap().keywords)?; + } else { + emit!(self, Instruction::PushNull); } - emit!(self, Instruction::CallFunctionEx { has_kwargs }); + emit!(self, Instruction::CallFunctionEx); } else { // Simple case: no starred bases, no **kwargs // Compile bases normally @@ -6961,11 +6962,12 @@ impl Compiler { } // Create an optional map with kw-args: - let has_kwargs = !arguments.keywords.is_empty(); - if has_kwargs { + if !arguments.keywords.is_empty() { self.compile_keywords(&arguments.keywords)?; + } else { + emit!(self, Instruction::PushNull); } - emit!(self, Instruction::CallFunctionEx { has_kwargs }); + emit!(self, Instruction::CallFunctionEx); } else if !arguments.keywords.is_empty() { // No **kwargs in this branch (has_double_star is false), // so all keywords have arg.is_some() diff --git a/crates/compiler-core/src/bytecode/instruction.rs b/crates/compiler-core/src/bytecode/instruction.rs index fe448f4574b..4faf67d273b 100644 --- a/crates/compiler-core/src/bytecode/instruction.rs +++ b/crates/compiler-core/src/bytecode/instruction.rs @@ -95,9 +95,7 @@ pub enum Instruction { Call { nargs: Arg, } = 53, - CallFunctionEx { - has_kwargs: Arg, - } = 54, + CallFunctionEx = 54, CallIntrinsic1 { func: Arg, } = 55, @@ -575,8 +573,8 @@ impl InstructionMetadata for Instruction { Self::Call { nargs } => -(nargs.get(arg) as i32) - 2 + 1, // CallKw: pops kw_names_tuple + nargs + self_or_null + callable, pushes result Self::CallKw { nargs } => -1 - (nargs.get(arg) as i32) - 2 + 1, - // CallFunctionEx: pops kwargs(if any) + args_tuple + self_or_null + callable, pushes result - Self::CallFunctionEx { has_kwargs } => -1 - (has_kwargs.get(arg) as i32) - 2 + 1, + // CallFunctionEx: always pops kwargs_or_null + args_tuple + self_or_null + callable, pushes result + Self::CallFunctionEx => -4 + 1, Self::CheckEgMatch => 0, // pops 2 (exc, type), pushes 2 (rest, match) Self::ConvertValue { .. } => 0, Self::FormatSimple => 0, @@ -880,7 +878,7 @@ impl InstructionMetadata for Instruction { Self::BuildTupleFromIter => w!(BUILD_TUPLE_FROM_ITER), Self::BuildTupleFromTuples { size } => w!(BUILD_TUPLE_FROM_TUPLES, size), Self::Call { nargs } => w!(CALL, nargs), - Self::CallFunctionEx { has_kwargs } => w!(CALL_FUNCTION_EX, has_kwargs), + Self::CallFunctionEx => w!(CALL_FUNCTION_EX), Self::CallKw { nargs } => w!(CALL_KW, nargs), Self::CallIntrinsic1 { func } => w!(CALL_INTRINSIC_1, ?func), Self::CallIntrinsic2 { func } => w!(CALL_INTRINSIC_2, ?func), diff --git a/crates/vm/src/frame.rs b/crates/vm/src/frame.rs index d83be841275..ee6142b8030 100644 --- a/crates/vm/src/frame.rs +++ b/crates/vm/src/frame.rs @@ -763,9 +763,9 @@ impl ExecutingFrame<'_> { let args = self.collect_keyword_args(nargs.get(arg)); self.execute_call(args, vm) } - Instruction::CallFunctionEx { has_kwargs } => { - // Stack: [callable, self_or_null, args_tuple, (kwargs_dict)?] - let args = self.collect_ex_args(vm, has_kwargs.get(arg))?; + Instruction::CallFunctionEx => { + // Stack: [callable, self_or_null, args_tuple, kwargs_or_null] + let args = self.collect_ex_args(vm)?; self.execute_call(args, vm) } Instruction::CallIntrinsic1 { func } => { @@ -2128,9 +2128,9 @@ impl ExecutingFrame<'_> { FuncArgs::with_kwargs_names(args, kwarg_names) } - fn collect_ex_args(&mut self, vm: &VirtualMachine, has_kwargs: bool) -> PyResult { - let kwargs = if has_kwargs { - let kw_obj = self.pop_value(); + fn collect_ex_args(&mut self, vm: &VirtualMachine) -> PyResult { + let kwargs_or_null = self.pop_value_opt(); + let kwargs = if let Some(kw_obj) = kwargs_or_null { let mut kwargs = IndexMap::new(); // Use keys() method for all mapping objects to preserve order