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