From 05dc1f16561c3a55497d4c7905b91b6e2e7dd4c6 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Fri, 22 May 2026 15:01:29 +0200 Subject: [PATCH 1/5] Add support for creating functions with the c-api --- Cargo.lock | 1 + crates/capi/Cargo.toml | 1 + crates/capi/src/lib.rs | 1 + crates/capi/src/methodobject.rs | 347 +++++++++++++++++++++++++++++++ crates/vm/src/function/method.rs | 7 +- crates/vm/src/vm/vm_new.rs | 2 +- 6 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 crates/capi/src/methodobject.rs diff --git a/Cargo.lock b/Cargo.lock index 4f211e65a19..10974510458 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3322,6 +3322,7 @@ dependencies = [ name = "rustpython-capi" version = "0.5.0" dependencies = [ + "bitflags 2.11.1", "num-complex", "pyo3", "rustpython-stdlib", diff --git a/crates/capi/Cargo.toml b/crates/capi/Cargo.toml index 8c54a8b26ae..685b44d1ee0 100644 --- a/crates/capi/Cargo.toml +++ b/crates/capi/Cargo.toml @@ -12,6 +12,7 @@ license.workspace = true crate-type = ["cdylib", "rlib"] [dependencies] +bitflags = { workspace = true } num-complex = { workspace = true } rustpython-vm = { workspace = true, features = ["threading", "compiler"] } rustpython-stdlib = {workspace = true, features = ["threading"] } diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs index ff44d87e6e8..d7f9f8b7464 100644 --- a/crates/capi/src/lib.rs +++ b/crates/capi/src/lib.rs @@ -18,6 +18,7 @@ pub mod floatobject; pub mod import; pub mod listobject; pub mod longobject; +pub mod methodobject; pub mod object; pub mod pycapsule; pub mod pyerrors; diff --git a/crates/capi/src/methodobject.rs b/crates/capi/src/methodobject.rs new file mode 100644 index 00000000000..e8f43e6383d --- /dev/null +++ b/crates/capi/src/methodobject.rs @@ -0,0 +1,347 @@ +use crate::PyObject; +use crate::object::PyTypeObject; +use crate::object::define_py_check; +use crate::pystate::with_vm; +use core::ffi::{CStr, c_char, c_int}; +use core::ptr::NonNull; +use rustpython_vm::function::{FuncArgs, HeapMethodDef, PosArgs, PyMethodFlags}; +use rustpython_vm::{AsObject, PyObjectRef, PyRef, PyResult, VirtualMachine}; + +define_py_check!(fn PyCFunction_Check, types.builtin_function_or_method_type); +define_py_check!(exact fn PyCFunction_CheckExact, types.builtin_function_or_method_type); + +#[repr(C)] +pub struct PyMethodDef { + pub ml_name: *const c_char, + pub ml_meth: PyMethodPointer, + pub ml_flags: c_int, + pub ml_doc: *const c_char, +} + +#[repr(C)] +#[derive(Copy, Clone)] +#[allow(non_snake_case)] +pub union PyMethodPointer { + pub PyCFunction: unsafe extern "C" fn(slf: *mut PyObject, args: *mut PyObject) -> *mut PyObject, + pub PyCFunctionWithKeywords: unsafe extern "C" fn( + slf: *mut PyObject, + args: *mut PyObject, + kwargs: *mut PyObject, + ) -> *mut PyObject, + pub PyCFunctionFast: unsafe extern "C" fn( + slf: *mut PyObject, + args: *mut *mut PyObject, + nargs: isize, + ) -> *mut PyObject, + pub PyCFunctionFastWithKeywords: unsafe extern "C" fn( + slf: *mut PyObject, + args: *const *mut PyObject, + nargs: isize, + kwnames: *mut PyObject, + ) -> *mut PyObject, +} + +pub(crate) fn build_method_def( + vm: &VirtualMachine, + ml: &PyMethodDef, + has_self: bool, +) -> PyRef { + let name = unsafe { CStr::from_ptr(ml.ml_name) } + .to_str() + .expect("Method name was not valid UTF-8"); + + let doc = NonNull::new(ml.ml_doc.cast_mut()).map(|doc| { + unsafe { CStr::from_ptr(doc.as_ptr()) } + .to_str() + .expect("Method doc was not valid UTF-8") + }); + + let flags = + PyMethodFlags::from_bits(ml.ml_flags as u32).expect("PyMethodDef contains unknown flags"); + + let method = ml.ml_meth; + + if flags.contains(PyMethodFlags::METHOD) { + panic!("METH_METHOD is not supported on abi3") + } + + let call_flags = flags + & (PyMethodFlags::VARARGS + | PyMethodFlags::KEYWORDS + | PyMethodFlags::NOARGS + | PyMethodFlags::O + | PyMethodFlags::FASTCALL); + + bitflags::bitflags_match!(call_flags, { + PyMethodFlags::NOARGS => { + if has_self { + let callable = move |zelf: PyObjectRef, vm: &VirtualMachine| unsafe { + call_function(vm, method, flags, Some(vec![zelf])) + }; + vm.ctx.new_method_def(name, callable, flags, doc) + } else { + let callable = move |vm: &VirtualMachine| unsafe { + call_function::(vm, method, flags, None) + }; + vm.ctx.new_method_def(name, callable, flags, doc) + } + }, + PyMethodFlags::VARARGS => { + let callable = move |args: PosArgs, vm: &VirtualMachine| unsafe { + call_function(vm, method, flags, Some(args)) + }; + vm.ctx.new_method_def(name, callable, flags, doc) + }, + PyMethodFlags::VARARGS | PyMethodFlags::KEYWORDS => { + let callable = move | args: FuncArgs, vm: &VirtualMachine| unsafe { + call_function_with_keywords(vm, method, flags, args) + }; + vm.ctx.new_method_def(name, callable, flags, doc) + }, + PyMethodFlags::FASTCALL | PyMethodFlags::KEYWORDS => { + let callable = move |args: FuncArgs, vm: &VirtualMachine| unsafe { + call_fast_function_with_keywords(vm, method, flags, args) + }; + vm.ctx.new_method_def(name, callable, flags, doc) + }, + PyMethodFlags::FASTCALL => { + let callable = move |_args: PosArgs, _vm: &VirtualMachine| -> PyResult { + todo!("METH_FASTCALL without METH_KEYWORDS is not supported yet") + }; + vm.ctx.new_method_def(name, callable, flags, doc) + }, + PyMethodFlags::O => { + let f = unsafe { method.PyCFunction }; + if has_self { + let callable = move |zelf: PyObjectRef, arg: PyObjectRef, vm: &VirtualMachine| -> PyResult { + let ret_ptr = unsafe { f(zelf.as_raw().cast_mut(), arg.as_raw().cast_mut()) }; + ret_ptr_to_pyresult(vm, ret_ptr) + }; + vm.ctx.new_method_def(name, callable, flags, doc) + } else { + let callable = move |arg: PyObjectRef, vm: &VirtualMachine| -> PyResult { + let ret_ptr = unsafe { f(core::ptr::null_mut(), arg.as_raw().cast_mut()) }; + ret_ptr_to_pyresult(vm, ret_ptr) + }; + vm.ctx.new_method_def(name, callable, flags, doc) + } + }, + _ => { + todo!("function {name} has unsupported or invalid calling-convention flags: {flags:?}") + }, + }) +} + +unsafe fn call_function>( + vm: &VirtualMachine, + method: PyMethodPointer, + flags: PyMethodFlags, + args: Option, +) -> PyResult { + let f = unsafe { method.PyCFunction }; + let (slf, arg_tuple) = if let Some(mut args) = args.map(Into::into) { + let slf = take_self_arg(&mut args, flags); + let arg_tuple = vm.ctx.new_tuple(args.args); + (slf, Some(arg_tuple)) + } else { + (None, None) + }; + + let slf_ptr = slf + .as_ref() + .map(|obj| obj.as_object().as_raw().cast_mut()) + .unwrap_or_default(); + + let arg_ptr = arg_tuple + .as_ref() + .map(|tuple| tuple.as_object().as_raw().cast_mut()) + .unwrap_or_default(); + + let ret_ptr = unsafe { f(slf_ptr, arg_ptr) }; + ret_ptr_to_pyresult(vm, ret_ptr) +} + +unsafe fn call_function_with_keywords( + vm: &VirtualMachine, + method: PyMethodPointer, + flags: PyMethodFlags, + mut args: FuncArgs, +) -> PyResult { + let f = unsafe { method.PyCFunctionWithKeywords }; + let slf = take_self_arg(&mut args, flags); + let slf_ptr = slf + .as_ref() + .map(|obj| obj.as_object().as_raw().cast_mut()) + .unwrap_or_default(); + let arg_tuple = vm.ctx.new_tuple(args.args); + let kwargs = vm.ctx.new_dict(); + for (k, v) in args.kwargs { + kwargs.set_item(&*k, v, vm)?; + } + let ret_ptr = unsafe { + f( + slf_ptr, + arg_tuple.as_object().as_raw().cast_mut(), + kwargs.as_object().as_raw().cast_mut(), + ) + }; + ret_ptr_to_pyresult(vm, ret_ptr) +} + +unsafe fn call_fast_function_with_keywords( + vm: &VirtualMachine, + method: PyMethodPointer, + flags: PyMethodFlags, + mut args: FuncArgs, +) -> PyResult { + let f = unsafe { method.PyCFunctionFastWithKeywords }; + let slf = take_self_arg(&mut args, flags); + let slf_ptr = slf + .as_ref() + .map(|obj| obj.as_object().as_raw().cast_mut()) + .unwrap_or_default(); + let nargs = args.args.len(); + let mut fastcall_args = args.args; + let kwnames_tuple = if !args.kwargs.is_empty() { + let mut kwnames = Vec::with_capacity(args.kwargs.len()); + for (k, v) in args.kwargs { + kwnames.push(vm.ctx.new_str(k).into()); + fastcall_args.push(v); + } + Some(vm.ctx.new_tuple(kwnames)) + } else { + None + }; + let fastcall_arg_ptrs = fastcall_args + .iter() + .map(|obj| obj.as_object().as_raw().cast_mut()) + .collect::>(); + let kwnames_ptr = kwnames_tuple + .as_ref() + .map(|tuple| tuple.as_object().as_raw().cast_mut()) + .unwrap_or_default(); + let ret_ptr = unsafe { + f( + slf_ptr, + fastcall_arg_ptrs.as_ptr(), + nargs as isize, + kwnames_ptr, + ) + }; + ret_ptr_to_pyresult(vm, ret_ptr) +} + +fn ret_ptr_to_pyresult(vm: &VirtualMachine, ret_ptr: *mut PyObject) -> PyResult { + let ret_ptr = NonNull::new(ret_ptr).ok_or_else(|| { + vm.take_raised_exception() + .expect("Native function returned NULL, but there was no exception set") + })?; + Ok(unsafe { PyObjectRef::from_raw(ret_ptr) }) +} + +fn take_self_arg(args: &mut FuncArgs, flags: PyMethodFlags) -> Option { + if flags.contains(PyMethodFlags::STATIC) { + None + } else { + args.take_positional() + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyCMethod_New( + ml: *mut PyMethodDef, + slf: *mut PyObject, + _module: *mut PyObject, + _cls: *mut PyTypeObject, +) -> *mut PyObject { + with_vm(|vm| -> PyResult { + assert!( + _cls.is_null(), + "PyCMethod_New does not support METH_METHOD on abi3" + ); + let ml = unsafe { &*ml }; + let zelf = unsafe { slf.as_ref().map(|obj| obj.to_owned()) }; + Ok(build_method_def(vm, ml, zelf.is_some()) + .build_function(vm, zelf) + .into()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyCFunction_New( + ml: *mut PyMethodDef, + slf: *mut PyObject, +) -> *mut PyObject { + unsafe { PyCMethod_New(ml, slf, core::ptr::null_mut(), core::ptr::null_mut()) } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyCFunction_NewEx( + ml: *mut PyMethodDef, + slf: *mut PyObject, + module: *mut PyObject, +) -> *mut PyObject { + unsafe { PyCMethod_New(ml, slf, module, core::ptr::null_mut()) } +} + +#[cfg(false)] +mod tests { + use pyo3::exceptions::PyException; + use pyo3::ffi::{PyLong_FromLong, PyObject}; + use pyo3::prelude::*; + use pyo3::types::{PyCFunction, PyInt, PyString}; + + #[test] + fn test_closure_function() { + Python::attach(|py| { + let f = PyCFunction::new_closure(py, None, None, |_args, _kwargs| "Hello from Rust!") + .unwrap(); + + assert_eq!( + f.call0().unwrap().cast::().unwrap(), + "Hello from Rust!" + ); + }) + } + + #[test] + fn test_function_no_args() { + Python::attach(|py| { + unsafe extern "C" fn c_fn(_self: *mut PyObject, _args: *mut PyObject) -> *mut PyObject { + assert!(_self.is_null()); + assert!(_args.is_null()); + unsafe { PyLong_FromLong(4200) } + } + + let py_fn = PyCFunction::new(py, c_fn, c"py_fn", c"", None).unwrap(); + + let result = py_fn + .call0() + .unwrap() + .cast::() + .unwrap() + .extract::() + .unwrap(); + assert_eq!(result, 4200); + + assert!(py_fn.call((1,), None).is_err()); + assert!(py_fn.call((1, 2), None).is_err()); + }) + } + + #[test] + fn test_closure_function_error() { + Python::attach(|py| { + let f = PyCFunction::new_closure(py, None, None, |_args, _kwargs| { + Err::<(), _>(PyException::new_err("Something went wrong")) + }) + .unwrap(); + + let err = f.call0().unwrap_err(); + assert_eq!( + err.value(py).repr().unwrap(), + "Exception('Something went wrong')" + ); + }) + } +} diff --git a/crates/vm/src/function/method.rs b/crates/vm/src/function/method.rs index 6c59172b633..ee483e361e6 100644 --- a/crates/vm/src/function/method.rs +++ b/crates/vm/src/function/method.rs @@ -299,9 +299,14 @@ impl Py { unsafe { &*(&self.method as *const _) } } - pub fn build_function(&self, vm: &VirtualMachine) -> PyRef { + pub fn build_function( + &self, + vm: &VirtualMachine, + zelf: Option, + ) -> PyRef { let mut function = unsafe { self.method() }.to_function(); function._method_def_owner = Some(self.to_owned().into()); + function.zelf = zelf; PyRef::new_ref( function, vm.ctx.types.builtin_function_or_method_type.to_owned(), diff --git a/crates/vm/src/vm/vm_new.rs b/crates/vm/src/vm/vm_new.rs index 7976e37b480..3b50c25695f 100644 --- a/crates/vm/src/vm/vm_new.rs +++ b/crates/vm/src/vm/vm_new.rs @@ -315,7 +315,7 @@ impl VirtualMachine { let def = self .ctx .new_method_def(name, f, PyMethodFlags::empty(), None); - def.build_function(self) + def.build_function(self, None) } pub fn new_method( From 4c7144e64d8dc00def3f6d4266e734397647093b Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Tue, 26 May 2026 11:32:22 +0200 Subject: [PATCH 2/5] Add support for fastcall --- crates/capi/src/methodobject.rs | 77 +++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/crates/capi/src/methodobject.rs b/crates/capi/src/methodobject.rs index e8f43e6383d..3d760b6cfa9 100644 --- a/crates/capi/src/methodobject.rs +++ b/crates/capi/src/methodobject.rs @@ -45,24 +45,26 @@ pub(crate) fn build_method_def( vm: &VirtualMachine, ml: &PyMethodDef, has_self: bool, -) -> PyRef { +) -> PyResult> { let name = unsafe { CStr::from_ptr(ml.ml_name) } .to_str() - .expect("Method name was not valid UTF-8"); + .map_err(|_| vm.new_system_error("Method name was not valid UTF-8"))?; - let doc = NonNull::new(ml.ml_doc.cast_mut()).map(|doc| { - unsafe { CStr::from_ptr(doc.as_ptr()) } - .to_str() - .expect("Method doc was not valid UTF-8") - }); + let doc = NonNull::new(ml.ml_doc.cast_mut()) + .map(|doc| { + unsafe { CStr::from_ptr(doc.as_ptr()) } + .to_str() + .map_err(|_| vm.new_system_error("Method doc was not valid UTF-8")) + }) + .transpose()?; - let flags = - PyMethodFlags::from_bits(ml.ml_flags as u32).expect("PyMethodDef contains unknown flags"); + let flags = PyMethodFlags::from_bits(ml.ml_flags as u32) + .ok_or_else(|| vm.new_system_error("PyMethodDef contains unknown flags"))?; let method = ml.ml_meth; if flags.contains(PyMethodFlags::METHOD) { - panic!("METH_METHOD is not supported on abi3") + return Err(vm.new_system_error("METH_METHOD is not supported on abi3")); } let call_flags = flags @@ -78,37 +80,37 @@ pub(crate) fn build_method_def( let callable = move |zelf: PyObjectRef, vm: &VirtualMachine| unsafe { call_function(vm, method, flags, Some(vec![zelf])) }; - vm.ctx.new_method_def(name, callable, flags, doc) + Ok(vm.ctx.new_method_def(name, callable, flags, doc)) } else { let callable = move |vm: &VirtualMachine| unsafe { call_function::(vm, method, flags, None) }; - vm.ctx.new_method_def(name, callable, flags, doc) + Ok(vm.ctx.new_method_def(name, callable, flags, doc)) } }, PyMethodFlags::VARARGS => { let callable = move |args: PosArgs, vm: &VirtualMachine| unsafe { call_function(vm, method, flags, Some(args)) }; - vm.ctx.new_method_def(name, callable, flags, doc) + Ok(vm.ctx.new_method_def(name, callable, flags, doc)) }, PyMethodFlags::VARARGS | PyMethodFlags::KEYWORDS => { let callable = move | args: FuncArgs, vm: &VirtualMachine| unsafe { call_function_with_keywords(vm, method, flags, args) }; - vm.ctx.new_method_def(name, callable, flags, doc) + Ok(vm.ctx.new_method_def(name, callable, flags, doc)) }, PyMethodFlags::FASTCALL | PyMethodFlags::KEYWORDS => { let callable = move |args: FuncArgs, vm: &VirtualMachine| unsafe { call_fast_function_with_keywords(vm, method, flags, args) }; - vm.ctx.new_method_def(name, callable, flags, doc) + Ok(vm.ctx.new_method_def(name, callable, flags, doc)) }, PyMethodFlags::FASTCALL => { - let callable = move |_args: PosArgs, _vm: &VirtualMachine| -> PyResult { - todo!("METH_FASTCALL without METH_KEYWORDS is not supported yet") + let callable = move |args: PosArgs, vm: &VirtualMachine| unsafe { + call_fast_function(vm, method, flags, args) }; - vm.ctx.new_method_def(name, callable, flags, doc) + Ok(vm.ctx.new_method_def(name, callable, flags, doc)) }, PyMethodFlags::O => { let f = unsafe { method.PyCFunction }; @@ -117,17 +119,19 @@ pub(crate) fn build_method_def( let ret_ptr = unsafe { f(zelf.as_raw().cast_mut(), arg.as_raw().cast_mut()) }; ret_ptr_to_pyresult(vm, ret_ptr) }; - vm.ctx.new_method_def(name, callable, flags, doc) + Ok(vm.ctx.new_method_def(name, callable, flags, doc)) } else { let callable = move |arg: PyObjectRef, vm: &VirtualMachine| -> PyResult { let ret_ptr = unsafe { f(core::ptr::null_mut(), arg.as_raw().cast_mut()) }; ret_ptr_to_pyresult(vm, ret_ptr) }; - vm.ctx.new_method_def(name, callable, flags, doc) + Ok(vm.ctx.new_method_def(name, callable, flags, doc)) } }, _ => { - todo!("function {name} has unsupported or invalid calling-convention flags: {flags:?}") + Err(vm.new_system_error(format!( + "function {name} has unsupported or invalid calling-convention flags: {flags:?}" + ))) }, }) } @@ -231,6 +235,35 @@ unsafe fn call_fast_function_with_keywords( ret_ptr_to_pyresult(vm, ret_ptr) } +unsafe fn call_fast_function( + vm: &VirtualMachine, + method: PyMethodPointer, + flags: PyMethodFlags, + args: PosArgs, +) -> PyResult { + let f = unsafe { method.PyCFunctionFast }; + let mut args: FuncArgs = args.into(); + let slf = take_self_arg(&mut args, flags); + let slf_ptr = slf + .as_ref() + .map(|obj| obj.as_object().as_raw().cast_mut()) + .unwrap_or_default(); + // TODO can we avoid creating a new vec here? + let mut fastcall_arg_ptrs = args + .args + .iter() + .map(|obj| obj.as_object().as_raw().cast_mut()) + .collect::>(); + let ret_ptr = unsafe { + f( + slf_ptr, + fastcall_arg_ptrs.as_mut_ptr(), + fastcall_arg_ptrs.len() as isize, + ) + }; + ret_ptr_to_pyresult(vm, ret_ptr) +} + fn ret_ptr_to_pyresult(vm: &VirtualMachine, ret_ptr: *mut PyObject) -> PyResult { let ret_ptr = NonNull::new(ret_ptr).ok_or_else(|| { vm.take_raised_exception() @@ -261,7 +294,7 @@ pub unsafe extern "C" fn PyCMethod_New( ); let ml = unsafe { &*ml }; let zelf = unsafe { slf.as_ref().map(|obj| obj.to_owned()) }; - Ok(build_method_def(vm, ml, zelf.is_some()) + Ok(build_method_def(vm, ml, zelf.is_some())? .build_function(vm, zelf) .into()) }) From eba367d2d212558b20ea656148df2a83a7147548 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Tue, 26 May 2026 11:41:48 +0200 Subject: [PATCH 3/5] Avoid creating extra args vec in fastcall functions --- crates/capi/src/methodobject.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/crates/capi/src/methodobject.rs b/crates/capi/src/methodobject.rs index 3d760b6cfa9..3ac4f088374 100644 --- a/crates/capi/src/methodobject.rs +++ b/crates/capi/src/methodobject.rs @@ -30,7 +30,7 @@ pub union PyMethodPointer { ) -> *mut PyObject, pub PyCFunctionFast: unsafe extern "C" fn( slf: *mut PyObject, - args: *mut *mut PyObject, + args: *const *mut PyObject, nargs: isize, ) -> *mut PyObject, pub PyCFunctionFastWithKeywords: unsafe extern "C" fn( @@ -216,18 +216,18 @@ unsafe fn call_fast_function_with_keywords( } else { None }; - let fastcall_arg_ptrs = fastcall_args - .iter() - .map(|obj| obj.as_object().as_raw().cast_mut()) - .collect::>(); let kwnames_ptr = kwnames_tuple .as_ref() .map(|tuple| tuple.as_object().as_raw().cast_mut()) .unwrap_or_default(); + // SAFETY: PyObjectRef is repr(transparent) over a pointer to PyObject, so a + // Vec has a layout-compatible contiguous backing buffer. The + // vector is kept alive for the duration of the call. + let fastcall_arg_ptrs = fastcall_args.as_ptr().cast::<*mut PyObject>(); let ret_ptr = unsafe { f( slf_ptr, - fastcall_arg_ptrs.as_ptr(), + fastcall_arg_ptrs, nargs as isize, kwnames_ptr, ) @@ -248,17 +248,15 @@ unsafe fn call_fast_function( .as_ref() .map(|obj| obj.as_object().as_raw().cast_mut()) .unwrap_or_default(); - // TODO can we avoid creating a new vec here? - let mut fastcall_arg_ptrs = args - .args - .iter() - .map(|obj| obj.as_object().as_raw().cast_mut()) - .collect::>(); + // SAFETY: PyObjectRef is repr(transparent) over a pointer to PyObject, so a + // Vec has a layout-compatible contiguous backing buffer. The + // vector is kept alive for the duration of the call. + let fastcall_arg_ptrs = args.args.as_mut_ptr().cast::<*mut PyObject>(); let ret_ptr = unsafe { f( slf_ptr, - fastcall_arg_ptrs.as_mut_ptr(), - fastcall_arg_ptrs.len() as isize, + fastcall_arg_ptrs, + args.args.len() as isize, ) }; ret_ptr_to_pyresult(vm, ret_ptr) From 30d3f4d9b235d2db172e83766eaa05e7e1246579 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Tue, 26 May 2026 17:22:41 +0200 Subject: [PATCH 4/5] Format --- crates/capi/src/methodobject.rs | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/crates/capi/src/methodobject.rs b/crates/capi/src/methodobject.rs index 3ac4f088374..c14cb3cff6b 100644 --- a/crates/capi/src/methodobject.rs +++ b/crates/capi/src/methodobject.rs @@ -224,14 +224,7 @@ unsafe fn call_fast_function_with_keywords( // Vec has a layout-compatible contiguous backing buffer. The // vector is kept alive for the duration of the call. let fastcall_arg_ptrs = fastcall_args.as_ptr().cast::<*mut PyObject>(); - let ret_ptr = unsafe { - f( - slf_ptr, - fastcall_arg_ptrs, - nargs as isize, - kwnames_ptr, - ) - }; + let ret_ptr = unsafe { f(slf_ptr, fastcall_arg_ptrs, nargs as isize, kwnames_ptr) }; ret_ptr_to_pyresult(vm, ret_ptr) } @@ -252,13 +245,7 @@ unsafe fn call_fast_function( // Vec has a layout-compatible contiguous backing buffer. The // vector is kept alive for the duration of the call. let fastcall_arg_ptrs = args.args.as_mut_ptr().cast::<*mut PyObject>(); - let ret_ptr = unsafe { - f( - slf_ptr, - fastcall_arg_ptrs, - args.args.len() as isize, - ) - }; + let ret_ptr = unsafe { f(slf_ptr, fastcall_arg_ptrs, args.args.len() as isize) }; ret_ptr_to_pyresult(vm, ret_ptr) } From cc4b1c7d3cbadfcbe3eb97f0026f6f7f7c06f7c7 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Tue, 26 May 2026 17:24:11 +0200 Subject: [PATCH 5/5] Review --- crates/capi/src/methodobject.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/capi/src/methodobject.rs b/crates/capi/src/methodobject.rs index c14cb3cff6b..c0a6611a01f 100644 --- a/crates/capi/src/methodobject.rs +++ b/crates/capi/src/methodobject.rs @@ -78,12 +78,16 @@ pub(crate) fn build_method_def( PyMethodFlags::NOARGS => { if has_self { let callable = move |zelf: PyObjectRef, vm: &VirtualMachine| unsafe { - call_function(vm, method, flags, Some(vec![zelf])) + let f = method.PyCFunction; + let ret_ptr = f(zelf.as_raw().cast_mut(), core::ptr::null_mut()); + ret_ptr_to_pyresult(vm, ret_ptr) }; Ok(vm.ctx.new_method_def(name, callable, flags, doc)) } else { let callable = move |vm: &VirtualMachine| unsafe { - call_function::(vm, method, flags, None) + let f = method.PyCFunction; + let ret_ptr = f(core::ptr::null_mut(), core::ptr::null_mut()); + ret_ptr_to_pyresult(vm, ret_ptr) }; Ok(vm.ctx.new_method_def(name, callable, flags, doc)) }