1 | use crate::{ |
2 | method::{FnArg, FnSpec}, |
3 | pyfunction::FunctionSignature, |
4 | quotes::some_wrap, |
5 | }; |
6 | use proc_macro2::{Span, TokenStream}; |
7 | use quote::{quote, quote_spanned}; |
8 | use syn::spanned::Spanned; |
9 | use syn::Result; |
10 | |
11 | /// Return true if the argument list is simply (*args, **kwds). |
12 | pub fn is_forwarded_args(signature: &FunctionSignature<'_>) -> bool { |
13 | matches!( |
14 | signature.arguments.as_slice(), |
15 | [ |
16 | FnArg { |
17 | is_varargs: true, |
18 | .. |
19 | }, |
20 | FnArg { |
21 | is_kwargs: true, |
22 | .. |
23 | }, |
24 | ] |
25 | ) |
26 | } |
27 | |
28 | pub fn impl_arg_params( |
29 | spec: &FnSpec<'_>, |
30 | self_: Option<&syn::Type>, |
31 | fastcall: bool, |
32 | ) -> Result<(TokenStream, Vec<TokenStream>)> { |
33 | let args_array = syn::Ident::new("output" , Span::call_site()); |
34 | |
35 | if !fastcall && is_forwarded_args(&spec.signature) { |
36 | // In the varargs convention, we can just pass though if the signature |
37 | // is (*args, **kwds). |
38 | let arg_convert = spec |
39 | .signature |
40 | .arguments |
41 | .iter() |
42 | .map(|arg| impl_arg_param(arg, &mut 0, &args_array)) |
43 | .collect::<Result<_>>()?; |
44 | return Ok(( |
45 | quote! { |
46 | let _args = py.from_borrowed_ptr::<_pyo3::types::PyTuple>(_args); |
47 | let _kwargs: ::std::option::Option<&_pyo3::types::PyDict> = py.from_borrowed_ptr_or_opt(_kwargs); |
48 | }, |
49 | arg_convert, |
50 | )); |
51 | }; |
52 | |
53 | let positional_parameter_names = &spec.signature.python_signature.positional_parameters; |
54 | let positional_only_parameters = &spec.signature.python_signature.positional_only_parameters; |
55 | let required_positional_parameters = &spec |
56 | .signature |
57 | .python_signature |
58 | .required_positional_parameters; |
59 | let keyword_only_parameters = spec |
60 | .signature |
61 | .python_signature |
62 | .keyword_only_parameters |
63 | .iter() |
64 | .map(|(name, required)| { |
65 | quote! { |
66 | _pyo3::impl_::extract_argument::KeywordOnlyParameterDescription { |
67 | name: #name, |
68 | required: #required, |
69 | } |
70 | } |
71 | }); |
72 | |
73 | let num_params = positional_parameter_names.len() + keyword_only_parameters.len(); |
74 | |
75 | let mut option_pos = 0; |
76 | let param_conversion = spec |
77 | .signature |
78 | .arguments |
79 | .iter() |
80 | .map(|arg| impl_arg_param(arg, &mut option_pos, &args_array)) |
81 | .collect::<Result<_>>()?; |
82 | |
83 | let args_handler = if spec.signature.python_signature.varargs.is_some() { |
84 | quote! { _pyo3::impl_::extract_argument::TupleVarargs } |
85 | } else { |
86 | quote! { _pyo3::impl_::extract_argument::NoVarargs } |
87 | }; |
88 | let kwargs_handler = if spec.signature.python_signature.kwargs.is_some() { |
89 | quote! { _pyo3::impl_::extract_argument::DictVarkeywords } |
90 | } else { |
91 | quote! { _pyo3::impl_::extract_argument::NoVarkeywords } |
92 | }; |
93 | |
94 | let cls_name = if let Some(cls) = self_ { |
95 | quote! { ::std::option::Option::Some(<#cls as _pyo3::type_object::PyTypeInfo>::NAME) } |
96 | } else { |
97 | quote! { ::std::option::Option::None } |
98 | }; |
99 | let python_name = &spec.python_name; |
100 | |
101 | let extract_expression = if fastcall { |
102 | quote! { |
103 | DESCRIPTION.extract_arguments_fastcall::<#args_handler, #kwargs_handler>( |
104 | py, |
105 | _args, |
106 | _nargs, |
107 | _kwnames, |
108 | &mut #args_array |
109 | )? |
110 | } |
111 | } else { |
112 | quote! { |
113 | DESCRIPTION.extract_arguments_tuple_dict::<#args_handler, #kwargs_handler>( |
114 | py, |
115 | _args, |
116 | _kwargs, |
117 | &mut #args_array |
118 | )? |
119 | } |
120 | }; |
121 | |
122 | // create array of arguments, and then parse |
123 | Ok(( |
124 | quote! { |
125 | const DESCRIPTION: _pyo3::impl_::extract_argument::FunctionDescription = _pyo3::impl_::extract_argument::FunctionDescription { |
126 | cls_name: #cls_name, |
127 | func_name: stringify!(#python_name), |
128 | positional_parameter_names: &[#(#positional_parameter_names),*], |
129 | positional_only_parameters: #positional_only_parameters, |
130 | required_positional_parameters: #required_positional_parameters, |
131 | keyword_only_parameters: &[#(#keyword_only_parameters),*], |
132 | }; |
133 | |
134 | let mut #args_array = [::std::option::Option::None; #num_params]; |
135 | let (_args, _kwargs) = #extract_expression; |
136 | }, |
137 | param_conversion, |
138 | )) |
139 | } |
140 | |
141 | /// Re option_pos: The option slice doesn't contain the py: Python argument, so the argument |
142 | /// index and the index in option diverge when using py: Python |
143 | fn impl_arg_param( |
144 | arg: &FnArg<'_>, |
145 | option_pos: &mut usize, |
146 | args_array: &syn::Ident, |
147 | ) -> Result<TokenStream> { |
148 | // Use this macro inside this function, to ensure that all code generated here is associated |
149 | // with the function argument |
150 | macro_rules! quote_arg_span { |
151 | ($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => $($tokens)*) } |
152 | } |
153 | |
154 | if arg.py { |
155 | return Ok(quote! { py }); |
156 | } |
157 | |
158 | let name = arg.name; |
159 | let name_str = name.to_string(); |
160 | |
161 | if arg.is_varargs { |
162 | ensure_spanned!( |
163 | arg.optional.is_none(), |
164 | arg.name.span() => "args cannot be optional" |
165 | ); |
166 | return Ok(quote_arg_span! { |
167 | _pyo3::impl_::extract_argument::extract_argument( |
168 | _args, |
169 | &mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT }, |
170 | #name_str |
171 | )? |
172 | }); |
173 | } else if arg.is_kwargs { |
174 | ensure_spanned!( |
175 | arg.optional.is_some(), |
176 | arg.name.span() => "kwargs must be Option<_>" |
177 | ); |
178 | return Ok(quote_arg_span! { |
179 | _pyo3::impl_::extract_argument::extract_optional_argument( |
180 | _kwargs.map(::std::convert::AsRef::as_ref), |
181 | &mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT }, |
182 | #name_str, |
183 | || ::std::option::Option::None |
184 | )? |
185 | }); |
186 | } |
187 | |
188 | let arg_value = quote_arg_span!(#args_array[#option_pos]); |
189 | *option_pos += 1; |
190 | |
191 | let mut default = arg.default.as_ref().map(|expr| quote!(#expr)); |
192 | |
193 | // Option<T> arguments have special treatment: the default should be specified _without_ the |
194 | // Some() wrapper. Maybe this should be changed in future?! |
195 | if arg.optional.is_some() { |
196 | default = Some(default.map_or_else(|| quote!(::std::option::Option::None), some_wrap)); |
197 | } |
198 | |
199 | let tokens = if let Some(expr_path) = arg.attrs.from_py_with.as_ref().map(|attr| &attr.value) { |
200 | if let Some(default) = default { |
201 | quote_arg_span! { |
202 | #[allow(clippy::redundant_closure)] |
203 | _pyo3::impl_::extract_argument::from_py_with_with_default( |
204 | #arg_value, |
205 | #name_str, |
206 | #expr_path, |
207 | || #default |
208 | )? |
209 | } |
210 | } else { |
211 | quote_arg_span! { |
212 | _pyo3::impl_::extract_argument::from_py_with( |
213 | _pyo3::impl_::extract_argument::unwrap_required_argument(#arg_value), |
214 | #name_str, |
215 | #expr_path, |
216 | )? |
217 | } |
218 | } |
219 | } else if arg.optional.is_some() { |
220 | quote_arg_span! { |
221 | #[allow(clippy::redundant_closure)] |
222 | _pyo3::impl_::extract_argument::extract_optional_argument( |
223 | #arg_value, |
224 | &mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT }, |
225 | #name_str, |
226 | || #default |
227 | )? |
228 | } |
229 | } else if let Some(default) = default { |
230 | quote_arg_span! { |
231 | #[allow(clippy::redundant_closure)] |
232 | _pyo3::impl_::extract_argument::extract_argument_with_default( |
233 | #arg_value, |
234 | &mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT }, |
235 | #name_str, |
236 | || #default |
237 | )? |
238 | } |
239 | } else { |
240 | quote_arg_span! { |
241 | _pyo3::impl_::extract_argument::extract_argument( |
242 | _pyo3::impl_::extract_argument::unwrap_required_argument(#arg_value), |
243 | &mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT }, |
244 | #name_str |
245 | )? |
246 | } |
247 | }; |
248 | Ok(tokens) |
249 | } |
250 | |