Visitar URL original
Infer native calling convention flags from argument metadata · RustPython/RustPython@3624faf · GitHub
Skip to content

Commit 3624faf

Browse files
committed
Infer native calling convention flags from argument metadata
Remove `KeywordDispatch` and the `keyword_dispatch` field of `PyMethodDef`. Native functions and method descriptors reject nonempty keywords when `flags` lacks `KEYWORDS`, and report the unqualified name when `flags` contains `VARARGS`. `PyMethodDef::new_const`, `PyMethodDef::new_raw_const` and `Context::new_method_def` add the calling convention (`NOARGS`, `O`, `FASTCALL`, or `FASTCALL | KEYWORDS`) inferred from the `FromArgs::PARAMS` of each argument, exposed as `IntoPyNativeFn::ARGS`. Flags that already contain a calling convention, such as C-API `ml_flags`, are kept. For `METHOD` and `CLASS` the first argument is the bound receiver. Raw functions get `FASTCALL | KEYWORDS`. The derive macros pass only binding flags and no longer compute call flags from type names. Definitions built without the macros, such as the `__new__` wrapper and `vm.new_function`, are inferred the same way. `Context::new_method_def` takes `(name, f, flags, doc)` again. Assisted-by: Claude Code:claude-opus-5-5
1 parent 84d629f commit 3624faf

15 files changed

Lines changed: 186 additions & 205 deletions

File tree

‎crates/capi/src/methodobject.rs‎

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ use crate::pystate::with_vm;
55
use crate::util::{CStrExt, FfiPtrExt};
66
use core::ffi::{c_char, c_int};
77
use core::fmt::Debug;
8-
use rustpython_vm::function::{
9-
FuncArgs, HeapMethodDef, ItemDoc, KeywordDispatch, PosArgs, PyMethodFlags,
10-
};
8+
use rustpython_vm::function::{FuncArgs, HeapMethodDef, ItemDoc, PosArgs, PyMethodFlags};
119
use rustpython_vm::{AsObject, PyObjectRef, PyRef, PyResult, VirtualMachine};
1210

1311
define_py_check!(fn PyCFunction_Check, types.builtin_function_or_method_type);
@@ -82,13 +80,6 @@ pub(crate) fn build_method_def(
8280

8381
let flags = PyMethodFlags::from_bits(ml.ml_flags as u32)
8482
.ok_or_else(|| vm.new_system_error("PyMethodDef contains unknown flags"))?;
85-
let keyword_dispatch = if flags.contains(PyMethodFlags::KEYWORDS) {
86-
KeywordDispatch::PassToBinder
87-
} else if flags.contains(PyMethodFlags::VARARGS) {
88-
KeywordDispatch::RejectNonemptyUnqualified
89-
} else {
90-
KeywordDispatch::RejectNonempty
91-
};
9283
let has_self = has_self && !flags.contains(PyMethodFlags::STATIC);
9384

9485
let method = ml.ml_meth;
@@ -111,51 +102,51 @@ pub(crate) fn build_method_def(
111102
let f = method.PyCFunction;
112103
f(zelf.as_raw().cast_mut(), core::ptr::null_mut()).assume_owned_or_err(vm)
113104
};
114-
Ok(vm.ctx.new_method_def(name, callable, flags, doc, keyword_dispatch))
105+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
115106
} else {
116107
let callable = move |vm: &VirtualMachine| unsafe {
117108
let f = method.PyCFunction;
118109
f(core::ptr::null_mut(), core::ptr::null_mut()).assume_owned_or_err(vm)
119110
};
120-
Ok(vm.ctx.new_method_def(name, callable, flags, doc, keyword_dispatch))
111+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
121112
}
122113
},
123114
PyMethodFlags::VARARGS => {
124115
let callable = move |args: PosArgs, vm: &VirtualMachine| unsafe {
125116
call_function(vm, method, flags, Some(args))
126117
};
127-
Ok(vm.ctx.new_method_def(name, callable, flags, doc, keyword_dispatch))
118+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
128119
},
129120
PyMethodFlags::VARARGS | PyMethodFlags::KEYWORDS => {
130121
let callable = move | args: FuncArgs, vm: &VirtualMachine| unsafe {
131122
call_function_with_keywords(vm, method, flags, args)
132123
};
133-
Ok(vm.ctx.new_method_def(name, callable, flags, doc, keyword_dispatch))
124+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
134125
},
135126
PyMethodFlags::FASTCALL | PyMethodFlags::KEYWORDS => {
136127
let callable = move |args: FuncArgs, vm: &VirtualMachine| unsafe {
137128
call_fast_function_with_keywords(vm, method, flags, args)
138129
};
139-
Ok(vm.ctx.new_method_def(name, callable, flags, doc, keyword_dispatch))
130+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
140131
},
141132
PyMethodFlags::FASTCALL => {
142133
let callable = move |args: PosArgs, vm: &VirtualMachine| unsafe {
143134
call_fast_function(vm, method, flags, args)
144135
};
145-
Ok(vm.ctx.new_method_def(name, callable, flags, doc, keyword_dispatch))
136+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
146137
},
147138
PyMethodFlags::O => {
148139
let f = unsafe { method.PyCFunction };
149140
if has_self {
150141
let callable = move |zelf: PyObjectRef, arg: PyObjectRef, vm: &VirtualMachine| -> PyResult {
151142
unsafe { f(zelf.as_raw().cast_mut(), arg.as_raw().cast_mut()).assume_owned_or_err(vm) }
152143
};
153-
Ok(vm.ctx.new_method_def(name, callable, flags, doc, keyword_dispatch))
144+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
154145
} else {
155146
let callable = move |arg: PyObjectRef, vm: &VirtualMachine| -> PyResult {
156147
unsafe { f(core::ptr::null_mut(), arg.as_raw().cast_mut()).assume_owned_or_err(vm) }
157148
};
158-
Ok(vm.ctx.new_method_def(name, callable, flags, doc, keyword_dispatch))
149+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
159150
}
160151
},
161152
_ => {

‎crates/derive-impl/src/pyclass.rs‎

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
use super::Diagnostic;
22
use crate::util::{
33
ALL_ALLOWED_NAMES, ClassItemMeta, ContentItem, ContentItemInner, ErrorVec, ExceptionItemMeta,
4-
ItemMeta, ItemMetaInner, ItemNursery, SimpleItemMeta, infer_native_call_flags,
5-
internal_doc_tokens, keyword_dispatch_tokens, pyclass_ident_and_attrs,
6-
pyexception_ident_and_attrs,
4+
ItemMeta, ItemMetaInner, ItemNursery, SimpleItemMeta, internal_doc_tokens,
5+
pyclass_ident_and_attrs, pyexception_ident_and_attrs,
76
};
87
use core::str::FromStr;
98
use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree};
@@ -1308,14 +1307,6 @@ where
13081307
_ => None,
13091308
}
13101309
};
1311-
let drop_first_typed = usize::from(implicit_self.is_some());
1312-
let call_flags = infer_native_call_flags(func.sig(), drop_first_typed);
1313-
let keyword_dispatch = keyword_dispatch_tokens(
1314-
func.sig(),
1315-
implicit_self,
1316-
args.context.self_ty_subst.as_ref(),
1317-
);
1318-
13191310
// Add #[allow(non_snake_case)] for setter methods like set___name__
13201311
let method_name = ident.to_string();
13211312
if method_name.starts_with("set_") && method_name.contains("__") {
@@ -1345,10 +1336,8 @@ where
13451336
doc,
13461337
doc_body_pending,
13471338
raw,
1348-
keyword_dispatch,
13491339
coexist,
13501340
attr_name: self.inner.attr_name,
1351-
call_flags,
13521341
});
13531342
Ok(())
13541343
}
@@ -1541,12 +1530,10 @@ struct MethodNurseryItem {
15411530
cfgs: Vec<Attribute>,
15421531
ident: Ident,
15431532
raw: bool,
1544-
keyword_dispatch: TokenStream,
15451533
coexist: bool,
15461534
doc: TokenStream,
15471535
doc_body_pending: TokenStream,
15481536
attr_name: AttrName,
1549-
call_flags: TokenStream,
15501537
}
15511538

15521539
impl MethodNursery {
@@ -1574,7 +1561,6 @@ impl ToTokens for MethodNursery {
15741561
let cfgs = &item.cfgs;
15751562
let doc = &item.doc;
15761563
let doc_body_pending = &item.doc_body_pending;
1577-
let keyword_dispatch = &item.keyword_dispatch;
15781564
let binding_flags = match &item.attr_name {
15791565
AttrName::Method => {
15801566
quote! { rustpython_vm::function::PyMethodFlags::METHOD }
@@ -1587,15 +1573,14 @@ impl ToTokens for MethodNursery {
15871573
}
15881574
_ => unreachable!(),
15891575
};
1590-
let call_flags = &item.call_flags;
15911576
let coexist_flags = if item.coexist {
15921577
quote! { | rustpython_vm::function::PyMethodFlags::COEXIST.bits() }
15931578
} else {
15941579
quote! {}
15951580
};
15961581
let flags = quote! {
15971582
rustpython_vm::function::PyMethodFlags::from_bits_retain(
1598-
(#binding_flags).bits() | (#call_flags).bits() #coexist_flags
1583+
(#binding_flags).bits() #coexist_flags
15991584
)
16001585
};
16011586
// TODO: intern
@@ -1618,7 +1603,7 @@ impl ToTokens for MethodNursery {
16181603
Self::#ident,
16191604
#flags,
16201605
#doc,
1621-
).with_keyword_dispatch(#keyword_dispatch);
1606+
);
16221607
::rustpython_vm::__cfg_doc!({
16231608
def.doc_body_pending = #doc_body_pending;
16241609
} else {});

‎crates/derive-impl/src/pymodule.rs‎

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use crate::error::Diagnostic;
22
use crate::pystructseq::PyStructSequenceMeta;
33
use crate::util::{
44
ALL_ALLOWED_NAMES, AttrItemMeta, AttributeExt, ClassItemMeta, ContentItem, ContentItemInner,
5-
ErrorVec, ItemMeta, ItemNursery, ModuleItemMeta, SimpleItemMeta, infer_native_call_flags,
6-
internal_doc_tokens, iter_use_idents, keyword_dispatch_tokens, pyclass_ident_and_attrs,
5+
ErrorVec, ItemMeta, ItemNursery, ModuleItemMeta, SimpleItemMeta, internal_doc_tokens,
6+
iter_use_idents, pyclass_ident_and_attrs,
77
};
88
use core::str::FromStr;
99
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
@@ -536,8 +536,6 @@ struct FunctionNurseryItem {
536536
ident: Ident,
537537
/// One internal doc per [`py_names`](Self::py_names) entry.
538538
docs: Vec<TokenStream>,
539-
call_flags: TokenStream,
540-
keyword_dispatch: TokenStream,
541539
}
542540

543541
impl FunctionNursery {
@@ -567,17 +565,15 @@ impl ToTokens for ValidatedFunctionNursery {
567565
let ident = &item.ident;
568566
let cfgs = &item.cfgs;
569567
let cfgs = quote!(#(#cfgs)*);
570-
let flags = &item.call_flags;
571-
let keyword_dispatch = &item.keyword_dispatch;
572568
for (py_name, doc) in item.py_names.iter().zip(&item.docs) {
573569
inner_tokens.extend(quote![
574570
#cfgs
575571
rustpython_vm::function::PyMethodDef::new_const(
576572
#py_name,
577573
#ident,
578-
#flags,
574+
rustpython_vm::function::PyMethodFlags::empty(),
579575
#doc,
580-
).with_keyword_dispatch(#keyword_dispatch),
576+
),
581577
]);
582578
}
583579
}
@@ -713,16 +709,12 @@ impl ModuleItem for FunctionItem {
713709
internal_doc_tokens(func.sig(), py_name, None, doc, None, Some("$module"))
714710
})
715711
.collect();
716-
let call_flags = infer_native_call_flags(func.sig(), 0);
717-
let keyword_dispatch = keyword_dispatch_tokens(func.sig(), None, None);
718712

719713
args.context.function_items.add_item(FunctionNurseryItem {
720714
ident: ident.to_owned(),
721715
py_names,
722716
cfgs: args.cfgs.to_vec(),
723717
docs,
724-
call_flags,
725-
keyword_dispatch,
726718
});
727719
Ok(())
728720
}

‎crates/derive-impl/src/util.rs‎

Lines changed: 0 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -936,91 +936,3 @@ pub(crate) fn internal_doc_tokens(
936936
}
937937
}
938938
}
939-
940-
pub(crate) fn keyword_dispatch_tokens(
941-
sig: &Signature,
942-
implicit_self: Option<&str>,
943-
self_ty: Option<&Type>,
944-
) -> TokenStream {
945-
let args = args_const(&sig_pieces(sig, implicit_self, self_ty, None));
946-
quote! {
947-
{
948-
#args
949-
const POLICY: ::rustpython_vm::function::KeywordDispatch =
950-
::rustpython_vm::function::keyword_dispatch(ARGS);
951-
POLICY
952-
}
953-
}
954-
}
955-
956-
pub(crate) fn infer_native_call_flags(sig: &Signature, drop_first_typed: usize) -> TokenStream {
957-
// Best-effort mapping of Rust function signatures to CPython-style
958-
// METH_* calling convention flags used by CALL specialization.
959-
let mut typed_args = Vec::new();
960-
for arg in &sig.inputs {
961-
let FnArg::Typed(typed) = arg else {
962-
continue;
963-
};
964-
let ty_tokens = &typed.ty;
965-
let ty = quote!(#ty_tokens).to_string().replace(' ', "");
966-
// The interpreter supplies `vm` and `callee`; a Python call never
967-
// passes them.
968-
if (ty.starts_with('&') && ty.ends_with("VirtualMachine")) || ty.ends_with("Callee") {
969-
continue;
970-
}
971-
typed_args.push(ty);
972-
}
973-
974-
let mut user_args = typed_args.into_iter();
975-
for _ in 0..drop_first_typed {
976-
if user_args.next().is_none() {
977-
break;
978-
}
979-
}
980-
981-
let mut has_keywords = false;
982-
let mut variable_arity = false;
983-
let mut fixed_positional = 0usize;
984-
985-
for ty in user_args {
986-
let is_named = |name: &str| {
987-
ty == name
988-
|| ty.starts_with(&format!("{name}<"))
989-
|| ty.contains(&format!("::{name}<"))
990-
|| ty.ends_with(&format!("::{name}"))
991-
};
992-
993-
if is_named("FuncArgs") {
994-
has_keywords = true;
995-
variable_arity = true;
996-
continue;
997-
}
998-
if is_named("KwArgs") {
999-
has_keywords = true;
1000-
variable_arity = true;
1001-
continue;
1002-
}
1003-
if is_named("PosArgs") || is_named("OptionalArg") || is_named("OptionalOption") {
1004-
variable_arity = true;
1005-
continue;
1006-
}
1007-
fixed_positional += 1;
1008-
}
1009-
1010-
if has_keywords {
1011-
quote! {
1012-
rustpython_vm::function::PyMethodFlags::from_bits_retain(
1013-
rustpython_vm::function::PyMethodFlags::FASTCALL.bits()
1014-
| rustpython_vm::function::PyMethodFlags::KEYWORDS.bits()
1015-
)
1016-
}
1017-
} else if variable_arity {
1018-
quote! { rustpython_vm::function::PyMethodFlags::FASTCALL }
1019-
} else {
1020-
match fixed_positional {
1021-
0 => quote! { rustpython_vm::function::PyMethodFlags::NOARGS },
1022-
1 => quote! { rustpython_vm::function::PyMethodFlags::O },
1023-
_ => quote! { rustpython_vm::function::PyMethodFlags::FASTCALL },
1024-
}
1025-
}
1026-
}

‎crates/vm/src/builtins/builtin_func.rs‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ use crate::{
44
class::PyClassImpl,
55
common::wtf8::Wtf8,
66
convert::TryFromObject,
7-
function::{
8-
Callee, FuncArgs, KeywordDispatch, PyComparisonValue, PyMethodDef, PyMethodFlags,
9-
PyNativeFn,
10-
},
7+
function::{Callee, FuncArgs, PyComparisonValue, PyMethodDef, PyMethodFlags, PyNativeFn},
118
types::{Callable, Comparable, PyComparisonOp, Representable},
129
};
1310
use alloc::fmt;
@@ -90,7 +87,7 @@ impl Callable for PyNativeFunction {
9087
type Args = FuncArgs;
9188
#[inline]
9289
fn call(zelf: &Py<Self>, mut args: FuncArgs, vm: &VirtualMachine) -> PyResult {
93-
if !args.kwargs.is_empty() && zelf.value.keyword_dispatch != KeywordDispatch::PassToBinder {
90+
if !args.kwargs.is_empty() && !zelf.value.flags.contains(PyMethodFlags::KEYWORDS) {
9491
return Err(native_no_keywords_error(zelf, vm)?);
9592
}
9693
let mut callee = Callee::named(zelf.value.name);
@@ -275,7 +272,7 @@ fn vectorcall_native_function(
275272
let zelf: &Py<PyNativeFunction> = zelf_obj.downcast_ref().unwrap();
276273

277274
if kwnames.is_some_and(|names| !names.is_empty())
278-
&& zelf.value.keyword_dispatch != KeywordDispatch::PassToBinder
275+
&& !zelf.value.flags.contains(PyMethodFlags::KEYWORDS)
279276
{
280277
return Err(native_no_keywords_error(zelf, vm)?);
281278
}
@@ -305,7 +302,7 @@ fn native_no_keywords_error(
305302
zelf: &Py<PyNativeFunction>,
306303
vm: &VirtualMachine,
307304
) -> PyResult<crate::exceptions::types::PyBaseExceptionRef> {
308-
if zelf.value.keyword_dispatch == KeywordDispatch::RejectNonemptyUnqualified {
305+
if zelf.value.flags.contains(PyMethodFlags::VARARGS) {
309306
return Ok(vm.new_type_error(format!("{}() takes no keyword arguments", zelf.value.name)));
310307
}
311308
let qualname = PyNativeFunction::__qualname__(NativeFunctionOrMethod(zelf.to_owned()), vm)?;

‎crates/vm/src/builtins/descriptor.rs‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ use crate::{
66
common::hash::PyHash,
77
convert::{ToPyObject, ToPyResult},
88
function::{
9-
Callee, FuncArgs, ItemDoc, KeywordDispatch, PyMethodDef, PyMethodFlags, PySetterValue,
10-
PySsize, plain_doc,
9+
Callee, FuncArgs, ItemDoc, PyMethodDef, PyMethodFlags, PySetterValue, PySsize, plain_doc,
1110
},
1211
protocol::{PyNumberBinaryFunc, PyNumberTernaryFunc, PyNumberUnaryFunc},
1312
types::{
@@ -114,8 +113,7 @@ impl Callable for PyMethodDescriptor {
114113
#[inline]
115114
fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
116115
method_descr_check_self(zelf, args.args.first().map(|obj| &**obj), vm)?;
117-
if !args.kwargs.is_empty() && zelf.method.keyword_dispatch != KeywordDispatch::PassToBinder
118-
{
116+
if !args.kwargs.is_empty() && !zelf.method.flags.contains(PyMethodFlags::KEYWORDS) {
119117
return Err(method_descr_no_keywords_error(zelf, vm));
120118
}
121119
(zelf.method.func)(
@@ -1612,7 +1610,7 @@ fn vectorcall_method_descriptor(
16121610
vm,
16131611
)?;
16141612
if kwnames.is_some_and(|names| !names.is_empty())
1615-
&& zelf.method.keyword_dispatch != KeywordDispatch::PassToBinder
1613+
&& !zelf.method.flags.contains(PyMethodFlags::KEYWORDS)
16161614
{
16171615
return Err(method_descr_no_keywords_error(zelf, vm));
16181616
}

‎crates/vm/src/codecs.rs‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ impl CodecsRegistry {
177177
name: $name,
178178
func: crate::function::static_func($func),
179179
flags: crate::function::PyMethodFlags::O,
180-
keyword_dispatch: crate::function::KeywordDispatch::PassToBinder,
181180
#[cfg(feature = "doc")]
182181
doc_off: DOC.offset,
183182
#[cfg(feature = "doc")]

0 commit comments

Comments
 (0)