pyo3_macros_backend/
method.rs

1use std::borrow::Cow;
2use std::ffi::CString;
3use std::fmt::Display;
4
5use proc_macro2::{Span, TokenStream};
6use quote::{format_ident, quote, quote_spanned, ToTokens};
7use syn::{ext::IdentExt, spanned::Spanned, Ident, Result};
8
9use crate::deprecations::deprecate_trailing_option_default;
10use crate::pyversions::is_abi3_before;
11use crate::utils::{Ctx, LitCStr};
12use crate::{
13    attributes::{FromPyWithAttribute, TextSignatureAttribute, TextSignatureAttributeValue},
14    params::{impl_arg_params, Holders},
15    pyfunction::{
16        FunctionSignature, PyFunctionArgPyO3Attributes, PyFunctionOptions, SignatureAttribute,
17    },
18    quotes,
19    utils::{self, PythonDoc},
20};
21
22#[derive(Clone, Debug)]
23pub struct RegularArg<'a> {
24    pub name: Cow<'a, syn::Ident>,
25    pub ty: &'a syn::Type,
26    pub from_py_with: Option<FromPyWithAttribute>,
27    pub default_value: Option<syn::Expr>,
28    pub option_wrapped_type: Option<&'a syn::Type>,
29}
30
31/// Pythons *args argument
32#[derive(Clone, Debug)]
33pub struct VarargsArg<'a> {
34    pub name: Cow<'a, syn::Ident>,
35    pub ty: &'a syn::Type,
36}
37
38/// Pythons **kwarg argument
39#[derive(Clone, Debug)]
40pub struct KwargsArg<'a> {
41    pub name: Cow<'a, syn::Ident>,
42    pub ty: &'a syn::Type,
43}
44
45#[derive(Clone, Debug)]
46pub struct CancelHandleArg<'a> {
47    pub name: &'a syn::Ident,
48    pub ty: &'a syn::Type,
49}
50
51#[derive(Clone, Debug)]
52pub struct PyArg<'a> {
53    pub name: &'a syn::Ident,
54    pub ty: &'a syn::Type,
55}
56
57#[derive(Clone, Debug)]
58pub enum FnArg<'a> {
59    Regular(RegularArg<'a>),
60    VarArgs(VarargsArg<'a>),
61    KwArgs(KwargsArg<'a>),
62    Py(PyArg<'a>),
63    CancelHandle(CancelHandleArg<'a>),
64}
65
66impl<'a> FnArg<'a> {
67    pub fn name(&self) -> &syn::Ident {
68        match self {
69            FnArg::Regular(RegularArg { name, .. }) => name,
70            FnArg::VarArgs(VarargsArg { name, .. }) => name,
71            FnArg::KwArgs(KwargsArg { name, .. }) => name,
72            FnArg::Py(PyArg { name, .. }) => name,
73            FnArg::CancelHandle(CancelHandleArg { name, .. }) => name,
74        }
75    }
76
77    pub fn ty(&self) -> &'a syn::Type {
78        match self {
79            FnArg::Regular(RegularArg { ty, .. }) => ty,
80            FnArg::VarArgs(VarargsArg { ty, .. }) => ty,
81            FnArg::KwArgs(KwargsArg { ty, .. }) => ty,
82            FnArg::Py(PyArg { ty, .. }) => ty,
83            FnArg::CancelHandle(CancelHandleArg { ty, .. }) => ty,
84        }
85    }
86
87    #[allow(clippy::wrong_self_convention)]
88    pub fn from_py_with(&self) -> Option<&FromPyWithAttribute> {
89        if let FnArg::Regular(RegularArg { from_py_with, .. }) = self {
90            from_py_with.as_ref()
91        } else {
92            None
93        }
94    }
95
96    pub fn to_varargs_mut(&mut self) -> Result<&mut Self> {
97        if let Self::Regular(RegularArg {
98            name,
99            ty,
100            option_wrapped_type: None,
101            ..
102        }) = self
103        {
104            *self = Self::VarArgs(VarargsArg {
105                name: name.clone(),
106                ty,
107            });
108            Ok(self)
109        } else {
110            bail_spanned!(self.name().span() => "args cannot be optional")
111        }
112    }
113
114    pub fn to_kwargs_mut(&mut self) -> Result<&mut Self> {
115        if let Self::Regular(RegularArg {
116            name,
117            ty,
118            option_wrapped_type: Some(..),
119            ..
120        }) = self
121        {
122            *self = Self::KwArgs(KwargsArg {
123                name: name.clone(),
124                ty,
125            });
126            Ok(self)
127        } else {
128            bail_spanned!(self.name().span() => "kwargs must be Option<_>")
129        }
130    }
131
132    /// Transforms a rust fn arg parsed with syn into a method::FnArg
133    pub fn parse(arg: &'a mut syn::FnArg) -> Result<Self> {
134        match arg {
135            syn::FnArg::Receiver(recv) => {
136                bail_spanned!(recv.span() => "unexpected receiver")
137            } // checked in parse_fn_type
138            syn::FnArg::Typed(cap) => {
139                if let syn::Type::ImplTrait(_) = &*cap.ty {
140                    bail_spanned!(cap.ty.span() => IMPL_TRAIT_ERR);
141                }
142
143                let PyFunctionArgPyO3Attributes {
144                    from_py_with,
145                    cancel_handle,
146                } = PyFunctionArgPyO3Attributes::from_attrs(&mut cap.attrs)?;
147                let ident = match &*cap.pat {
148                    syn::Pat::Ident(syn::PatIdent { ident, .. }) => ident,
149                    other => return Err(handle_argument_error(other)),
150                };
151
152                if utils::is_python(&cap.ty) {
153                    return Ok(Self::Py(PyArg {
154                        name: ident,
155                        ty: &cap.ty,
156                    }));
157                }
158
159                if cancel_handle.is_some() {
160                    // `PyFunctionArgPyO3Attributes::from_attrs` validates that
161                    // only compatible attributes are specified, either
162                    // `cancel_handle` or `from_py_with`, dublicates and any
163                    // combination of the two are already rejected.
164                    return Ok(Self::CancelHandle(CancelHandleArg {
165                        name: ident,
166                        ty: &cap.ty,
167                    }));
168                }
169
170                Ok(Self::Regular(RegularArg {
171                    name: Cow::Borrowed(ident),
172                    ty: &cap.ty,
173                    from_py_with,
174                    default_value: None,
175                    option_wrapped_type: utils::option_type_argument(&cap.ty),
176                }))
177            }
178        }
179    }
180}
181
182fn handle_argument_error(pat: &syn::Pat) -> syn::Error {
183    let span = pat.span();
184    let msg = match pat {
185        syn::Pat::Wild(_) => "wildcard argument names are not supported",
186        syn::Pat::Struct(_)
187        | syn::Pat::Tuple(_)
188        | syn::Pat::TupleStruct(_)
189        | syn::Pat::Slice(_) => "destructuring in arguments is not supported",
190        _ => "unsupported argument",
191    };
192    syn::Error::new(span, msg)
193}
194
195/// Represents what kind of a function a pyfunction or pymethod is
196#[derive(Clone, Debug)]
197pub enum FnType {
198    /// Represents a pymethod annotated with `#[getter]`
199    Getter(SelfType),
200    /// Represents a pymethod annotated with `#[setter]`
201    Setter(SelfType),
202    /// Represents a regular pymethod
203    Fn(SelfType),
204    /// Represents a pymethod annotated with `#[new]`, i.e. the `__new__` dunder.
205    FnNew,
206    /// Represents a pymethod annotated with both `#[new]` and `#[classmethod]` (in either order)
207    FnNewClass(Span),
208    /// Represents a pymethod annotated with `#[classmethod]`, like a `@classmethod`
209    FnClass(Span),
210    /// Represents a pyfunction or a pymethod annotated with `#[staticmethod]`, like a `@staticmethod`
211    FnStatic,
212    /// Represents a pyfunction annotated with `#[pyo3(pass_module)]
213    FnModule(Span),
214    /// Represents a pymethod or associated constant annotated with `#[classattr]`
215    ClassAttribute,
216}
217
218impl FnType {
219    pub fn skip_first_rust_argument_in_python_signature(&self) -> bool {
220        match self {
221            FnType::Getter(_)
222            | FnType::Setter(_)
223            | FnType::Fn(_)
224            | FnType::FnClass(_)
225            | FnType::FnNewClass(_)
226            | FnType::FnModule(_) => true,
227            FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => false,
228        }
229    }
230
231    pub fn signature_attribute_allowed(&self) -> bool {
232        match self {
233            FnType::Fn(_)
234            | FnType::FnNew
235            | FnType::FnStatic
236            | FnType::FnClass(_)
237            | FnType::FnNewClass(_)
238            | FnType::FnModule(_) => true,
239            // Setter, Getter and ClassAttribute all have fixed signatures (either take 0 or 1
240            // arguments) so cannot have a `signature = (...)` attribute.
241            FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => false,
242        }
243    }
244
245    pub fn self_arg(
246        &self,
247        cls: Option<&syn::Type>,
248        error_mode: ExtractErrorMode,
249        holders: &mut Holders,
250        ctx: &Ctx,
251    ) -> Option<TokenStream> {
252        let Ctx { pyo3_path, .. } = ctx;
253        match self {
254            FnType::Getter(st) | FnType::Setter(st) | FnType::Fn(st) => {
255                let mut receiver = st.receiver(
256                    cls.expect("no class given for Fn with a \"self\" receiver"),
257                    error_mode,
258                    holders,
259                    ctx,
260                );
261                syn::Token![,](Span::call_site()).to_tokens(&mut receiver);
262                Some(receiver)
263            }
264            FnType::FnClass(span) | FnType::FnNewClass(span) => {
265                let py = syn::Ident::new("py", Span::call_site());
266                let slf: Ident = syn::Ident::new("_slf", Span::call_site());
267                let pyo3_path = pyo3_path.to_tokens_spanned(*span);
268                let ret = quote_spanned! { *span =>
269                    #[allow(clippy::useless_conversion)]
270                    ::std::convert::Into::into(
271                        #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &*(&#slf as *const _ as *const *mut _))
272                            .downcast_unchecked::<#pyo3_path::types::PyType>()
273                    )
274                };
275                Some(quote! { unsafe { #ret }, })
276            }
277            FnType::FnModule(span) => {
278                let py = syn::Ident::new("py", Span::call_site());
279                let slf: Ident = syn::Ident::new("_slf", Span::call_site());
280                let pyo3_path = pyo3_path.to_tokens_spanned(*span);
281                let ret = quote_spanned! { *span =>
282                    #[allow(clippy::useless_conversion)]
283                    ::std::convert::Into::into(
284                        #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &*(&#slf as *const _ as *const *mut _))
285                            .downcast_unchecked::<#pyo3_path::types::PyModule>()
286                    )
287                };
288                Some(quote! { unsafe { #ret }, })
289            }
290            FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => None,
291        }
292    }
293}
294
295#[derive(Clone, Debug)]
296pub enum SelfType {
297    Receiver { mutable: bool, span: Span },
298    TryFromBoundRef(Span),
299}
300
301#[derive(Clone, Copy)]
302pub enum ExtractErrorMode {
303    NotImplemented,
304    Raise,
305}
306
307impl ExtractErrorMode {
308    pub fn handle_error(self, extract: TokenStream, ctx: &Ctx) -> TokenStream {
309        let Ctx { pyo3_path, .. } = ctx;
310        match self {
311            ExtractErrorMode::Raise => quote! { #extract? },
312            ExtractErrorMode::NotImplemented => quote! {
313                match #extract {
314                    ::std::result::Result::Ok(value) => value,
315                    ::std::result::Result::Err(_) => { return #pyo3_path::impl_::callback::convert(py, py.NotImplemented()); },
316                }
317            },
318        }
319    }
320}
321
322impl SelfType {
323    pub fn receiver(
324        &self,
325        cls: &syn::Type,
326        error_mode: ExtractErrorMode,
327        holders: &mut Holders,
328        ctx: &Ctx,
329    ) -> TokenStream {
330        // Due to use of quote_spanned in this function, need to bind these idents to the
331        // main macro callsite.
332        let py = syn::Ident::new("py", Span::call_site());
333        let slf = syn::Ident::new("_slf", Span::call_site());
334        let Ctx { pyo3_path, .. } = ctx;
335        let bound_ref =
336            quote! { unsafe { #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &#slf) } };
337        match self {
338            SelfType::Receiver { span, mutable } => {
339                let method = if *mutable {
340                    syn::Ident::new("extract_pyclass_ref_mut", *span)
341                } else {
342                    syn::Ident::new("extract_pyclass_ref", *span)
343                };
344                let holder = holders.push_holder(*span);
345                let pyo3_path = pyo3_path.to_tokens_spanned(*span);
346                error_mode.handle_error(
347                    quote_spanned! { *span =>
348                        #pyo3_path::impl_::extract_argument::#method::<#cls>(
349                            #bound_ref.0,
350                            &mut #holder,
351                        )
352                    },
353                    ctx,
354                )
355            }
356            SelfType::TryFromBoundRef(span) => {
357                let pyo3_path = pyo3_path.to_tokens_spanned(*span);
358                error_mode.handle_error(
359                    quote_spanned! { *span =>
360                        #bound_ref.downcast::<#cls>()
361                            .map_err(::std::convert::Into::<#pyo3_path::PyErr>::into)
362                            .and_then(
363                                #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)]  // In case slf is Py<Self> (unknown_lints can be removed when MSRV is 1.75+)
364                                |bound| ::std::convert::TryFrom::try_from(bound).map_err(::std::convert::Into::into)
365                            )
366
367                    },
368                    ctx
369                )
370            }
371        }
372    }
373}
374
375/// Determines which CPython calling convention a given FnSpec uses.
376#[derive(Clone, Debug)]
377pub enum CallingConvention {
378    Noargs,   // METH_NOARGS
379    Varargs,  // METH_VARARGS | METH_KEYWORDS
380    Fastcall, // METH_FASTCALL | METH_KEYWORDS (not compatible with `abi3` feature before 3.10)
381    TpNew,    // special convention for tp_new
382}
383
384impl CallingConvention {
385    /// Determine default calling convention from an argument signature.
386    ///
387    /// Different other slots (tp_call, tp_new) can have other requirements
388    /// and are set manually (see `parse_fn_type` below).
389    pub fn from_signature(signature: &FunctionSignature<'_>) -> Self {
390        if signature.python_signature.has_no_args() {
391            Self::Noargs
392        } else if signature.python_signature.kwargs.is_none() && !is_abi3_before(3, 10) {
393            // For functions that accept **kwargs, always prefer varargs for now based on
394            // historical performance testing.
395            //
396            // FASTCALL not compatible with `abi3` before 3.10
397            Self::Fastcall
398        } else {
399            Self::Varargs
400        }
401    }
402}
403
404pub struct FnSpec<'a> {
405    pub tp: FnType,
406    // Rust function name
407    pub name: &'a syn::Ident,
408    // Wrapped python name. This should not have any leading r#.
409    // r# can be removed by syn::ext::IdentExt::unraw()
410    pub python_name: syn::Ident,
411    pub signature: FunctionSignature<'a>,
412    pub convention: CallingConvention,
413    pub text_signature: Option<TextSignatureAttribute>,
414    pub asyncness: Option<syn::Token![async]>,
415    pub unsafety: Option<syn::Token![unsafe]>,
416}
417
418pub fn parse_method_receiver(arg: &syn::FnArg) -> Result<SelfType> {
419    match arg {
420        syn::FnArg::Receiver(
421            recv @ syn::Receiver {
422                reference: None, ..
423            },
424        ) => {
425            bail_spanned!(recv.span() => RECEIVER_BY_VALUE_ERR);
426        }
427        syn::FnArg::Receiver(recv @ syn::Receiver { mutability, .. }) => Ok(SelfType::Receiver {
428            mutable: mutability.is_some(),
429            span: recv.span(),
430        }),
431        syn::FnArg::Typed(syn::PatType { ty, .. }) => {
432            if let syn::Type::ImplTrait(_) = &**ty {
433                bail_spanned!(ty.span() => IMPL_TRAIT_ERR);
434            }
435            Ok(SelfType::TryFromBoundRef(ty.span()))
436        }
437    }
438}
439
440impl<'a> FnSpec<'a> {
441    /// Parser function signature and function attributes
442    pub fn parse(
443        // Signature is mutable to remove the `Python` argument.
444        sig: &'a mut syn::Signature,
445        meth_attrs: &mut Vec<syn::Attribute>,
446        options: PyFunctionOptions,
447    ) -> Result<FnSpec<'a>> {
448        let PyFunctionOptions {
449            text_signature,
450            name,
451            signature,
452            ..
453        } = options;
454
455        let mut python_name = name.map(|name| name.value.0);
456
457        let fn_type = Self::parse_fn_type(sig, meth_attrs, &mut python_name)?;
458        ensure_signatures_on_valid_method(&fn_type, signature.as_ref(), text_signature.as_ref())?;
459
460        let name = &sig.ident;
461        let python_name = python_name.as_ref().unwrap_or(name).unraw();
462
463        let arguments: Vec<_> = sig
464            .inputs
465            .iter_mut()
466            .skip(if fn_type.skip_first_rust_argument_in_python_signature() {
467                1
468            } else {
469                0
470            })
471            .map(FnArg::parse)
472            .collect::<Result<_>>()?;
473
474        let signature = if let Some(signature) = signature {
475            FunctionSignature::from_arguments_and_attribute(arguments, signature)?
476        } else {
477            FunctionSignature::from_arguments(arguments)?
478        };
479
480        let convention = if matches!(fn_type, FnType::FnNew | FnType::FnNewClass(_)) {
481            CallingConvention::TpNew
482        } else {
483            CallingConvention::from_signature(&signature)
484        };
485
486        Ok(FnSpec {
487            tp: fn_type,
488            name,
489            convention,
490            python_name,
491            signature,
492            text_signature,
493            asyncness: sig.asyncness,
494            unsafety: sig.unsafety,
495        })
496    }
497
498    pub fn null_terminated_python_name(&self, ctx: &Ctx) -> LitCStr {
499        let name = self.python_name.to_string();
500        let name = CString::new(name).unwrap();
501        LitCStr::new(name, self.python_name.span(), ctx)
502    }
503
504    fn parse_fn_type(
505        sig: &syn::Signature,
506        meth_attrs: &mut Vec<syn::Attribute>,
507        python_name: &mut Option<syn::Ident>,
508    ) -> Result<FnType> {
509        let mut method_attributes = parse_method_attributes(meth_attrs)?;
510
511        let name = &sig.ident;
512        let parse_receiver = |msg: &'static str| {
513            let first_arg = sig
514                .inputs
515                .first()
516                .ok_or_else(|| err_spanned!(sig.span() => msg))?;
517            parse_method_receiver(first_arg)
518        };
519
520        // strip get_ or set_
521        let strip_fn_name = |prefix: &'static str| {
522            name.unraw()
523                .to_string()
524                .strip_prefix(prefix)
525                .map(|stripped| syn::Ident::new(stripped, name.span()))
526        };
527
528        let mut set_name_to_new = || {
529            if let Some(name) = &python_name {
530                bail_spanned!(name.span() => "`name` not allowed with `#[new]`");
531            }
532            *python_name = Some(syn::Ident::new("__new__", Span::call_site()));
533            Ok(())
534        };
535
536        let fn_type = match method_attributes.as_mut_slice() {
537            [] => FnType::Fn(parse_receiver(
538                "static method needs #[staticmethod] attribute",
539            )?),
540            [MethodTypeAttribute::StaticMethod(_)] => FnType::FnStatic,
541            [MethodTypeAttribute::ClassAttribute(_)] => FnType::ClassAttribute,
542            [MethodTypeAttribute::New(_)] => {
543                set_name_to_new()?;
544                FnType::FnNew
545            }
546            [MethodTypeAttribute::New(_), MethodTypeAttribute::ClassMethod(span)]
547            | [MethodTypeAttribute::ClassMethod(span), MethodTypeAttribute::New(_)] => {
548                set_name_to_new()?;
549                FnType::FnNewClass(*span)
550            }
551            [MethodTypeAttribute::ClassMethod(_)] => {
552                // Add a helpful hint if the classmethod doesn't look like a classmethod
553                let span = match sig.inputs.first() {
554                    // Don't actually bother checking the type of the first argument, the compiler
555                    // will error on incorrect type.
556                    Some(syn::FnArg::Typed(first_arg)) => first_arg.ty.span(),
557                    Some(syn::FnArg::Receiver(_)) | None => bail_spanned!(
558                        sig.paren_token.span.join() => "Expected `&Bound<PyType>` or `Py<PyType>` as the first argument to `#[classmethod]`"
559                    ),
560                };
561                FnType::FnClass(span)
562            }
563            [MethodTypeAttribute::Getter(_, name)] => {
564                if let Some(name) = name.take() {
565                    ensure_spanned!(
566                        python_name.replace(name).is_none(),
567                        python_name.span() => "`name` may only be specified once"
568                    );
569                } else if python_name.is_none() {
570                    // Strip off "get_" prefix if needed
571                    *python_name = strip_fn_name("get_");
572                }
573
574                FnType::Getter(parse_receiver("expected receiver for `#[getter]`")?)
575            }
576            [MethodTypeAttribute::Setter(_, name)] => {
577                if let Some(name) = name.take() {
578                    ensure_spanned!(
579                        python_name.replace(name).is_none(),
580                        python_name.span() => "`name` may only be specified once"
581                    );
582                } else if python_name.is_none() {
583                    // Strip off "set_" prefix if needed
584                    *python_name = strip_fn_name("set_");
585                }
586
587                FnType::Setter(parse_receiver("expected receiver for `#[setter]`")?)
588            }
589            [first, rest @ .., last] => {
590                // Join as many of the spans together as possible
591                let span = rest
592                    .iter()
593                    .fold(first.span(), |s, next| s.join(next.span()).unwrap_or(s));
594                let span = span.join(last.span()).unwrap_or(span);
595                // List all the attributes in the error message
596                let mut msg = format!("`{}` may not be combined with", first);
597                let mut is_first = true;
598                for attr in &*rest {
599                    msg.push_str(&format!(" `{}`", attr));
600                    if is_first {
601                        is_first = false;
602                    } else {
603                        msg.push(',');
604                    }
605                }
606                if !rest.is_empty() {
607                    msg.push_str(" and");
608                }
609                msg.push_str(&format!(" `{}`", last));
610                bail_spanned!(span => msg)
611            }
612        };
613        Ok(fn_type)
614    }
615
616    /// Return a C wrapper function for this signature.
617    pub fn get_wrapper_function(
618        &self,
619        ident: &proc_macro2::Ident,
620        cls: Option<&syn::Type>,
621        ctx: &Ctx,
622    ) -> Result<TokenStream> {
623        let Ctx {
624            pyo3_path,
625            output_span,
626        } = ctx;
627        let mut cancel_handle_iter = self
628            .signature
629            .arguments
630            .iter()
631            .filter(|arg| matches!(arg, FnArg::CancelHandle(..)));
632        let cancel_handle = cancel_handle_iter.next();
633        if let Some(FnArg::CancelHandle(CancelHandleArg { name, .. })) = cancel_handle {
634            ensure_spanned!(self.asyncness.is_some(), name.span() => "`cancel_handle` attribute can only be used with `async fn`");
635            if let Some(FnArg::CancelHandle(CancelHandleArg { name, .. })) =
636                cancel_handle_iter.next()
637            {
638                bail_spanned!(name.span() => "`cancel_handle` may only be specified once");
639            }
640        }
641
642        if self.asyncness.is_some() {
643            ensure_spanned!(
644                cfg!(feature = "experimental-async"),
645                self.asyncness.span() => "async functions are only supported with the `experimental-async` feature"
646            );
647        }
648
649        let rust_call = |args: Vec<TokenStream>, holders: &mut Holders| {
650            let mut self_arg = || self.tp.self_arg(cls, ExtractErrorMode::Raise, holders, ctx);
651
652            let call = if self.asyncness.is_some() {
653                let throw_callback = if cancel_handle.is_some() {
654                    quote! { Some(__throw_callback) }
655                } else {
656                    quote! { None }
657                };
658                let python_name = &self.python_name;
659                let qualname_prefix = match cls {
660                    Some(cls) => quote!(Some(<#cls as #pyo3_path::PyTypeInfo>::NAME)),
661                    None => quote!(None),
662                };
663                let arg_names = (0..args.len())
664                    .map(|i| format_ident!("arg_{}", i))
665                    .collect::<Vec<_>>();
666                let future = match self.tp {
667                    FnType::Fn(SelfType::Receiver { mutable: false, .. }) => {
668                        quote! {{
669                            #(let #arg_names = #args;)*
670                            let __guard = unsafe { #pyo3_path::impl_::coroutine::RefGuard::<#cls>::new(&#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf))? };
671                            async move { function(&__guard, #(#arg_names),*).await }
672                        }}
673                    }
674                    FnType::Fn(SelfType::Receiver { mutable: true, .. }) => {
675                        quote! {{
676                            #(let #arg_names = #args;)*
677                            let mut __guard = unsafe { #pyo3_path::impl_::coroutine::RefMutGuard::<#cls>::new(&#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf))? };
678                            async move { function(&mut __guard, #(#arg_names),*).await }
679                        }}
680                    }
681                    _ => {
682                        if let Some(self_arg) = self_arg() {
683                            quote! {
684                                function(
685                                    // NB #self_arg includes a comma, so none inserted here
686                                    #self_arg
687                                    #(#args),*
688                                )
689                            }
690                        } else {
691                            quote! { function(#(#args),*) }
692                        }
693                    }
694                };
695                let mut call = quote! {{
696                    let future = #future;
697                    #pyo3_path::impl_::coroutine::new_coroutine(
698                        #pyo3_path::intern!(py, stringify!(#python_name)),
699                        #qualname_prefix,
700                        #throw_callback,
701                        async move {
702                            let fut = future.await;
703                            #pyo3_path::impl_::wrap::converter(&fut).wrap(fut)
704                        },
705                    )
706                }};
707                if cancel_handle.is_some() {
708                    call = quote! {{
709                        let __cancel_handle = #pyo3_path::coroutine::CancelHandle::new();
710                        let __throw_callback = __cancel_handle.throw_callback();
711                        #call
712                    }};
713                }
714                call
715            } else if let Some(self_arg) = self_arg() {
716                quote! {
717                    function(
718                        // NB #self_arg includes a comma, so none inserted here
719                        #self_arg
720                        #(#args),*
721                    )
722                }
723            } else {
724                quote! { function(#(#args),*) }
725            };
726
727            // We must assign the output_span to the return value of the call,
728            // but *not* of the call itself otherwise the spans get really weird
729            let ret_ident = Ident::new("ret", *output_span);
730            let ret_expr = quote! { let #ret_ident = #call; };
731            let return_conversion =
732                quotes::map_result_into_ptr(quotes::ok_wrap(ret_ident.to_token_stream(), ctx), ctx);
733            quote! {
734                {
735                    #ret_expr
736                    #return_conversion
737                }
738            }
739        };
740
741        let func_name = &self.name;
742        let rust_name = if let Some(cls) = cls {
743            quote!(#cls::#func_name)
744        } else {
745            quote!(#func_name)
746        };
747
748        let deprecation = deprecate_trailing_option_default(self);
749
750        Ok(match self.convention {
751            CallingConvention::Noargs => {
752                let mut holders = Holders::new();
753                let args = self
754                    .signature
755                    .arguments
756                    .iter()
757                    .map(|arg| match arg {
758                        FnArg::Py(..) => quote!(py),
759                        FnArg::CancelHandle(..) => quote!(__cancel_handle),
760                        _ => unreachable!("`CallingConvention::Noargs` should not contain any arguments (reaching Python) except for `self`, which is handled below."),
761                    })
762                    .collect();
763                let call = rust_call(args, &mut holders);
764                let init_holders = holders.init_holders(ctx);
765                quote! {
766                    unsafe fn #ident<'py>(
767                        py: #pyo3_path::Python<'py>,
768                        _slf: *mut #pyo3_path::ffi::PyObject,
769                    ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
770                        #deprecation
771                        let function = #rust_name; // Shadow the function name to avoid #3017
772                        #init_holders
773                        let result = #call;
774                        result
775                    }
776                }
777            }
778            CallingConvention::Fastcall => {
779                let mut holders = Holders::new();
780                let (arg_convert, args) = impl_arg_params(self, cls, true, &mut holders, ctx);
781                let call = rust_call(args, &mut holders);
782                let init_holders = holders.init_holders(ctx);
783
784                quote! {
785                    unsafe fn #ident<'py>(
786                        py: #pyo3_path::Python<'py>,
787                        _slf: *mut #pyo3_path::ffi::PyObject,
788                        _args: *const *mut #pyo3_path::ffi::PyObject,
789                        _nargs: #pyo3_path::ffi::Py_ssize_t,
790                        _kwnames: *mut #pyo3_path::ffi::PyObject
791                    ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
792                        #deprecation
793                        let function = #rust_name; // Shadow the function name to avoid #3017
794                        #arg_convert
795                        #init_holders
796                        let result = #call;
797                        result
798                    }
799                }
800            }
801            CallingConvention::Varargs => {
802                let mut holders = Holders::new();
803                let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx);
804                let call = rust_call(args, &mut holders);
805                let init_holders = holders.init_holders(ctx);
806
807                quote! {
808                    unsafe fn #ident<'py>(
809                        py: #pyo3_path::Python<'py>,
810                        _slf: *mut #pyo3_path::ffi::PyObject,
811                        _args: *mut #pyo3_path::ffi::PyObject,
812                        _kwargs: *mut #pyo3_path::ffi::PyObject
813                    ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
814                        #deprecation
815                        let function = #rust_name; // Shadow the function name to avoid #3017
816                        #arg_convert
817                        #init_holders
818                        let result = #call;
819                        result
820                    }
821                }
822            }
823            CallingConvention::TpNew => {
824                let mut holders = Holders::new();
825                let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx);
826                let self_arg = self
827                    .tp
828                    .self_arg(cls, ExtractErrorMode::Raise, &mut holders, ctx);
829                let call = quote_spanned! {*output_span=> #rust_name(#self_arg #(#args),*) };
830                let init_holders = holders.init_holders(ctx);
831                quote! {
832                    unsafe fn #ident(
833                        py: #pyo3_path::Python<'_>,
834                        _slf: *mut #pyo3_path::ffi::PyTypeObject,
835                        _args: *mut #pyo3_path::ffi::PyObject,
836                        _kwargs: *mut #pyo3_path::ffi::PyObject
837                    ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> {
838                        use #pyo3_path::impl_::callback::IntoPyCallbackOutput;
839                        #deprecation
840                        let function = #rust_name; // Shadow the function name to avoid #3017
841                        #arg_convert
842                        #init_holders
843                        let result = #call;
844                        let initializer: #pyo3_path::PyClassInitializer::<#cls> = result.convert(py)?;
845                        #pyo3_path::impl_::pymethods::tp_new_impl(py, initializer, _slf)
846                    }
847                }
848            }
849        })
850    }
851
852    /// Return a `PyMethodDef` constructor for this function, matching the selected
853    /// calling convention.
854    pub fn get_methoddef(&self, wrapper: impl ToTokens, doc: &PythonDoc, ctx: &Ctx) -> TokenStream {
855        let Ctx { pyo3_path, .. } = ctx;
856        let python_name = self.null_terminated_python_name(ctx);
857        match self.convention {
858            CallingConvention::Noargs => quote! {
859                #pyo3_path::impl_::pymethods::PyMethodDef::noargs(
860                    #python_name,
861                    {
862                        unsafe extern "C" fn trampoline(
863                            _slf: *mut #pyo3_path::ffi::PyObject,
864                            _args: *mut #pyo3_path::ffi::PyObject,
865                        ) -> *mut #pyo3_path::ffi::PyObject
866                        {
867                            unsafe {
868                                #pyo3_path::impl_::trampoline::noargs(
869                                    _slf,
870                                    _args,
871                                    #wrapper
872                                )
873                            }
874                        }
875                        trampoline
876                    },
877                    #doc,
878                )
879            },
880            CallingConvention::Fastcall => quote! {
881                #pyo3_path::impl_::pymethods::PyMethodDef::fastcall_cfunction_with_keywords(
882                    #python_name,
883                    {
884                        unsafe extern "C" fn trampoline(
885                            _slf: *mut #pyo3_path::ffi::PyObject,
886                            _args: *const *mut #pyo3_path::ffi::PyObject,
887                            _nargs: #pyo3_path::ffi::Py_ssize_t,
888                            _kwnames: *mut #pyo3_path::ffi::PyObject
889                        ) -> *mut #pyo3_path::ffi::PyObject
890                        {
891                            #pyo3_path::impl_::trampoline::fastcall_with_keywords(
892                                _slf,
893                                _args,
894                                _nargs,
895                                _kwnames,
896                                #wrapper
897                            )
898                        }
899                        trampoline
900                    },
901                    #doc,
902                )
903            },
904            CallingConvention::Varargs => quote! {
905                #pyo3_path::impl_::pymethods::PyMethodDef::cfunction_with_keywords(
906                    #python_name,
907                    {
908                        unsafe extern "C" fn trampoline(
909                            _slf: *mut #pyo3_path::ffi::PyObject,
910                            _args: *mut #pyo3_path::ffi::PyObject,
911                            _kwargs: *mut #pyo3_path::ffi::PyObject,
912                        ) -> *mut #pyo3_path::ffi::PyObject
913                        {
914                            #pyo3_path::impl_::trampoline::cfunction_with_keywords(
915                                _slf,
916                                _args,
917                                _kwargs,
918                                #wrapper
919                            )
920                        }
921                        trampoline
922                    },
923                    #doc,
924                )
925            },
926            CallingConvention::TpNew => unreachable!("tp_new cannot get a methoddef"),
927        }
928    }
929
930    /// Forwards to [utils::get_doc] with the text signature of this spec.
931    pub fn get_doc(&self, attrs: &[syn::Attribute], ctx: &Ctx) -> PythonDoc {
932        let text_signature = self
933            .text_signature_call_signature()
934            .map(|sig| format!("{}{}", self.python_name, sig));
935        utils::get_doc(attrs, text_signature, ctx)
936    }
937
938    /// Creates the parenthesised arguments list for `__text_signature__` snippet based on this spec's signature
939    /// and/or attributes. Prepend the callable name to make a complete `__text_signature__`.
940    pub fn text_signature_call_signature(&self) -> Option<String> {
941        let self_argument = match &self.tp {
942            // Getters / Setters / ClassAttribute are not callables on the Python side
943            FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => return None,
944            FnType::Fn(_) => Some("self"),
945            FnType::FnModule(_) => Some("module"),
946            FnType::FnClass(_) | FnType::FnNewClass(_) => Some("cls"),
947            FnType::FnStatic | FnType::FnNew => None,
948        };
949
950        match self.text_signature.as_ref().map(|attr| &attr.value) {
951            Some(TextSignatureAttributeValue::Str(s)) => Some(s.value()),
952            None => Some(self.signature.text_signature(self_argument)),
953            Some(TextSignatureAttributeValue::Disabled(_)) => None,
954        }
955    }
956}
957
958enum MethodTypeAttribute {
959    New(Span),
960    ClassMethod(Span),
961    StaticMethod(Span),
962    Getter(Span, Option<Ident>),
963    Setter(Span, Option<Ident>),
964    ClassAttribute(Span),
965}
966
967impl MethodTypeAttribute {
968    fn span(&self) -> Span {
969        match self {
970            MethodTypeAttribute::New(span)
971            | MethodTypeAttribute::ClassMethod(span)
972            | MethodTypeAttribute::StaticMethod(span)
973            | MethodTypeAttribute::Getter(span, _)
974            | MethodTypeAttribute::Setter(span, _)
975            | MethodTypeAttribute::ClassAttribute(span) => *span,
976        }
977    }
978
979    /// Attempts to parse a method type attribute.
980    ///
981    /// If the attribute does not match one of the attribute names, returns `Ok(None)`.
982    ///
983    /// Otherwise will either return a parse error or the attribute.
984    fn parse_if_matching_attribute(attr: &syn::Attribute) -> Result<Option<Self>> {
985        fn ensure_no_arguments(meta: &syn::Meta, ident: &str) -> syn::Result<()> {
986            match meta {
987                syn::Meta::Path(_) => Ok(()),
988                syn::Meta::List(l) => bail_spanned!(
989                    l.span() => format!(
990                        "`#[{ident}]` does not take any arguments\n= help: did you mean `#[{ident}] #[pyo3({meta})]`?",
991                        ident = ident,
992                        meta = l.tokens,
993                    )
994                ),
995                syn::Meta::NameValue(nv) => {
996                    bail_spanned!(nv.eq_token.span() => format!(
997                        "`#[{}]` does not take any arguments\n= note: this was previously accepted and ignored",
998                        ident
999                    ))
1000                }
1001            }
1002        }
1003
1004        fn extract_name(meta: &syn::Meta, ident: &str) -> Result<Option<Ident>> {
1005            match meta {
1006                syn::Meta::Path(_) => Ok(None),
1007                syn::Meta::NameValue(nv) => bail_spanned!(
1008                    nv.eq_token.span() => format!("expected `#[{}(name)]` to set the name", ident)
1009                ),
1010                syn::Meta::List(l) => {
1011                    if let Ok(name) = l.parse_args::<syn::Ident>() {
1012                        Ok(Some(name))
1013                    } else if let Ok(name) = l.parse_args::<syn::LitStr>() {
1014                        name.parse().map(Some)
1015                    } else {
1016                        bail_spanned!(l.tokens.span() => "expected ident or string literal for property name");
1017                    }
1018                }
1019            }
1020        }
1021
1022        let meta = &attr.meta;
1023        let path = meta.path();
1024
1025        if path.is_ident("new") {
1026            ensure_no_arguments(meta, "new")?;
1027            Ok(Some(MethodTypeAttribute::New(path.span())))
1028        } else if path.is_ident("classmethod") {
1029            ensure_no_arguments(meta, "classmethod")?;
1030            Ok(Some(MethodTypeAttribute::ClassMethod(path.span())))
1031        } else if path.is_ident("staticmethod") {
1032            ensure_no_arguments(meta, "staticmethod")?;
1033            Ok(Some(MethodTypeAttribute::StaticMethod(path.span())))
1034        } else if path.is_ident("classattr") {
1035            ensure_no_arguments(meta, "classattr")?;
1036            Ok(Some(MethodTypeAttribute::ClassAttribute(path.span())))
1037        } else if path.is_ident("getter") {
1038            let name = extract_name(meta, "getter")?;
1039            Ok(Some(MethodTypeAttribute::Getter(path.span(), name)))
1040        } else if path.is_ident("setter") {
1041            let name = extract_name(meta, "setter")?;
1042            Ok(Some(MethodTypeAttribute::Setter(path.span(), name)))
1043        } else {
1044            Ok(None)
1045        }
1046    }
1047}
1048
1049impl Display for MethodTypeAttribute {
1050    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1051        match self {
1052            MethodTypeAttribute::New(_) => "#[new]".fmt(f),
1053            MethodTypeAttribute::ClassMethod(_) => "#[classmethod]".fmt(f),
1054            MethodTypeAttribute::StaticMethod(_) => "#[staticmethod]".fmt(f),
1055            MethodTypeAttribute::Getter(_, _) => "#[getter]".fmt(f),
1056            MethodTypeAttribute::Setter(_, _) => "#[setter]".fmt(f),
1057            MethodTypeAttribute::ClassAttribute(_) => "#[classattr]".fmt(f),
1058        }
1059    }
1060}
1061
1062fn parse_method_attributes(attrs: &mut Vec<syn::Attribute>) -> Result<Vec<MethodTypeAttribute>> {
1063    let mut new_attrs = Vec::new();
1064    let mut found_attrs = Vec::new();
1065
1066    for attr in attrs.drain(..) {
1067        match MethodTypeAttribute::parse_if_matching_attribute(&attr)? {
1068            Some(attr) => found_attrs.push(attr),
1069            None => new_attrs.push(attr),
1070        }
1071    }
1072
1073    *attrs = new_attrs;
1074
1075    Ok(found_attrs)
1076}
1077
1078const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments";
1079const RECEIVER_BY_VALUE_ERR: &str =
1080    "Python objects are shared, so 'self' cannot be moved out of the Python interpreter.
1081Try `&self`, `&mut self, `slf: PyRef<'_, Self>` or `slf: PyRefMut<'_, Self>`.";
1082
1083fn ensure_signatures_on_valid_method(
1084    fn_type: &FnType,
1085    signature: Option<&SignatureAttribute>,
1086    text_signature: Option<&TextSignatureAttribute>,
1087) -> syn::Result<()> {
1088    if let Some(signature) = signature {
1089        match fn_type {
1090            FnType::Getter(_) => {
1091                debug_assert!(!fn_type.signature_attribute_allowed());
1092                bail_spanned!(signature.kw.span() => "`signature` not allowed with `getter`")
1093            }
1094            FnType::Setter(_) => {
1095                debug_assert!(!fn_type.signature_attribute_allowed());
1096                bail_spanned!(signature.kw.span() => "`signature` not allowed with `setter`")
1097            }
1098            FnType::ClassAttribute => {
1099                debug_assert!(!fn_type.signature_attribute_allowed());
1100                bail_spanned!(signature.kw.span() => "`signature` not allowed with `classattr`")
1101            }
1102            _ => debug_assert!(fn_type.signature_attribute_allowed()),
1103        }
1104    }
1105    if let Some(text_signature) = text_signature {
1106        match fn_type {
1107            FnType::Getter(_) => {
1108                bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `getter`")
1109            }
1110            FnType::Setter(_) => {
1111                bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `setter`")
1112            }
1113            FnType::ClassAttribute => {
1114                bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `classattr`")
1115            }
1116            _ => {}
1117        }
1118    }
1119    Ok(())
1120}