1use crate::{
2 method::{FnArg, FnSpec},
3 pyfunction::FunctionSignature,
4 quotes::some_wrap,
5};
6use proc_macro2::{Span, TokenStream};
7use quote::{quote, quote_spanned};
8use syn::spanned::Spanned;
9use syn::Result;
10
11/// Return true if the argument list is simply (*args, **kwds).
12pub 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
28pub 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
143fn 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