mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Infer native calling convention flags from argument metadata (#9005)
`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 infer call flags from Rust type names. Definitions built without the macros, such as the `__new__` wrapper and `vm.new_function`, get calling convention flags the same way. Assisted-by: Claude Code:claude-opus-5-5
This commit is contained in:
1 parent
ca9dbb40f8
commit
bb9ef5d0c2
7 files changed
+180
-96
No files matched your search
@@ -1,8 +1,8 @@
|
||||
use super::Diagnostic;
|
||||
use crate::util::{
|
||||
ALL_ALLOWED_NAMES, ClassItemMeta, ContentItem, ContentItemInner, ErrorVec, ExceptionItemMeta,
|
||||
ItemMeta, ItemMetaInner, ItemNursery, SimpleItemMeta, infer_native_call_flags,
|
||||
internal_doc_tokens, pyclass_ident_and_attrs, pyexception_ident_and_attrs,
|
||||
ItemMeta, ItemMetaInner, ItemNursery, SimpleItemMeta, internal_doc_tokens,
|
||||
pyclass_ident_and_attrs, pyexception_ident_and_attrs,
|
||||
};
|
||||
use core::str::FromStr;
|
||||
use proc_macro2::{Delimiter, Group, Span, TokenStream, TokenTree};
|
||||
@@ -1307,9 +1307,6 @@ where
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
let drop_first_typed = usize::from(implicit_self.is_some());
|
||||
let call_flags = infer_native_call_flags(func.sig(), drop_first_typed);
|
||||
|
||||
// Add #[allow(non_snake_case)] for setter methods like set___name__
|
||||
let method_name = ident.to_string();
|
||||
if method_name.starts_with("set_") && method_name.contains("__") {
|
||||
@@ -1341,7 +1338,6 @@ where
|
||||
raw,
|
||||
coexist,
|
||||
attr_name: self.inner.attr_name,
|
||||
call_flags,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
@@ -1538,7 +1534,6 @@ struct MethodNurseryItem {
|
||||
doc: TokenStream,
|
||||
doc_body_pending: TokenStream,
|
||||
attr_name: AttrName,
|
||||
call_flags: TokenStream,
|
||||
}
|
||||
|
||||
impl MethodNursery {
|
||||
@@ -1578,7 +1573,6 @@ impl ToTokens for MethodNursery {
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let call_flags = &item.call_flags;
|
||||
let coexist_flags = if item.coexist {
|
||||
quote! { | rustpython_vm::function::PyMethodFlags::COEXIST.bits() }
|
||||
} else {
|
||||
@@ -1586,7 +1580,7 @@ impl ToTokens for MethodNursery {
|
||||
};
|
||||
let flags = quote! {
|
||||
rustpython_vm::function::PyMethodFlags::from_bits_retain(
|
||||
(#binding_flags).bits() | (#call_flags).bits() #coexist_flags
|
||||
(#binding_flags).bits() #coexist_flags
|
||||
)
|
||||
};
|
||||
// TODO: intern
|
||||
|
||||
@@ -2,8 +2,8 @@ use crate::error::Diagnostic;
|
||||
use crate::pystructseq::PyStructSequenceMeta;
|
||||
use crate::util::{
|
||||
ALL_ALLOWED_NAMES, AttrItemMeta, AttributeExt, ClassItemMeta, ContentItem, ContentItemInner,
|
||||
ErrorVec, ItemMeta, ItemNursery, ModuleItemMeta, SimpleItemMeta, infer_native_call_flags,
|
||||
internal_doc_tokens, iter_use_idents, pyclass_ident_and_attrs,
|
||||
ErrorVec, ItemMeta, ItemNursery, ModuleItemMeta, SimpleItemMeta, internal_doc_tokens,
|
||||
iter_use_idents, pyclass_ident_and_attrs,
|
||||
};
|
||||
use core::str::FromStr;
|
||||
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
|
||||
@@ -536,7 +536,6 @@ struct FunctionNurseryItem {
|
||||
ident: Ident,
|
||||
/// One internal doc per [`py_names`](Self::py_names) entry.
|
||||
docs: Vec<TokenStream>,
|
||||
call_flags: TokenStream,
|
||||
}
|
||||
|
||||
impl FunctionNursery {
|
||||
@@ -566,14 +565,13 @@ impl ToTokens for ValidatedFunctionNursery {
|
||||
let ident = &item.ident;
|
||||
let cfgs = &item.cfgs;
|
||||
let cfgs = quote!(#(#cfgs)*);
|
||||
let flags = &item.call_flags;
|
||||
for (py_name, doc) in item.py_names.iter().zip(&item.docs) {
|
||||
inner_tokens.extend(quote![
|
||||
#cfgs
|
||||
rustpython_vm::function::PyMethodDef::new_const(
|
||||
#py_name,
|
||||
#ident,
|
||||
#flags,
|
||||
rustpython_vm::function::PyMethodFlags::empty(),
|
||||
#doc,
|
||||
),
|
||||
]);
|
||||
@@ -711,14 +709,12 @@ impl ModuleItem for FunctionItem {
|
||||
internal_doc_tokens(func.sig(), py_name, None, doc, None, Some("$module"))
|
||||
})
|
||||
.collect();
|
||||
let call_flags = infer_native_call_flags(func.sig(), 0);
|
||||
|
||||
args.context.function_items.add_item(FunctionNurseryItem {
|
||||
ident: ident.to_owned(),
|
||||
py_names,
|
||||
cfgs: args.cfgs.to_vec(),
|
||||
docs,
|
||||
call_flags,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -936,75 +936,3 @@ pub(crate) fn internal_doc_tokens(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn infer_native_call_flags(sig: &Signature, drop_first_typed: usize) -> TokenStream {
|
||||
// Best-effort mapping of Rust function signatures to CPython-style
|
||||
// METH_* calling convention flags used by CALL specialization.
|
||||
let mut typed_args = Vec::new();
|
||||
for arg in &sig.inputs {
|
||||
let FnArg::Typed(typed) = arg else {
|
||||
continue;
|
||||
};
|
||||
let ty_tokens = &typed.ty;
|
||||
let ty = quote!(#ty_tokens).to_string().replace(' ', "");
|
||||
// The interpreter supplies `vm` and `callee`; a Python call never
|
||||
// passes them.
|
||||
if (ty.starts_with('&') && ty.ends_with("VirtualMachine")) || ty.ends_with("Callee") {
|
||||
continue;
|
||||
}
|
||||
typed_args.push(ty);
|
||||
}
|
||||
|
||||
let mut user_args = typed_args.into_iter();
|
||||
for _ in 0..drop_first_typed {
|
||||
if user_args.next().is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut has_keywords = false;
|
||||
let mut variable_arity = false;
|
||||
let mut fixed_positional = 0usize;
|
||||
|
||||
for ty in user_args {
|
||||
let is_named = |name: &str| {
|
||||
ty == name
|
||||
|| ty.starts_with(&format!("{name}<"))
|
||||
|| ty.contains(&format!("::{name}<"))
|
||||
|| ty.ends_with(&format!("::{name}"))
|
||||
};
|
||||
|
||||
if is_named("FuncArgs") {
|
||||
has_keywords = true;
|
||||
variable_arity = true;
|
||||
continue;
|
||||
}
|
||||
if is_named("KwArgs") {
|
||||
has_keywords = true;
|
||||
variable_arity = true;
|
||||
continue;
|
||||
}
|
||||
if is_named("PosArgs") || is_named("OptionalArg") || is_named("OptionalOption") {
|
||||
variable_arity = true;
|
||||
continue;
|
||||
}
|
||||
fixed_positional += 1;
|
||||
}
|
||||
|
||||
if has_keywords {
|
||||
quote! {
|
||||
rustpython_vm::function::PyMethodFlags::from_bits_retain(
|
||||
rustpython_vm::function::PyMethodFlags::FASTCALL.bits()
|
||||
| rustpython_vm::function::PyMethodFlags::KEYWORDS.bits()
|
||||
)
|
||||
}
|
||||
} else if variable_arity {
|
||||
quote! { rustpython_vm::function::PyMethodFlags::FASTCALL }
|
||||
} else {
|
||||
match fixed_positional {
|
||||
0 => quote! { rustpython_vm::function::PyMethodFlags::NOARGS },
|
||||
1 => quote! { rustpython_vm::function::PyMethodFlags::O },
|
||||
_ => quote! { rustpython_vm::function::PyMethodFlags::FASTCALL },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{Callee, FromArgs, FuncArgs};
|
||||
use super::{Callee, FromArgs, FuncArgs, SigArg};
|
||||
use crate::{
|
||||
Py, PyPayload, PyRef, PyResult, VirtualMachine, convert::ToPyResult,
|
||||
object::PyThreadingConstraint,
|
||||
@@ -37,6 +37,10 @@ impl<F> PyNativeFn for F where
|
||||
/// just pass an unconstrained generic type, e.g.
|
||||
/// `fn foo<F, FKind>(f: F) where F: IntoPyNativeFn<FKind>`
|
||||
pub trait IntoPyNativeFn<Kind>: Sized + PyThreadingConstraint + 'static {
|
||||
/// Binding metadata of each argument, from which the calling convention
|
||||
/// is inferred. A `&self` receiver is the `$self` marker.
|
||||
const ARGS: &'static [SigArg] = &[SigArg::from_arg::<FuncArgs>("args")];
|
||||
|
||||
fn call(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult;
|
||||
|
||||
/// `IntoPyNativeFn::into_func()` generates a PyNativeFn that performs the
|
||||
@@ -89,6 +93,8 @@ impl<F, T, R, VM> IntoPyNativeFn<(T, R, VM)> for F
|
||||
where
|
||||
F: PyNativeFnInternal<T, R, VM>,
|
||||
{
|
||||
const ARGS: &'static [SigArg] = F::ARGS;
|
||||
|
||||
#[inline(always)]
|
||||
fn call(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult {
|
||||
self.call_(vm, args, callee)
|
||||
@@ -98,6 +104,7 @@ where
|
||||
mod sealed {
|
||||
use super::*;
|
||||
pub trait PyNativeFnInternal<T, R, VM>: Sized + PyThreadingConstraint + 'static {
|
||||
const ARGS: &'static [SigArg];
|
||||
fn call_(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult;
|
||||
}
|
||||
}
|
||||
@@ -124,6 +131,8 @@ macro_rules! into_py_native_fn_tuple {
|
||||
$($T: FromArgs,)*
|
||||
R: ToPyResult,
|
||||
{
|
||||
const ARGS: &'static [SigArg] = &[$(SigArg::from_arg::<$T>("")),*];
|
||||
|
||||
fn call_(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult {
|
||||
let ($($n,)*) = args.bind_for::<($($T,)*)>(vm, callee)?;
|
||||
|
||||
@@ -138,6 +147,9 @@ macro_rules! into_py_native_fn_tuple {
|
||||
$($T: FromArgs,)*
|
||||
R: ToPyResult,
|
||||
{
|
||||
const ARGS: &'static [SigArg] =
|
||||
&[SigArg::marker("$self") $(, SigArg::from_arg::<$T>(""))*];
|
||||
|
||||
fn call_(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult {
|
||||
let (zelf, $($n,)*) = args.bind_for::<(PyRef<S>, $($T,)*)>(vm, callee)?;
|
||||
|
||||
@@ -152,6 +164,9 @@ macro_rules! into_py_native_fn_tuple {
|
||||
$($T: FromArgs,)*
|
||||
R: ToPyResult,
|
||||
{
|
||||
const ARGS: &'static [SigArg] =
|
||||
&[SigArg::marker("$self") $(, SigArg::from_arg::<$T>(""))*];
|
||||
|
||||
fn call_(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult {
|
||||
let (zelf, $($n,)*) = args.bind_for::<(PyRef<S>, $($T,)*)>(vm, callee)?;
|
||||
|
||||
@@ -165,6 +180,8 @@ macro_rules! into_py_native_fn_tuple {
|
||||
$($T: FromArgs,)*
|
||||
R: ToPyResult,
|
||||
{
|
||||
const ARGS: &'static [SigArg] = &[$(SigArg::from_arg::<$T>("")),*];
|
||||
|
||||
fn call_(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult {
|
||||
let ($($n,)*) = args.bind_for::<($($T,)*)>(vm, callee)?;
|
||||
|
||||
@@ -179,6 +196,9 @@ macro_rules! into_py_native_fn_tuple {
|
||||
$($T: FromArgs,)*
|
||||
R: ToPyResult,
|
||||
{
|
||||
const ARGS: &'static [SigArg] =
|
||||
&[SigArg::marker("$self") $(, SigArg::from_arg::<$T>(""))*];
|
||||
|
||||
fn call_(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult {
|
||||
let (zelf, $($n,)*) = args.bind_for::<(PyRef<S>, $($T,)*)>(vm, callee)?;
|
||||
|
||||
@@ -193,6 +213,9 @@ macro_rules! into_py_native_fn_tuple {
|
||||
$($T: FromArgs,)*
|
||||
R: ToPyResult,
|
||||
{
|
||||
const ARGS: &'static [SigArg] =
|
||||
&[SigArg::marker("$self") $(, SigArg::from_arg::<$T>(""))*];
|
||||
|
||||
fn call_(&self, vm: &VirtualMachine, args: FuncArgs, callee: Callee) -> PyResult {
|
||||
let (zelf, $($n,)*) = args.bind_for::<(PyRef<S>, $($T,)*)>(vm, callee)?;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
descriptor::{PyClassMethodDescriptor, PyMethodDescriptor},
|
||||
},
|
||||
class::PyClassDef,
|
||||
function::{IntoPyNativeFn, PyNativeFn},
|
||||
function::{FuncArgs, IntoPyNativeFn, PyNativeFn, SigArg},
|
||||
};
|
||||
|
||||
bitflags::bitflags! {
|
||||
@@ -49,6 +49,22 @@ bitflags::bitflags! {
|
||||
impl PyMethodFlags {
|
||||
// FIXME: macro temp
|
||||
pub const EMPTY: Self = Self::empty();
|
||||
|
||||
const CALL_CONVENTION: Self = Self::VARARGS
|
||||
.union(Self::KEYWORDS)
|
||||
.union(Self::NOARGS)
|
||||
.union(Self::O)
|
||||
.union(Self::FASTCALL);
|
||||
|
||||
/// Adds the calling convention inferred from `args` unless one is set.
|
||||
/// For METHOD and CLASS the first argument is the bound receiver.
|
||||
pub(crate) const fn with_call_convention(self, args: &[SigArg]) -> Self {
|
||||
if self.intersects(Self::CALL_CONVENTION) {
|
||||
return self;
|
||||
}
|
||||
let receiver = self.intersects(Self::METHOD.union(Self::CLASS));
|
||||
self.union(super::signature::native_call_flags(args, receiver))
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
@@ -115,16 +131,16 @@ impl PyMethodDef {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn new_const<Kind>(
|
||||
pub const fn new_const<Kind, F: IntoPyNativeFn<Kind>>(
|
||||
name: &'static str,
|
||||
func: impl IntoPyNativeFn<Kind>,
|
||||
func: F,
|
||||
flags: PyMethodFlags,
|
||||
doc: super::ItemDoc,
|
||||
) -> Self {
|
||||
Self {
|
||||
name,
|
||||
func: super::static_func(func),
|
||||
flags,
|
||||
flags: flags.with_call_convention(F::ARGS),
|
||||
#[cfg(feature = "doc")]
|
||||
doc_off: doc.offset,
|
||||
#[cfg(feature = "doc")]
|
||||
@@ -145,7 +161,7 @@ impl PyMethodDef {
|
||||
Self {
|
||||
name,
|
||||
func: super::static_raw_func(func),
|
||||
flags,
|
||||
flags: flags.with_call_convention(&[SigArg::from_arg::<FuncArgs>("args")]),
|
||||
#[cfg(feature = "doc")]
|
||||
doc_off: doc.offset,
|
||||
#[cfg(feature = "doc")]
|
||||
|
||||
@@ -135,6 +135,81 @@ impl SigArg {
|
||||
}
|
||||
}
|
||||
|
||||
/// Calling-convention bits from each argument's binding metadata.
|
||||
///
|
||||
/// Inspect arguments separately: a tuple's `PARAMS` can erase leaf metadata.
|
||||
/// With `receiver`, the first argument is the bound `$self` / `$type`; it
|
||||
/// contributes only the parameters its type supplies.
|
||||
/// A keyword-capable parameter uses `FASTCALL | KEYWORDS`.
|
||||
#[must_use]
|
||||
pub(crate) const fn native_call_flags(args: &[SigArg], receiver: bool) -> super::PyMethodFlags {
|
||||
let mut has_keywords = false;
|
||||
let mut variable = false;
|
||||
let mut fixed = 0usize;
|
||||
let mut i = 0;
|
||||
while i < args.len() {
|
||||
match args[i].params {
|
||||
Some(params) => count_params(params, &mut has_keywords, &mut variable, &mut fixed),
|
||||
None if receiver && i == 0 => {}
|
||||
None => fixed += 1,
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if has_keywords {
|
||||
super::PyMethodFlags::FASTCALL.union(super::PyMethodFlags::KEYWORDS)
|
||||
} else if variable {
|
||||
super::PyMethodFlags::FASTCALL
|
||||
} else {
|
||||
match fixed {
|
||||
0 => super::PyMethodFlags::NOARGS,
|
||||
1 => super::PyMethodFlags::O,
|
||||
_ => super::PyMethodFlags::FASTCALL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn count_params(
|
||||
params: &[Param],
|
||||
has_keywords: &mut bool,
|
||||
variable: &mut bool,
|
||||
fixed: &mut usize,
|
||||
) {
|
||||
let mut i = 0;
|
||||
while i < params.len() {
|
||||
count_param(¶ms[i], has_keywords, variable, fixed);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const fn count_param(
|
||||
param: &Param,
|
||||
has_keywords: &mut bool,
|
||||
variable: &mut bool,
|
||||
fixed: &mut usize,
|
||||
) {
|
||||
match param.kind {
|
||||
ParamKind::PositionalOrKeyword => {
|
||||
*has_keywords = true;
|
||||
if param.default.is_some() {
|
||||
*variable = true;
|
||||
} else {
|
||||
*fixed += 1;
|
||||
}
|
||||
}
|
||||
ParamKind::KeywordOnly | ParamKind::VarKeyword => *has_keywords = true,
|
||||
ParamKind::VarPositional => *variable = true,
|
||||
ParamKind::PositionalOnly => {
|
||||
if param.default.is_some() {
|
||||
*variable = true;
|
||||
} else {
|
||||
*fixed += 1;
|
||||
}
|
||||
}
|
||||
ParamKind::Flatten(Some(inner)) => count_params(inner, has_keywords, variable, fixed),
|
||||
ParamKind::Flatten(None) => {}
|
||||
}
|
||||
}
|
||||
|
||||
const fn name_eq(name: &str, bytes: &[u8]) -> bool {
|
||||
let got = name.as_bytes();
|
||||
if got.len() != bytes.len() {
|
||||
@@ -631,7 +706,8 @@ const fn write_args(buf: &mut [u8], mut st: St, args: &[SigArg]) -> St {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{DefaultRepr, Param, ParamKind, St, write_one};
|
||||
use super::{DefaultRepr, Param, ParamKind, SigArg, St, native_call_flags, write_one};
|
||||
use crate::function::PyMethodFlags;
|
||||
|
||||
fn rendered(default: DefaultRepr) -> String {
|
||||
let mut buf = [0u8; 64];
|
||||
@@ -674,4 +750,55 @@ mod tests {
|
||||
assert_eq!(rendered(DefaultRepr::Raw("sys.maxsize")), "sys.maxsize");
|
||||
assert_eq!(rendered(DefaultRepr::Unrepresentable), "<unrepresentable>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_call_flags_from_params() {
|
||||
const fn arg(params: Option<&'static [Param]>) -> SigArg {
|
||||
SigArg { name: "", params }
|
||||
}
|
||||
let keywords = PyMethodFlags::FASTCALL.union(PyMethodFlags::KEYWORDS);
|
||||
assert_eq!(native_call_flags(&[], false), PyMethodFlags::NOARGS);
|
||||
assert_eq!(native_call_flags(&[arg(None)], false), PyMethodFlags::O);
|
||||
assert_eq!(
|
||||
native_call_flags(&[arg(None), arg(None)], false),
|
||||
PyMethodFlags::FASTCALL
|
||||
);
|
||||
// The receiver is not a Python argument.
|
||||
assert_eq!(
|
||||
native_call_flags(&[SigArg::marker("$self"), arg(None)], true),
|
||||
PyMethodFlags::O
|
||||
);
|
||||
assert_eq!(native_call_flags(&[arg(None)], true), PyMethodFlags::NOARGS);
|
||||
|
||||
const OPTIONAL: &[Param] = &[Param {
|
||||
name: "",
|
||||
kind: ParamKind::PositionalOnly,
|
||||
default: Some(DefaultRepr::Unrepresentable),
|
||||
}];
|
||||
assert_eq!(
|
||||
native_call_flags(&[arg(Some(OPTIONAL))], false),
|
||||
PyMethodFlags::FASTCALL
|
||||
);
|
||||
|
||||
const KW: &[Param] = &[Param::positional_or_keyword("exp")];
|
||||
assert_eq!(native_call_flags(&[arg(Some(KW))], false), keywords);
|
||||
|
||||
const FUNCARGS: &[Param] = &[Param::var_positional("args"), Param::var_keyword("kwargs")];
|
||||
assert_eq!(native_call_flags(&[arg(Some(FUNCARGS))], false), keywords);
|
||||
// A receiver that binds the whole call keeps its parameters.
|
||||
assert_eq!(native_call_flags(&[arg(Some(FUNCARGS))], true), keywords);
|
||||
|
||||
const POSARGS: &[Param] = &[Param::var_positional("args")];
|
||||
assert_eq!(
|
||||
native_call_flags(&[arg(Some(POSARGS))], false),
|
||||
PyMethodFlags::FASTCALL
|
||||
);
|
||||
|
||||
const INNER: &[Param] = &[Param::keyword_only(
|
||||
"reverse",
|
||||
Some(DefaultRepr::Bool(false)),
|
||||
)];
|
||||
const FLAT: &[Param] = &[Param::flatten(Some(INNER))];
|
||||
assert_eq!(native_call_flags(&[arg(Some(FLAT))], false), keywords);
|
||||
}
|
||||
}
|
||||
@@ -662,7 +662,7 @@ impl Context {
|
||||
let def = PyMethodDef {
|
||||
name,
|
||||
func: Box::leak(Box::new(f.into_func())),
|
||||
flags,
|
||||
flags: flags.with_call_convention(F::ARGS),
|
||||
#[cfg(feature = "doc")]
|
||||
doc_off: doc.offset,
|
||||
#[cfg(feature = "doc")]
|
||||
|
||||
Reference in new issue
Block a user