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