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
39pub 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 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 (
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
227pub(crate) fn impl_regular_arg_param(
230 arg: &RegularArg<'_>,
231 from_py_with: syn::Ident,
232 arg_value: TokenStream, 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 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 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}