1use std::borrow::Cow;
2use std::ffi::CString;
3use std::fmt::Display;
4
5use proc_macro2::{Span, TokenStream};
6use quote::{format_ident, quote, quote_spanned, ToTokens};
7use syn::{ext::IdentExt, spanned::Spanned, Ident, Result};
8
9use crate::pyversions::is_abi3_before;
10use crate::utils::{Ctx, LitCStr};
11use 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)]
22pub 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)]
32pub 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)]
39pub struct KwargsArg<'a> {
40 pub name: Cow<'a, syn::Ident>,
41 pub ty: &'a syn::Type,
42}
43
44#[derive(Clone, Debug)]
45pub struct CancelHandleArg<'a> {
46 pub name: &'a syn::Ident,
47 pub ty: &'a syn::Type,
48}
49
50#[derive(Clone, Debug)]
51pub 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)]
58pub 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
66impl<'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
182fn 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)]
197pub 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
218impl 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![,](Span::call_site()).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)]
296pub enum SelfType {
297 Receiver { mutable: bool, span: Span },
298 TryFromBoundRef(Span),
299}
300
301#[derive(Clone, Copy)]
302pub enum ExtractErrorMode {
303 NotImplemented,
304 Raise,
305}
306
307impl 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
322impl 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)]
377pub 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
384impl 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
404pub 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
418pub 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
440impl<'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
952enum 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
961impl 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
1043impl 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
1056fn 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
1072const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments";
1073const RECEIVER_BY_VALUE_ERR: &str =
1074 "Python objects are shared, so 'self' cannot be moved out of the Python interpreter.
1075Try `&self`, `&mut self, `slf: PyRef<'_, Self>` or `slf: PyRefMut<'_, Self>`.";
1076
1077fn 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