1 | use std::fmt::Display; |
2 | |
3 | use crate::attributes::{TextSignatureAttribute, TextSignatureAttributeValue}; |
4 | use crate::deprecations::{Deprecation, Deprecations}; |
5 | use crate::params::impl_arg_params; |
6 | use crate::pyfunction::{FunctionSignature, PyFunctionArgPyO3Attributes}; |
7 | use crate::pyfunction::{PyFunctionOptions, SignatureAttribute}; |
8 | use crate::quotes; |
9 | use crate::utils::{self, is_abi3, PythonDoc}; |
10 | use proc_macro2::{Span, TokenStream}; |
11 | use quote::ToTokens; |
12 | use quote::{quote, quote_spanned}; |
13 | use syn::ext::IdentExt; |
14 | use syn::spanned::Spanned; |
15 | use syn::{Ident, Result}; |
16 | |
17 | #[derive (Clone, Debug)] |
18 | pub struct FnArg<'a> { |
19 | pub name: &'a syn::Ident, |
20 | pub ty: &'a syn::Type, |
21 | pub optional: Option<&'a syn::Type>, |
22 | pub default: Option<syn::Expr>, |
23 | pub py: bool, |
24 | pub attrs: PyFunctionArgPyO3Attributes, |
25 | pub is_varargs: bool, |
26 | pub is_kwargs: bool, |
27 | } |
28 | |
29 | impl<'a> FnArg<'a> { |
30 | /// Transforms a rust fn arg parsed with syn into a method::FnArg |
31 | pub fn parse(arg: &'a mut syn::FnArg) -> Result<Self> { |
32 | match arg { |
33 | syn::FnArg::Receiver(recv) => { |
34 | bail_spanned!(recv.span() => "unexpected receiver" ) |
35 | } // checked in parse_fn_type |
36 | syn::FnArg::Typed(cap) => { |
37 | if let syn::Type::ImplTrait(_) = &*cap.ty { |
38 | bail_spanned!(cap.ty.span() => IMPL_TRAIT_ERR); |
39 | } |
40 | |
41 | let arg_attrs = PyFunctionArgPyO3Attributes::from_attrs(&mut cap.attrs)?; |
42 | let ident = match &*cap.pat { |
43 | syn::Pat::Ident(syn::PatIdent { ident, .. }) => ident, |
44 | other => return Err(handle_argument_error(other)), |
45 | }; |
46 | |
47 | Ok(FnArg { |
48 | name: ident, |
49 | ty: &cap.ty, |
50 | optional: utils::option_type_argument(&cap.ty), |
51 | default: None, |
52 | py: utils::is_python(&cap.ty), |
53 | attrs: arg_attrs, |
54 | is_varargs: false, |
55 | is_kwargs: false, |
56 | }) |
57 | } |
58 | } |
59 | } |
60 | } |
61 | |
62 | fn handle_argument_error(pat: &syn::Pat) -> syn::Error { |
63 | let span: Span = pat.span(); |
64 | let msg: &str = match pat { |
65 | syn::Pat::Wild(_) => "wildcard argument names are not supported" , |
66 | syn::Pat::Struct(_) |
67 | | syn::Pat::Tuple(_) |
68 | | syn::Pat::TupleStruct(_) |
69 | | syn::Pat::Slice(_) => "destructuring in arguments is not supported" , |
70 | _ => "unsupported argument" , |
71 | }; |
72 | syn::Error::new(span, message:msg) |
73 | } |
74 | |
75 | #[derive (Clone, Debug)] |
76 | pub enum FnType { |
77 | Getter(SelfType), |
78 | Setter(SelfType), |
79 | Fn(SelfType), |
80 | FnNew, |
81 | FnNewClass(Span), |
82 | FnClass(Span), |
83 | FnStatic, |
84 | FnModule(Span), |
85 | ClassAttribute, |
86 | } |
87 | |
88 | impl FnType { |
89 | pub fn skip_first_rust_argument_in_python_signature(&self) -> bool { |
90 | match self { |
91 | FnType::Getter(_) |
92 | | FnType::Setter(_) |
93 | | FnType::Fn(_) |
94 | | FnType::FnClass(_) |
95 | | FnType::FnNewClass(_) |
96 | | FnType::FnModule(_) => true, |
97 | FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => false, |
98 | } |
99 | } |
100 | |
101 | pub fn self_arg(&self, cls: Option<&syn::Type>, error_mode: ExtractErrorMode) -> TokenStream { |
102 | match self { |
103 | FnType::Getter(st) | FnType::Setter(st) | FnType::Fn(st) => { |
104 | let mut receiver = st.receiver( |
105 | cls.expect("no class given for Fn with a \"self \" receiver" ), |
106 | error_mode, |
107 | ); |
108 | syn::Token![,](Span::call_site()).to_tokens(&mut receiver); |
109 | receiver |
110 | } |
111 | FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => { |
112 | quote!() |
113 | } |
114 | FnType::FnClass(span) | FnType::FnNewClass(span) => { |
115 | let py = syn::Ident::new("py" , Span::call_site()); |
116 | let slf: Ident = syn::Ident::new("_slf" , Span::call_site()); |
117 | quote_spanned! { *span => |
118 | #[allow(clippy::useless_conversion)] |
119 | ::std::convert::Into::into(_pyo3::types::PyType::from_type_ptr(#py, #slf.cast())), |
120 | } |
121 | } |
122 | FnType::FnModule(span) => { |
123 | quote_spanned! { *span => |
124 | #[allow(clippy::useless_conversion)] |
125 | ::std::convert::Into::into(py.from_borrowed_ptr::<_pyo3::types::PyModule>(_slf)), |
126 | } |
127 | } |
128 | } |
129 | } |
130 | } |
131 | |
132 | #[derive (Clone, Debug)] |
133 | pub enum SelfType { |
134 | Receiver { mutable: bool, span: Span }, |
135 | TryFromPyCell(Span), |
136 | } |
137 | |
138 | #[derive (Clone, Copy)] |
139 | pub enum ExtractErrorMode { |
140 | NotImplemented, |
141 | Raise, |
142 | } |
143 | |
144 | impl ExtractErrorMode { |
145 | pub fn handle_error(self, extract: TokenStream) -> TokenStream { |
146 | match self { |
147 | ExtractErrorMode::Raise => quote! { #extract? }, |
148 | ExtractErrorMode::NotImplemented => quote! { |
149 | match #extract { |
150 | ::std::result::Result::Ok(value) => value, |
151 | ::std::result::Result::Err(_) => { return _pyo3::callback::convert(py, py.NotImplemented()); }, |
152 | } |
153 | }, |
154 | } |
155 | } |
156 | } |
157 | |
158 | impl SelfType { |
159 | pub fn receiver(&self, cls: &syn::Type, error_mode: ExtractErrorMode) -> TokenStream { |
160 | // Due to use of quote_spanned in this function, need to bind these idents to the |
161 | // main macro callsite. |
162 | let py = syn::Ident::new("py" , Span::call_site()); |
163 | let slf = syn::Ident::new("_slf" , Span::call_site()); |
164 | match self { |
165 | SelfType::Receiver { span, mutable } => { |
166 | let method = if *mutable { |
167 | syn::Ident::new("extract_pyclass_ref_mut" , *span) |
168 | } else { |
169 | syn::Ident::new("extract_pyclass_ref" , *span) |
170 | }; |
171 | error_mode.handle_error(quote_spanned! { *span => |
172 | _pyo3::impl_::extract_argument::#method::<#cls>( |
173 | #py.from_borrowed_ptr::<_pyo3::PyAny>(#slf), |
174 | &mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT }, |
175 | ) |
176 | }) |
177 | } |
178 | SelfType::TryFromPyCell(span) => { |
179 | error_mode.handle_error( |
180 | quote_spanned! { *span => |
181 | #py.from_borrowed_ptr::<_pyo3::PyAny>(#slf).downcast::<_pyo3::PyCell<#cls>>() |
182 | .map_err(::std::convert::Into::<_pyo3::PyErr>::into) |
183 | .and_then( |
184 | #[allow(clippy::useless_conversion)] // In case slf is PyCell<Self> |
185 | #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] // In case slf is Py<Self> (unknown_lints can be removed when MSRV is 1.75+) |
186 | |cell| ::std::convert::TryFrom::try_from(cell).map_err(::std::convert::Into::into) |
187 | ) |
188 | |
189 | } |
190 | ) |
191 | } |
192 | } |
193 | } |
194 | } |
195 | |
196 | /// Determines which CPython calling convention a given FnSpec uses. |
197 | #[derive (Clone, Debug)] |
198 | pub enum CallingConvention { |
199 | Noargs, // METH_NOARGS |
200 | Varargs, // METH_VARARGS | METH_KEYWORDS |
201 | Fastcall, // METH_FASTCALL | METH_KEYWORDS (not compatible with `abi3` feature) |
202 | TpNew, // special convention for tp_new |
203 | } |
204 | |
205 | impl CallingConvention { |
206 | /// Determine default calling convention from an argument signature. |
207 | /// |
208 | /// Different other slots (tp_call, tp_new) can have other requirements |
209 | /// and are set manually (see `parse_fn_type` below). |
210 | pub fn from_signature(signature: &FunctionSignature<'_>) -> Self { |
211 | if signature.python_signature.has_no_args() { |
212 | Self::Noargs |
213 | } else if signature.python_signature.kwargs.is_some() { |
214 | // for functions that accept **kwargs, always prefer varargs |
215 | Self::Varargs |
216 | } else if !is_abi3() { |
217 | // FIXME: available in the stable ABI since 3.10 |
218 | Self::Fastcall |
219 | } else { |
220 | Self::Varargs |
221 | } |
222 | } |
223 | } |
224 | |
225 | pub struct FnSpec<'a> { |
226 | pub tp: FnType, |
227 | // Rust function name |
228 | pub name: &'a syn::Ident, |
229 | // Wrapped python name. This should not have any leading r#. |
230 | // r# can be removed by syn::ext::IdentExt::unraw() |
231 | pub python_name: syn::Ident, |
232 | pub signature: FunctionSignature<'a>, |
233 | pub output: syn::Type, |
234 | pub convention: CallingConvention, |
235 | pub text_signature: Option<TextSignatureAttribute>, |
236 | pub unsafety: Option<syn::Token![unsafe]>, |
237 | pub deprecations: Deprecations, |
238 | } |
239 | |
240 | pub fn get_return_info(output: &syn::ReturnType) -> syn::Type { |
241 | match output { |
242 | syn::ReturnType::Default => syn::Type::Infer(syn::parse_quote! {_}), |
243 | syn::ReturnType::Type(_, ty: &Box) => *ty.clone(), |
244 | } |
245 | } |
246 | |
247 | pub fn parse_method_receiver(arg: &syn::FnArg) -> Result<SelfType> { |
248 | match arg { |
249 | syn::FnArg::Receiver( |
250 | recv: &Receiver @ syn::Receiver { |
251 | reference: None, .. |
252 | }, |
253 | ) => { |
254 | bail_spanned!(recv.span() => RECEIVER_BY_VALUE_ERR); |
255 | } |
256 | syn::FnArg::Receiver(recv: &Receiver @ syn::Receiver { mutability: &Option, .. }) => Ok(SelfType::Receiver { |
257 | mutable: mutability.is_some(), |
258 | span: recv.span(), |
259 | }), |
260 | syn::FnArg::Typed(syn::PatType { ty: &Box, .. }) => { |
261 | if let syn::Type::ImplTrait(_) = &**ty { |
262 | bail_spanned!(ty.span() => IMPL_TRAIT_ERR); |
263 | } |
264 | Ok(SelfType::TryFromPyCell(ty.span())) |
265 | } |
266 | } |
267 | } |
268 | |
269 | impl<'a> FnSpec<'a> { |
270 | /// Parser function signature and function attributes |
271 | pub fn parse( |
272 | // Signature is mutable to remove the `Python` argument. |
273 | sig: &'a mut syn::Signature, |
274 | meth_attrs: &mut Vec<syn::Attribute>, |
275 | options: PyFunctionOptions, |
276 | ) -> Result<FnSpec<'a>> { |
277 | let PyFunctionOptions { |
278 | text_signature, |
279 | name, |
280 | signature, |
281 | .. |
282 | } = options; |
283 | |
284 | let mut python_name = name.map(|name| name.value.0); |
285 | let mut deprecations = Deprecations::new(); |
286 | |
287 | let fn_type = Self::parse_fn_type(sig, meth_attrs, &mut python_name, &mut deprecations)?; |
288 | ensure_signatures_on_valid_method(&fn_type, signature.as_ref(), text_signature.as_ref())?; |
289 | |
290 | let name = &sig.ident; |
291 | let ty = get_return_info(&sig.output); |
292 | let python_name = python_name.as_ref().unwrap_or(name).unraw(); |
293 | |
294 | let arguments: Vec<_> = sig |
295 | .inputs |
296 | .iter_mut() |
297 | .skip(if fn_type.skip_first_rust_argument_in_python_signature() { |
298 | 1 |
299 | } else { |
300 | 0 |
301 | }) |
302 | .map(FnArg::parse) |
303 | .collect::<Result<_>>()?; |
304 | |
305 | let signature = if let Some(signature) = signature { |
306 | FunctionSignature::from_arguments_and_attribute(arguments, signature)? |
307 | } else { |
308 | FunctionSignature::from_arguments(arguments)? |
309 | }; |
310 | |
311 | let convention = if matches!(fn_type, FnType::FnNew | FnType::FnNewClass(_)) { |
312 | CallingConvention::TpNew |
313 | } else { |
314 | CallingConvention::from_signature(&signature) |
315 | }; |
316 | |
317 | Ok(FnSpec { |
318 | tp: fn_type, |
319 | name, |
320 | convention, |
321 | python_name, |
322 | signature, |
323 | output: ty, |
324 | text_signature, |
325 | unsafety: sig.unsafety, |
326 | deprecations, |
327 | }) |
328 | } |
329 | |
330 | pub fn null_terminated_python_name(&self) -> syn::LitStr { |
331 | syn::LitStr::new(&format!(" {}\0" , self.python_name), self.python_name.span()) |
332 | } |
333 | |
334 | fn parse_fn_type( |
335 | sig: &syn::Signature, |
336 | meth_attrs: &mut Vec<syn::Attribute>, |
337 | python_name: &mut Option<syn::Ident>, |
338 | deprecations: &mut Deprecations, |
339 | ) -> Result<FnType> { |
340 | let mut method_attributes = parse_method_attributes(meth_attrs, deprecations)?; |
341 | |
342 | let name = &sig.ident; |
343 | let parse_receiver = |msg: &'static str| { |
344 | let first_arg = sig |
345 | .inputs |
346 | .first() |
347 | .ok_or_else(|| err_spanned!(sig.span() => msg))?; |
348 | parse_method_receiver(first_arg) |
349 | }; |
350 | |
351 | // strip get_ or set_ |
352 | let strip_fn_name = |prefix: &'static str| { |
353 | name.unraw() |
354 | .to_string() |
355 | .strip_prefix(prefix) |
356 | .map(|stripped| syn::Ident::new(stripped, name.span())) |
357 | }; |
358 | |
359 | let mut set_name_to_new = || { |
360 | if let Some(name) = &python_name { |
361 | bail_spanned!(name.span() => "`name` not allowed with `#[new]`" ); |
362 | } |
363 | *python_name = Some(syn::Ident::new("__new__" , Span::call_site())); |
364 | Ok(()) |
365 | }; |
366 | |
367 | let fn_type = match method_attributes.as_mut_slice() { |
368 | [] => FnType::Fn(parse_receiver( |
369 | "static method needs #[staticmethod] attribute" , |
370 | )?), |
371 | [MethodTypeAttribute::StaticMethod(_)] => FnType::FnStatic, |
372 | [MethodTypeAttribute::ClassAttribute(_)] => FnType::ClassAttribute, |
373 | [MethodTypeAttribute::New(_)] => { |
374 | set_name_to_new()?; |
375 | FnType::FnNew |
376 | } |
377 | [MethodTypeAttribute::New(_), MethodTypeAttribute::ClassMethod(span)] |
378 | | [MethodTypeAttribute::ClassMethod(span), MethodTypeAttribute::New(_)] => { |
379 | set_name_to_new()?; |
380 | FnType::FnNewClass(*span) |
381 | } |
382 | [MethodTypeAttribute::ClassMethod(_)] => { |
383 | // Add a helpful hint if the classmethod doesn't look like a classmethod |
384 | let span = match sig.inputs.first() { |
385 | // Don't actually bother checking the type of the first argument, the compiler |
386 | // will error on incorrect type. |
387 | Some(syn::FnArg::Typed(first_arg)) => first_arg.ty.span(), |
388 | Some(syn::FnArg::Receiver(_)) | None => bail_spanned!( |
389 | sig.paren_token.span.join() => "Expected `&PyType` or `Py<PyType>` as the first argument to `#[classmethod]`" |
390 | ), |
391 | }; |
392 | FnType::FnClass(span) |
393 | } |
394 | [MethodTypeAttribute::Getter(_, name)] => { |
395 | if let Some(name) = name.take() { |
396 | ensure_spanned!( |
397 | python_name.replace(name).is_none(), |
398 | python_name.span() => "`name` may only be specified once" |
399 | ); |
400 | } else if python_name.is_none() { |
401 | // Strip off "get_" prefix if needed |
402 | *python_name = strip_fn_name("get_" ); |
403 | } |
404 | |
405 | FnType::Getter(parse_receiver("expected receiver for `#[getter]`" )?) |
406 | } |
407 | [MethodTypeAttribute::Setter(_, name)] => { |
408 | if let Some(name) = name.take() { |
409 | ensure_spanned!( |
410 | python_name.replace(name).is_none(), |
411 | python_name.span() => "`name` may only be specified once" |
412 | ); |
413 | } else if python_name.is_none() { |
414 | // Strip off "set_" prefix if needed |
415 | *python_name = strip_fn_name("set_" ); |
416 | } |
417 | |
418 | FnType::Setter(parse_receiver("expected receiver for `#[setter]`" )?) |
419 | } |
420 | [first, rest @ .., last] => { |
421 | // Join as many of the spans together as possible |
422 | let span = rest |
423 | .iter() |
424 | .fold(first.span(), |s, next| s.join(next.span()).unwrap_or(s)); |
425 | let span = span.join(last.span()).unwrap_or(span); |
426 | // List all the attributes in the error message |
427 | let mut msg = format!("` {}` may not be combined with" , first); |
428 | let mut is_first = true; |
429 | for attr in &*rest { |
430 | msg.push_str(&format!(" ` {}`" , attr)); |
431 | if is_first { |
432 | is_first = false; |
433 | } else { |
434 | msg.push(',' ); |
435 | } |
436 | } |
437 | if !rest.is_empty() { |
438 | msg.push_str(" and" ); |
439 | } |
440 | msg.push_str(&format!(" ` {}`" , last)); |
441 | bail_spanned!(span => msg) |
442 | } |
443 | }; |
444 | Ok(fn_type) |
445 | } |
446 | |
447 | /// Return a C wrapper function for this signature. |
448 | pub fn get_wrapper_function( |
449 | &self, |
450 | ident: &proc_macro2::Ident, |
451 | cls: Option<&syn::Type>, |
452 | ) -> Result<TokenStream> { |
453 | let self_arg = self.tp.self_arg(cls, ExtractErrorMode::Raise); |
454 | let func_name = &self.name; |
455 | |
456 | let rust_call = |args: Vec<TokenStream>| { |
457 | quotes::map_result_into_ptr(quotes::ok_wrap(quote! { function(#self_arg #(#args),*) })) |
458 | }; |
459 | |
460 | let rust_name = if let Some(cls) = cls { |
461 | quote!(#cls::#func_name) |
462 | } else { |
463 | quote!(#func_name) |
464 | }; |
465 | |
466 | Ok(match self.convention { |
467 | CallingConvention::Noargs => { |
468 | let call = if !self.signature.arguments.is_empty() { |
469 | // Only `py` arg can be here |
470 | rust_call(vec![quote!(py)]) |
471 | } else { |
472 | rust_call(vec![]) |
473 | }; |
474 | |
475 | quote! { |
476 | unsafe fn #ident<'py>( |
477 | py: _pyo3::Python<'py>, |
478 | _slf: *mut _pyo3::ffi::PyObject, |
479 | ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { |
480 | let function = #rust_name; // Shadow the function name to avoid #3017 |
481 | #call |
482 | } |
483 | } |
484 | } |
485 | CallingConvention::Fastcall => { |
486 | let (arg_convert, args) = impl_arg_params(self, cls, true)?; |
487 | let call = rust_call(args); |
488 | quote! { |
489 | unsafe fn #ident<'py>( |
490 | py: _pyo3::Python<'py>, |
491 | _slf: *mut _pyo3::ffi::PyObject, |
492 | _args: *const *mut _pyo3::ffi::PyObject, |
493 | _nargs: _pyo3::ffi::Py_ssize_t, |
494 | _kwnames: *mut _pyo3::ffi::PyObject |
495 | ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { |
496 | let function = #rust_name; // Shadow the function name to avoid #3017 |
497 | #arg_convert |
498 | #call |
499 | } |
500 | } |
501 | } |
502 | CallingConvention::Varargs => { |
503 | let (arg_convert, args) = impl_arg_params(self, cls, false)?; |
504 | let call = rust_call(args); |
505 | quote! { |
506 | unsafe fn #ident<'py>( |
507 | py: _pyo3::Python<'py>, |
508 | _slf: *mut _pyo3::ffi::PyObject, |
509 | _args: *mut _pyo3::ffi::PyObject, |
510 | _kwargs: *mut _pyo3::ffi::PyObject |
511 | ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { |
512 | let function = #rust_name; // Shadow the function name to avoid #3017 |
513 | #arg_convert |
514 | #call |
515 | } |
516 | } |
517 | } |
518 | CallingConvention::TpNew => { |
519 | let (arg_convert, args) = impl_arg_params(self, cls, false)?; |
520 | let self_arg = self.tp.self_arg(cls, ExtractErrorMode::Raise); |
521 | let call = quote! { #rust_name(#self_arg #(#args),*) }; |
522 | quote! { |
523 | unsafe fn #ident( |
524 | py: _pyo3::Python<'_>, |
525 | _slf: *mut _pyo3::ffi::PyTypeObject, |
526 | _args: *mut _pyo3::ffi::PyObject, |
527 | _kwargs: *mut _pyo3::ffi::PyObject |
528 | ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { |
529 | use _pyo3::callback::IntoPyCallbackOutput; |
530 | let function = #rust_name; // Shadow the function name to avoid #3017 |
531 | #arg_convert |
532 | let result = #call; |
533 | let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(py)?; |
534 | let cell = initializer.create_cell_from_subtype(py, _slf)?; |
535 | ::std::result::Result::Ok(cell as *mut _pyo3::ffi::PyObject) |
536 | } |
537 | } |
538 | } |
539 | }) |
540 | } |
541 | |
542 | /// Return a `PyMethodDef` constructor for this function, matching the selected |
543 | /// calling convention. |
544 | pub fn get_methoddef(&self, wrapper: impl ToTokens, doc: &PythonDoc) -> TokenStream { |
545 | let python_name = self.null_terminated_python_name(); |
546 | match self.convention { |
547 | CallingConvention::Noargs => quote! { |
548 | _pyo3::impl_::pymethods::PyMethodDef::noargs( |
549 | #python_name, |
550 | _pyo3::impl_::pymethods::PyCFunction({ |
551 | unsafe extern "C" fn trampoline( |
552 | _slf: *mut _pyo3::ffi::PyObject, |
553 | _args: *mut _pyo3::ffi::PyObject, |
554 | ) -> *mut _pyo3::ffi::PyObject |
555 | { |
556 | _pyo3::impl_::trampoline::noargs( |
557 | _slf, |
558 | _args, |
559 | #wrapper |
560 | ) |
561 | } |
562 | trampoline |
563 | }), |
564 | #doc, |
565 | ) |
566 | }, |
567 | CallingConvention::Fastcall => quote! { |
568 | _pyo3::impl_::pymethods::PyMethodDef::fastcall_cfunction_with_keywords( |
569 | #python_name, |
570 | _pyo3::impl_::pymethods::PyCFunctionFastWithKeywords({ |
571 | unsafe extern "C" fn trampoline( |
572 | _slf: *mut _pyo3::ffi::PyObject, |
573 | _args: *const *mut _pyo3::ffi::PyObject, |
574 | _nargs: _pyo3::ffi::Py_ssize_t, |
575 | _kwnames: *mut _pyo3::ffi::PyObject |
576 | ) -> *mut _pyo3::ffi::PyObject |
577 | { |
578 | _pyo3::impl_::trampoline::fastcall_with_keywords( |
579 | _slf, |
580 | _args, |
581 | _nargs, |
582 | _kwnames, |
583 | #wrapper |
584 | ) |
585 | } |
586 | trampoline |
587 | }), |
588 | #doc, |
589 | ) |
590 | }, |
591 | CallingConvention::Varargs => quote! { |
592 | _pyo3::impl_::pymethods::PyMethodDef::cfunction_with_keywords( |
593 | #python_name, |
594 | _pyo3::impl_::pymethods::PyCFunctionWithKeywords({ |
595 | unsafe extern "C" fn trampoline( |
596 | _slf: *mut _pyo3::ffi::PyObject, |
597 | _args: *mut _pyo3::ffi::PyObject, |
598 | _kwargs: *mut _pyo3::ffi::PyObject, |
599 | ) -> *mut _pyo3::ffi::PyObject |
600 | { |
601 | _pyo3::impl_::trampoline::cfunction_with_keywords( |
602 | _slf, |
603 | _args, |
604 | _kwargs, |
605 | #wrapper |
606 | ) |
607 | } |
608 | trampoline |
609 | }), |
610 | #doc, |
611 | ) |
612 | }, |
613 | CallingConvention::TpNew => unreachable!("tp_new cannot get a methoddef" ), |
614 | } |
615 | } |
616 | |
617 | /// Forwards to [utils::get_doc] with the text signature of this spec. |
618 | pub fn get_doc(&self, attrs: &[syn::Attribute]) -> PythonDoc { |
619 | let text_signature = self |
620 | .text_signature_call_signature() |
621 | .map(|sig| format!(" {}{}" , self.python_name, sig)); |
622 | utils::get_doc(attrs, text_signature) |
623 | } |
624 | |
625 | /// Creates the parenthesised arguments list for `__text_signature__` snippet based on this spec's signature |
626 | /// and/or attributes. Prepend the callable name to make a complete `__text_signature__`. |
627 | pub fn text_signature_call_signature(&self) -> Option<String> { |
628 | let self_argument = match &self.tp { |
629 | // Getters / Setters / ClassAttribute are not callables on the Python side |
630 | FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => return None, |
631 | FnType::Fn(_) => Some("self" ), |
632 | FnType::FnModule(_) => Some("module" ), |
633 | FnType::FnClass(_) | FnType::FnNewClass(_) => Some("cls" ), |
634 | FnType::FnStatic | FnType::FnNew => None, |
635 | }; |
636 | |
637 | match self.text_signature.as_ref().map(|attr| &attr.value) { |
638 | Some(TextSignatureAttributeValue::Str(s)) => Some(s.value()), |
639 | None => Some(self.signature.text_signature(self_argument)), |
640 | Some(TextSignatureAttributeValue::Disabled(_)) => None, |
641 | } |
642 | } |
643 | } |
644 | |
645 | enum MethodTypeAttribute { |
646 | New(Span), |
647 | ClassMethod(Span), |
648 | StaticMethod(Span), |
649 | Getter(Span, Option<Ident>), |
650 | Setter(Span, Option<Ident>), |
651 | ClassAttribute(Span), |
652 | } |
653 | |
654 | impl MethodTypeAttribute { |
655 | fn span(&self) -> Span { |
656 | match self { |
657 | MethodTypeAttribute::New(span) |
658 | | MethodTypeAttribute::ClassMethod(span) |
659 | | MethodTypeAttribute::StaticMethod(span) |
660 | | MethodTypeAttribute::Getter(span, _) |
661 | | MethodTypeAttribute::Setter(span, _) |
662 | | MethodTypeAttribute::ClassAttribute(span) => *span, |
663 | } |
664 | } |
665 | |
666 | /// Attempts to parse a method type attribute. |
667 | /// |
668 | /// If the attribute does not match one of the attribute names, returns `Ok(None)`. |
669 | /// |
670 | /// Otherwise will either return a parse error or the attribute. |
671 | fn parse_if_matching_attribute( |
672 | attr: &syn::Attribute, |
673 | deprecations: &mut Deprecations, |
674 | ) -> Result<Option<Self>> { |
675 | fn ensure_no_arguments(meta: &syn::Meta, ident: &str) -> syn::Result<()> { |
676 | match meta { |
677 | syn::Meta::Path(_) => Ok(()), |
678 | syn::Meta::List(l) => bail_spanned!( |
679 | l.span() => format!( |
680 | "`#[ {ident}]` does not take any arguments \n= help: did you mean `#[ {ident}] #[pyo3( {meta})]`?" , |
681 | ident = ident, |
682 | meta = l.tokens, |
683 | ) |
684 | ), |
685 | syn::Meta::NameValue(nv) => { |
686 | bail_spanned!(nv.eq_token.span() => format!( |
687 | "`#[ {}]` does not take any arguments \n= note: this was previously accepted and ignored" , |
688 | ident |
689 | )) |
690 | } |
691 | } |
692 | } |
693 | |
694 | fn extract_name(meta: &syn::Meta, ident: &str) -> Result<Option<Ident>> { |
695 | match meta { |
696 | syn::Meta::Path(_) => Ok(None), |
697 | syn::Meta::NameValue(nv) => bail_spanned!( |
698 | nv.eq_token.span() => format!("expected `#[ {}(name)]` to set the name" , ident) |
699 | ), |
700 | syn::Meta::List(l) => { |
701 | if let Ok(name) = l.parse_args::<syn::Ident>() { |
702 | Ok(Some(name)) |
703 | } else if let Ok(name) = l.parse_args::<syn::LitStr>() { |
704 | name.parse().map(Some) |
705 | } else { |
706 | bail_spanned!(l.tokens.span() => "expected ident or string literal for property name" ); |
707 | } |
708 | } |
709 | } |
710 | } |
711 | |
712 | let meta = &attr.meta; |
713 | let path = meta.path(); |
714 | |
715 | if path.is_ident("new" ) { |
716 | ensure_no_arguments(meta, "new" )?; |
717 | Ok(Some(MethodTypeAttribute::New(path.span()))) |
718 | } else if path.is_ident("__new__" ) { |
719 | let span = path.span(); |
720 | deprecations.push(Deprecation::PyMethodsNewDeprecatedForm, span); |
721 | ensure_no_arguments(meta, "__new__" )?; |
722 | Ok(Some(MethodTypeAttribute::New(span))) |
723 | } else if path.is_ident("classmethod" ) { |
724 | ensure_no_arguments(meta, "classmethod" )?; |
725 | Ok(Some(MethodTypeAttribute::ClassMethod(path.span()))) |
726 | } else if path.is_ident("staticmethod" ) { |
727 | ensure_no_arguments(meta, "staticmethod" )?; |
728 | Ok(Some(MethodTypeAttribute::StaticMethod(path.span()))) |
729 | } else if path.is_ident("classattr" ) { |
730 | ensure_no_arguments(meta, "classattr" )?; |
731 | Ok(Some(MethodTypeAttribute::ClassAttribute(path.span()))) |
732 | } else if path.is_ident("getter" ) { |
733 | let name = extract_name(meta, "getter" )?; |
734 | Ok(Some(MethodTypeAttribute::Getter(path.span(), name))) |
735 | } else if path.is_ident("setter" ) { |
736 | let name = extract_name(meta, "setter" )?; |
737 | Ok(Some(MethodTypeAttribute::Setter(path.span(), name))) |
738 | } else { |
739 | Ok(None) |
740 | } |
741 | } |
742 | } |
743 | |
744 | impl Display for MethodTypeAttribute { |
745 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
746 | match self { |
747 | MethodTypeAttribute::New(_) => "#[new]" .fmt(f), |
748 | MethodTypeAttribute::ClassMethod(_) => "#[classmethod]" .fmt(f), |
749 | MethodTypeAttribute::StaticMethod(_) => "#[staticmethod]" .fmt(f), |
750 | MethodTypeAttribute::Getter(_, _) => "#[getter]" .fmt(f), |
751 | MethodTypeAttribute::Setter(_, _) => "#[setter]" .fmt(f), |
752 | MethodTypeAttribute::ClassAttribute(_) => "#[classattr]" .fmt(f), |
753 | } |
754 | } |
755 | } |
756 | |
757 | fn parse_method_attributes( |
758 | attrs: &mut Vec<syn::Attribute>, |
759 | deprecations: &mut Deprecations, |
760 | ) -> Result<Vec<MethodTypeAttribute>> { |
761 | let mut new_attrs: Vec = Vec::new(); |
762 | let mut found_attrs: Vec = Vec::new(); |
763 | |
764 | for attr: Attribute in attrs.drain(..) { |
765 | match MethodTypeAttribute::parse_if_matching_attribute(&attr, deprecations)? { |
766 | Some(attr: MethodTypeAttribute) => found_attrs.push(attr), |
767 | None => new_attrs.push(attr), |
768 | } |
769 | } |
770 | |
771 | *attrs = new_attrs; |
772 | |
773 | Ok(found_attrs) |
774 | } |
775 | |
776 | const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments" ; |
777 | const RECEIVER_BY_VALUE_ERR: &str = |
778 | "Python objects are shared, so 'self' cannot be moved out of the Python interpreter. |
779 | Try `&self`, `&mut self, `slf: PyRef<'_, Self>` or `slf: PyRefMut<'_, Self>`." ; |
780 | |
781 | fn ensure_signatures_on_valid_method( |
782 | fn_type: &FnType, |
783 | signature: Option<&SignatureAttribute>, |
784 | text_signature: Option<&TextSignatureAttribute>, |
785 | ) -> syn::Result<()> { |
786 | if let Some(signature) = signature { |
787 | match fn_type { |
788 | FnType::Getter(_) => { |
789 | bail_spanned!(signature.kw.span() => "`signature` not allowed with `getter`" ) |
790 | } |
791 | FnType::Setter(_) => { |
792 | bail_spanned!(signature.kw.span() => "`signature` not allowed with `setter`" ) |
793 | } |
794 | FnType::ClassAttribute => { |
795 | bail_spanned!(signature.kw.span() => "`signature` not allowed with `classattr`" ) |
796 | } |
797 | _ => {} |
798 | } |
799 | } |
800 | if let Some(text_signature) = text_signature { |
801 | match fn_type { |
802 | FnType::Getter(_) => { |
803 | bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `getter`" ) |
804 | } |
805 | FnType::Setter(_) => { |
806 | bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `setter`" ) |
807 | } |
808 | FnType::ClassAttribute => { |
809 | bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `classattr`" ) |
810 | } |
811 | _ => {} |
812 | } |
813 | } |
814 | Ok(()) |
815 | } |
816 | |