X Tutup
Skip to content

Add vectorcall (PEP 590) dispatch for function calls#7329

Merged
youknowone merged 4 commits intoRustPython:mainfrom
youknowone:skip-class
Mar 3, 2026
Merged

Add vectorcall (PEP 590) dispatch for function calls#7329
youknowone merged 4 commits intoRustPython:mainfrom
youknowone:skip-class

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Mar 3, 2026

Add VectorCallFunc slot to PyTypeSlots and vectorcall dispatch path in the interpreter loop for Call and CallKw instructions.

Implement vectorcall for PyFunction (with fast path for simple positional-only calls that fills fastlocals directly), PyBoundMethod (avoids prepend_arg O(n) shift), and PyNativeFunction.

Add FuncArgs::from_vectorcall helper for fallback conversion. Vectorcall slot is inherited with call slot and cleared when call is overridden in Python subclasses.

Summary by CodeRabbit

Release Notes

  • Refactor
    • Implemented vectorcall (PEP 590) support throughout the VM for more efficient function call handling, reducing memory allocations and improving function call performance across builtin functions, regular functions, bound methods, and frame execution.

Add VectorCallFunc slot to PyTypeSlots and vectorcall dispatch path
in the interpreter loop for Call and CallKw instructions.

Implement vectorcall for PyFunction (with fast path for simple
positional-only calls that fills fastlocals directly), PyBoundMethod
(avoids prepend_arg O(n) shift), and PyNativeFunction.

Add FuncArgs::from_vectorcall helper for fallback conversion.
Vectorcall slot is inherited with call slot and cleared when
__call__ is overridden in Python subclasses.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

📝 Walkthrough

Walkthrough

This PR implements PEP 590 vectorcall support across the VM to enable fast-path calling for builtin functions, Python functions, and bound methods. It adds vectorcall slots to the type system, modifies frame dispatch to use vectorcall when available with fallback to existing paths, and enables owned-argument optimization to reduce allocations.

Changes

Cohort / File(s) Summary
Type System & Slot Infrastructure
crates/vm/src/types/slot.rs, crates/vm/src/types/slot_defs.rs
Introduces VectorCallFunc type alias and vectorcall field in PyTypeSlots. Extends slot inheritance and copying logic to propagate vectorcall alongside TpCall in MRO and base-slot paths.
Function Argument Handling
crates/vm/src/function/argument.rs
Adds FuncArgs::from_vectorcall constructor to build FuncArgs from vectorcall-style arguments (positional + kwnames pairs).
Builtin Function Vectorcall
crates/vm/src/builtins/builtin_func.rs
Implements vectorcall_native_function for builtin functions with self-handling logic, avoiding O(n) prepend; registers vectorcall on builtin_function_or_method_type slots.
Python Function & Bound Method Vectorcall
crates/vm/src/builtins/function.rs
Refactors invoke_exact_args to accept owned Vec<PyObjectRef>; adds vectorcall_function for Python functions and vectorcall_bound_method for bound methods; registers both in type initialization.
Callable Protocol
crates/vm/src/protocol/callable.rs
Extends PyCallable with vectorcall field and new invoke_vectorcall method; dispatches through vectorcall slot when available with fallback to FuncArgs-based invocation; supports tracing integration.
Frame Execution & Dispatch
crates/vm/src/frame.rs
Adds execute_call_vectorcall and execute_call_kw_vectorcall helpers to dispatch through vectorcall slots in Call and CallKw instruction handling; updates specialized call paths to use vectorcall when available.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ShaharNaveh

Poem

🐰 A vectorcall swift,
No clones, no prepends in sight,
Fast args take flight!
Slots aligned with grace,
PEP 590 sets the pace. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add vectorcall (PEP 590) dispatch for function calls' directly and accurately reflects the main objective of this pull request, which is to implement vectorcall support across the interpreter.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

Code has been automatically formatted

The code in this PR has been formatted using:

  • cargo fmt --all
    Please pull the latest changes before pushing again:
git pull origin skip-class

…ecialized paths

- invoke_exact_args takes Vec by value and uses drain() to move args
  into fastlocals instead of cloning (eliminates refcount overhead)
- CallPyGeneral and CallBoundMethodGeneral now call vectorcall_function
  directly instead of going through FuncArgs + prepend_arg + invoke
- CallKwPy and CallKwBoundMethod use vectorcall_function with kwnames
- vectorcall_bound_method uses insert(0) on existing Vec instead of
  allocating a second Vec
github-actions bot and others added 2 commits March 3, 2026 12:35
When needs_self was true and kwargs were present, pos_args only
contained positional args (self + original positionals) but
from_vectorcall expected kwarg values to follow in the slice.

Build the full args array (self + all original args including kwarg
values) before passing to from_vectorcall.
@ShaharNaveh
Copy link
Contributor

Super excited about this one ngl

@youknowone youknowone marked this pull request as ready for review March 3, 2026 14:33
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
crates/vm/src/frame.rs (1)

5695-5816: Consider extracting shared vectorcall argument marshalling into one helper.

execute_call_vectorcall and execute_call_kw_vectorcall duplicate stack-index math and vector construction. A small internal helper for shared stack slicing/consumption would reduce drift risk and simplify future fixes.

As per coding guidelines "When branches differ only in a value but share common logic, extract the differing value first, then call the common logic once to avoid duplicate code".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/frame.rs` around lines 5695 - 5816, Duplicate stack-index math
and args construction in execute_call_vectorcall and execute_call_kw_vectorcall;
extract a small helper to centralize that logic. Create an internal method
(e.g., collect_vectorcall_args) used by both execute_call_vectorcall and
execute_call_kw_vectorcall that: computes
callable_idx/self_or_null_idx/args_start from nargs and optional kw_count,
consumes the stack values (including self_or_null if present and kw values) into
a Vec<PyObjectRef>, returns (args_vec, effective_nargs, optional_kwnames) and
leaves the stack truncated at callable_idx; then call
callable_obj.vectorcall(args_vec, effective_nargs, kwnames_opt, vm) and
push_value(result). Reuse existing helpers like
collect_positional_args/pop_multiple/pop_value_opt where appropriate and update
both functions to call this new helper and remove the duplicated index math and
loop logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/vm/src/builtins/function.rs`:
- Around line 1280-1289: The vectorcall fast path currently eagerly allocates a
new dict for NEWLOCALS (via vm.ctx.new_dict() and ArgMapping::from_dict_exact)
before creating the Frame; change this to use a None-local mapping so Scope::new
receives None when code.flags contains bytecode::CodeFlags::NEWLOCALS, matching
invoke_with_locals/invoke_exact_args behavior and allowing Frame initialization
to use FrameLocals::lazy() (defer allocation). Update the locals binding used by
Frame::new (the locals variable) to be an Option that is None for NEWLOCALS
instead of constructing vm.ctx.new_dict().

In `@crates/vm/src/function/argument.rs`:
- Around line 146-151: In from_vectorcall validate the vectorcall layout before
any unchecked slicing: ensure nargs <= args.len() and if kwnames.is_some() then
nargs + kwnames.len() <= args.len(), returning a Python TypeError/ValueError
(Err) instead of panicking; replace the .expect() downcast on kwnames entries
with safe handling (use .get() or iterate with .zip and match on PyString
downcast) and return an Err TypeError when a keyword name is not a string;
update construction of pos_args and kwargs to use checked slices (or .get()
results) so no unchecked indexing or .expect() can panic the VM.

---

Nitpick comments:
In `@crates/vm/src/frame.rs`:
- Around line 5695-5816: Duplicate stack-index math and args construction in
execute_call_vectorcall and execute_call_kw_vectorcall; extract a small helper
to centralize that logic. Create an internal method (e.g.,
collect_vectorcall_args) used by both execute_call_vectorcall and
execute_call_kw_vectorcall that: computes
callable_idx/self_or_null_idx/args_start from nargs and optional kw_count,
consumes the stack values (including self_or_null if present and kw values) into
a Vec<PyObjectRef>, returns (args_vec, effective_nargs, optional_kwnames) and
leaves the stack truncated at callable_idx; then call
callable_obj.vectorcall(args_vec, effective_nargs, kwnames_opt, vm) and
push_value(result). Reuse existing helpers like
collect_positional_args/pop_multiple/pop_value_opt where appropriate and update
both functions to call this new helper and remove the duplicated index math and
loop logic.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 257b0c0 and 4d8cf97.

📒 Files selected for processing (7)
  • crates/vm/src/builtins/builtin_func.rs
  • crates/vm/src/builtins/function.rs
  • crates/vm/src/frame.rs
  • crates/vm/src/function/argument.rs
  • crates/vm/src/protocol/callable.rs
  • crates/vm/src/types/slot.rs
  • crates/vm/src/types/slot_defs.rs

Comment on lines +1280 to +1289
let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
ArgMapping::from_dict_exact(vm.ctx.new_dict())
} else {
ArgMapping::from_dict_exact(zelf.globals.clone())
};

let frame = Frame::new(
code.to_owned(),
Scope::new(Some(locals), zelf.globals.clone()),
zelf.builtins.clone(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's examine the code at the specified lines and nearby functions
head -n 1300 crates/vm/src/builtins/function.rs | tail -n 100

Repository: RustPython/RustPython

Length of output: 3133


🏁 Script executed:

#!/bin/bash
# Search for Scope::new() calls to understand the pattern
rg -n "Scope::new\(" crates/vm/src/builtins/function.rs -B 2 -A 2

Repository: RustPython/RustPython

Length of output: 770


🏁 Script executed:

#!/bin/bash
# Examine invoke_with_locals around line 557
sed -n '540,580p' crates/vm/src/builtins/function.rs

Repository: RustPython/RustPython

Length of output: 1528


🏁 Script executed:

#!/bin/bash
# Examine invoke_exact_args around line 666
sed -n '650,690p' crates/vm/src/builtins/function.rs

Repository: RustPython/RustPython

Length of output: 1686


🏁 Script executed:

#!/bin/bash
# Find the Scope struct definition to understand Scope::new() signature
rg -n "impl.*Scope|fn new" crates/vm/src/ -A 3 | grep -A 3 "Scope\|fn new"

Repository: RustPython/RustPython

Length of output: 50377


🏁 Script executed:

#!/bin/bash
# Find and examine the Scope struct definition
sed -n '1,100p' crates/vm/src/scope.rs

Repository: RustPython/RustPython

Length of output: 3555


🏁 Script executed:

#!/bin/bash
# Look for how Scope.locals is used in the frame execution
rg -n "scope\.locals|frame\.scope\.locals" crates/vm/src/ -A 2 -B 2 | head -100

Repository: RustPython/RustPython

Length of output: 497


🏁 Script executed:

#!/bin/bash
# Compare how NEWLOCALS is handled in all three paths more carefully
echo "=== vectorcall_function (lines 1270-1305) ===" && sed -n '1270,1305p' crates/vm/src/builtins/function.rs
echo ""
echo "=== invoke_with_locals (lines 540-575) ===" && sed -n '540,575p' crates/vm/src/builtins/function.rs
echo ""
echo "=== invoke_exact_args (lines 650-685) ===" && sed -n '650,685p' crates/vm/src/builtins/function.rs

Repository: RustPython/RustPython

Length of output: 4488


🏁 Script executed:

#!/bin/bash
# Examine FrameLocals enum and its lazy() method
rg -n "enum FrameLocals|fn lazy|fn with_locals" crates/vm/src/frame.rs -A 5 | head -80

Repository: RustPython/RustPython

Length of output: 362


🏁 Script executed:

#!/bin/bash
# Get more detail on FrameLocals implementation
sed -n '1,150p' crates/vm/src/frame.rs | grep -A 30 "enum FrameLocals"

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Find FrameLocals struct definition
sed -n '100,200p' crates/vm/src/frame.rs

Repository: RustPython/RustPython

Length of output: 3120


🏁 Script executed:

#!/bin/bash
# Look at how FrameLocals::lazy() is used in frame execution
rg -n "FrameLocals::lazy" crates/vm/src/frame.rs -B 5 -A 10

Repository: RustPython/RustPython

Length of output: 756


🏁 Script executed:

#!/bin/bash
# Check if there's lazy initialization for NEWLOCALS
rg -n "NEWLOCALS" crates/vm/src/frame.rs -B 3 -A 3

Repository: RustPython/RustPython

Length of output: 1218


Use lazy allocation for NEWLOCALS scope in vectorcall fast path for consistency.

The vectorcall fast path (line 1282) eagerly allocates a new dict for NEWLOCALS via vm.ctx.new_dict(), while both invoke_with_locals (line 554) and invoke_exact_args (line 666) pass None to enable lazy allocation. When Scope::new() receives None with NEWLOCALS, Frame initialization uses FrameLocals::lazy() (frame.rs:356), deferring dict creation until first access. This approach is more efficient since most function frames never access the locals dict, and aligns with Rust memory management best practices.

🔧 Proposed fix
-        let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
-            ArgMapping::from_dict_exact(vm.ctx.new_dict())
-        } else {
-            ArgMapping::from_dict_exact(zelf.globals.clone())
-        };
+        let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
+            None
+        } else {
+            Some(ArgMapping::from_dict_exact(zelf.globals.clone()))
+        };

         let frame = Frame::new(
             code.to_owned(),
-            Scope::new(Some(locals), zelf.globals.clone()),
+            Scope::new(locals, zelf.globals.clone()),
             zelf.builtins.clone(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/builtins/function.rs` around lines 1280 - 1289, The vectorcall
fast path currently eagerly allocates a new dict for NEWLOCALS (via
vm.ctx.new_dict() and ArgMapping::from_dict_exact) before creating the Frame;
change this to use a None-local mapping so Scope::new receives None when
code.flags contains bytecode::CodeFlags::NEWLOCALS, matching
invoke_with_locals/invoke_exact_args behavior and allowing Frame initialization
to use FrameLocals::lazy() (defer allocation). Update the locals binding used by
Frame::new (the locals variable) to be an Option that is None for NEWLOCALS
instead of constructing vm.ctx.new_dict().

Comment on lines +146 to +151
let pos_args = args[..nargs].to_vec();
let kwargs = if let Some(names) = kwnames {
names
.iter()
.zip(&args[nargs..nargs + names.len()])
.map(|(name, val)| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

❓ Verification inconclusive

Script executed:

#!/bin/bash
# First, verify the file exists and check the specific lines mentioned
wc -l crates/vm/src/function/argument.rs

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

626 crates/vm/src/function/argument.rs

Script executed:

#!/bin/bash
# Read the function to see the current implementation
sed -n '130,170p' crates/vm/src/function/argument.rs | cat -n

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

     1	        let kwargs = kwarg_names.zip_eq(args).collect::<IndexMap<_, _>>();
     2	
     3	        Self {
     4	            args: pos_args,
     5	            kwargs,
     6	        }
     7	    }
     8	
     9	    /// Create FuncArgs from a vectorcall-style argument slice (PEP 590).
    10	    /// `args[..nargs]` are positional, and if `kwnames` is provided,
    11	    /// the last `kwnames.len()` entries in `args[nargs..]` are keyword values.
    12	    pub fn from_vectorcall(
    13	        args: &[PyObjectRef],
    14	        nargs: usize,
    15	        kwnames: Option<&[PyObjectRef]>,
    16	    ) -> Self {
    17	        let pos_args = args[..nargs].to_vec();
    18	        let kwargs = if let Some(names) = kwnames {
    19	            names
    20	                .iter()
    21	                .zip(&args[nargs..nargs + names.len()])
    22	                .map(|(name, val)| {
    23	                    let key = name
    24	                        .downcast_ref::<crate::builtins::PyUtf8Str>()
    25	                        .expect("kwnames must be strings")
    26	                        .as_str()
    27	                        .to_owned();
    28	                    (key, val.clone())
    29	                })
    30	                .collect()
    31	        } else {
    32	            IndexMap::new()
    33	        };
    34	        Self {
    35	            args: pos_args,
    36	            kwargs,
    37	        }
    38	    }
    39	
    40	    pub fn is_empty(&self) -> bool {
    41	        self.args.is_empty() && self.kwargs.is_empty()

Script executed:

#!/bin/bash
# Search for the from_vectorcall function definition
rg -n 'from_vectorcall' crates/vm/src/function/argument.rs

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

141:    pub fn from_vectorcall(

Script executed:

#!/bin/bash
# Search for all call sites of from_vectorcall
rg -n 'from_vectorcall' --type rs

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 2

stderr:

unrecognized file type: rs

Script executed:

#!/bin/bash
# Also check if there are tests for from_vectorcall
rg -n 'from_vectorcall|vectorcall' crates/vm/src/ -l | head -20

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

crates/vm/src/types/slot_defs.rs
crates/vm/src/types/slot.rs
crates/vm/src/protocol/callable.rs
crates/vm/src/frame.rs
crates/vm/src/function/argument.rs
crates/vm/src/builtins/function.rs
crates/vm/src/builtins/descriptor.rs
crates/vm/src/builtins/builtin_func.rs

Script executed:

#!/bin/bash
# Find all call sites of from_vectorcall with context
rg -n -B2 -A2 'from_vectorcall' crates/vm/src/types/slot_defs.rs crates/vm/src/types/slot.rs crates/vm/src/protocol/callable.rs crates/vm/src/frame.rs crates/vm/src/builtins/function.rs crates/vm/src/builtins/descriptor.rs crates/vm/src/builtins/builtin_func.rs

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

crates/vm/src/builtins/builtin_func.rs-247-        all_args.push(self_obj);
crates/vm/src/builtins/builtin_func.rs-248-        all_args.extend(args);
crates/vm/src/builtins/builtin_func.rs:249:        FuncArgs::from_vectorcall(&all_args, nargs + 1, kwnames)
crates/vm/src/builtins/builtin_func.rs-250-    } else {
crates/vm/src/builtins/builtin_func.rs:251:        FuncArgs::from_vectorcall(&args, nargs, kwnames)
crates/vm/src/builtins/builtin_func.rs-252-    };
crates/vm/src/builtins/builtin_func.rs-253-
--
crates/vm/src/builtins/function.rs-1314-    // SLOW PATH: construct FuncArgs from owned Vec and delegate to invoke()
crates/vm/src/builtins/function.rs-1315-    let func_args = if has_kwargs {
crates/vm/src/builtins/function.rs:1316:        FuncArgs::from_vectorcall(&args, nargs, kwnames)
crates/vm/src/builtins/function.rs-1317-    } else {
crates/vm/src/builtins/function.rs-1318-        args.truncate(nargs);
--
crates/vm/src/protocol/callable.rs-131-                args: args[..nargs].to_vec(),
crates/vm/src/protocol/callable.rs-132-                kwargs: if let Some(kwn) = kwnames {
crates/vm/src/protocol/callable.rs:133:                    FuncArgs::from_vectorcall(&args, nargs, Some(kwn)).kwargs
crates/vm/src/protocol/callable.rs-134-                } else {
crates/vm/src/protocol/callable.rs-135-                    indexmap::IndexMap::new()

Script executed:

#!/bin/bash
# Check how vectorcall is invoked - look for where nargs and kwnames come from
rg -n 'vectorcall|nargs|kwnames' crates/vm/src/protocol/callable.rs | head -30

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

37:    /// Vectorcall: call with owned positional args + optional kwnames.
38:    /// Falls back to FuncArgs-based call if no vectorcall slot.
40:    pub fn vectorcall(
43:        nargs: usize,
44:        kwnames: Option<&[PyObjectRef]>,
52:        callable.invoke_vectorcall(args, nargs, kwnames, vm)
60:    pub vectorcall: Option<VectorCallFunc>,
67:        let vectorcall = slots.vectorcall.load();
71:            vectorcall,
100:    /// Vectorcall dispatch: use vectorcall slot if available, else fall back to FuncArgs.
102:    pub fn invoke_vectorcall(
105:        nargs: usize,
106:        kwnames: Option<&[PyObjectRef]>,
109:        if let Some(vc) = self.vectorcall {
111:                return vc(self.obj, args, nargs, kwnames, vm);
116:                vc(self.obj, args, nargs, kwnames, vm)
120:                let result = vc(self.obj, args, nargs, kwnames, vm);
131:                args: args[..nargs].to_vec(),
132:                kwargs: if let Some(kwn) = kwnames {
133:                    FuncArgs::from_vectorcall(&args, nargs, Some(kwn)).kwargs

Script executed:

#!/bin/bash
# Let's look at the protocol/callable.rs file to understand the vectorcall protocol
head -100 crates/vm/src/protocol/callable.rs | tail -50

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

        };
        callable.invoke_vectorcall(args, nargs, kwnames, vm)
    }
}

#[derive(Debug)]
pub struct PyCallable<'a> {
    pub obj: &'a PyObject,
    pub call: GenericMethod,
    pub vectorcall: Option<VectorCallFunc>,
}

impl<'a> PyCallable<'a> {
    pub fn new(obj: &'a PyObject) -> Option<Self> {
        let slots = &obj.class().slots;
        let call = slots.call.load()?;
        let vectorcall = slots.vectorcall.load();
        Some(PyCallable {
            obj,
            call,
            vectorcall,
        })
    }

    pub fn invoke(&self, args: impl IntoFuncArgs, vm: &VirtualMachine) -> PyResult {
        let args = args.into_args(vm);
        if !vm.use_tracing.get() {
            return (self.call)(self.obj, args, vm);
        }
        // Python functions get 'call'/'return' events from with_frame().
        // Bound methods delegate to the inner callable, which fires its own events.
        // All other callables (built-in functions, etc.) get 'c_call'/'c_return'/'c_exception'.
        let is_python_callable = self.obj.downcast_ref::<PyFunction>().is_some()
            || self.obj.downcast_ref::<PyBoundMethod>().is_some();
        if is_python_callable {
            (self.call)(self.obj, args, vm)
        } else {
            let callable = self.obj.to_owned();
            vm.trace_event(TraceEvent::CCall, Some(callable.clone()))?;
            let result = (self.call)(self.obj, args, vm);
            if result.is_ok() {
                vm.trace_event(TraceEvent::CReturn, Some(callable))?;
            } else {
                let _ = vm.trace_event(TraceEvent::CException, Some(callable));
            }
            result
        }
    }

    /// Vectorcall dispatch: use vectorcall slot if available, else fall back to FuncArgs.

Script executed:

#!/bin/bash
# Look at how from_vectorcall is actually being used
rg -B5 -A5 'from_vectorcall' crates/vm/src/builtins/function.rs | head -40

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

        return vm.run_frame(frame);
    }

    // SLOW PATH: construct FuncArgs from owned Vec and delegate to invoke()
    let func_args = if has_kwargs {
        FuncArgs::from_vectorcall(&args, nargs, kwnames)
    } else {
        args.truncate(nargs);
        FuncArgs::from(args)
    };
    zelf.invoke(func_args, vm)

Script executed:

#!/bin/bash
# Check builtin_func.rs context around the calls
sed -n '235,260p' crates/vm/src/builtins/builtin_func.rs | cat -n

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

     1	) -> PyResult {
     2	    let zelf: &Py<PyNativeFunction> = zelf_obj.downcast_ref().unwrap();
     3	
     4	    // Build FuncArgs with self already at position 0 (no insert(0) needed)
     5	    let needs_self = zelf
     6	        .zelf
     7	        .as_ref()
     8	        .is_some_and(|_| !zelf.value.flags.contains(PyMethodFlags::STATIC));
     9	
    10	    let func_args = if needs_self {
    11	        let self_obj = zelf.zelf.as_ref().unwrap().clone();
    12	        let mut all_args = Vec::with_capacity(args.len() + 1);
    13	        all_args.push(self_obj);
    14	        all_args.extend(args);
    15	        FuncArgs::from_vectorcall(&all_args, nargs + 1, kwnames)
    16	    } else {
    17	        FuncArgs::from_vectorcall(&args, nargs, kwnames)
    18	    };
    19	
    20	    (zelf.value.func)(vm, func_args)
    21	}
    22	
    23	pub fn init(context: &'static Context) {
    24	    PyNativeFunction::extend_class(context, context.types.builtin_function_or_method_type);
    25	    context
    26	        .types

Script executed:

#!/bin/bash
# Check function.rs context around the calls
sed -n '1305,1325p' crates/vm/src/builtins/function.rs | cat -n

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

     1	            for (cell_idx, arg_idx) in cell2arg.iter().enumerate().filter(|(_, i)| **i != -1) {
     2	                let x = fastlocals[*arg_idx as usize].take();
     3	                frame.set_cell_contents(cell_idx, x);
     4	            }
     5	        }
     6	
     7	        return vm.run_frame(frame);
     8	    }
     9	
    10	    // SLOW PATH: construct FuncArgs from owned Vec and delegate to invoke()
    11	    let func_args = if has_kwargs {
    12	        FuncArgs::from_vectorcall(&args, nargs, kwnames)
    13	    } else {
    14	        args.truncate(nargs);
    15	        FuncArgs::from(args)
    16	    };
    17	    zelf.invoke(func_args, vm)
    18	}
    19	
    20	/// Vectorcall implementation for PyBoundMethod (PEP 590).
    21	fn vectorcall_bound_method(

Script executed:

#!/bin/bash
# Look for where vectorcall invocations happen - check how nargs is passed in
rg -B10 'invoke_vectorcall' crates/vm/src/ | head -60

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

crates/vm/src/protocol/callable.rs-        args: Vec<PyObjectRef>,
crates/vm/src/protocol/callable.rs-        nargs: usize,
crates/vm/src/protocol/callable.rs-        kwnames: Option<&[PyObjectRef]>,
crates/vm/src/protocol/callable.rs-        vm: &VirtualMachine,
crates/vm/src/protocol/callable.rs-    ) -> PyResult {
crates/vm/src/protocol/callable.rs-        let Some(callable) = self.to_callable() else {
crates/vm/src/protocol/callable.rs-            return Err(
crates/vm/src/protocol/callable.rs-                vm.new_type_error(format!("'{}' object is not callable", self.class().name()))
crates/vm/src/protocol/callable.rs-            );
crates/vm/src/protocol/callable.rs-        };
crates/vm/src/protocol/callable.rs:        callable.invoke_vectorcall(args, nargs, kwnames, vm)
--
crates/vm/src/protocol/callable.rs-                vm.trace_event(TraceEvent::CReturn, Some(callable))?;
crates/vm/src/protocol/callable.rs-            } else {
crates/vm/src/protocol/callable.rs-                let _ = vm.trace_event(TraceEvent::CException, Some(callable));
crates/vm/src/protocol/callable.rs-            }
crates/vm/src/protocol/callable.rs-            result
crates/vm/src/protocol/callable.rs-        }
crates/vm/src/protocol/callable.rs-    }
crates/vm/src/protocol/callable.rs-
crates/vm/src/protocol/callable.rs-    /// Vectorcall dispatch: use vectorcall slot if available, else fall back to FuncArgs.
crates/vm/src/protocol/callable.rs-    #[inline]
crates/vm/src/protocol/callable.rs:    pub fn invoke_vectorcall(

Script executed:

#!/bin/bash
# Check if vectorcall is coming from external C API or internal calls
rg -n 'VectorCallFunc|vectorcall_slot' crates/vm/src/ | head -20

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

crates/vm/src/types/slot.rs:144:    pub vectorcall: AtomicCell<Option<VectorCallFunc>>,
crates/vm/src/types/slot.rs:274:pub(crate) type VectorCallFunc = fn(
crates/vm/src/protocol/callable.rs:4:    types::{GenericMethod, VectorCallFunc},
crates/vm/src/protocol/callable.rs:60:    pub vectorcall: Option<VectorCallFunc>,

Script executed:

#!/bin/bash
# Let's check the actual implementation in the current code (checking if line 17 is really unchecked)
sed -n '141,180p' crates/vm/src/function/argument.rs | cat -n

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

     1	    pub fn from_vectorcall(
     2	        args: &[PyObjectRef],
     3	        nargs: usize,
     4	        kwnames: Option<&[PyObjectRef]>,
     5	    ) -> Self {
     6	        let pos_args = args[..nargs].to_vec();
     7	        let kwargs = if let Some(names) = kwnames {
     8	            names
     9	                .iter()
    10	                .zip(&args[nargs..nargs + names.len()])
    11	                .map(|(name, val)| {
    12	                    let key = name
    13	                        .downcast_ref::<crate::builtins::PyUtf8Str>()
    14	                        .expect("kwnames must be strings")
    15	                        .as_str()
    16	                        .to_owned();
    17	                    (key, val.clone())
    18	                })
    19	                .collect()
    20	        } else {
    21	            IndexMap::new()
    22	        };
    23	        Self {
    24	            args: pos_args,
    25	            kwargs,
    26	        }
    27	    }
    28	
    29	    pub fn is_empty(&self) -> bool {
    30	        self.args.is_empty() && self.kwargs.is_empty()
    31	    }
    32	
    33	    pub fn prepend_arg(&mut self, item: PyObjectRef) {
    34	        self.args.reserve_exact(1);
    35	        self.args.insert(0, item)
    36	    }
    37	
    38	    pub fn shift(&mut self) -> PyObjectRef {
    39	        self.args.remove(0)
    40	    }

Script executed:

#!/bin/bash
# Check if there are any validation guards before from_vectorcall is called
# Look for nargs validation in builtin_func.rs around the call site
sed -n '220,260p' crates/vm/src/builtins/builtin_func.rs | cat -n

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

     1	            "builtin method of {:?} with {:?}",
     2	            &*self.class.name(),
     3	            &self.func
     4	        )
     5	    }
     6	}
     7	
     8	/// Vectorcall for builtin functions (PEP 590).
     9	/// Avoids `prepend_arg` O(n) shift by building args with self at front.
    10	fn vectorcall_native_function(
    11	    zelf_obj: &PyObject,
    12	    args: Vec<PyObjectRef>,
    13	    nargs: usize,
    14	    kwnames: Option<&[PyObjectRef]>,
    15	    vm: &VirtualMachine,
    16	) -> PyResult {
    17	    let zelf: &Py<PyNativeFunction> = zelf_obj.downcast_ref().unwrap();
    18	
    19	    // Build FuncArgs with self already at position 0 (no insert(0) needed)
    20	    let needs_self = zelf
    21	        .zelf
    22	        .as_ref()
    23	        .is_some_and(|_| !zelf.value.flags.contains(PyMethodFlags::STATIC));
    24	
    25	    let func_args = if needs_self {
    26	        let self_obj = zelf.zelf.as_ref().unwrap().clone();
    27	        let mut all_args = Vec::with_capacity(args.len() + 1);
    28	        all_args.push(self_obj);
    29	        all_args.extend(args);
    30	        FuncArgs::from_vectorcall(&all_args, nargs + 1, kwnames)
    31	    } else {
    32	        FuncArgs::from_vectorcall(&args, nargs, kwnames)
    33	    };
    34	
    35	    (zelf.value.func)(vm, func_args)
    36	}
    37	
    38	pub fn init(context: &'static Context) {
    39	    PyNativeFunction::extend_class(context, context.types.builtin_function_or_method_type);
    40	    context
    41	        .types

Script executed:

#!/bin/bash
# Check function.rs context - look for the full function where from_vectorcall is called
sed -n '1280,1330p' crates/vm/src/builtins/function.rs | cat -n

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

     1	        let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
     2	            ArgMapping::from_dict_exact(vm.ctx.new_dict())
     3	        } else {
     4	            ArgMapping::from_dict_exact(zelf.globals.clone())
     5	        };
     6	
     7	        let frame = Frame::new(
     8	            code.to_owned(),
     9	            Scope::new(Some(locals), zelf.globals.clone()),
    10	            zelf.builtins.clone(),
    11	            zelf.closure.as_ref().map_or(&[], |c| c.as_slice()),
    12	            Some(zelf.to_owned().into()),
    13	            vm,
    14	        )
    15	        .into_ref(&vm.ctx);
    16	
    17	        {
    18	            let fastlocals = unsafe { frame.fastlocals.borrow_mut() };
    19	            for (slot, arg) in fastlocals.iter_mut().zip(args.drain(..nargs)) {
    20	                *slot = Some(arg);
    21	            }
    22	        }
    23	
    24	        if let Some(cell2arg) = code.cell2arg.as_deref() {
    25	            let fastlocals = unsafe { frame.fastlocals.borrow_mut() };
    26	            for (cell_idx, arg_idx) in cell2arg.iter().enumerate().filter(|(_, i)| **i != -1) {
    27	                let x = fastlocals[*arg_idx as usize].take();
    28	                frame.set_cell_contents(cell_idx, x);
    29	            }
    30	        }
    31	
    32	        return vm.run_frame(frame);
    33	    }
    34	
    35	    // SLOW PATH: construct FuncArgs from owned Vec and delegate to invoke()
    36	    let func_args = if has_kwargs {
    37	        FuncArgs::from_vectorcall(&args, nargs, kwnames)
    38	    } else {
    39	        args.truncate(nargs);
    40	        FuncArgs::from(args)
    41	    };
    42	    zelf.invoke(func_args, vm)
    43	}
    44	
    45	/// Vectorcall implementation for PyBoundMethod (PEP 590).
    46	fn vectorcall_bound_method(
    47	    zelf_obj: &PyObject,
    48	    mut args: Vec<PyObjectRef>,
    49	    nargs: usize,
    50	    kwnames: Option<&[PyObjectRef]>,
    51	    vm: &VirtualMachine,

Script executed:

#!/bin/bash
# Check if there's any comment or documentation about vectorcall protocol requirements
rg -n 'vectorcall|PEP 590' crates/vm/src/function/argument.rs -B2 -A2

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

136-    }
137-
138:    /// Create FuncArgs from a vectorcall-style argument slice (PEP 590).
139-    /// `args[..nargs]` are positional, and if `kwnames` is provided,
140-    /// the last `kwnames.len()` entries in `args[nargs..]` are keyword values.
141:    pub fn from_vectorcall(
142-        args: &[PyObjectRef],
143-        nargs: usize,

Script executed:

#!/bin/bash
# Look for any tests of from_vectorcall with edge cases
rg -n 'from_vectorcall' crates/vm/src/function/argument.rs -A20 | tail -40

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

141:    pub fn from_vectorcall(
142-        args: &[PyObjectRef],
143-        nargs: usize,
144-        kwnames: Option<&[PyObjectRef]>,
145-    ) -> Self {
146-        let pos_args = args[..nargs].to_vec();
147-        let kwargs = if let Some(names) = kwnames {
148-            names
149-                .iter()
150-                .zip(&args[nargs..nargs + names.len()])
151-                .map(|(name, val)| {
152-                    let key = name
153-                        .downcast_ref::<crate::builtins::PyUtf8Str>()
154-                        .expect("kwnames must be strings")
155-                        .as_str()
156-                        .to_owned();
157-                    (key, val.clone())
158-                })
159-                .collect()
160-        } else {
161-            IndexMap::new()

Script executed:

#!/bin/bash
# Check if downcast failures are handled elsewhere in similar code patterns
rg -n 'downcast_ref.*expect' crates/vm/src/function/argument.rs crates/vm/src/builtins/function.rs

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 1


Script executed:

#!/bin/bash
# Check the full context of where these vectorcall functions are registered and called
# Look for any place where nargs is validated before it reaches from_vectorcall
rg -n 'vectorcall_native_function|vectorcall_function' crates/vm/src/ -B5 | head -40

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

crates/vm/src/frame.rs-10-        PyStr, PyStrInterned, PyTemplate, PyTraceback, PyType, PyUtf8Str,
crates/vm/src/frame.rs-11-        asyncgenerator::PyAsyncGenWrappedValue,
crates/vm/src/frame.rs-12-        builtin_func::PyNativeFunction,
crates/vm/src/frame.rs-13-        descriptor::{MemberGetter, PyMemberDescriptor, PyMethodDescriptor},
crates/vm/src/frame.rs-14-        frame::stack_analysis,
crates/vm/src/frame.rs:15:        function::{PyCell, PyCellRef, PyFunction, vectorcall_function},
--
crates/vm/src/frame.rs-3945-                        (v, nargs_usize + 1)
crates/vm/src/frame.rs-3946-                    } else {
crates/vm/src/frame.rs-3947-                        (pos_args, nargs_usize)
crates/vm/src/frame.rs-3948-                    };
crates/vm/src/frame.rs-3949-                    let result =
crates/vm/src/frame.rs:3950:                        vectorcall_function(&callable, args_vec, effective_nargs, None, vm)?;
--
crates/vm/src/frame.rs-3972-                    let callable = self.pop_value();
crates/vm/src/frame.rs-3973-                    let mut args_vec = Vec::with_capacity(nargs_usize + 1);
crates/vm/src/frame.rs-3974-                    args_vec.push(self_val);
crates/vm/src/frame.rs-3975-                    args_vec.extend(pos_args);
crates/vm/src/frame.rs-3976-                    let result =
crates/vm/src/frame.rs:3977:                        vectorcall_function(&callable, args_vec, nargs_usize + 1, None, vm)?;
--
crates/vm/src/frame.rs-4252-                        (v, pos_count + 1)
crates/vm/src/frame.rs-4253-                    } else {
crates/vm/src/frame.rs-4254-                        (all_args, pos_count)
crates/vm/src/frame.rs-4255-                    };
crates/vm/src/frame.rs-4256-                    let kwnames = kwarg_names_tuple.as_slice();
crates/vm/src/frame.rs:4257:                    let result = vectorcall_function(
--
crates/vm/src/frame.rs-4292-                    let mut args_vec = Vec::with_capacity(nargs_usize + 1);
crates/vm/src/frame.rs-4293-                    args_vec.push(self_val);
crates/vm/src/frame.rs-4294-                    args_vec.extend(all_args);
crates/vm/src/frame.rs-4295-                    let kwnames = kwarg_names_tuple.as_slice();
crates/vm/src/frame.rs-4296-                    let result =
crates/vm/src/frame.rs:4297:                        vectorcall_function(&callable, args_vec, pos_count + 1, Some(kwnames), vm)?;
--
crates/vm/src/builtins/function.rs-1253-    }
crates/vm/src/builtins/function.rs-1254-}
crates/vm/src/builtins/function.rs-1255-
crates/vm/src/builtins/function.rs-1256-/// Vectorcall implementation for PyFunction (PEP 590).
crates/vm/src/builtins/function.rs-1257-/// Takes owned args to avoid cloning when filling fastlocals.

Script executed:

#!/bin/bash
# Check if there's any documentation about the vectorcall protocol requirements
rg -n 'PEP 590|vectorcall' crates/vm/src/protocol/callable.rs -B3 -A3 | head -50

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

35-    }
36-
37-    /// Vectorcall: call with owned positional args + optional kwnames.
38:    /// Falls back to FuncArgs-based call if no vectorcall slot.
39-    #[inline]
40:    pub fn vectorcall(
41-        &self,
42-        args: Vec<PyObjectRef>,
43-        nargs: usize,
--
49-                vm.new_type_error(format!("'{}' object is not callable", self.class().name()))
50-            );
51-        };
52:        callable.invoke_vectorcall(args, nargs, kwnames, vm)
53-    }
54-}
55-
--
57-pub struct PyCallable<'a> {
58-    pub obj: &'a PyObject,
59-    pub call: GenericMethod,
60:    pub vectorcall: Option<VectorCallFunc>,
61-}
62-
63-impl<'a> PyCallable<'a> {
64-    pub fn new(obj: &'a PyObject) -> Option<Self> {
65-        let slots = &obj.class().slots;
66-        let call = slots.call.load()?;
67:        let vectorcall = slots.vectorcall.load();
68-        Some(PyCallable {
69-            obj,
70-            call,
71:            vectorcall,
72-        })
73-    }
74-
--
97-        }
98-    }
99-
100:    /// Vectorcall dispatch: use vectorcall slot if available, else fall back to FuncArgs.
101-    #[inline]
102:    pub fn invoke_vectorcall(
103-        &self,
104-        args: Vec<PyObjectRef>,
105-        nargs: usize,
106-        kwnames: Option<&[PyObjectRef]>,
107-        vm: &VirtualMachine,
108-    ) -> PyResult {
109:        if let Some(vc) = self.vectorcall {

Script executed:

#!/build/bash
# Check if there's anywhere in the codebase that validates nargs bounds before calling vectorcall
rg -n 'nargs.*len\(args\)|nargs.*<=' crates/vm/src/ | head -20

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0


Script executed:

#!/bin/bash
# Check if the issue might be mitigated by how args are constructed
# Look at how vectorcall functions receive their args
rg -n 'pub.*fn.*vectorcall' crates/vm/src/ -A3 | head -40

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

crates/vm/src/function/argument.rs:141:    pub fn from_vectorcall(
crates/vm/src/function/argument.rs-142-        args: &[PyObjectRef],
crates/vm/src/function/argument.rs-143-        nargs: usize,
crates/vm/src/function/argument.rs-144-        kwnames: Option<&[PyObjectRef]>,
--
crates/vm/src/protocol/callable.rs:40:    pub fn vectorcall(
crates/vm/src/protocol/callable.rs-41-        &self,
crates/vm/src/protocol/callable.rs-42-        args: Vec<PyObjectRef>,
crates/vm/src/protocol/callable.rs-43-        nargs: usize,
--
crates/vm/src/protocol/callable.rs:102:    pub fn invoke_vectorcall(
crates/vm/src/protocol/callable.rs-103-        &self,
crates/vm/src/protocol/callable.rs-104-        args: Vec<PyObjectRef>,
crates/vm/src/protocol/callable.rs-105-        nargs: usize,
--
crates/vm/src/builtins/function.rs:1258:pub(crate) fn vectorcall_function(
crates/vm/src/builtins/function.rs-1259-    zelf_obj: &PyObject,
crates/vm/src/builtins/function.rs-1260-    mut args: Vec<PyObjectRef>,
crates/vm/src/builtins/function.rs-1261-    nargs: usize,

Script executed:

#!/bin/bash
# Check if there are tests for from_vectorcall
find crates/vm/src -name "*.rs" -type f | xargs rg -l "from_vectorcall|test.*vectorcall"

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

crates/vm/src/protocol/callable.rs
crates/vm/src/function/argument.rs
crates/vm/src/builtins/function.rs
crates/vm/src/builtins/builtin_func.rs

Script executed:

#!/bin/bash
# Look for any tests that exercise from_vectorcall
rg -n "test.*from_vectorcall|#\[test\]" crates/vm/src/function/argument.rs -A10

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 1


Script executed:

#!/bin/bash
# Check if there's defensive code elsewhere that validates vectorcall inputs
rg -n "nargs.*args.*len|assert.*nargs" crates/vm/src/ | head -20

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

crates/vm/src/function/argument.rs:150:                .zip(&args[nargs..nargs + names.len()])
crates/vm/src/builtins/function.rs:229:        let nargs = func_args.args.len();
crates/vm/src/builtins/function/jit.rs:161:    let nargs = func_args.args.len();

Script executed:

#!/bin/bash
# Check protocol/callable.rs line 131 more carefully - the fallback path
sed -n '125,145p' crates/vm/src/protocol/callable.rs | cat -n

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

     1	                }
     2	                result
     3	            }
     4	        } else {
     5	            // Fallback: convert owned Vec to FuncArgs
     6	            let func_args = FuncArgs {
     7	                args: args[..nargs].to_vec(),
     8	                kwargs: if let Some(kwn) = kwnames {
     9	                    FuncArgs::from_vectorcall(&args, nargs, Some(kwn)).kwargs
    10	                } else {
    11	                    indexmap::IndexMap::new()
    12	                },
    13	            };
    14	            self.invoke(func_args, vm)
    15	        }
    16	    }
    17	}
    18	
    19	/// Trace events for sys.settrace and sys.setprofile.
    20	pub(crate) enum TraceEvent {
    21	    Call,

Script executed:

#!/bin/bash
# Check if args is ever passed to vectorcall functions with untrusted nargs
# Look at all the places where vectorcall_function is called
rg -B5 'vectorcall_function\(' crates/vm/src/frame.rs | head -80

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

                        (v, nargs_usize + 1)
                    } else {
                        (pos_args, nargs_usize)
                    };
                    let result =
                        vectorcall_function(&callable, args_vec, effective_nargs, None, vm)?;
--
                    let callable = self.pop_value();
                    let mut args_vec = Vec::with_capacity(nargs_usize + 1);
                    args_vec.push(self_val);
                    args_vec.extend(pos_args);
                    let result =
                        vectorcall_function(&callable, args_vec, nargs_usize + 1, None, vm)?;
--
                        (v, pos_count + 1)
                    } else {
                        (all_args, pos_count)
                    };
                    let kwnames = kwarg_names_tuple.as_slice();
                    let result = vectorcall_function(
--
                    let mut args_vec = Vec::with_capacity(nargs_usize + 1);
                    args_vec.push(self_val);
                    args_vec.extend(all_args);
                    let kwnames = kwarg_names_tuple.as_slice();
                    let result =
                        vectorcall_function(&callable, args_vec, pos_count + 1, Some(kwnames), vm)?;

Script executed:

#!/bin/bash
# Check the type signature of VectorCallFunc to understand the contract
sed -n '270,290p' crates/vm/src/types/slot.rs

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

/// Vectorcall function pointer (PEP 590).
/// args: owned positional args followed by kwarg values.
/// nargs: number of positional args (self prepended by caller if needed).
/// kwnames: keyword argument names (last kwnames.len() entries in args are kwarg values).
pub(crate) type VectorCallFunc = fn(
    &PyObject,              // callable
    Vec<PyObjectRef>,       // owned args (positional + kwarg values)
    usize,                  // nargs (positional count)
    Option<&[PyObjectRef]>, // kwnames (keyword argument names)
    &VirtualMachine,
) -> PyResult;
pub(crate) type HashFunc = fn(&PyObject, &VirtualMachine) -> PyResult<PyHash>;
// CallFunc = GenericMethod
pub(crate) type StringifyFunc = fn(&PyObject, &VirtualMachine) -> PyResult<PyRef<PyStr>>;
pub(crate) type GetattroFunc = fn(&PyObject, &Py<PyStr>, &VirtualMachine) -> PyResult;
pub(crate) type SetattroFunc =
    fn(&PyObject, &Py<PyStr>, PySetterValue, &VirtualMachine) -> PyResult<()>;
pub(crate) type AsBufferFunc = fn(&PyObject, &VirtualMachine) -> PyResult<PyBuffer>;
pub(crate) type RichCompareFunc = fn(
    &PyObject,
    &PyObject,

Script executed:

#!/bin/bash
# Check if there's a comment or spec about the vectorcall protocol requirements
rg -n 'vectorcall|nargs.*kwnames' crates/vm/src/function/argument.rs crates/vm/src/protocol/callable.rs -B2 | grep -E 'vectorcall|nargs|PEP|///'

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

crates/vm/src/protocol/callable.rs-37-    /// Vectorcall: call with owned positional args + optional kwnames.
crates/vm/src/protocol/callable.rs:38:    /// Falls back to FuncArgs-based call if no vectorcall slot.
crates/vm/src/protocol/callable.rs:40:    pub fn vectorcall(
crates/vm/src/protocol/callable.rs:52:        callable.invoke_vectorcall(args, nargs, kwnames, vm)
crates/vm/src/protocol/callable.rs:60:    pub vectorcall: Option<VectorCallFunc>,
crates/vm/src/protocol/callable.rs:67:        let vectorcall = slots.vectorcall.load();
crates/vm/src/protocol/callable.rs:71:            vectorcall,
crates/vm/src/protocol/callable.rs:100:    /// Vectorcall dispatch: use vectorcall slot if available, else fall back to FuncArgs.
crates/vm/src/protocol/callable.rs:102:    pub fn invoke_vectorcall(
crates/vm/src/protocol/callable.rs:109:        if let Some(vc) = self.vectorcall {
crates/vm/src/protocol/callable.rs:111:                return vc(self.obj, args, nargs, kwnames, vm);
crates/vm/src/protocol/callable.rs:116:                vc(self.obj, args, nargs, kwnames, vm)
crates/vm/src/protocol/callable.rs:120:                let result = vc(self.obj, args, nargs, kwnames, vm);
crates/vm/src/protocol/callable.rs-131-                args: args[..nargs].to_vec(),
crates/vm/src/protocol/callable.rs:133:                    FuncArgs::from_vectorcall(&args, nargs, Some(kwn)).kwargs
crates/vm/src/function/argument.rs:138:    /// Create FuncArgs from a vectorcall-style argument slice (PEP 590).
crates/vm/src/function/argument.rs:139:    /// `args[..nargs]` are positional, and if `kwnames` is provided,
crates/vm/src/function/argument.rs-140-    /// the last `kwnames.len()` entries in `args[nargs..]` are keyword values.
crates/vm/src/function/argument.rs:141:    pub fn from_vectorcall(

Script executed:

#!/bin/bash
# Verify that the protocol/callable.rs also has the same unchecked slicing
sed -n '128,135p' crates/vm/src/protocol/callable.rs

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

        } else {
            // Fallback: convert owned Vec to FuncArgs
            let func_args = FuncArgs {
                args: args[..nargs].to_vec(),
                kwargs: if let Some(kwn) = kwnames {
                    FuncArgs::from_vectorcall(&args, nargs, Some(kwn)).kwargs
                } else {
                    indexmap::IndexMap::new()

Script executed:

#!/bin/bash
# Let me check if there's any validation of nargs before from_vectorcall is called
# Look at the complete vectorcall_function in function.rs
sed -n '1258,1320p' crates/vm/src/builtins/function.rs | cat -n

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

     1	pub(crate) fn vectorcall_function(
     2	    zelf_obj: &PyObject,
     3	    mut args: Vec<PyObjectRef>,
     4	    nargs: usize,
     5	    kwnames: Option<&[PyObjectRef]>,
     6	    vm: &VirtualMachine,
     7	) -> PyResult {
     8	    let zelf: &Py<PyFunction> = zelf_obj.downcast_ref().unwrap();
     9	    let code: &Py<PyCode> = &zelf.code;
    10	
    11	    let has_kwargs = kwnames.is_some_and(|kw| !kw.is_empty());
    12	    let is_simple = !has_kwargs
    13	        && !code.flags.contains(bytecode::CodeFlags::VARARGS)
    14	        && !code.flags.contains(bytecode::CodeFlags::VARKEYWORDS)
    15	        && code.kwonlyarg_count == 0
    16	        && !code
    17	            .flags
    18	            .intersects(bytecode::CodeFlags::GENERATOR | bytecode::CodeFlags::COROUTINE);
    19	
    20	    if is_simple && nargs == code.arg_count as usize {
    21	        // FAST PATH: simple positional-only call, exact arg count.
    22	        // Move owned args directly into fastlocals — no clone needed.
    23	        let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
    24	            ArgMapping::from_dict_exact(vm.ctx.new_dict())
    25	        } else {
    26	            ArgMapping::from_dict_exact(zelf.globals.clone())
    27	        };
    28	
    29	        let frame = Frame::new(
    30	            code.to_owned(),
    31	            Scope::new(Some(locals), zelf.globals.clone()),
    32	            zelf.builtins.clone(),
    33	            zelf.closure.as_ref().map_or(&[], |c| c.as_slice()),
    34	            Some(zelf.to_owned().into()),
    35	            vm,
    36	        )
    37	        .into_ref(&vm.ctx);
    38	
    39	        {
    40	            let fastlocals = unsafe { frame.fastlocals.borrow_mut() };
    41	            for (slot, arg) in fastlocals.iter_mut().zip(args.drain(..nargs)) {
    42	                *slot = Some(arg);
    43	            }
    44	        }
    45	
    46	        if let Some(cell2arg) = code.cell2arg.as_deref() {
    47	            let fastlocals = unsafe { frame.fastlocals.borrow_mut() };
    48	            for (cell_idx, arg_idx) in cell2arg.iter().enumerate().filter(|(_, i)| **i != -1) {
    49	                let x = fastlocals[*arg_idx as usize].take();
    50	                frame.set_cell_contents(cell_idx, x);
    51	            }
    52	        }
    53	
    54	        return vm.run_frame(frame);
    55	    }
    56	
    57	    // SLOW PATH: construct FuncArgs from owned Vec and delegate to invoke()
    58	    let func_args = if has_kwargs {
    59	        FuncArgs::from_vectorcall(&args, nargs, kwnames)
    60	    } else {
    61	        args.truncate(nargs);
    62	        FuncArgs::from(args)
    63	    };

Script executed:

#!/bin/bash
# Check if there's any guard/validation of the nargs/kwnames invariants anywhere
rg -n 'debug_assert|assert.*nargs|assert.*kwnames' crates/vm/src/builtins/function.rs crates/vm/src/builtins/builtin_func.rs

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

crates/vm/src/builtins/function.rs:654:        debug_assert_eq!(args.len(), code.arg_count as usize);
crates/vm/src/builtins/function.rs:655:        debug_assert!(code.flags.contains(bytecode::CodeFlags::NEWLOCALS));
crates/vm/src/builtins/function.rs:656:        debug_assert!(!code.flags.intersects(
crates/vm/src/builtins/function.rs:662:        debug_assert_eq!(code.kwonlyarg_count, 0);

Script executed:

#!/bin/bash
# Check if there are any comments or guarantees about vectorcall protocol requirements
# Look at the documentation in slot.rs about VectorCallFunc
sed -n '270,285p' crates/vm/src/types/slot.rs

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

/// Vectorcall function pointer (PEP 590).
/// args: owned positional args followed by kwarg values.
/// nargs: number of positional args (self prepended by caller if needed).
/// kwnames: keyword argument names (last kwnames.len() entries in args are kwarg values).
pub(crate) type VectorCallFunc = fn(
    &PyObject,              // callable
    Vec<PyObjectRef>,       // owned args (positional + kwarg values)
    usize,                  // nargs (positional count)
    Option<&[PyObjectRef]>, // kwnames (keyword argument names)
    &VirtualMachine,
) -> PyResult;
pub(crate) type HashFunc = fn(&PyObject, &VirtualMachine) -> PyResult<PyHash>;
// CallFunc = GenericMethod
pub(crate) type StringifyFunc = fn(&PyObject, &VirtualMachine) -> PyResult<PyRef<PyStr>>;
pub(crate) type GetattroFunc = fn(&PyObject, &Py<PyStr>, &VirtualMachine) -> PyResult;
pub(crate) type SetattroFunc =

Script executed:

#!/bin/bash
# Check if from_vectorcall is part of public API (pub) or internal (pub(crate))
rg -n 'pub.*fn from_vectorcall' crates/vm/src/function/argument.rs

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

141:    pub fn from_vectorcall(

Script executed:

#!/bin/bash
# Look at the fallback code path in protocol/callable.rs more carefully - line 131
sed -n '125,145p' crates/vm/src/protocol/callable.rs

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

                }
                result
            }
        } else {
            // Fallback: convert owned Vec to FuncArgs
            let func_args = FuncArgs {
                args: args[..nargs].to_vec(),
                kwargs: if let Some(kwn) = kwnames {
                    FuncArgs::from_vectorcall(&args, nargs, Some(kwn)).kwargs
                } else {
                    indexmap::IndexMap::new()
                },
            };
            self.invoke(func_args, vm)
        }
    }
}

/// Trace events for sys.settrace and sys.setprofile.
pub(crate) enum TraceEvent {
    Call,

Script executed:

#!/antml:
# Check if there are any fuzzing or property-based tests
find crates -name "*.rs" -type f | xargs rg -l "proptest|quickcheck|fuzz"

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 123


Script executed:

#!/bin/bash
# Check if from_vectorcall is called from outside crates/vm
rg -r 'from_vectorcall' crates/ --glob '!crates/vm/**'

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

Lib/test/test_inspect/test_inspect.py:    `@unittest.expectedFailure`  # TODO: RUSTPYTHON; AssertionError: 'The source is: <<<def f():\n    print(0)\n    return 1 + 2\n>>>' not found in 'Traceback (most recent call last):\n  File "<stdin>", line 1, in <module>\n  File "/Users/al03219714/Projects/RustPython4/from_vectorcallpylib/Lib/inspect.py", line 1161, in getsource\n    lines, lnum = getsourcelines(object)\n                  ~~~~~~~~~~~~~~^^^^^^^^\n  File "/Users/al03219714/Projects/RustPython4/from_vectorcallpylib/Lib/inspect.py", line 1143, in getsourcelines\n    lines, lnum = findsource(object)\n                  ~~~~~~~~~~^^^^^^^^\n  File "/Users/al03219714/Projects/RustPython4/from_vectorcallpylib/Lib/inspect.py", line 978, in findsource\n    raise OSError(\'could not get source code\')\nOSError: could not get source code\n'
Lib/test/test_trace.py:    `@unittest.expectedFailure`  # TODO: RUSTPYTHON; +  ('/Users/al03219714/Projects/RustPython4/from_vectorcallpylib/Lib/test/test_trace.py', 48): 1}
Lib/test/test_trace.py:    `@unittest.expectedFailure`  # TODO: RUSTPYTHON; +  ('/Users/al03219714/Projects/RustPython4/from_vectorcallpylib/Lib/test/test_trace.py', 54): 1}
Lib/test/test_trace.py:    `@unittest.expectedFailure`  # TODO: RUSTPYTHON; +  ('/Users/al03219714/Projects/RustPython4/from_vectorcallpylib/Lib/test/tracedmodules/testmod.py', 3): 1}
Lib/test/test_trace.py:    `@unittest.expectedFailure`  # TODO: RUSTPYTHON; +  ('/Users/al03219714/Projects/RustPython4/from_vectorcallpylib/Lib/test/test_trace.py', 76): 10}
Lib/test/test_trace.py:    `@unittest.expectedFailure`  # TODO: RUSTPYTHON; +  ('/Users/al03219714/Projects/RustPython4/from_vectorcallpylib/Lib/test/test_trace.py', 87): 1}
Lib/test/test_trace.py:    `@unittest.expectedFailure`  # TODO: RUSTPYTHON; + {('/Users/al03219714/Projects/RustPython4/from_vectorcallpylib/Lib/test/test_trace.py', 108): 1}
Lib/test/test_trace.py:    `@unittest.expectedFailure`  # TODO: RUSTPYTHON; KeyError: ('/Users/al03219714/Projects/RustPython4/from_vectorcallpylib/Lib/test/test_trace.py', 51)
Lib/test/test_traceback.py:    `@unittest.expectedFailure`  # TODO: RUSTPYTHON; AssertionError: /Users/al03219714/Projects/RustPython/from_vectorcallpylib/Lib/test/levenshtein_examples.json is missing. Run `make regen-test-levenshtein`
Lib/test/test_zipimport.py:    `@unittest.skip`("TODO: RUSTPYTHON; (intermittent success/failures); ValueError: name=\"RustPython/from_vectorcallpylib/Lib/test/zipimport_data/sparse-zip64-c0-0x000000000.part\" does not fit expected pattern.")
Lib/test/test_zoneinfo/test_zoneinfo.py:    `@unittest.expectedFailure`  # TODO: RUSTPYTHON; + /home/runner/work/RustPython/RustPython/from_vectorcallpylib/Lib/test/test_zoneinfo/test_zoneinfo.py
Lib/test/test_warnings/__init__.py:    `@unittest.expectedFailure`  # TODO: RUSTPYTHON; + /Users/al03219714/Projects/RustPython1/from_vectorcallpylib/Lib/test/test_warnings/__init__.py
Lib/test/test_io.py:    `@unittest.skip`("TODO: RUSTPYTHON; thread 'main' (103833) panicked at from_vectorcallvm/src/stdlib/signal.rs:233:43: RefCell already borrowed")
Lib/test/test_io.py:    `@unittest.skip`("TODO: RUSTPYTHON; thread 'main' (103833) panicked at from_vectorcallvm/src/stdlib/signal.rs:233:43: RefCell already borrowed")
Lib/test/test_io.py:    `@unittest.skip`("TODO: RUSTPYTHON; thread 'main' (103833) panicked at from_vectorcallvm/src/stdlib/signal.rs:233:43: RefCell already borrowed")
wasm/notebook/webpack.config.js:                    env.rustpythonPkg || '../../from_vectorcallwasm/pkg',
wasm/demo/webpack.config.js:                    env.rustpythonPkg || '../../from_vectorcallwasm/pkg',
wasm/demo/webpack.config.js:                crateDirectory: path.join(__dirname, '../../from_vectorcallwasm'),
scripts/generate_sre_constants.py:# This script generates from_vectorcallsre_engine/src/constants.rs from Lib/re/_constants.py.
scripts/generate_sre_constants.py:    outfile_constants="from_vectorcallsre_engine/src/constants.rs",
deny.toml:# Certain from_vectorcallversions that will be skipped when doing duplicate detection.
example_projects/frozen_stdlib/Cargo.toml:rustpython-vm = { path = "../../from_vectorcallvm", default-features = false, features = ["freeze-stdlib"] }
example_projects/frozen_stdlib/Cargo.toml:rustpython-pylib = { path = "../../from_vectorcallpylib", default-features = false, features = ["freeze-stdlib"] }
example_projects/barebone/Cargo.toml:rustpython-vm = { path = "../../from_vectorcallvm", default-features = false }
example_projects/wasm32_without_js/rustpython-without-js/Cargo.toml:rustpython-vm = { path = "../../../from_vectorcallvm", default-features = false, features = ["compiler"] }
crates/derive-impl/src/error.rs:// Taken from https://github.com/rustwasm/wasm-bindgen/blob/master/from_vectorcallbackend/src/error.rs
README.md:[![Crates.io](https://img.shields.io/from_vectorcallv/rustpython)](https://crates.io/from_vectorcallrustpython)
DEVELOPMENT.md:- `from_vectorcallcompiler/src`: python compilation to bytecode
DEVELOPMENT.md:  - `from_vectorcallcompiler-core/src`: python bytecode representation in rust structures
DEVELOPMENT.md:- `from_vectorcallderive/src` and `from_vectorcallderive-impl/src`: Rust language extensions and macros specific to rustpython
DEVELOPMENT.md:- `from_vectorcallvm/src`: python virtual machine
DEVELOPMENT.md:- `from_vectorcallwasm`: Binary crate and resources for WebAssembly build
DEVELOPMENT.md:- `rustpython-compiler` (implementation in `from_vectorcallcompiler/src`)
DEVELOPMENT.md:- `rustpython-vm` (implementation in `from_vectorcallvm/src`)
DEVELOPMENT.md:`from_vectorcallcompiler/src` directory. The compiler implements Python's symbol table,
DEVELOPMENT.md:Implementation of bytecode structure in Rust is found in the `from_vectorcallcompiler-core/src`
DEVELOPMENT.md:directory. `from_vectorcallcompiler-core/src/bytecode.rs` contains the representation of
DEVELOPMENT.md:executes Python's instructions. The `from_vectorcallvm/src` directory contains code to
DEVELOPMENT.md:Python Standard Library modules in Rust (`from_vectorcallvm/src/stdlib`). In Python
DEVELOPMENT.md:everything can be represented as an object. The `from_vectorcallvm/src/builtins` directory holds
DEVELOPMENT.md:`from_vectorcallvm/src/object/core.rs`.
architecture/architecture.md:- Compiler: `compile`, located in [`from_vectorcallvm/src/vm/compile.rs`][11], this eventually forwards a call to [`compiler::compile`][12].
architecture/architecture.md:- VM: `run_code_obj`, located in [`from_vectorcallvm/src/vm/mod.rs`][13]. This creates a new frame in which the bytecode is executed.
architecture/architecture.md:- The interface and helper for the REPL are defined in this package, but the actual REPL can be found in `from_vectorcallvm/src/readline.rs`
architecture/architecture.md:[10]: https://github.com/astral-sh/ruff/tree/2bffef59665ce7d2630dfd72ee99846663660db8/from_vectorcallruff_python_parser
architecture/architecture.md:[11]: https://github.com/RustPython/RustPython/blob/d36a2cffdef2218f3264cef9145a1f781d474ea3/from_vectorcallvm/src/vm/compile.rs#L10
architecture/architecture.md:[12]: https://github.com/RustPython/RustPython/blob/d36a2cffdef2218f3264cef9145a1f781d474ea3/from_vectorcallvm/src/vm/compile.rs#L26
architecture/architecture.md:[13]: https://github.com/RustPython/RustPython/blob/d36a2cffdef2218f3264cef9145a1f781d474ea3/from_vectorcallvm/src/vm/mod.rs#L433
architecture/architecture.md:[14]: https://github.com/RustPython/RustPython/blob/d36a2cffdef2218f3264cef9145a1f781d474ea3/from_vectorcallcommon/src/float_ops.rs
Cargo.toml:    "from_vectorcall*",
Cargo.toml:rustpython-compiler-core = { path = "from_vectorcallcompiler-core", version = "0.4.0" }
Cargo.toml:rustpython-compiler = { path = "from_vectorcallcompiler", version = "0.4.0" }
Cargo.toml:rustpython-codegen = { path = "from_vectorcallcodegen", version = "0.4.0" }
Cargo.toml:rustpython-common = { path = "from_vectorcallcommon", version = "0.4.0" }
Cargo.toml:rustpython-derive = { path = "from_vectorcallderive", version = "0.4.0" }
Cargo.toml:rustpython-derive-impl = { path = "from_vectorcallderive-impl", version = "0.4.0" }
Cargo.toml:rustpython-jit = { path = "from_vectorcalljit", version = "0.4.0" }
Cargo.toml:rustpython-literal = { path = "from_vectorcallliteral", version = "0.4.0" }
Cargo.toml:rustpython-vm = { path = "from_vectorcallvm", default-features = false, version = "0.4.0" }
Cargo.toml:rustpython-pylib = { path = "from_vectorcallpylib", version = "0.4.0" }
Cargo.toml:rustpython-stdlib = { path = "from_vectorcallstdlib", default-features = false, version = "0.4.0" }
Cargo.toml:rustpython-sre_engine = { path = "from_vectorcallsre_engine", version = "0.4.0" }
Cargo.toml:rustpython-wtf8 = { path = "from_vectorcallwtf8", version = "0.4.0" }
Cargo.toml:rustpython-doc = { path = "from_vectorcalldoc", version = "0.4.0" }
crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_double_async_with.snap:source: from_vectorcallcodegen/src/compile.rs
crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ors.snap:source: from_vectorcallcodegen/src/compile.rs
crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_mixed.snap:source: from_vectorcallcodegen/src/compile.rs
crates/codegen/src/snapshots/rustpython_codegen__compile__tests__if_ands.snap:source: from_vectorcallcodegen/src/compile.rs
crates/codegen/src/snapshots/rustpython_codegen__compile__tests__nested_bool_op.snap:source: from_vectorcallcodegen/src/compile.rs

Script executed:

#!/bin/bash
# Final check: Look at the documentation comment for from_vectorcall to see if it documents preconditions
sed -n '138,145p' crates/vm/src/function/argument.rs

Repository: RustPython/RustPython


Repository: RustPython/RustPython
Exit code: 0

stdout:

    /// Create FuncArgs from a vectorcall-style argument slice (PEP 590).
    /// `args[..nargs]` are positional, and if `kwnames` is provided,
    /// the last `kwnames.len()` entries in `args[nargs..]` are keyword values.
    pub fn from_vectorcall(
        args: &[PyObjectRef],
        nargs: usize,
        kwnames: Option<&[PyObjectRef]>,
    ) -> Self {

Add bounds validation to from_vectorcall to prevent VM panics on invalid vectorcall layouts.

Line 146 and line 150 perform unchecked slicing (args[..nargs] and args[nargs..nargs + names.len()]), and line 154 uses .expect() to downcast kwnames entries. A caller providing invalid nargs or non-string keyword names will panic the VM instead of raising a Python exception. The documentation does not explicitly state preconditions about bounds, making this unsafe for public API callers.

Validate that nargs <= args.len() and nargs + kwnames.len() <= args.len() before slicing. Replace the .expect() downcast with proper error handling, or use debug_assert! paired with safe .get() slicing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/vm/src/function/argument.rs` around lines 146 - 151, In
from_vectorcall validate the vectorcall layout before any unchecked slicing:
ensure nargs <= args.len() and if kwnames.is_some() then nargs + kwnames.len()
<= args.len(), returning a Python TypeError/ValueError (Err) instead of
panicking; replace the .expect() downcast on kwnames entries with safe handling
(use .get() or iterate with .zip and match on PyString downcast) and return an
Err TypeError when a keyword name is not a string; update construction of
pos_args and kwargs to use checked slices (or .get() results) so no unchecked
indexing or .expect() can panic the VM.

@youknowone youknowone merged commit be0c3ca into RustPython:main Mar 3, 2026
13 checks passed
@youknowone youknowone deleted the skip-class branch March 3, 2026 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

X Tutup