1use std::fmt::Display;
2
3use crate::attributes::{TextSignatureAttribute, TextSignatureAttributeValue};
4use crate::deprecations::{Deprecation, Deprecations};
5use crate::params::impl_arg_params;
6use crate::pyfunction::{FunctionSignature, PyFunctionArgPyO3Attributes};
7use crate::pyfunction::{PyFunctionOptions, SignatureAttribute};
8use crate::quotes;
9use crate::utils::{self, is_abi3, PythonDoc};
10use proc_macro2::{Span, TokenStream};
11use quote::ToTokens;
12use quote::{quote, quote_spanned};
13use syn::ext::IdentExt;
14use syn::spanned::Spanned;
15use syn::{Ident, Result};
16
17#[derive(Clone, Debug)]
18pub 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
29impl<'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
62fn 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)]
76pub 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
88impl 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)]
133pub enum SelfType {
134 Receiver { mutable: bool, span: Span },
135 TryFromPyCell(Span),
136}
137
138#[derive(Clone, Copy)]
139pub enum ExtractErrorMode {
140 NotImplemented,
141 Raise,
142}
143
144impl 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
158impl 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)]
198pub 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
205impl 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
225pub 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
240pub 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
247pub 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
269impl<'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
645enum 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
654impl 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
744impl 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
757fn 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
776const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments";
777const RECEIVER_BY_VALUE_ERR: &str =
778 "Python objects are shared, so 'self' cannot be moved out of the Python interpreter.
779Try `&self`, `&mut self, `slf: PyRef<'_, Self>` or `slf: PyRefMut<'_, Self>`.";
780
781fn 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