| 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 | |