pyo3_macros_backend/
params.rs

1use crate::utils::Ctx;
2use crate::{
3    method::{FnArg, FnSpec, RegularArg},
4    pyfunction::FunctionSignature,
5    quotes::some_wrap,
6};
7use proc_macro2::{Span, TokenStream};
8use quote::{format_ident, quote, quote_spanned};
9use syn::spanned::Spanned;
10
11pub struct Holders {
12    holders: Vec<syn::Ident>,
13}
14
15impl Holders {
16    pub fn new() -> Self {
17        Holders {
18            holders: Vec::new(),
19        }
20    }
21
22    pub fn push_holder(&mut self, span: Span) -> syn::Ident {
23        let holder = syn::Ident::new(&format!("holder_{}", self.holders.len()), span);
24        self.holders.push(holder.clone());
25        holder
26    }
27
28    pub fn init_holders(&self, ctx: &Ctx) -> TokenStream {
29        let Ctx { pyo3_path, .. } = ctx;
30        let holders = &self.holders;
31        quote! {
32            #[allow(clippy::let_unit_value)]
33            #(let mut #holders = #pyo3_path::impl_::extract_argument::FunctionArgumentHolder::INIT;)*
34        }
35    }
36}
37
38/// Return true if the argument list is simply (*args, **kwds).
39pub fn is_forwarded_args(signature: &FunctionSignature<'_>) -> bool {
40    matches!(
41        signature.arguments.as_slice(),
42        [FnArg::VarArgs(..), FnArg::KwArgs(..),]
43    )
44}
45
46pub fn impl_arg_params(
47    spec: &FnSpec<'_>,
48    self_: Option<&syn::Type>,
49    fastcall: bool,
50    holders: &mut Holders,
51    ctx: &Ctx,
52) -> (TokenStream, Vec<TokenStream>) {
53    let args_array = syn::Ident::new("output", Span::call_site());
54    let Ctx { pyo3_path, .. } = ctx;
55
56    let from_py_with = spec
57        .signature
58        .arguments
59        .iter()
60        .enumerate()
61        .filter_map(|(i, arg)| {
62            let from_py_with = &arg.from_py_with()?.value;
63            let from_py_with_holder = format_ident!("from_py_with_{}", i);
64            Some(quote_spanned! { from_py_with.span() =>
65                let #from_py_with_holder = #from_py_with;
66            })
67        })
68        .collect::<TokenStream>();
69
70    if !fastcall && is_forwarded_args(&spec.signature) {
71        // In the varargs convention, we can just pass though if the signature
72        // is (*args, **kwds).
73        let arg_convert = spec
74            .signature
75            .arguments
76            .iter()
77            .enumerate()
78            .map(|(i, arg)| impl_arg_param(arg, i, &mut 0, holders, ctx))
79            .collect();
80        return (
81            quote! {
82                let _args = unsafe { #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &_args) };
83                let _kwargs = #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr_or_opt(py, &_kwargs);
84                #from_py_with
85            },
86            arg_convert,
87        );
88    };
89
90    let positional_parameter_names = &spec.signature.python_signature.positional_parameters;
91    let positional_only_parameters = &spec.signature.python_signature.positional_only_parameters;
92    let required_positional_parameters = &spec
93        .signature
94        .python_signature
95        .required_positional_parameters;
96    let keyword_only_parameters = spec
97        .signature
98        .python_signature
99        .keyword_only_parameters
100        .iter()
101        .map(|(name, required)| {
102            quote! {
103                #pyo3_path::impl_::extract_argument::KeywordOnlyParameterDescription {
104                    name: #name,
105                    required: #required,
106                }
107            }
108        });
109
110    let num_params = positional_parameter_names.len() + keyword_only_parameters.len();
111
112    let mut option_pos = 0usize;
113    let param_conversion = spec
114        .signature
115        .arguments
116        .iter()
117        .enumerate()
118        .map(|(i, arg)| impl_arg_param(arg, i, &mut option_pos, holders, ctx))
119        .collect();
120
121    let args_handler = if spec.signature.python_signature.varargs.is_some() {
122        quote! { #pyo3_path::impl_::extract_argument::TupleVarargs }
123    } else {
124        quote! { #pyo3_path::impl_::extract_argument::NoVarargs }
125    };
126    let kwargs_handler = if spec.signature.python_signature.kwargs.is_some() {
127        quote! { #pyo3_path::impl_::extract_argument::DictVarkeywords }
128    } else {
129        quote! { #pyo3_path::impl_::extract_argument::NoVarkeywords }
130    };
131
132    let cls_name = if let Some(cls) = self_ {
133        quote! { ::std::option::Option::Some(<#cls as #pyo3_path::type_object::PyTypeInfo>::NAME) }
134    } else {
135        quote! { ::std::option::Option::None }
136    };
137    let python_name = &spec.python_name;
138
139    let extract_expression = if fastcall {
140        quote! {
141            DESCRIPTION.extract_arguments_fastcall::<#args_handler, #kwargs_handler>(
142                py,
143                _args,
144                _nargs,
145                _kwnames,
146                &mut #args_array
147            )?
148        }
149    } else {
150        quote! {
151            DESCRIPTION.extract_arguments_tuple_dict::<#args_handler, #kwargs_handler>(
152                py,
153                _args,
154                _kwargs,
155                &mut #args_array
156            )?
157        }
158    };
159
160    // create array of arguments, and then parse
161    (
162        quote! {
163                const DESCRIPTION: #pyo3_path::impl_::extract_argument::FunctionDescription = #pyo3_path::impl_::extract_argument::FunctionDescription {
164                    cls_name: #cls_name,
165                    func_name: stringify!(#python_name),
166                    positional_parameter_names: &[#(#positional_parameter_names),*],
167                    positional_only_parameters: #positional_only_parameters,
168                    required_positional_parameters: #required_positional_parameters,
169                    keyword_only_parameters: &[#(#keyword_only_parameters),*],
170                };
171                let mut #args_array = [::std::option::Option::None; #num_params];
172                let (_args, _kwargs) = #extract_expression;
173                #from_py_with
174        },
175        param_conversion,
176    )
177}
178
179fn impl_arg_param(
180    arg: &FnArg<'_>,
181    pos: usize,
182    option_pos: &mut usize,
183    holders: &mut Holders,
184    ctx: &Ctx,
185) -> TokenStream {
186    let Ctx { pyo3_path, .. } = ctx;
187    let args_array = syn::Ident::new("output", Span::call_site());
188
189    match arg {
190        FnArg::Regular(arg) => {
191            let from_py_with = format_ident!("from_py_with_{}", pos);
192            let arg_value = quote!(#args_array[#option_pos].as_deref());
193            *option_pos += 1;
194            impl_regular_arg_param(arg, from_py_with, arg_value, holders, ctx)
195        }
196        FnArg::VarArgs(arg) => {
197            let holder = holders.push_holder(arg.name.span());
198            let name_str = arg.name.to_string();
199            quote_spanned! { arg.name.span() =>
200                #pyo3_path::impl_::extract_argument::extract_argument(
201                    &_args,
202                    &mut #holder,
203                    #name_str
204                )?
205            }
206        }
207        FnArg::KwArgs(arg) => {
208            let holder = holders.push_holder(arg.name.span());
209            let name_str = arg.name.to_string();
210            quote_spanned! { arg.name.span() =>
211                #pyo3_path::impl_::extract_argument::extract_optional_argument(
212                    _kwargs.as_deref(),
213                    &mut #holder,
214                    #name_str,
215                    || ::std::option::Option::None
216                )?
217            }
218        }
219        FnArg::Py(..) => quote! { py },
220        FnArg::CancelHandle(..) => quote! { __cancel_handle },
221    }
222}
223
224/// Re option_pos: The option slice doesn't contain the py: Python argument, so the argument
225/// index and the index in option diverge when using py: Python
226pub(crate) fn impl_regular_arg_param(
227    arg: &RegularArg<'_>,
228    from_py_with: syn::Ident,
229    arg_value: TokenStream, // expected type: Option<&'a Bound<'py, PyAny>>
230    holders: &mut Holders,
231    ctx: &Ctx,
232) -> TokenStream {
233    let Ctx { pyo3_path, .. } = ctx;
234    let pyo3_path = pyo3_path.to_tokens_spanned(arg.ty.span());
235
236    // Use this macro inside this function, to ensure that all code generated here is associated
237    // with the function argument
238    macro_rules! quote_arg_span {
239        ($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => $($tokens)*) }
240    }
241
242    let name_str = arg.name.to_string();
243    let mut default = arg.default_value.as_ref().map(|expr| quote!(#expr));
244
245    // Option<T> arguments have special treatment: the default should be specified _without_ the
246    // Some() wrapper. Maybe this should be changed in future?!
247    if arg.option_wrapped_type.is_some() {
248        default = Some(default.map_or_else(
249            || quote!(::std::option::Option::None),
250            |tokens| some_wrap(tokens, ctx),
251        ));
252    }
253
254    if arg.from_py_with.is_some() {
255        if let Some(default) = default {
256            quote_arg_span! {
257                #pyo3_path::impl_::extract_argument::from_py_with_with_default(
258                    #arg_value,
259                    #name_str,
260                    #from_py_with as fn(_) -> _,
261                    #[allow(clippy::redundant_closure)]
262                    {
263                        || #default
264                    }
265                )?
266            }
267        } else {
268            let unwrap = quote! {unsafe { #pyo3_path::impl_::extract_argument::unwrap_required_argument(#arg_value) }};
269            quote_arg_span! {
270                #pyo3_path::impl_::extract_argument::from_py_with(
271                    #unwrap,
272                    #name_str,
273                    #from_py_with as fn(_) -> _,
274                )?
275            }
276        }
277    } else if arg.option_wrapped_type.is_some() {
278        let holder = holders.push_holder(arg.name.span());
279        quote_arg_span! {
280            #pyo3_path::impl_::extract_argument::extract_optional_argument(
281                #arg_value,
282                &mut #holder,
283                #name_str,
284                #[allow(clippy::redundant_closure)]
285                {
286                    || #default
287                }
288            )?
289        }
290    } else if let Some(default) = default {
291        let holder = holders.push_holder(arg.name.span());
292        quote_arg_span! {
293            #pyo3_path::impl_::extract_argument::extract_argument_with_default(
294                #arg_value,
295                &mut #holder,
296                #name_str,
297                #[allow(clippy::redundant_closure)]
298                {
299                    || #default
300                }
301            )?
302        }
303    } else {
304        let holder = holders.push_holder(arg.name.span());
305        let unwrap = quote! {unsafe { #pyo3_path::impl_::extract_argument::unwrap_required_argument(#arg_value) }};
306        quote_arg_span! {
307            #pyo3_path::impl_::extract_argument::extract_argument(
308                #unwrap,
309                &mut #holder,
310                #name_str
311            )?
312        }
313    }
314}