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