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