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
38pub 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 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 (
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
224pub(crate) fn impl_regular_arg_param(
227 arg: &RegularArg<'_>,
228 from_py_with: syn::Ident,
229 arg_value: TokenStream, 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 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 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}