pyo3_macros_backend/
params.rs

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