1 | use crate::utils::{deprecated_from_py_with, Ctx, TypeExt as _}; |
2 | use crate::{ |
3 | attributes::FromPyWithAttribute, |
4 | method::{FnArg, FnSpec, RegularArg}, |
5 | pyfunction::FunctionSignature, |
6 | quotes::some_wrap, |
7 | }; |
8 | use proc_macro2::{Span, TokenStream}; |
9 | use quote::{format_ident, quote, quote_spanned}; |
10 | use syn::spanned::Spanned; |
11 | |
12 | pub struct Holders { |
13 | holders: Vec<syn::Ident>, |
14 | } |
15 | |
16 | impl 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: Ident = 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: &PyO3CratePath, .. } = ctx; |
31 | let holders: &Vec = &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). |
40 | pub fn is_forwarded_args(signature: &FunctionSignature<'_>) -> bool { |
41 | matches!( |
42 | signature.arguments.as_slice(), |
43 | [FnArg::VarArgs(..), FnArg::KwArgs(..),] |
44 | ) |
45 | } |
46 | |
47 | pub 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 | |
182 | fn 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::<_, false>( |
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::<_, false>( |
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 |
229 | pub(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 | let use_probe = quote! { |
242 | #[allow(unused_imports)] |
243 | use #pyo3_path::impl_::pyclass::Probe as _; |
244 | }; |
245 | macro_rules! quote_arg_span { |
246 | ($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => { #use_probe $($tokens)* }) } |
247 | } |
248 | |
249 | let name_str = arg.name.to_string(); |
250 | let mut default = arg.default_value.as_ref().map(|expr| quote!(#expr)); |
251 | |
252 | // Option<T> arguments have special treatment: the default should be specified _without_ the |
253 | // Some() wrapper. Maybe this should be changed in future?! |
254 | if arg.option_wrapped_type.is_some() { |
255 | default = default.map(|tokens| some_wrap(tokens, ctx)); |
256 | } |
257 | |
258 | let arg_ty = arg.ty.clone().elide_lifetimes(); |
259 | if let Some(FromPyWithAttribute { kw, .. }) = arg.from_py_with { |
260 | let extractor = quote_spanned! { kw.span => |
261 | { let from_py_with: fn(_) -> _ = #from_py_with; from_py_with } |
262 | }; |
263 | if let Some(default) = default { |
264 | quote_arg_span! { |
265 | #pyo3_path::impl_::extract_argument::from_py_with_with_default( |
266 | #arg_value, |
267 | #name_str, |
268 | #extractor, |
269 | #[allow(clippy::redundant_closure)] |
270 | { |
271 | || #default |
272 | } |
273 | )? |
274 | } |
275 | } else { |
276 | let unwrap = quote! {unsafe { #pyo3_path::impl_::extract_argument::unwrap_required_argument(#arg_value) }}; |
277 | quote_arg_span! { |
278 | #pyo3_path::impl_::extract_argument::from_py_with( |
279 | #unwrap, |
280 | #name_str, |
281 | #extractor, |
282 | )? |
283 | } |
284 | } |
285 | } else if let Some(default) = default { |
286 | let holder = holders.push_holder(arg.name.span()); |
287 | if let Some(arg_ty) = arg.option_wrapped_type { |
288 | let arg_ty = arg_ty.clone().elide_lifetimes(); |
289 | quote_arg_span! { |
290 | #pyo3_path::impl_::extract_argument::extract_optional_argument::< |
291 | _, |
292 | { #pyo3_path::impl_::pyclass::IsOption::<#arg_ty>::VALUE } |
293 | >( |
294 | #arg_value, |
295 | &mut #holder, |
296 | #name_str, |
297 | #[allow(clippy::redundant_closure)] |
298 | { |
299 | || #default |
300 | } |
301 | )? |
302 | } |
303 | } else { |
304 | quote_arg_span! { |
305 | #pyo3_path::impl_::extract_argument::extract_argument_with_default::< |
306 | _, |
307 | { #pyo3_path::impl_::pyclass::IsOption::<#arg_ty>::VALUE } |
308 | >( |
309 | #arg_value, |
310 | &mut #holder, |
311 | #name_str, |
312 | #[allow(clippy::redundant_closure)] |
313 | { |
314 | || #default |
315 | } |
316 | )? |
317 | } |
318 | } |
319 | } else { |
320 | let holder = holders.push_holder(arg.name.span()); |
321 | let unwrap = quote! {unsafe { #pyo3_path::impl_::extract_argument::unwrap_required_argument(#arg_value) }}; |
322 | quote_arg_span! { |
323 | #pyo3_path::impl_::extract_argument::extract_argument::< |
324 | _, |
325 | { #pyo3_path::impl_::pyclass::IsOption::<#arg_ty>::VALUE } |
326 | >( |
327 | #unwrap, |
328 | &mut #holder, |
329 | #name_str |
330 | )? |
331 | } |
332 | } |
333 | } |
334 | |