1 | use std::borrow::Cow; |
2 | use std::ffi::CString; |
3 | use std::fmt::Display; |
4 | |
5 | use proc_macro2::{Span, TokenStream}; |
6 | use quote::{format_ident, quote, quote_spanned, ToTokens}; |
7 | use syn::{ext::IdentExt, spanned::Spanned, Ident, Result}; |
8 | |
9 | use crate::pyversions::is_abi3_before; |
10 | use crate::utils::{Ctx, LitCStr}; |
11 | use crate::{ |
12 | attributes::{FromPyWithAttribute, TextSignatureAttribute, TextSignatureAttributeValue}, |
13 | params::{impl_arg_params, Holders}, |
14 | pyfunction::{ |
15 | FunctionSignature, PyFunctionArgPyO3Attributes, PyFunctionOptions, SignatureAttribute, |
16 | }, |
17 | quotes, |
18 | utils::{self, PythonDoc}, |
19 | }; |
20 | |
21 | #[derive (Clone, Debug)] |
22 | pub struct RegularArg<'a> { |
23 | pub name: Cow<'a, syn::Ident>, |
24 | pub ty: &'a syn::Type, |
25 | pub from_py_with: Option<FromPyWithAttribute>, |
26 | pub default_value: Option<syn::Expr>, |
27 | pub option_wrapped_type: Option<&'a syn::Type>, |
28 | } |
29 | |
30 | /// Pythons *args argument |
31 | #[derive (Clone, Debug)] |
32 | pub struct VarargsArg<'a> { |
33 | pub name: Cow<'a, syn::Ident>, |
34 | pub ty: &'a syn::Type, |
35 | } |
36 | |
37 | /// Pythons **kwarg argument |
38 | #[derive (Clone, Debug)] |
39 | pub struct KwargsArg<'a> { |
40 | pub name: Cow<'a, syn::Ident>, |
41 | pub ty: &'a syn::Type, |
42 | } |
43 | |
44 | #[derive (Clone, Debug)] |
45 | pub struct CancelHandleArg<'a> { |
46 | pub name: &'a syn::Ident, |
47 | pub ty: &'a syn::Type, |
48 | } |
49 | |
50 | #[derive (Clone, Debug)] |
51 | pub struct PyArg<'a> { |
52 | pub name: &'a syn::Ident, |
53 | pub ty: &'a syn::Type, |
54 | } |
55 | |
56 | #[allow (clippy::large_enum_variant)] // See #5039 |
57 | #[derive (Clone, Debug)] |
58 | pub enum FnArg<'a> { |
59 | Regular(RegularArg<'a>), |
60 | VarArgs(VarargsArg<'a>), |
61 | KwArgs(KwargsArg<'a>), |
62 | Py(PyArg<'a>), |
63 | CancelHandle(CancelHandleArg<'a>), |
64 | } |
65 | |
66 | impl<'a> FnArg<'a> { |
67 | pub fn name(&self) -> &syn::Ident { |
68 | match self { |
69 | FnArg::Regular(RegularArg { name, .. }) => name, |
70 | FnArg::VarArgs(VarargsArg { name, .. }) => name, |
71 | FnArg::KwArgs(KwargsArg { name, .. }) => name, |
72 | FnArg::Py(PyArg { name, .. }) => name, |
73 | FnArg::CancelHandle(CancelHandleArg { name, .. }) => name, |
74 | } |
75 | } |
76 | |
77 | pub fn ty(&self) -> &'a syn::Type { |
78 | match self { |
79 | FnArg::Regular(RegularArg { ty, .. }) => ty, |
80 | FnArg::VarArgs(VarargsArg { ty, .. }) => ty, |
81 | FnArg::KwArgs(KwargsArg { ty, .. }) => ty, |
82 | FnArg::Py(PyArg { ty, .. }) => ty, |
83 | FnArg::CancelHandle(CancelHandleArg { ty, .. }) => ty, |
84 | } |
85 | } |
86 | |
87 | #[allow (clippy::wrong_self_convention)] |
88 | pub fn from_py_with(&self) -> Option<&FromPyWithAttribute> { |
89 | if let FnArg::Regular(RegularArg { from_py_with, .. }) = self { |
90 | from_py_with.as_ref() |
91 | } else { |
92 | None |
93 | } |
94 | } |
95 | |
96 | pub fn to_varargs_mut(&mut self) -> Result<&mut Self> { |
97 | if let Self::Regular(RegularArg { |
98 | name, |
99 | ty, |
100 | option_wrapped_type: None, |
101 | .. |
102 | }) = self |
103 | { |
104 | *self = Self::VarArgs(VarargsArg { |
105 | name: name.clone(), |
106 | ty, |
107 | }); |
108 | Ok(self) |
109 | } else { |
110 | bail_spanned!(self.name().span() => "args cannot be optional" ) |
111 | } |
112 | } |
113 | |
114 | pub fn to_kwargs_mut(&mut self) -> Result<&mut Self> { |
115 | if let Self::Regular(RegularArg { |
116 | name, |
117 | ty, |
118 | option_wrapped_type: Some(..), |
119 | .. |
120 | }) = self |
121 | { |
122 | *self = Self::KwArgs(KwargsArg { |
123 | name: name.clone(), |
124 | ty, |
125 | }); |
126 | Ok(self) |
127 | } else { |
128 | bail_spanned!(self.name().span() => "kwargs must be Option<_>" ) |
129 | } |
130 | } |
131 | |
132 | /// Transforms a rust fn arg parsed with syn into a method::FnArg |
133 | pub fn parse(arg: &'a mut syn::FnArg) -> Result<Self> { |
134 | match arg { |
135 | syn::FnArg::Receiver(recv) => { |
136 | bail_spanned!(recv.span() => "unexpected receiver" ) |
137 | } // checked in parse_fn_type |
138 | syn::FnArg::Typed(cap) => { |
139 | if let syn::Type::ImplTrait(_) = &*cap.ty { |
140 | bail_spanned!(cap.ty.span() => IMPL_TRAIT_ERR); |
141 | } |
142 | |
143 | let PyFunctionArgPyO3Attributes { |
144 | from_py_with, |
145 | cancel_handle, |
146 | } = PyFunctionArgPyO3Attributes::from_attrs(&mut cap.attrs)?; |
147 | let ident = match &*cap.pat { |
148 | syn::Pat::Ident(syn::PatIdent { ident, .. }) => ident, |
149 | other => return Err(handle_argument_error(other)), |
150 | }; |
151 | |
152 | if utils::is_python(&cap.ty) { |
153 | return Ok(Self::Py(PyArg { |
154 | name: ident, |
155 | ty: &cap.ty, |
156 | })); |
157 | } |
158 | |
159 | if cancel_handle.is_some() { |
160 | // `PyFunctionArgPyO3Attributes::from_attrs` validates that |
161 | // only compatible attributes are specified, either |
162 | // `cancel_handle` or `from_py_with`, dublicates and any |
163 | // combination of the two are already rejected. |
164 | return Ok(Self::CancelHandle(CancelHandleArg { |
165 | name: ident, |
166 | ty: &cap.ty, |
167 | })); |
168 | } |
169 | |
170 | Ok(Self::Regular(RegularArg { |
171 | name: Cow::Borrowed(ident), |
172 | ty: &cap.ty, |
173 | from_py_with, |
174 | default_value: None, |
175 | option_wrapped_type: utils::option_type_argument(&cap.ty), |
176 | })) |
177 | } |
178 | } |
179 | } |
180 | } |
181 | |
182 | fn handle_argument_error(pat: &syn::Pat) -> syn::Error { |
183 | let span: Span = pat.span(); |
184 | let msg: &'static str = match pat { |
185 | syn::Pat::Wild(_) => "wildcard argument names are not supported" , |
186 | syn::Pat::Struct(_) |
187 | | syn::Pat::Tuple(_) |
188 | | syn::Pat::TupleStruct(_) |
189 | | syn::Pat::Slice(_) => "destructuring in arguments is not supported" , |
190 | _ => "unsupported argument" , |
191 | }; |
192 | syn::Error::new(span, message:msg) |
193 | } |
194 | |
195 | /// Represents what kind of a function a pyfunction or pymethod is |
196 | #[derive (Clone, Debug)] |
197 | pub enum FnType { |
198 | /// Represents a pymethod annotated with `#[getter]` |
199 | Getter(SelfType), |
200 | /// Represents a pymethod annotated with `#[setter]` |
201 | Setter(SelfType), |
202 | /// Represents a regular pymethod |
203 | Fn(SelfType), |
204 | /// Represents a pymethod annotated with `#[new]`, i.e. the `__new__` dunder. |
205 | FnNew, |
206 | /// Represents a pymethod annotated with both `#[new]` and `#[classmethod]` (in either order) |
207 | FnNewClass(Span), |
208 | /// Represents a pymethod annotated with `#[classmethod]`, like a `@classmethod` |
209 | FnClass(Span), |
210 | /// Represents a pyfunction or a pymethod annotated with `#[staticmethod]`, like a `@staticmethod` |
211 | FnStatic, |
212 | /// Represents a pyfunction annotated with `#[pyo3(pass_module)] |
213 | FnModule(Span), |
214 | /// Represents a pymethod or associated constant annotated with `#[classattr]` |
215 | ClassAttribute, |
216 | } |
217 | |
218 | impl FnType { |
219 | pub fn skip_first_rust_argument_in_python_signature(&self) -> bool { |
220 | match self { |
221 | FnType::Getter(_) |
222 | | FnType::Setter(_) |
223 | | FnType::Fn(_) |
224 | | FnType::FnClass(_) |
225 | | FnType::FnNewClass(_) |
226 | | FnType::FnModule(_) => true, |
227 | FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => false, |
228 | } |
229 | } |
230 | |
231 | pub fn signature_attribute_allowed(&self) -> bool { |
232 | match self { |
233 | FnType::Fn(_) |
234 | | FnType::FnNew |
235 | | FnType::FnStatic |
236 | | FnType::FnClass(_) |
237 | | FnType::FnNewClass(_) |
238 | | FnType::FnModule(_) => true, |
239 | // Setter, Getter and ClassAttribute all have fixed signatures (either take 0 or 1 |
240 | // arguments) so cannot have a `signature = (...)` attribute. |
241 | FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => false, |
242 | } |
243 | } |
244 | |
245 | pub fn self_arg( |
246 | &self, |
247 | cls: Option<&syn::Type>, |
248 | error_mode: ExtractErrorMode, |
249 | holders: &mut Holders, |
250 | ctx: &Ctx, |
251 | ) -> Option<TokenStream> { |
252 | let Ctx { pyo3_path, .. } = ctx; |
253 | match self { |
254 | FnType::Getter(st) | FnType::Setter(st) | FnType::Fn(st) => { |
255 | let mut receiver = st.receiver( |
256 | cls.expect("no class given for Fn with a \"self \" receiver" ), |
257 | error_mode, |
258 | holders, |
259 | ctx, |
260 | ); |
261 | syn::Token).to_tokens(&mut receiver); |
262 | Some(receiver) |
263 | } |
264 | FnType::FnClass(span) | FnType::FnNewClass(span) => { |
265 | let py = syn::Ident::new("py" , Span::call_site()); |
266 | let slf: Ident = syn::Ident::new("_slf" , Span::call_site()); |
267 | let pyo3_path = pyo3_path.to_tokens_spanned(*span); |
268 | let ret = quote_spanned! { *span => |
269 | #[allow(clippy::useless_conversion)] |
270 | ::std::convert::Into::into( |
271 | #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &*(&#slf as *const _ as *const *mut _)) |
272 | .downcast_unchecked::<#pyo3_path::types::PyType>() |
273 | ) |
274 | }; |
275 | Some(quote! { unsafe { #ret }, }) |
276 | } |
277 | FnType::FnModule(span) => { |
278 | let py = syn::Ident::new("py" , Span::call_site()); |
279 | let slf: Ident = syn::Ident::new("_slf" , Span::call_site()); |
280 | let pyo3_path = pyo3_path.to_tokens_spanned(*span); |
281 | let ret = quote_spanned! { *span => |
282 | #[allow(clippy::useless_conversion)] |
283 | ::std::convert::Into::into( |
284 | #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &*(&#slf as *const _ as *const *mut _)) |
285 | .downcast_unchecked::<#pyo3_path::types::PyModule>() |
286 | ) |
287 | }; |
288 | Some(quote! { unsafe { #ret }, }) |
289 | } |
290 | FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => None, |
291 | } |
292 | } |
293 | } |
294 | |
295 | #[derive (Clone, Debug)] |
296 | pub enum SelfType { |
297 | Receiver { mutable: bool, span: Span }, |
298 | TryFromBoundRef(Span), |
299 | } |
300 | |
301 | #[derive (Clone, Copy)] |
302 | pub enum ExtractErrorMode { |
303 | NotImplemented, |
304 | Raise, |
305 | } |
306 | |
307 | impl ExtractErrorMode { |
308 | pub fn handle_error(self, extract: TokenStream, ctx: &Ctx) -> TokenStream { |
309 | let Ctx { pyo3_path: &PyO3CratePath, .. } = ctx; |
310 | match self { |
311 | ExtractErrorMode::Raise => quote! { #extract? }, |
312 | ExtractErrorMode::NotImplemented => quote! { |
313 | match #extract { |
314 | ::std::result::Result::Ok(value) => value, |
315 | ::std::result::Result::Err(_) => { return #pyo3_path::impl_::callback::convert(py, py.NotImplemented()); }, |
316 | } |
317 | }, |
318 | } |
319 | } |
320 | } |
321 | |
322 | impl SelfType { |
323 | pub fn receiver( |
324 | &self, |
325 | cls: &syn::Type, |
326 | error_mode: ExtractErrorMode, |
327 | holders: &mut Holders, |
328 | ctx: &Ctx, |
329 | ) -> TokenStream { |
330 | // Due to use of quote_spanned in this function, need to bind these idents to the |
331 | // main macro callsite. |
332 | let py = syn::Ident::new("py" , Span::call_site()); |
333 | let slf = syn::Ident::new("_slf" , Span::call_site()); |
334 | let Ctx { pyo3_path, .. } = ctx; |
335 | let bound_ref = |
336 | quote! { unsafe { #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &#slf) } }; |
337 | match self { |
338 | SelfType::Receiver { span, mutable } => { |
339 | let method = if *mutable { |
340 | syn::Ident::new("extract_pyclass_ref_mut" , *span) |
341 | } else { |
342 | syn::Ident::new("extract_pyclass_ref" , *span) |
343 | }; |
344 | let holder = holders.push_holder(*span); |
345 | let pyo3_path = pyo3_path.to_tokens_spanned(*span); |
346 | error_mode.handle_error( |
347 | quote_spanned! { *span => |
348 | #pyo3_path::impl_::extract_argument::#method::<#cls>( |
349 | #bound_ref.0, |
350 | &mut #holder, |
351 | ) |
352 | }, |
353 | ctx, |
354 | ) |
355 | } |
356 | SelfType::TryFromBoundRef(span) => { |
357 | let pyo3_path = pyo3_path.to_tokens_spanned(*span); |
358 | error_mode.handle_error( |
359 | quote_spanned! { *span => |
360 | #bound_ref.downcast::<#cls>() |
361 | .map_err(::std::convert::Into::<#pyo3_path::PyErr>::into) |
362 | .and_then( |
363 | #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] // In case slf is Py<Self> (unknown_lints can be removed when MSRV is 1.75+) |
364 | |bound| ::std::convert::TryFrom::try_from(bound).map_err(::std::convert::Into::into) |
365 | ) |
366 | |
367 | }, |
368 | ctx |
369 | ) |
370 | } |
371 | } |
372 | } |
373 | } |
374 | |
375 | /// Determines which CPython calling convention a given FnSpec uses. |
376 | #[derive (Clone, Debug)] |
377 | pub enum CallingConvention { |
378 | Noargs, // METH_NOARGS |
379 | Varargs, // METH_VARARGS | METH_KEYWORDS |
380 | Fastcall, // METH_FASTCALL | METH_KEYWORDS (not compatible with `abi3` feature before 3.10) |
381 | TpNew, // special convention for tp_new |
382 | } |
383 | |
384 | impl CallingConvention { |
385 | /// Determine default calling convention from an argument signature. |
386 | /// |
387 | /// Different other slots (tp_call, tp_new) can have other requirements |
388 | /// and are set manually (see `parse_fn_type` below). |
389 | pub fn from_signature(signature: &FunctionSignature<'_>) -> Self { |
390 | if signature.python_signature.has_no_args() { |
391 | Self::Noargs |
392 | } else if signature.python_signature.kwargs.is_none() && !is_abi3_before(major:3, minor:10) { |
393 | // For functions that accept **kwargs, always prefer varargs for now based on |
394 | // historical performance testing. |
395 | // |
396 | // FASTCALL not compatible with `abi3` before 3.10 |
397 | Self::Fastcall |
398 | } else { |
399 | Self::Varargs |
400 | } |
401 | } |
402 | } |
403 | |
404 | pub struct FnSpec<'a> { |
405 | pub tp: FnType, |
406 | // Rust function name |
407 | pub name: &'a syn::Ident, |
408 | // Wrapped python name. This should not have any leading r#. |
409 | // r# can be removed by syn::ext::IdentExt::unraw() |
410 | pub python_name: syn::Ident, |
411 | pub signature: FunctionSignature<'a>, |
412 | pub convention: CallingConvention, |
413 | pub text_signature: Option<TextSignatureAttribute>, |
414 | pub asyncness: Option<syn::Token![async]>, |
415 | pub unsafety: Option<syn::Token![unsafe]>, |
416 | } |
417 | |
418 | pub fn parse_method_receiver(arg: &syn::FnArg) -> Result<SelfType> { |
419 | match arg { |
420 | syn::FnArg::Receiver( |
421 | recv: &Receiver @ syn::Receiver { |
422 | reference: None, .. |
423 | }, |
424 | ) => { |
425 | bail_spanned!(recv.span() => RECEIVER_BY_VALUE_ERR); |
426 | } |
427 | syn::FnArg::Receiver(recv: &Receiver @ syn::Receiver { mutability: &Option, .. }) => Ok(SelfType::Receiver { |
428 | mutable: mutability.is_some(), |
429 | span: recv.span(), |
430 | }), |
431 | syn::FnArg::Typed(syn::PatType { ty: &Box, .. }) => { |
432 | if let syn::Type::ImplTrait(_) = &**ty { |
433 | bail_spanned!(ty.span() => IMPL_TRAIT_ERR); |
434 | } |
435 | Ok(SelfType::TryFromBoundRef(ty.span())) |
436 | } |
437 | } |
438 | } |
439 | |
440 | impl<'a> FnSpec<'a> { |
441 | /// Parser function signature and function attributes |
442 | pub fn parse( |
443 | // Signature is mutable to remove the `Python` argument. |
444 | sig: &'a mut syn::Signature, |
445 | meth_attrs: &mut Vec<syn::Attribute>, |
446 | options: PyFunctionOptions, |
447 | ) -> Result<FnSpec<'a>> { |
448 | let PyFunctionOptions { |
449 | text_signature, |
450 | name, |
451 | signature, |
452 | .. |
453 | } = options; |
454 | |
455 | let mut python_name = name.map(|name| name.value.0); |
456 | |
457 | let fn_type = Self::parse_fn_type(sig, meth_attrs, &mut python_name)?; |
458 | ensure_signatures_on_valid_method(&fn_type, signature.as_ref(), text_signature.as_ref())?; |
459 | |
460 | let name = &sig.ident; |
461 | let python_name = python_name.as_ref().unwrap_or(name).unraw(); |
462 | |
463 | let arguments: Vec<_> = sig |
464 | .inputs |
465 | .iter_mut() |
466 | .skip(if fn_type.skip_first_rust_argument_in_python_signature() { |
467 | 1 |
468 | } else { |
469 | 0 |
470 | }) |
471 | .map(FnArg::parse) |
472 | .collect::<Result<_>>()?; |
473 | |
474 | let signature = if let Some(signature) = signature { |
475 | FunctionSignature::from_arguments_and_attribute(arguments, signature)? |
476 | } else { |
477 | FunctionSignature::from_arguments(arguments) |
478 | }; |
479 | |
480 | let convention = if matches!(fn_type, FnType::FnNew | FnType::FnNewClass(_)) { |
481 | CallingConvention::TpNew |
482 | } else { |
483 | CallingConvention::from_signature(&signature) |
484 | }; |
485 | |
486 | Ok(FnSpec { |
487 | tp: fn_type, |
488 | name, |
489 | convention, |
490 | python_name, |
491 | signature, |
492 | text_signature, |
493 | asyncness: sig.asyncness, |
494 | unsafety: sig.unsafety, |
495 | }) |
496 | } |
497 | |
498 | pub fn null_terminated_python_name(&self, ctx: &Ctx) -> LitCStr { |
499 | let name = self.python_name.to_string(); |
500 | let name = CString::new(name).unwrap(); |
501 | LitCStr::new(name, self.python_name.span(), ctx) |
502 | } |
503 | |
504 | fn parse_fn_type( |
505 | sig: &syn::Signature, |
506 | meth_attrs: &mut Vec<syn::Attribute>, |
507 | python_name: &mut Option<syn::Ident>, |
508 | ) -> Result<FnType> { |
509 | let mut method_attributes = parse_method_attributes(meth_attrs)?; |
510 | |
511 | let name = &sig.ident; |
512 | let parse_receiver = |msg: &'static str| { |
513 | let first_arg = sig |
514 | .inputs |
515 | .first() |
516 | .ok_or_else(|| err_spanned!(sig.span() => msg))?; |
517 | parse_method_receiver(first_arg) |
518 | }; |
519 | |
520 | // strip get_ or set_ |
521 | let strip_fn_name = |prefix: &'static str| { |
522 | name.unraw() |
523 | .to_string() |
524 | .strip_prefix(prefix) |
525 | .map(|stripped| syn::Ident::new(stripped, name.span())) |
526 | }; |
527 | |
528 | let mut set_name_to_new = || { |
529 | if let Some(name) = &python_name { |
530 | bail_spanned!(name.span() => "`name` not allowed with `#[new]`" ); |
531 | } |
532 | *python_name = Some(syn::Ident::new("__new__" , Span::call_site())); |
533 | Ok(()) |
534 | }; |
535 | |
536 | let fn_type = match method_attributes.as_mut_slice() { |
537 | [] => FnType::Fn(parse_receiver( |
538 | "static method needs #[staticmethod] attribute" , |
539 | )?), |
540 | [MethodTypeAttribute::StaticMethod(_)] => FnType::FnStatic, |
541 | [MethodTypeAttribute::ClassAttribute(_)] => FnType::ClassAttribute, |
542 | [MethodTypeAttribute::New(_)] => { |
543 | set_name_to_new()?; |
544 | FnType::FnNew |
545 | } |
546 | [MethodTypeAttribute::New(_), MethodTypeAttribute::ClassMethod(span)] |
547 | | [MethodTypeAttribute::ClassMethod(span), MethodTypeAttribute::New(_)] => { |
548 | set_name_to_new()?; |
549 | FnType::FnNewClass(*span) |
550 | } |
551 | [MethodTypeAttribute::ClassMethod(_)] => { |
552 | // Add a helpful hint if the classmethod doesn't look like a classmethod |
553 | let span = match sig.inputs.first() { |
554 | // Don't actually bother checking the type of the first argument, the compiler |
555 | // will error on incorrect type. |
556 | Some(syn::FnArg::Typed(first_arg)) => first_arg.ty.span(), |
557 | Some(syn::FnArg::Receiver(_)) | None => bail_spanned!( |
558 | sig.paren_token.span.join() => "Expected `&Bound<PyType>` or `Py<PyType>` as the first argument to `#[classmethod]`" |
559 | ), |
560 | }; |
561 | FnType::FnClass(span) |
562 | } |
563 | [MethodTypeAttribute::Getter(_, name)] => { |
564 | if let Some(name) = name.take() { |
565 | ensure_spanned!( |
566 | python_name.replace(name).is_none(), |
567 | python_name.span() => "`name` may only be specified once" |
568 | ); |
569 | } else if python_name.is_none() { |
570 | // Strip off "get_" prefix if needed |
571 | *python_name = strip_fn_name("get_" ); |
572 | } |
573 | |
574 | FnType::Getter(parse_receiver("expected receiver for `#[getter]`" )?) |
575 | } |
576 | [MethodTypeAttribute::Setter(_, name)] => { |
577 | if let Some(name) = name.take() { |
578 | ensure_spanned!( |
579 | python_name.replace(name).is_none(), |
580 | python_name.span() => "`name` may only be specified once" |
581 | ); |
582 | } else if python_name.is_none() { |
583 | // Strip off "set_" prefix if needed |
584 | *python_name = strip_fn_name("set_" ); |
585 | } |
586 | |
587 | FnType::Setter(parse_receiver("expected receiver for `#[setter]`" )?) |
588 | } |
589 | [first, rest @ .., last] => { |
590 | // Join as many of the spans together as possible |
591 | let span = rest |
592 | .iter() |
593 | .fold(first.span(), |s, next| s.join(next.span()).unwrap_or(s)); |
594 | let span = span.join(last.span()).unwrap_or(span); |
595 | // List all the attributes in the error message |
596 | let mut msg = format!("` {}` may not be combined with" , first); |
597 | let mut is_first = true; |
598 | for attr in &*rest { |
599 | msg.push_str(&format!(" ` {}`" , attr)); |
600 | if is_first { |
601 | is_first = false; |
602 | } else { |
603 | msg.push(',' ); |
604 | } |
605 | } |
606 | if !rest.is_empty() { |
607 | msg.push_str(" and" ); |
608 | } |
609 | msg.push_str(&format!(" ` {}`" , last)); |
610 | bail_spanned!(span => msg) |
611 | } |
612 | }; |
613 | Ok(fn_type) |
614 | } |
615 | |
616 | /// Return a C wrapper function for this signature. |
617 | pub fn get_wrapper_function( |
618 | &self, |
619 | ident: &proc_macro2::Ident, |
620 | cls: Option<&syn::Type>, |
621 | ctx: &Ctx, |
622 | ) -> Result<TokenStream> { |
623 | let Ctx { |
624 | pyo3_path, |
625 | output_span, |
626 | } = ctx; |
627 | let mut cancel_handle_iter = self |
628 | .signature |
629 | .arguments |
630 | .iter() |
631 | .filter(|arg| matches!(arg, FnArg::CancelHandle(..))); |
632 | let cancel_handle = cancel_handle_iter.next(); |
633 | if let Some(FnArg::CancelHandle(CancelHandleArg { name, .. })) = cancel_handle { |
634 | ensure_spanned!(self.asyncness.is_some(), name.span() => "`cancel_handle` attribute can only be used with `async fn`" ); |
635 | if let Some(FnArg::CancelHandle(CancelHandleArg { name, .. })) = |
636 | cancel_handle_iter.next() |
637 | { |
638 | bail_spanned!(name.span() => "`cancel_handle` may only be specified once" ); |
639 | } |
640 | } |
641 | |
642 | if self.asyncness.is_some() { |
643 | ensure_spanned!( |
644 | cfg!(feature = "experimental-async" ), |
645 | self.asyncness.span() => "async functions are only supported with the `experimental-async` feature" |
646 | ); |
647 | } |
648 | |
649 | let rust_call = |args: Vec<TokenStream>, holders: &mut Holders| { |
650 | let mut self_arg = || self.tp.self_arg(cls, ExtractErrorMode::Raise, holders, ctx); |
651 | |
652 | let call = if self.asyncness.is_some() { |
653 | let throw_callback = if cancel_handle.is_some() { |
654 | quote! { Some(__throw_callback) } |
655 | } else { |
656 | quote! { None } |
657 | }; |
658 | let python_name = &self.python_name; |
659 | let qualname_prefix = match cls { |
660 | Some(cls) => quote!(Some(<#cls as #pyo3_path::PyTypeInfo>::NAME)), |
661 | None => quote!(None), |
662 | }; |
663 | let arg_names = (0..args.len()) |
664 | .map(|i| format_ident!("arg_ {}" , i)) |
665 | .collect::<Vec<_>>(); |
666 | let future = match self.tp { |
667 | FnType::Fn(SelfType::Receiver { mutable: false, .. }) => { |
668 | quote! {{ |
669 | #(let #arg_names = #args;)* |
670 | let __guard = unsafe { #pyo3_path::impl_::coroutine::RefGuard::<#cls>::new(&#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf))? }; |
671 | async move { function(&__guard, #(#arg_names),*).await } |
672 | }} |
673 | } |
674 | FnType::Fn(SelfType::Receiver { mutable: true, .. }) => { |
675 | quote! {{ |
676 | #(let #arg_names = #args;)* |
677 | let mut __guard = unsafe { #pyo3_path::impl_::coroutine::RefMutGuard::<#cls>::new(&#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf))? }; |
678 | async move { function(&mut __guard, #(#arg_names),*).await } |
679 | }} |
680 | } |
681 | _ => { |
682 | if let Some(self_arg) = self_arg() { |
683 | quote! { |
684 | function( |
685 | // NB #self_arg includes a comma, so none inserted here |
686 | #self_arg |
687 | #(#args),* |
688 | ) |
689 | } |
690 | } else { |
691 | quote! { function(#(#args),*) } |
692 | } |
693 | } |
694 | }; |
695 | let mut call = quote! {{ |
696 | let future = #future; |
697 | #pyo3_path::impl_::coroutine::new_coroutine( |
698 | #pyo3_path::intern!(py, stringify!(#python_name)), |
699 | #qualname_prefix, |
700 | #throw_callback, |
701 | async move { |
702 | let fut = future.await; |
703 | #pyo3_path::impl_::wrap::converter(&fut).wrap(fut) |
704 | }, |
705 | ) |
706 | }}; |
707 | if cancel_handle.is_some() { |
708 | call = quote! {{ |
709 | let __cancel_handle = #pyo3_path::coroutine::CancelHandle::new(); |
710 | let __throw_callback = __cancel_handle.throw_callback(); |
711 | #call |
712 | }}; |
713 | } |
714 | call |
715 | } else if let Some(self_arg) = self_arg() { |
716 | quote! { |
717 | function( |
718 | // NB #self_arg includes a comma, so none inserted here |
719 | #self_arg |
720 | #(#args),* |
721 | ) |
722 | } |
723 | } else { |
724 | quote! { function(#(#args),*) } |
725 | }; |
726 | |
727 | // We must assign the output_span to the return value of the call, |
728 | // but *not* of the call itself otherwise the spans get really weird |
729 | let ret_ident = Ident::new("ret" , *output_span); |
730 | let ret_expr = quote! { let #ret_ident = #call; }; |
731 | let return_conversion = |
732 | quotes::map_result_into_ptr(quotes::ok_wrap(ret_ident.to_token_stream(), ctx), ctx); |
733 | quote! { |
734 | { |
735 | #ret_expr |
736 | #return_conversion |
737 | } |
738 | } |
739 | }; |
740 | |
741 | let func_name = &self.name; |
742 | let rust_name = if let Some(cls) = cls { |
743 | quote!(#cls::#func_name) |
744 | } else { |
745 | quote!(#func_name) |
746 | }; |
747 | |
748 | Ok(match self.convention { |
749 | CallingConvention::Noargs => { |
750 | let mut holders = Holders::new(); |
751 | let args = self |
752 | .signature |
753 | .arguments |
754 | .iter() |
755 | .map(|arg| match arg { |
756 | FnArg::Py(..) => quote!(py), |
757 | FnArg::CancelHandle(..) => quote!(__cancel_handle), |
758 | _ => unreachable!("`CallingConvention::Noargs` should not contain any arguments (reaching Python) except for `self`, which is handled below." ), |
759 | }) |
760 | .collect(); |
761 | let call = rust_call(args, &mut holders); |
762 | let init_holders = holders.init_holders(ctx); |
763 | quote! { |
764 | unsafe fn #ident<'py>( |
765 | py: #pyo3_path::Python<'py>, |
766 | _slf: *mut #pyo3_path::ffi::PyObject, |
767 | ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { |
768 | let function = #rust_name; // Shadow the function name to avoid #3017 |
769 | #init_holders |
770 | let result = #call; |
771 | result |
772 | } |
773 | } |
774 | } |
775 | CallingConvention::Fastcall => { |
776 | let mut holders = Holders::new(); |
777 | let (arg_convert, args) = impl_arg_params(self, cls, true, &mut holders, ctx); |
778 | let call = rust_call(args, &mut holders); |
779 | let init_holders = holders.init_holders(ctx); |
780 | |
781 | quote! { |
782 | unsafe fn #ident<'py>( |
783 | py: #pyo3_path::Python<'py>, |
784 | _slf: *mut #pyo3_path::ffi::PyObject, |
785 | _args: *const *mut #pyo3_path::ffi::PyObject, |
786 | _nargs: #pyo3_path::ffi::Py_ssize_t, |
787 | _kwnames: *mut #pyo3_path::ffi::PyObject |
788 | ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { |
789 | let function = #rust_name; // Shadow the function name to avoid #3017 |
790 | #arg_convert |
791 | #init_holders |
792 | let result = #call; |
793 | result |
794 | } |
795 | } |
796 | } |
797 | CallingConvention::Varargs => { |
798 | let mut holders = Holders::new(); |
799 | let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx); |
800 | let call = rust_call(args, &mut holders); |
801 | let init_holders = holders.init_holders(ctx); |
802 | |
803 | quote! { |
804 | unsafe fn #ident<'py>( |
805 | py: #pyo3_path::Python<'py>, |
806 | _slf: *mut #pyo3_path::ffi::PyObject, |
807 | _args: *mut #pyo3_path::ffi::PyObject, |
808 | _kwargs: *mut #pyo3_path::ffi::PyObject |
809 | ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { |
810 | let function = #rust_name; // Shadow the function name to avoid #3017 |
811 | #arg_convert |
812 | #init_holders |
813 | let result = #call; |
814 | result |
815 | } |
816 | } |
817 | } |
818 | CallingConvention::TpNew => { |
819 | let mut holders = Holders::new(); |
820 | let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx); |
821 | let self_arg = self |
822 | .tp |
823 | .self_arg(cls, ExtractErrorMode::Raise, &mut holders, ctx); |
824 | let call = quote_spanned! {*output_span=> #rust_name(#self_arg #(#args),*) }; |
825 | let init_holders = holders.init_holders(ctx); |
826 | quote! { |
827 | unsafe fn #ident( |
828 | py: #pyo3_path::Python<'_>, |
829 | _slf: *mut #pyo3_path::ffi::PyTypeObject, |
830 | _args: *mut #pyo3_path::ffi::PyObject, |
831 | _kwargs: *mut #pyo3_path::ffi::PyObject |
832 | ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { |
833 | use #pyo3_path::impl_::callback::IntoPyCallbackOutput; |
834 | let function = #rust_name; // Shadow the function name to avoid #3017 |
835 | #arg_convert |
836 | #init_holders |
837 | let result = #call; |
838 | let initializer: #pyo3_path::PyClassInitializer::<#cls> = result.convert(py)?; |
839 | #pyo3_path::impl_::pymethods::tp_new_impl(py, initializer, _slf) |
840 | } |
841 | } |
842 | } |
843 | }) |
844 | } |
845 | |
846 | /// Return a `PyMethodDef` constructor for this function, matching the selected |
847 | /// calling convention. |
848 | pub fn get_methoddef(&self, wrapper: impl ToTokens, doc: &PythonDoc, ctx: &Ctx) -> TokenStream { |
849 | let Ctx { pyo3_path, .. } = ctx; |
850 | let python_name = self.null_terminated_python_name(ctx); |
851 | match self.convention { |
852 | CallingConvention::Noargs => quote! { |
853 | #pyo3_path::impl_::pymethods::PyMethodDef::noargs( |
854 | #python_name, |
855 | { |
856 | unsafe extern "C" fn trampoline( |
857 | _slf: *mut #pyo3_path::ffi::PyObject, |
858 | _args: *mut #pyo3_path::ffi::PyObject, |
859 | ) -> *mut #pyo3_path::ffi::PyObject |
860 | { |
861 | unsafe { |
862 | #pyo3_path::impl_::trampoline::noargs( |
863 | _slf, |
864 | _args, |
865 | #wrapper |
866 | ) |
867 | } |
868 | } |
869 | trampoline |
870 | }, |
871 | #doc, |
872 | ) |
873 | }, |
874 | CallingConvention::Fastcall => quote! { |
875 | #pyo3_path::impl_::pymethods::PyMethodDef::fastcall_cfunction_with_keywords( |
876 | #python_name, |
877 | { |
878 | unsafe extern "C" fn trampoline( |
879 | _slf: *mut #pyo3_path::ffi::PyObject, |
880 | _args: *const *mut #pyo3_path::ffi::PyObject, |
881 | _nargs: #pyo3_path::ffi::Py_ssize_t, |
882 | _kwnames: *mut #pyo3_path::ffi::PyObject |
883 | ) -> *mut #pyo3_path::ffi::PyObject |
884 | { |
885 | #pyo3_path::impl_::trampoline::fastcall_with_keywords( |
886 | _slf, |
887 | _args, |
888 | _nargs, |
889 | _kwnames, |
890 | #wrapper |
891 | ) |
892 | } |
893 | trampoline |
894 | }, |
895 | #doc, |
896 | ) |
897 | }, |
898 | CallingConvention::Varargs => quote! { |
899 | #pyo3_path::impl_::pymethods::PyMethodDef::cfunction_with_keywords( |
900 | #python_name, |
901 | { |
902 | unsafe extern "C" fn trampoline( |
903 | _slf: *mut #pyo3_path::ffi::PyObject, |
904 | _args: *mut #pyo3_path::ffi::PyObject, |
905 | _kwargs: *mut #pyo3_path::ffi::PyObject, |
906 | ) -> *mut #pyo3_path::ffi::PyObject |
907 | { |
908 | #pyo3_path::impl_::trampoline::cfunction_with_keywords( |
909 | _slf, |
910 | _args, |
911 | _kwargs, |
912 | #wrapper |
913 | ) |
914 | } |
915 | trampoline |
916 | }, |
917 | #doc, |
918 | ) |
919 | }, |
920 | CallingConvention::TpNew => unreachable!("tp_new cannot get a methoddef" ), |
921 | } |
922 | } |
923 | |
924 | /// Forwards to [utils::get_doc] with the text signature of this spec. |
925 | pub fn get_doc(&self, attrs: &[syn::Attribute], ctx: &Ctx) -> PythonDoc { |
926 | let text_signature = self |
927 | .text_signature_call_signature() |
928 | .map(|sig| format!(" {}{}" , self.python_name, sig)); |
929 | utils::get_doc(attrs, text_signature, ctx) |
930 | } |
931 | |
932 | /// Creates the parenthesised arguments list for `__text_signature__` snippet based on this spec's signature |
933 | /// and/or attributes. Prepend the callable name to make a complete `__text_signature__`. |
934 | pub fn text_signature_call_signature(&self) -> Option<String> { |
935 | let self_argument = match &self.tp { |
936 | // Getters / Setters / ClassAttribute are not callables on the Python side |
937 | FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => return None, |
938 | FnType::Fn(_) => Some("self" ), |
939 | FnType::FnModule(_) => Some("module" ), |
940 | FnType::FnClass(_) | FnType::FnNewClass(_) => Some("cls" ), |
941 | FnType::FnStatic | FnType::FnNew => None, |
942 | }; |
943 | |
944 | match self.text_signature.as_ref().map(|attr| &attr.value) { |
945 | Some(TextSignatureAttributeValue::Str(s)) => Some(s.value()), |
946 | None => Some(self.signature.text_signature(self_argument)), |
947 | Some(TextSignatureAttributeValue::Disabled(_)) => None, |
948 | } |
949 | } |
950 | } |
951 | |
952 | enum MethodTypeAttribute { |
953 | New(Span), |
954 | ClassMethod(Span), |
955 | StaticMethod(Span), |
956 | Getter(Span, Option<Ident>), |
957 | Setter(Span, Option<Ident>), |
958 | ClassAttribute(Span), |
959 | } |
960 | |
961 | impl MethodTypeAttribute { |
962 | fn span(&self) -> Span { |
963 | match self { |
964 | MethodTypeAttribute::New(span) |
965 | | MethodTypeAttribute::ClassMethod(span) |
966 | | MethodTypeAttribute::StaticMethod(span) |
967 | | MethodTypeAttribute::Getter(span, _) |
968 | | MethodTypeAttribute::Setter(span, _) |
969 | | MethodTypeAttribute::ClassAttribute(span) => *span, |
970 | } |
971 | } |
972 | |
973 | /// Attempts to parse a method type attribute. |
974 | /// |
975 | /// If the attribute does not match one of the attribute names, returns `Ok(None)`. |
976 | /// |
977 | /// Otherwise will either return a parse error or the attribute. |
978 | fn parse_if_matching_attribute(attr: &syn::Attribute) -> Result<Option<Self>> { |
979 | fn ensure_no_arguments(meta: &syn::Meta, ident: &str) -> syn::Result<()> { |
980 | match meta { |
981 | syn::Meta::Path(_) => Ok(()), |
982 | syn::Meta::List(l) => bail_spanned!( |
983 | l.span() => format!( |
984 | "`#[ {ident}]` does not take any arguments \n= help: did you mean `#[ {ident}] #[pyo3( {meta})]`?" , |
985 | ident = ident, |
986 | meta = l.tokens, |
987 | ) |
988 | ), |
989 | syn::Meta::NameValue(nv) => { |
990 | bail_spanned!(nv.eq_token.span() => format!( |
991 | "`#[ {}]` does not take any arguments \n= note: this was previously accepted and ignored" , |
992 | ident |
993 | )) |
994 | } |
995 | } |
996 | } |
997 | |
998 | fn extract_name(meta: &syn::Meta, ident: &str) -> Result<Option<Ident>> { |
999 | match meta { |
1000 | syn::Meta::Path(_) => Ok(None), |
1001 | syn::Meta::NameValue(nv) => bail_spanned!( |
1002 | nv.eq_token.span() => format!("expected `#[ {}(name)]` to set the name" , ident) |
1003 | ), |
1004 | syn::Meta::List(l) => { |
1005 | if let Ok(name) = l.parse_args::<syn::Ident>() { |
1006 | Ok(Some(name)) |
1007 | } else if let Ok(name) = l.parse_args::<syn::LitStr>() { |
1008 | name.parse().map(Some) |
1009 | } else { |
1010 | bail_spanned!(l.tokens.span() => "expected ident or string literal for property name" ); |
1011 | } |
1012 | } |
1013 | } |
1014 | } |
1015 | |
1016 | let meta = &attr.meta; |
1017 | let path = meta.path(); |
1018 | |
1019 | if path.is_ident("new" ) { |
1020 | ensure_no_arguments(meta, "new" )?; |
1021 | Ok(Some(MethodTypeAttribute::New(path.span()))) |
1022 | } else if path.is_ident("classmethod" ) { |
1023 | ensure_no_arguments(meta, "classmethod" )?; |
1024 | Ok(Some(MethodTypeAttribute::ClassMethod(path.span()))) |
1025 | } else if path.is_ident("staticmethod" ) { |
1026 | ensure_no_arguments(meta, "staticmethod" )?; |
1027 | Ok(Some(MethodTypeAttribute::StaticMethod(path.span()))) |
1028 | } else if path.is_ident("classattr" ) { |
1029 | ensure_no_arguments(meta, "classattr" )?; |
1030 | Ok(Some(MethodTypeAttribute::ClassAttribute(path.span()))) |
1031 | } else if path.is_ident("getter" ) { |
1032 | let name = extract_name(meta, "getter" )?; |
1033 | Ok(Some(MethodTypeAttribute::Getter(path.span(), name))) |
1034 | } else if path.is_ident("setter" ) { |
1035 | let name = extract_name(meta, "setter" )?; |
1036 | Ok(Some(MethodTypeAttribute::Setter(path.span(), name))) |
1037 | } else { |
1038 | Ok(None) |
1039 | } |
1040 | } |
1041 | } |
1042 | |
1043 | impl Display for MethodTypeAttribute { |
1044 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
1045 | match self { |
1046 | MethodTypeAttribute::New(_) => "#[new]" .fmt(f), |
1047 | MethodTypeAttribute::ClassMethod(_) => "#[classmethod]" .fmt(f), |
1048 | MethodTypeAttribute::StaticMethod(_) => "#[staticmethod]" .fmt(f), |
1049 | MethodTypeAttribute::Getter(_, _) => "#[getter]" .fmt(f), |
1050 | MethodTypeAttribute::Setter(_, _) => "#[setter]" .fmt(f), |
1051 | MethodTypeAttribute::ClassAttribute(_) => "#[classattr]" .fmt(f), |
1052 | } |
1053 | } |
1054 | } |
1055 | |
1056 | fn parse_method_attributes(attrs: &mut Vec<syn::Attribute>) -> Result<Vec<MethodTypeAttribute>> { |
1057 | let mut new_attrs: Vec = Vec::new(); |
1058 | let mut found_attrs: Vec = Vec::new(); |
1059 | |
1060 | for attr: Attribute in attrs.drain(..) { |
1061 | match MethodTypeAttribute::parse_if_matching_attribute(&attr)? { |
1062 | Some(attr: MethodTypeAttribute) => found_attrs.push(attr), |
1063 | None => new_attrs.push(attr), |
1064 | } |
1065 | } |
1066 | |
1067 | *attrs = new_attrs; |
1068 | |
1069 | Ok(found_attrs) |
1070 | } |
1071 | |
1072 | const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments" ; |
1073 | const RECEIVER_BY_VALUE_ERR: &str = |
1074 | "Python objects are shared, so 'self' cannot be moved out of the Python interpreter. |
1075 | Try `&self`, `&mut self, `slf: PyRef<'_, Self>` or `slf: PyRefMut<'_, Self>`." ; |
1076 | |
1077 | fn ensure_signatures_on_valid_method( |
1078 | fn_type: &FnType, |
1079 | signature: Option<&SignatureAttribute>, |
1080 | text_signature: Option<&TextSignatureAttribute>, |
1081 | ) -> syn::Result<()> { |
1082 | if let Some(signature) = signature { |
1083 | match fn_type { |
1084 | FnType::Getter(_) => { |
1085 | debug_assert!(!fn_type.signature_attribute_allowed()); |
1086 | bail_spanned!(signature.kw.span() => "`signature` not allowed with `getter`" ) |
1087 | } |
1088 | FnType::Setter(_) => { |
1089 | debug_assert!(!fn_type.signature_attribute_allowed()); |
1090 | bail_spanned!(signature.kw.span() => "`signature` not allowed with `setter`" ) |
1091 | } |
1092 | FnType::ClassAttribute => { |
1093 | debug_assert!(!fn_type.signature_attribute_allowed()); |
1094 | bail_spanned!(signature.kw.span() => "`signature` not allowed with `classattr`" ) |
1095 | } |
1096 | _ => debug_assert!(fn_type.signature_attribute_allowed()), |
1097 | } |
1098 | } |
1099 | if let Some(text_signature) = text_signature { |
1100 | match fn_type { |
1101 | FnType::Getter(_) => { |
1102 | bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `getter`" ) |
1103 | } |
1104 | FnType::Setter(_) => { |
1105 | bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `setter`" ) |
1106 | } |
1107 | FnType::ClassAttribute => { |
1108 | bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `classattr`" ) |
1109 | } |
1110 | _ => {} |
1111 | } |
1112 | } |
1113 | Ok(()) |
1114 | } |
1115 | |