pyo3-macros-backend-0.20.2/.cargo_vcs_info.json0000644000000001610000000000100146540ustar { "git": { "sha1": "bcef18b988a519aca93c29039fa0894a310d2eaf" }, "path_in_vcs": "pyo3-macros-backend" }pyo3-macros-backend-0.20.2/Cargo.toml0000644000000034740000000000100126640ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "pyo3-macros-backend" version = "0.20.2" authors = ["PyO3 Project and Contributors "] description = "Code generation for PyO3 package" homepage = "https://github.com/pyo3/pyo3" keywords = [ "pyo3", "python", "cpython", "ffi", ] categories = [ "api-bindings", "development-tools::ffi", ] license = "MIT OR Apache-2.0" repository = "https://github.com/pyo3/pyo3" [dependencies.heck] version = "0.4" [dependencies.proc-macro2] version = "1" default-features = false [dependencies.quote] version = "1" default-features = false [dependencies.syn] version = "2" features = [ "derive", "parsing", "printing", "clone-impls", "full", "extra-traits", ] default-features = false [features] abi3 = [] [lints.clippy] checked_conversions = "warn" dbg_macro = "warn" explicit_into_iter_loop = "warn" explicit_iter_loop = "warn" filter_map_next = "warn" flat_map_option = "warn" let_unit_value = "warn" manual_assert = "warn" manual_ok_or = "warn" todo = "warn" unnecessary_wraps = "warn" used_underscore_binding = "warn" useless_transmute = "warn" [lints.rust] elided_lifetimes_in_paths = "warn" invalid_doc_attributes = "warn" rust_2018_idioms = "warn" rust_2021_prelude_collisions = "warn" unused_lifetimes = "warn" [lints.rustdoc] bare_urls = "warn" broken_intra_doc_links = "warn" pyo3-macros-backend-0.20.2/Cargo.toml.orig000064400000000000000000000016121046102023000163350ustar 00000000000000[package] name = "pyo3-macros-backend" version = "0.20.2" description = "Code generation for PyO3 package" authors = ["PyO3 Project and Contributors "] keywords = ["pyo3", "python", "cpython", "ffi"] homepage = "https://github.com/pyo3/pyo3" repository = "https://github.com/pyo3/pyo3" categories = ["api-bindings", "development-tools::ffi"] license = "MIT OR Apache-2.0" edition = "2021" # Note: we use default-features = false for proc-macro related crates # not to depend on proc-macro itself. # See https://github.com/PyO3/pyo3/pull/810 for more. [dependencies] quote = { version = "1", default-features = false } proc-macro2 = { version = "1", default-features = false } heck = "0.4" [dependencies.syn] version = "2" default-features = false features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"] [features] abi3 = [] [lints] workspace = true pyo3-macros-backend-0.20.2/LICENSE-APACHE000064400000000000000000000250351046102023000153770ustar 00000000000000 Copyright (c) 2017-present PyO3 Project and Contributors. https://github.com/PyO3 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. pyo3-macros-backend-0.20.2/LICENSE-MIT000064400000000000000000000021231046102023000151000ustar 00000000000000Copyright (c) 2023-present PyO3 Project and Contributors. https://github.com/PyO3 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pyo3-macros-backend-0.20.2/src/attributes.rs000064400000000000000000000172101046102023000167720ustar 00000000000000use proc_macro2::TokenStream; use quote::ToTokens; use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Expr, ExprPath, Ident, LitStr, Path, Result, Token, }; pub mod kw { syn::custom_keyword!(args); syn::custom_keyword!(annotation); syn::custom_keyword!(attribute); syn::custom_keyword!(dict); syn::custom_keyword!(extends); syn::custom_keyword!(freelist); syn::custom_keyword!(from_py_with); syn::custom_keyword!(frozen); syn::custom_keyword!(gc); syn::custom_keyword!(get); syn::custom_keyword!(get_all); syn::custom_keyword!(item); syn::custom_keyword!(from_item_all); syn::custom_keyword!(mapping); syn::custom_keyword!(module); syn::custom_keyword!(name); syn::custom_keyword!(pass_module); syn::custom_keyword!(rename_all); syn::custom_keyword!(sequence); syn::custom_keyword!(set); syn::custom_keyword!(set_all); syn::custom_keyword!(signature); syn::custom_keyword!(subclass); syn::custom_keyword!(text_signature); syn::custom_keyword!(transparent); syn::custom_keyword!(unsendable); syn::custom_keyword!(weakref); } #[derive(Clone, Debug)] pub struct KeywordAttribute { pub kw: K, pub value: V, } /// A helper type which parses the inner type via a literal string /// e.g. `LitStrValue` -> parses "some::path" in quotes. #[derive(Clone, Debug, PartialEq, Eq)] pub struct LitStrValue(pub T); impl Parse for LitStrValue { fn parse(input: ParseStream<'_>) -> Result { let lit_str: LitStr = input.parse()?; lit_str.parse().map(LitStrValue) } } impl ToTokens for LitStrValue { fn to_tokens(&self, tokens: &mut TokenStream) { self.0.to_tokens(tokens) } } /// A helper type which parses a name via a literal string #[derive(Clone, Debug, PartialEq, Eq)] pub struct NameLitStr(pub Ident); impl Parse for NameLitStr { fn parse(input: ParseStream<'_>) -> Result { let string_literal: LitStr = input.parse()?; if let Ok(ident) = string_literal.parse() { Ok(NameLitStr(ident)) } else { bail_spanned!(string_literal.span() => "expected a single identifier in double quotes") } } } impl ToTokens for NameLitStr { fn to_tokens(&self, tokens: &mut TokenStream) { self.0.to_tokens(tokens) } } /// Available renaming rules #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RenamingRule { CamelCase, KebabCase, Lowercase, PascalCase, ScreamingKebabCase, ScreamingSnakeCase, SnakeCase, Uppercase, } /// A helper type which parses a renaming rule via a literal string #[derive(Clone, Debug, PartialEq, Eq)] pub struct RenamingRuleLitStr { pub lit: LitStr, pub rule: RenamingRule, } impl Parse for RenamingRuleLitStr { fn parse(input: ParseStream<'_>) -> Result { let string_literal: LitStr = input.parse()?; let rule = match string_literal.value().as_ref() { "camelCase" => RenamingRule::CamelCase, "kebab-case" => RenamingRule::KebabCase, "lowercase" => RenamingRule::Lowercase, "PascalCase" => RenamingRule::PascalCase, "SCREAMING-KEBAB-CASE" => RenamingRule::ScreamingKebabCase, "SCREAMING_SNAKE_CASE" => RenamingRule::ScreamingSnakeCase, "snake_case" => RenamingRule::SnakeCase, "UPPERCASE" => RenamingRule::Uppercase, _ => { bail_spanned!(string_literal.span() => "expected a valid renaming rule, possible values are: \"camelCase\", \"kebab-case\", \"lowercase\", \"PascalCase\", \"SCREAMING-KEBAB-CASE\", \"SCREAMING_SNAKE_CASE\", \"snake_case\", \"UPPERCASE\"") } }; Ok(Self { lit: string_literal, rule, }) } } impl ToTokens for RenamingRuleLitStr { fn to_tokens(&self, tokens: &mut TokenStream) { self.lit.to_tokens(tokens) } } /// Text signatue can be either a literal string or opt-in/out #[derive(Clone, Debug, PartialEq, Eq)] pub enum TextSignatureAttributeValue { Str(LitStr), // `None` ident to disable automatic text signature generation Disabled(Ident), } impl Parse for TextSignatureAttributeValue { fn parse(input: ParseStream<'_>) -> Result { if let Ok(lit_str) = input.parse::() { return Ok(TextSignatureAttributeValue::Str(lit_str)); } let err_span = match input.parse::() { Ok(ident) if ident == "None" => { return Ok(TextSignatureAttributeValue::Disabled(ident)); } Ok(other_ident) => other_ident.span(), Err(e) => e.span(), }; Err(err_spanned!(err_span => "expected a string literal or `None`")) } } impl ToTokens for TextSignatureAttributeValue { fn to_tokens(&self, tokens: &mut TokenStream) { match self { TextSignatureAttributeValue::Str(s) => s.to_tokens(tokens), TextSignatureAttributeValue::Disabled(b) => b.to_tokens(tokens), } } } pub type ExtendsAttribute = KeywordAttribute; pub type FreelistAttribute = KeywordAttribute>; pub type ModuleAttribute = KeywordAttribute; pub type NameAttribute = KeywordAttribute; pub type RenameAllAttribute = KeywordAttribute; pub type TextSignatureAttribute = KeywordAttribute; impl Parse for KeywordAttribute { fn parse(input: ParseStream<'_>) -> Result { let kw: K = input.parse()?; let _: Token![=] = input.parse()?; let value = input.parse()?; Ok(KeywordAttribute { kw, value }) } } impl ToTokens for KeywordAttribute { fn to_tokens(&self, tokens: &mut TokenStream) { self.kw.to_tokens(tokens); Token![=](self.kw.span()).to_tokens(tokens); self.value.to_tokens(tokens); } } pub type FromPyWithAttribute = KeywordAttribute>; /// For specifying the path to the pyo3 crate. pub type CrateAttribute = KeywordAttribute>; pub fn get_pyo3_options(attr: &syn::Attribute) -> Result>> { if attr.path().is_ident("pyo3") { attr.parse_args_with(Punctuated::parse_terminated).map(Some) } else { Ok(None) } } /// Takes attributes from an attribute vector. /// /// For each attribute in `attrs`, `extractor` is called. If `extractor` returns `Ok(true)`, then /// the attribute will be removed from the vector. /// /// This is similar to `Vec::retain` except the closure is fallible and the condition is reversed. /// (In `retain`, returning `true` keeps the element, here it removes it.) pub fn take_attributes( attrs: &mut Vec, mut extractor: impl FnMut(&Attribute) -> Result, ) -> Result<()> { *attrs = attrs .drain(..) .filter_map(|attr| { extractor(&attr) .map(move |attribute_handled| if attribute_handled { None } else { Some(attr) }) .transpose() }) .collect::>()?; Ok(()) } pub fn take_pyo3_options(attrs: &mut Vec) -> Result> { let mut out = Vec::new(); take_attributes(attrs, |attr| { if let Some(options) = get_pyo3_options(attr)? { out.extend(options); Ok(true) } else { Ok(false) } })?; Ok(out) } pyo3-macros-backend-0.20.2/src/deprecations.rs000064400000000000000000000022631046102023000172660ustar 00000000000000use proc_macro2::{Span, TokenStream}; use quote::{quote_spanned, ToTokens}; pub enum Deprecation { PyClassTextSignature, PyMethodsNewDeprecatedForm, } impl Deprecation { fn ident(&self, span: Span) -> syn::Ident { let string = match self { Deprecation::PyClassTextSignature => "PYCLASS_TEXT_SIGNATURE", Deprecation::PyMethodsNewDeprecatedForm => "PYMETHODS_NEW_DEPRECATED_FORM", }; syn::Ident::new(string, span) } } #[derive(Default)] pub struct Deprecations(Vec<(Deprecation, Span)>); impl Deprecations { pub fn new() -> Self { Deprecations(Vec::new()) } pub fn push(&mut self, deprecation: Deprecation, span: Span) { self.0.push((deprecation, span)) } } impl ToTokens for Deprecations { fn to_tokens(&self, tokens: &mut TokenStream) { for (deprecation, span) in &self.0 { let ident = deprecation.ident(*span); quote_spanned!( *span => #[allow(clippy::let_unit_value)] { let _ = _pyo3::impl_::deprecations::#ident; } ) .to_tokens(tokens) } } } pyo3-macros-backend-0.20.2/src/frompyobject.rs000064400000000000000000000573071046102023000173220ustar 00000000000000use crate::{ attributes::{self, get_pyo3_options, CrateAttribute, FromPyWithAttribute}, utils::get_pyo3_crate, }; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{ parenthesized, parse::{Parse, ParseStream}, parse_quote, punctuated::Punctuated, spanned::Spanned, Attribute, DataEnum, DeriveInput, Fields, Ident, LitStr, Result, Token, }; /// Describes derivation input of an enum. struct Enum<'a> { enum_ident: &'a Ident, variants: Vec>, } impl<'a> Enum<'a> { /// Construct a new enum representation. /// /// `data_enum` is the `syn` representation of the input enum, `ident` is the /// `Identifier` of the enum. fn new(data_enum: &'a DataEnum, ident: &'a Ident) -> Result { ensure_spanned!( !data_enum.variants.is_empty(), ident.span() => "cannot derive FromPyObject for empty enum" ); let variants = data_enum .variants .iter() .map(|variant| { let attrs = ContainerOptions::from_attrs(&variant.attrs)?; let var_ident = &variant.ident; Container::new(&variant.fields, parse_quote!(#ident::#var_ident), attrs) }) .collect::>>()?; Ok(Enum { enum_ident: ident, variants, }) } /// Build derivation body for enums. fn build(&self) -> TokenStream { let mut var_extracts = Vec::new(); let mut variant_names = Vec::new(); let mut error_names = Vec::new(); for var in &self.variants { let struct_derive = var.build(); let ext = quote!({ let maybe_ret = || -> _pyo3::PyResult { #struct_derive }(); match maybe_ret { ok @ ::std::result::Result::Ok(_) => return ok, ::std::result::Result::Err(err) => err } }); var_extracts.push(ext); variant_names.push(var.path.segments.last().unwrap().ident.to_string()); error_names.push(&var.err_name); } let ty_name = self.enum_ident.to_string(); quote!( let errors = [ #(#var_extracts),* ]; ::std::result::Result::Err( _pyo3::impl_::frompyobject::failed_to_extract_enum( obj.py(), #ty_name, &[#(#variant_names),*], &[#(#error_names),*], &errors ) ) ) } } struct NamedStructField<'a> { ident: &'a syn::Ident, getter: Option, from_py_with: Option, } struct TupleStructField { from_py_with: Option, } /// Container Style /// /// Covers Structs, Tuplestructs and corresponding Newtypes. enum ContainerType<'a> { /// Struct Container, e.g. `struct Foo { a: String }` /// /// Variant contains the list of field identifiers and the corresponding extraction call. Struct(Vec>), /// Newtype struct container, e.g. `#[transparent] struct Foo { a: String }` /// /// The field specified by the identifier is extracted directly from the object. StructNewtype(&'a syn::Ident, Option), /// Tuple struct, e.g. `struct Foo(String)`. /// /// Variant contains a list of conversion methods for each of the fields that are directly /// extracted from the tuple. Tuple(Vec), /// Tuple newtype, e.g. `#[transparent] struct Foo(String)` /// /// The wrapped field is directly extracted from the object. TupleNewtype(Option), } /// Data container /// /// Either describes a struct or an enum variant. struct Container<'a> { path: syn::Path, ty: ContainerType<'a>, err_name: String, } impl<'a> Container<'a> { /// Construct a container based on fields, identifier and attributes. /// /// Fails if the variant has no fields or incompatible attributes. fn new(fields: &'a Fields, path: syn::Path, options: ContainerOptions) -> Result { let style = match fields { Fields::Unnamed(unnamed) if !unnamed.unnamed.is_empty() => { let mut tuple_fields = unnamed .unnamed .iter() .map(|field| { let attrs = FieldPyO3Attributes::from_attrs(&field.attrs)?; ensure_spanned!( attrs.getter.is_none(), field.span() => "`getter` is not permitted on tuple struct elements." ); Ok(TupleStructField { from_py_with: attrs.from_py_with, }) }) .collect::>>()?; if tuple_fields.len() == 1 { // Always treat a 1-length tuple struct as "transparent", even without the // explicit annotation. let field = tuple_fields.pop().unwrap(); ContainerType::TupleNewtype(field.from_py_with) } else if options.transparent { bail_spanned!( fields.span() => "transparent structs and variants can only have 1 field" ); } else { ContainerType::Tuple(tuple_fields) } } Fields::Named(named) if !named.named.is_empty() => { let mut struct_fields = named .named .iter() .map(|field| { let ident = field .ident .as_ref() .expect("Named fields should have identifiers"); let mut attrs = FieldPyO3Attributes::from_attrs(&field.attrs)?; if let Some(ref from_item_all) = options.from_item_all { if let Some(replaced) = attrs.getter.replace(FieldGetter::GetItem(None)) { match replaced { FieldGetter::GetItem(Some(item_name)) => { attrs.getter = Some(FieldGetter::GetItem(Some(item_name))); } FieldGetter::GetItem(None) => bail_spanned!(from_item_all.span() => "Useless `item` - the struct is already annotated with `from_item_all`"), FieldGetter::GetAttr(_) => bail_spanned!( from_item_all.span() => "The struct is already annotated with `from_item_all`, `attribute` is not allowed" ), } } } Ok(NamedStructField { ident, getter: attrs.getter, from_py_with: attrs.from_py_with, }) }) .collect::>>()?; if options.transparent { ensure_spanned!( struct_fields.len() == 1, fields.span() => "transparent structs and variants can only have 1 field" ); let field = struct_fields.pop().unwrap(); ensure_spanned!( field.getter.is_none(), field.ident.span() => "`transparent` structs may not have a `getter` for the inner field" ); ContainerType::StructNewtype(field.ident, field.from_py_with) } else { ContainerType::Struct(struct_fields) } } _ => bail_spanned!( fields.span() => "cannot derive FromPyObject for empty structs and variants" ), }; let err_name = options.annotation.map_or_else( || path.segments.last().unwrap().ident.to_string(), |lit_str| lit_str.value(), ); let v = Container { path, ty: style, err_name, }; Ok(v) } fn name(&self) -> String { let mut value = String::new(); for segment in &self.path.segments { if !value.is_empty() { value.push_str("::"); } value.push_str(&segment.ident.to_string()); } value } /// Build derivation body for a struct. fn build(&self) -> TokenStream { match &self.ty { ContainerType::StructNewtype(ident, from_py_with) => { self.build_newtype_struct(Some(ident), from_py_with) } ContainerType::TupleNewtype(from_py_with) => { self.build_newtype_struct(None, from_py_with) } ContainerType::Tuple(tups) => self.build_tuple_struct(tups), ContainerType::Struct(tups) => self.build_struct(tups), } } fn build_newtype_struct( &self, field_ident: Option<&Ident>, from_py_with: &Option, ) -> TokenStream { let self_ty = &self.path; let struct_name = self.name(); if let Some(ident) = field_ident { let field_name = ident.to_string(); match from_py_with { None => quote! { Ok(#self_ty { #ident: _pyo3::impl_::frompyobject::extract_struct_field(obj, #struct_name, #field_name)? }) }, Some(FromPyWithAttribute { value: expr_path, .. }) => quote! { Ok(#self_ty { #ident: _pyo3::impl_::frompyobject::extract_struct_field_with(#expr_path, obj, #struct_name, #field_name)? }) }, } } else { match from_py_with { None => quote!( _pyo3::impl_::frompyobject::extract_tuple_struct_field(obj, #struct_name, 0).map(#self_ty) ), Some(FromPyWithAttribute { value: expr_path, .. }) => quote! ( _pyo3::impl_::frompyobject::extract_tuple_struct_field_with(#expr_path, obj, #struct_name, 0).map(#self_ty) ), } } } fn build_tuple_struct(&self, struct_fields: &[TupleStructField]) -> TokenStream { let self_ty = &self.path; let struct_name = &self.name(); let field_idents: Vec<_> = (0..struct_fields.len()) .map(|i| format_ident!("arg{}", i)) .collect(); let fields = struct_fields.iter().zip(&field_idents).enumerate().map(|(index, (field, ident))| { match &field.from_py_with { None => quote!( _pyo3::impl_::frompyobject::extract_tuple_struct_field(#ident, #struct_name, #index)? ), Some(FromPyWithAttribute { value: expr_path, .. }) => quote! ( _pyo3::impl_::frompyobject::extract_tuple_struct_field_with(#expr_path, #ident, #struct_name, #index)? ), } }); quote!( match obj.extract() { ::std::result::Result::Ok((#(#field_idents),*)) => ::std::result::Result::Ok(#self_ty(#(#fields),*)), ::std::result::Result::Err(err) => ::std::result::Result::Err(err), } ) } fn build_struct(&self, struct_fields: &[NamedStructField<'_>]) -> TokenStream { let self_ty = &self.path; let struct_name = &self.name(); let mut fields: Punctuated = Punctuated::new(); for field in struct_fields { let ident = &field.ident; let field_name = ident.to_string(); let getter = match field.getter.as_ref().unwrap_or(&FieldGetter::GetAttr(None)) { FieldGetter::GetAttr(Some(name)) => { quote!(getattr(_pyo3::intern!(obj.py(), #name))) } FieldGetter::GetAttr(None) => { quote!(getattr(_pyo3::intern!(obj.py(), #field_name))) } FieldGetter::GetItem(Some(syn::Lit::Str(key))) => { quote!(get_item(_pyo3::intern!(obj.py(), #key))) } FieldGetter::GetItem(Some(key)) => quote!(get_item(#key)), FieldGetter::GetItem(None) => { quote!(get_item(_pyo3::intern!(obj.py(), #field_name))) } }; let extractor = match &field.from_py_with { None => { quote!(_pyo3::impl_::frompyobject::extract_struct_field(obj.#getter?, #struct_name, #field_name)?) } Some(FromPyWithAttribute { value: expr_path, .. }) => { quote! (_pyo3::impl_::frompyobject::extract_struct_field_with(#expr_path, obj.#getter?, #struct_name, #field_name)?) } }; fields.push(quote!(#ident: #extractor)); } quote!(::std::result::Result::Ok(#self_ty{#fields})) } } #[derive(Default)] struct ContainerOptions { /// Treat the Container as a Wrapper, directly extract its fields from the input object. transparent: bool, /// Force every field to be extracted from item of source Python object. from_item_all: Option, /// Change the name of an enum variant in the generated error message. annotation: Option, /// Change the path for the pyo3 crate krate: Option, } /// Attributes for deriving FromPyObject scoped on containers. enum ContainerPyO3Attribute { /// Treat the Container as a Wrapper, directly extract its fields from the input object. Transparent(attributes::kw::transparent), /// Force every field to be extracted from item of source Python object. ItemAll(attributes::kw::from_item_all), /// Change the name of an enum variant in the generated error message. ErrorAnnotation(LitStr), /// Change the path for the pyo3 crate Crate(CrateAttribute), } impl Parse for ContainerPyO3Attribute { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::transparent) { let kw: attributes::kw::transparent = input.parse()?; Ok(ContainerPyO3Attribute::Transparent(kw)) } else if lookahead.peek(attributes::kw::from_item_all) { let kw: attributes::kw::from_item_all = input.parse()?; Ok(ContainerPyO3Attribute::ItemAll(kw)) } else if lookahead.peek(attributes::kw::annotation) { let _: attributes::kw::annotation = input.parse()?; let _: Token![=] = input.parse()?; input.parse().map(ContainerPyO3Attribute::ErrorAnnotation) } else if lookahead.peek(Token![crate]) { input.parse().map(ContainerPyO3Attribute::Crate) } else { Err(lookahead.error()) } } } impl ContainerOptions { fn from_attrs(attrs: &[Attribute]) -> Result { let mut options = ContainerOptions::default(); for attr in attrs { if let Some(pyo3_attrs) = get_pyo3_options(attr)? { for pyo3_attr in pyo3_attrs { match pyo3_attr { ContainerPyO3Attribute::Transparent(kw) => { ensure_spanned!( !options.transparent, kw.span() => "`transparent` may only be provided once" ); options.transparent = true; } ContainerPyO3Attribute::ItemAll(kw) => { ensure_spanned!( options.from_item_all.is_none(), kw.span() => "`from_item_all` may only be provided once" ); options.from_item_all = Some(kw); } ContainerPyO3Attribute::ErrorAnnotation(lit_str) => { ensure_spanned!( options.annotation.is_none(), lit_str.span() => "`annotation` may only be provided once" ); options.annotation = Some(lit_str); } ContainerPyO3Attribute::Crate(path) => { ensure_spanned!( options.krate.is_none(), path.span() => "`crate` may only be provided once" ); options.krate = Some(path); } } } } } Ok(options) } } /// Attributes for deriving FromPyObject scoped on fields. #[derive(Clone, Debug)] struct FieldPyO3Attributes { getter: Option, from_py_with: Option, } #[derive(Clone, Debug)] enum FieldGetter { GetItem(Option), GetAttr(Option), } enum FieldPyO3Attribute { Getter(FieldGetter), FromPyWith(FromPyWithAttribute), } impl Parse for FieldPyO3Attribute { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::attribute) { let _: attributes::kw::attribute = input.parse()?; if input.peek(syn::token::Paren) { let content; let _ = parenthesized!(content in input); let attr_name: LitStr = content.parse()?; if !content.is_empty() { return Err(content.error( "expected at most one argument: `attribute` or `attribute(\"name\")`", )); } ensure_spanned!( !attr_name.value().is_empty(), attr_name.span() => "attribute name cannot be empty" ); Ok(FieldPyO3Attribute::Getter(FieldGetter::GetAttr(Some( attr_name, )))) } else { Ok(FieldPyO3Attribute::Getter(FieldGetter::GetAttr(None))) } } else if lookahead.peek(attributes::kw::item) { let _: attributes::kw::item = input.parse()?; if input.peek(syn::token::Paren) { let content; let _ = parenthesized!(content in input); let key = content.parse()?; if !content.is_empty() { return Err( content.error("expected at most one argument: `item` or `item(key)`") ); } Ok(FieldPyO3Attribute::Getter(FieldGetter::GetItem(Some(key)))) } else { Ok(FieldPyO3Attribute::Getter(FieldGetter::GetItem(None))) } } else if lookahead.peek(attributes::kw::from_py_with) { input.parse().map(FieldPyO3Attribute::FromPyWith) } else { Err(lookahead.error()) } } } impl FieldPyO3Attributes { /// Extract the field attributes. fn from_attrs(attrs: &[Attribute]) -> Result { let mut getter = None; let mut from_py_with = None; for attr in attrs { if let Some(pyo3_attrs) = get_pyo3_options(attr)? { for pyo3_attr in pyo3_attrs { match pyo3_attr { FieldPyO3Attribute::Getter(field_getter) => { ensure_spanned!( getter.is_none(), attr.span() => "only one of `attribute` or `item` can be provided" ); getter = Some(field_getter); } FieldPyO3Attribute::FromPyWith(from_py_with_attr) => { ensure_spanned!( from_py_with.is_none(), attr.span() => "`from_py_with` may only be provided once" ); from_py_with = Some(from_py_with_attr); } } } } } Ok(FieldPyO3Attributes { getter, from_py_with, }) } } fn verify_and_get_lifetime(generics: &syn::Generics) -> Result> { let mut lifetimes = generics.lifetimes(); let lifetime = lifetimes.next(); ensure_spanned!( lifetimes.next().is_none(), generics.span() => "FromPyObject can be derived with at most one lifetime parameter" ); Ok(lifetime) } /// Derive FromPyObject for enums and structs. /// /// * Max 1 lifetime specifier, will be tied to `FromPyObject`'s specifier /// * At least one field, in case of `#[transparent]`, exactly one field /// * At least one variant for enums. /// * Fields of input structs and enums must implement `FromPyObject` or be annotated with `from_py_with` /// * Derivation for structs with generic fields like `struct Foo(T)` /// adds `T: FromPyObject` on the derived implementation. pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result { let mut trait_generics = tokens.generics.clone(); let generics = &tokens.generics; let lt_param = if let Some(lt) = verify_and_get_lifetime(generics)? { lt.clone() } else { trait_generics.params.push(parse_quote!('source)); parse_quote!('source) }; let mut where_clause: syn::WhereClause = parse_quote!(where); for param in generics.type_params() { let gen_ident = ¶m.ident; where_clause .predicates .push(parse_quote!(#gen_ident: FromPyObject<#lt_param>)) } let options = ContainerOptions::from_attrs(&tokens.attrs)?; let krate = get_pyo3_crate(&options.krate); let derives = match &tokens.data { syn::Data::Enum(en) => { if options.transparent || options.annotation.is_some() { bail_spanned!(tokens.span() => "`transparent` or `annotation` is not supported \ at top level for enums"); } let en = Enum::new(en, &tokens.ident)?; en.build() } syn::Data::Struct(st) => { if let Some(lit_str) = &options.annotation { bail_spanned!(lit_str.span() => "`annotation` is unsupported for structs"); } let ident = &tokens.ident; let st = Container::new(&st.fields, parse_quote!(#ident), options)?; st.build() } syn::Data::Union(_) => bail_spanned!( tokens.span() => "#[derive(FromPyObject)] is not supported for unions" ), }; let ident = &tokens.ident; Ok(quote!( const _: () = { use #krate as _pyo3; #[automatically_derived] impl #trait_generics _pyo3::FromPyObject<#lt_param> for #ident #generics #where_clause { fn extract(obj: &#lt_param _pyo3::PyAny) -> _pyo3::PyResult { #derives } } }; )) } pyo3-macros-backend-0.20.2/src/konst.rs000064400000000000000000000051041046102023000157410ustar 00000000000000use std::borrow::Cow; use crate::{ attributes::{self, get_pyo3_options, take_attributes, NameAttribute}, deprecations::Deprecations, }; use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, spanned::Spanned, Result, }; pub struct ConstSpec { pub rust_ident: syn::Ident, pub attributes: ConstAttributes, } impl ConstSpec { pub fn python_name(&self) -> Cow<'_, Ident> { if let Some(name) = &self.attributes.name { Cow::Borrowed(&name.value.0) } else { Cow::Owned(self.rust_ident.unraw()) } } /// Null-terminated Python name pub fn null_terminated_python_name(&self) -> TokenStream { let name = format!("{}\0", self.python_name()); quote!({#name}) } } pub struct ConstAttributes { pub is_class_attr: bool, pub name: Option, pub deprecations: Deprecations, } pub enum PyO3ConstAttribute { Name(NameAttribute), } impl Parse for PyO3ConstAttribute { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::name) { input.parse().map(PyO3ConstAttribute::Name) } else { Err(lookahead.error()) } } } impl ConstAttributes { pub fn from_attrs(attrs: &mut Vec) -> syn::Result { let mut attributes = ConstAttributes { is_class_attr: false, name: None, deprecations: Deprecations::new(), }; take_attributes(attrs, |attr| { if attr.path().is_ident("classattr") { ensure_spanned!( matches!(attr.meta, syn::Meta::Path(..)), attr.span() => "`#[classattr]` does not take any arguments" ); attributes.is_class_attr = true; Ok(true) } else if let Some(pyo3_attributes) = get_pyo3_options(attr)? { for pyo3_attr in pyo3_attributes { match pyo3_attr { PyO3ConstAttribute::Name(name) => attributes.set_name(name)?, } } Ok(true) } else { Ok(false) } })?; Ok(attributes) } fn set_name(&mut self, name: NameAttribute) -> Result<()> { ensure_spanned!( self.name.is_none(), name.span() => "`name` may only be specified once" ); self.name = Some(name); Ok(()) } } pyo3-macros-backend-0.20.2/src/lib.rs000064400000000000000000000014641046102023000153560ustar 00000000000000//! This crate contains the implementation of the proc macro attributes #![warn(elided_lifetimes_in_paths, unused_lifetimes)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![recursion_limit = "1024"] // Listed first so that macros in this module are available in the rest of the crate. #[macro_use] mod utils; mod attributes; mod deprecations; mod frompyobject; mod konst; mod method; mod module; mod params; mod pyclass; mod pyfunction; mod pyimpl; mod pymethod; mod quotes; pub use frompyobject::build_derive_from_pyobject; pub use module::{process_functions_in_module, pymodule_impl, PyModuleOptions}; pub use pyclass::{build_py_class, build_py_enum, PyClassArgs}; pub use pyfunction::{build_py_function, PyFunctionOptions}; pub use pyimpl::{build_py_methods, PyClassMethodsType}; pub use utils::get_doc; pyo3-macros-backend-0.20.2/src/method.rs000064400000000000000000000761771046102023000161050ustar 00000000000000use std::fmt::Display; use crate::attributes::{TextSignatureAttribute, TextSignatureAttributeValue}; use crate::deprecations::{Deprecation, Deprecations}; use crate::params::impl_arg_params; use crate::pyfunction::{FunctionSignature, PyFunctionArgPyO3Attributes}; use crate::pyfunction::{PyFunctionOptions, SignatureAttribute}; use crate::quotes; use crate::utils::{self, PythonDoc}; use proc_macro2::{Span, TokenStream}; use quote::ToTokens; use quote::{quote, quote_spanned}; use syn::ext::IdentExt; use syn::spanned::Spanned; use syn::{Ident, Result}; #[derive(Clone, Debug)] pub struct FnArg<'a> { pub name: &'a syn::Ident, pub ty: &'a syn::Type, pub optional: Option<&'a syn::Type>, pub default: Option, pub py: bool, pub attrs: PyFunctionArgPyO3Attributes, pub is_varargs: bool, pub is_kwargs: bool, } impl<'a> FnArg<'a> { /// Transforms a rust fn arg parsed with syn into a method::FnArg pub fn parse(arg: &'a mut syn::FnArg) -> Result { match arg { syn::FnArg::Receiver(recv) => { bail_spanned!(recv.span() => "unexpected receiver") } // checked in parse_fn_type syn::FnArg::Typed(cap) => { if let syn::Type::ImplTrait(_) = &*cap.ty { bail_spanned!(cap.ty.span() => IMPL_TRAIT_ERR); } let arg_attrs = PyFunctionArgPyO3Attributes::from_attrs(&mut cap.attrs)?; let ident = match &*cap.pat { syn::Pat::Ident(syn::PatIdent { ident, .. }) => ident, other => return Err(handle_argument_error(other)), }; Ok(FnArg { name: ident, ty: &cap.ty, optional: utils::option_type_argument(&cap.ty), default: None, py: utils::is_python(&cap.ty), attrs: arg_attrs, is_varargs: false, is_kwargs: false, }) } } } } fn handle_argument_error(pat: &syn::Pat) -> syn::Error { let span = pat.span(); let msg = match pat { syn::Pat::Wild(_) => "wildcard argument names are not supported", syn::Pat::Struct(_) | syn::Pat::Tuple(_) | syn::Pat::TupleStruct(_) | syn::Pat::Slice(_) => "destructuring in arguments is not supported", _ => "unsupported argument", }; syn::Error::new(span, msg) } #[derive(Clone, Debug)] pub enum FnType { Getter(SelfType), Setter(SelfType), Fn(SelfType), FnNew, FnNewClass(Span), FnClass(Span), FnStatic, FnModule(Span), ClassAttribute, } impl FnType { pub fn skip_first_rust_argument_in_python_signature(&self) -> bool { match self { FnType::Getter(_) | FnType::Setter(_) | FnType::Fn(_) | FnType::FnClass(_) | FnType::FnNewClass(_) | FnType::FnModule(_) => true, FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => false, } } pub fn self_arg(&self, cls: Option<&syn::Type>, error_mode: ExtractErrorMode) -> TokenStream { match self { FnType::Getter(st) | FnType::Setter(st) | FnType::Fn(st) => { let mut receiver = st.receiver( cls.expect("no class given for Fn with a \"self\" receiver"), error_mode, ); syn::Token![,](Span::call_site()).to_tokens(&mut receiver); receiver } FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => { quote!() } FnType::FnClass(span) | FnType::FnNewClass(span) => { let py = syn::Ident::new("py", Span::call_site()); let slf: Ident = syn::Ident::new("_slf", Span::call_site()); quote_spanned! { *span => #[allow(clippy::useless_conversion)] ::std::convert::Into::into(_pyo3::types::PyType::from_type_ptr(#py, #slf.cast())), } } FnType::FnModule(span) => { quote_spanned! { *span => #[allow(clippy::useless_conversion)] ::std::convert::Into::into(py.from_borrowed_ptr::<_pyo3::types::PyModule>(_slf)), } } } } } #[derive(Clone, Debug)] pub enum SelfType { Receiver { mutable: bool, span: Span }, TryFromPyCell(Span), } #[derive(Clone, Copy)] pub enum ExtractErrorMode { NotImplemented, Raise, } impl ExtractErrorMode { pub fn handle_error(self, extract: TokenStream) -> TokenStream { match self { ExtractErrorMode::Raise => quote! { #extract? }, ExtractErrorMode::NotImplemented => quote! { match #extract { ::std::result::Result::Ok(value) => value, ::std::result::Result::Err(_) => { return _pyo3::callback::convert(py, py.NotImplemented()); }, } }, } } } impl SelfType { pub fn receiver(&self, cls: &syn::Type, error_mode: ExtractErrorMode) -> TokenStream { // Due to use of quote_spanned in this function, need to bind these idents to the // main macro callsite. let py = syn::Ident::new("py", Span::call_site()); let slf = syn::Ident::new("_slf", Span::call_site()); match self { SelfType::Receiver { span, mutable } => { let method = if *mutable { syn::Ident::new("extract_pyclass_ref_mut", *span) } else { syn::Ident::new("extract_pyclass_ref", *span) }; error_mode.handle_error(quote_spanned! { *span => _pyo3::impl_::extract_argument::#method::<#cls>( #py.from_borrowed_ptr::<_pyo3::PyAny>(#slf), &mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT }, ) }) } SelfType::TryFromPyCell(span) => { error_mode.handle_error( quote_spanned! { *span => #py.from_borrowed_ptr::<_pyo3::PyAny>(#slf).downcast::<_pyo3::PyCell<#cls>>() .map_err(::std::convert::Into::<_pyo3::PyErr>::into) .and_then( #[allow(clippy::useless_conversion)] // In case slf is PyCell #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] // In case slf is Py (unknown_lints can be removed when MSRV is 1.75+) |cell| ::std::convert::TryFrom::try_from(cell).map_err(::std::convert::Into::into) ) } ) } } } } /// Determines which CPython calling convention a given FnSpec uses. #[derive(Clone, Debug)] pub enum CallingConvention { Noargs, // METH_NOARGS Varargs, // METH_VARARGS | METH_KEYWORDS Fastcall, // METH_FASTCALL | METH_KEYWORDS (not compatible with `abi3` feature) TpNew, // special convention for tp_new } impl CallingConvention { /// Determine default calling convention from an argument signature. /// /// Different other slots (tp_call, tp_new) can have other requirements /// and are set manually (see `parse_fn_type` below). pub fn from_signature(signature: &FunctionSignature<'_>) -> Self { if signature.python_signature.has_no_args() { Self::Noargs } else if signature.python_signature.kwargs.is_some() { // for functions that accept **kwargs, always prefer varargs Self::Varargs } else if cfg!(not(feature = "abi3")) { // Not available in the Stable ABI as of Python 3.10 Self::Fastcall } else { Self::Varargs } } } pub struct FnSpec<'a> { pub tp: FnType, // Rust function name pub name: &'a syn::Ident, // Wrapped python name. This should not have any leading r#. // r# can be removed by syn::ext::IdentExt::unraw() pub python_name: syn::Ident, pub signature: FunctionSignature<'a>, pub output: syn::Type, pub convention: CallingConvention, pub text_signature: Option, pub unsafety: Option, pub deprecations: Deprecations, } pub fn get_return_info(output: &syn::ReturnType) -> syn::Type { match output { syn::ReturnType::Default => syn::Type::Infer(syn::parse_quote! {_}), syn::ReturnType::Type(_, ty) => *ty.clone(), } } pub fn parse_method_receiver(arg: &syn::FnArg) -> Result { match arg { syn::FnArg::Receiver( recv @ syn::Receiver { reference: None, .. }, ) => { bail_spanned!(recv.span() => RECEIVER_BY_VALUE_ERR); } syn::FnArg::Receiver(recv @ syn::Receiver { mutability, .. }) => Ok(SelfType::Receiver { mutable: mutability.is_some(), span: recv.span(), }), syn::FnArg::Typed(syn::PatType { ty, .. }) => { if let syn::Type::ImplTrait(_) = &**ty { bail_spanned!(ty.span() => IMPL_TRAIT_ERR); } Ok(SelfType::TryFromPyCell(ty.span())) } } } impl<'a> FnSpec<'a> { /// Parser function signature and function attributes pub fn parse( // Signature is mutable to remove the `Python` argument. sig: &'a mut syn::Signature, meth_attrs: &mut Vec, options: PyFunctionOptions, ) -> Result> { let PyFunctionOptions { text_signature, name, signature, .. } = options; let mut python_name = name.map(|name| name.value.0); let mut deprecations = Deprecations::new(); let fn_type = Self::parse_fn_type(sig, meth_attrs, &mut python_name, &mut deprecations)?; ensure_signatures_on_valid_method(&fn_type, signature.as_ref(), text_signature.as_ref())?; let name = &sig.ident; let ty = get_return_info(&sig.output); let python_name = python_name.as_ref().unwrap_or(name).unraw(); let arguments: Vec<_> = sig .inputs .iter_mut() .skip(if fn_type.skip_first_rust_argument_in_python_signature() { 1 } else { 0 }) .map(FnArg::parse) .collect::>()?; let signature = if let Some(signature) = signature { FunctionSignature::from_arguments_and_attribute(arguments, signature)? } else { FunctionSignature::from_arguments(arguments)? }; let convention = if matches!(fn_type, FnType::FnNew | FnType::FnNewClass(_)) { CallingConvention::TpNew } else { CallingConvention::from_signature(&signature) }; Ok(FnSpec { tp: fn_type, name, convention, python_name, signature, output: ty, text_signature, unsafety: sig.unsafety, deprecations, }) } pub fn null_terminated_python_name(&self) -> syn::LitStr { syn::LitStr::new(&format!("{}\0", self.python_name), self.python_name.span()) } fn parse_fn_type( sig: &syn::Signature, meth_attrs: &mut Vec, python_name: &mut Option, deprecations: &mut Deprecations, ) -> Result { let mut method_attributes = parse_method_attributes(meth_attrs, deprecations)?; let name = &sig.ident; let parse_receiver = |msg: &'static str| { let first_arg = sig .inputs .first() .ok_or_else(|| err_spanned!(sig.span() => msg))?; parse_method_receiver(first_arg) }; // strip get_ or set_ let strip_fn_name = |prefix: &'static str| { name.unraw() .to_string() .strip_prefix(prefix) .map(|stripped| syn::Ident::new(stripped, name.span())) }; let mut set_name_to_new = || { if let Some(name) = &python_name { bail_spanned!(name.span() => "`name` not allowed with `#[new]`"); } *python_name = Some(syn::Ident::new("__new__", Span::call_site())); Ok(()) }; let fn_type = match method_attributes.as_mut_slice() { [] => FnType::Fn(parse_receiver( "static method needs #[staticmethod] attribute", )?), [MethodTypeAttribute::StaticMethod(_)] => FnType::FnStatic, [MethodTypeAttribute::ClassAttribute(_)] => FnType::ClassAttribute, [MethodTypeAttribute::New(_)] => { set_name_to_new()?; FnType::FnNew } [MethodTypeAttribute::New(_), MethodTypeAttribute::ClassMethod(span)] | [MethodTypeAttribute::ClassMethod(span), MethodTypeAttribute::New(_)] => { set_name_to_new()?; FnType::FnNewClass(*span) } [MethodTypeAttribute::ClassMethod(_)] => { // Add a helpful hint if the classmethod doesn't look like a classmethod let span = match sig.inputs.first() { // Don't actually bother checking the type of the first argument, the compiler // will error on incorrect type. Some(syn::FnArg::Typed(first_arg)) => first_arg.ty.span(), Some(syn::FnArg::Receiver(_)) | None => bail_spanned!( sig.paren_token.span.join() => "Expected `&PyType` or `Py` as the first argument to `#[classmethod]`" ), }; FnType::FnClass(span) } [MethodTypeAttribute::Getter(_, name)] => { if let Some(name) = name.take() { ensure_spanned!( python_name.replace(name).is_none(), python_name.span() => "`name` may only be specified once" ); } else if python_name.is_none() { // Strip off "get_" prefix if needed *python_name = strip_fn_name("get_"); } FnType::Getter(parse_receiver("expected receiver for `#[getter]`")?) } [MethodTypeAttribute::Setter(_, name)] => { if let Some(name) = name.take() { ensure_spanned!( python_name.replace(name).is_none(), python_name.span() => "`name` may only be specified once" ); } else if python_name.is_none() { // Strip off "set_" prefix if needed *python_name = strip_fn_name("set_"); } FnType::Setter(parse_receiver("expected receiver for `#[setter]`")?) } [first, rest @ .., last] => { // Join as many of the spans together as possible let span = rest .iter() .fold(first.span(), |s, next| s.join(next.span()).unwrap_or(s)); let span = span.join(last.span()).unwrap_or(span); // List all the attributes in the error message let mut msg = format!("`{}` may not be combined with", first); let mut is_first = true; for attr in &*rest { msg.push_str(&format!(" `{}`", attr)); if is_first { is_first = false; } else { msg.push(','); } } if !rest.is_empty() { msg.push_str(" and"); } msg.push_str(&format!(" `{}`", last)); bail_spanned!(span => msg) } }; Ok(fn_type) } /// Return a C wrapper function for this signature. pub fn get_wrapper_function( &self, ident: &proc_macro2::Ident, cls: Option<&syn::Type>, ) -> Result { let self_arg = self.tp.self_arg(cls, ExtractErrorMode::Raise); let func_name = &self.name; let rust_call = |args: Vec| { quotes::map_result_into_ptr(quotes::ok_wrap(quote! { function(#self_arg #(#args),*) })) }; let rust_name = if let Some(cls) = cls { quote!(#cls::#func_name) } else { quote!(#func_name) }; Ok(match self.convention { CallingConvention::Noargs => { let call = if !self.signature.arguments.is_empty() { // Only `py` arg can be here rust_call(vec![quote!(py)]) } else { rust_call(vec![]) }; quote! { unsafe fn #ident<'py>( py: _pyo3::Python<'py>, _slf: *mut _pyo3::ffi::PyObject, ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { let function = #rust_name; // Shadow the function name to avoid #3017 #call } } } CallingConvention::Fastcall => { let (arg_convert, args) = impl_arg_params(self, cls, true)?; let call = rust_call(args); quote! { unsafe fn #ident<'py>( py: _pyo3::Python<'py>, _slf: *mut _pyo3::ffi::PyObject, _args: *const *mut _pyo3::ffi::PyObject, _nargs: _pyo3::ffi::Py_ssize_t, _kwnames: *mut _pyo3::ffi::PyObject ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { let function = #rust_name; // Shadow the function name to avoid #3017 #arg_convert #call } } } CallingConvention::Varargs => { let (arg_convert, args) = impl_arg_params(self, cls, false)?; let call = rust_call(args); quote! { unsafe fn #ident<'py>( py: _pyo3::Python<'py>, _slf: *mut _pyo3::ffi::PyObject, _args: *mut _pyo3::ffi::PyObject, _kwargs: *mut _pyo3::ffi::PyObject ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { let function = #rust_name; // Shadow the function name to avoid #3017 #arg_convert #call } } } CallingConvention::TpNew => { let (arg_convert, args) = impl_arg_params(self, cls, false)?; let self_arg = self.tp.self_arg(cls, ExtractErrorMode::Raise); let call = quote! { #rust_name(#self_arg #(#args),*) }; quote! { unsafe fn #ident( py: _pyo3::Python<'_>, _slf: *mut _pyo3::ffi::PyTypeObject, _args: *mut _pyo3::ffi::PyObject, _kwargs: *mut _pyo3::ffi::PyObject ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { use _pyo3::callback::IntoPyCallbackOutput; let function = #rust_name; // Shadow the function name to avoid #3017 #arg_convert let result = #call; let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(py)?; let cell = initializer.create_cell_from_subtype(py, _slf)?; ::std::result::Result::Ok(cell as *mut _pyo3::ffi::PyObject) } } } }) } /// Return a `PyMethodDef` constructor for this function, matching the selected /// calling convention. pub fn get_methoddef(&self, wrapper: impl ToTokens, doc: &PythonDoc) -> TokenStream { let python_name = self.null_terminated_python_name(); match self.convention { CallingConvention::Noargs => quote! { _pyo3::impl_::pymethods::PyMethodDef::noargs( #python_name, _pyo3::impl_::pymethods::PyCFunction({ unsafe extern "C" fn trampoline( _slf: *mut _pyo3::ffi::PyObject, _args: *mut _pyo3::ffi::PyObject, ) -> *mut _pyo3::ffi::PyObject { _pyo3::impl_::trampoline::noargs( _slf, _args, #wrapper ) } trampoline }), #doc, ) }, CallingConvention::Fastcall => quote! { _pyo3::impl_::pymethods::PyMethodDef::fastcall_cfunction_with_keywords( #python_name, _pyo3::impl_::pymethods::PyCFunctionFastWithKeywords({ unsafe extern "C" fn trampoline( _slf: *mut _pyo3::ffi::PyObject, _args: *const *mut _pyo3::ffi::PyObject, _nargs: _pyo3::ffi::Py_ssize_t, _kwnames: *mut _pyo3::ffi::PyObject ) -> *mut _pyo3::ffi::PyObject { _pyo3::impl_::trampoline::fastcall_with_keywords( _slf, _args, _nargs, _kwnames, #wrapper ) } trampoline }), #doc, ) }, CallingConvention::Varargs => quote! { _pyo3::impl_::pymethods::PyMethodDef::cfunction_with_keywords( #python_name, _pyo3::impl_::pymethods::PyCFunctionWithKeywords({ unsafe extern "C" fn trampoline( _slf: *mut _pyo3::ffi::PyObject, _args: *mut _pyo3::ffi::PyObject, _kwargs: *mut _pyo3::ffi::PyObject, ) -> *mut _pyo3::ffi::PyObject { _pyo3::impl_::trampoline::cfunction_with_keywords( _slf, _args, _kwargs, #wrapper ) } trampoline }), #doc, ) }, CallingConvention::TpNew => unreachable!("tp_new cannot get a methoddef"), } } /// Forwards to [utils::get_doc] with the text signature of this spec. pub fn get_doc(&self, attrs: &[syn::Attribute]) -> PythonDoc { let text_signature = self .text_signature_call_signature() .map(|sig| format!("{}{}", self.python_name, sig)); utils::get_doc(attrs, text_signature) } /// Creates the parenthesised arguments list for `__text_signature__` snippet based on this spec's signature /// and/or attributes. Prepend the callable name to make a complete `__text_signature__`. pub fn text_signature_call_signature(&self) -> Option { let self_argument = match &self.tp { // Getters / Setters / ClassAttribute are not callables on the Python side FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => return None, FnType::Fn(_) => Some("self"), FnType::FnModule(_) => Some("module"), FnType::FnClass(_) | FnType::FnNewClass(_) => Some("cls"), FnType::FnStatic | FnType::FnNew => None, }; match self.text_signature.as_ref().map(|attr| &attr.value) { Some(TextSignatureAttributeValue::Str(s)) => Some(s.value()), None => Some(self.signature.text_signature(self_argument)), Some(TextSignatureAttributeValue::Disabled(_)) => None, } } } enum MethodTypeAttribute { New(Span), ClassMethod(Span), StaticMethod(Span), Getter(Span, Option), Setter(Span, Option), ClassAttribute(Span), } impl MethodTypeAttribute { fn span(&self) -> Span { match self { MethodTypeAttribute::New(span) | MethodTypeAttribute::ClassMethod(span) | MethodTypeAttribute::StaticMethod(span) | MethodTypeAttribute::Getter(span, _) | MethodTypeAttribute::Setter(span, _) | MethodTypeAttribute::ClassAttribute(span) => *span, } } /// Attempts to parse a method type attribute. /// /// If the attribute does not match one of the attribute names, returns `Ok(None)`. /// /// Otherwise will either return a parse error or the attribute. fn parse_if_matching_attribute( attr: &syn::Attribute, deprecations: &mut Deprecations, ) -> Result> { fn ensure_no_arguments(meta: &syn::Meta, ident: &str) -> syn::Result<()> { match meta { syn::Meta::Path(_) => Ok(()), syn::Meta::List(l) => bail_spanned!( l.span() => format!( "`#[{ident}]` does not take any arguments\n= help: did you mean `#[{ident}] #[pyo3({meta})]`?", ident = ident, meta = l.tokens, ) ), syn::Meta::NameValue(nv) => { bail_spanned!(nv.eq_token.span() => format!( "`#[{}]` does not take any arguments\n= note: this was previously accepted and ignored", ident )) } } } fn extract_name(meta: &syn::Meta, ident: &str) -> Result> { match meta { syn::Meta::Path(_) => Ok(None), syn::Meta::NameValue(nv) => bail_spanned!( nv.eq_token.span() => format!("expected `#[{}(name)]` to set the name", ident) ), syn::Meta::List(l) => { if let Ok(name) = l.parse_args::() { Ok(Some(name)) } else if let Ok(name) = l.parse_args::() { name.parse().map(Some) } else { bail_spanned!(l.tokens.span() => "expected ident or string literal for property name"); } } } } let meta = &attr.meta; let path = meta.path(); if path.is_ident("new") { ensure_no_arguments(meta, "new")?; Ok(Some(MethodTypeAttribute::New(path.span()))) } else if path.is_ident("__new__") { let span = path.span(); deprecations.push(Deprecation::PyMethodsNewDeprecatedForm, span); ensure_no_arguments(meta, "__new__")?; Ok(Some(MethodTypeAttribute::New(span))) } else if path.is_ident("classmethod") { ensure_no_arguments(meta, "classmethod")?; Ok(Some(MethodTypeAttribute::ClassMethod(path.span()))) } else if path.is_ident("staticmethod") { ensure_no_arguments(meta, "staticmethod")?; Ok(Some(MethodTypeAttribute::StaticMethod(path.span()))) } else if path.is_ident("classattr") { ensure_no_arguments(meta, "classattr")?; Ok(Some(MethodTypeAttribute::ClassAttribute(path.span()))) } else if path.is_ident("getter") { let name = extract_name(meta, "getter")?; Ok(Some(MethodTypeAttribute::Getter(path.span(), name))) } else if path.is_ident("setter") { let name = extract_name(meta, "setter")?; Ok(Some(MethodTypeAttribute::Setter(path.span(), name))) } else { Ok(None) } } } impl Display for MethodTypeAttribute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { MethodTypeAttribute::New(_) => "#[new]".fmt(f), MethodTypeAttribute::ClassMethod(_) => "#[classmethod]".fmt(f), MethodTypeAttribute::StaticMethod(_) => "#[staticmethod]".fmt(f), MethodTypeAttribute::Getter(_, _) => "#[getter]".fmt(f), MethodTypeAttribute::Setter(_, _) => "#[setter]".fmt(f), MethodTypeAttribute::ClassAttribute(_) => "#[classattr]".fmt(f), } } } fn parse_method_attributes( attrs: &mut Vec, deprecations: &mut Deprecations, ) -> Result> { let mut new_attrs = Vec::new(); let mut found_attrs = Vec::new(); for attr in attrs.drain(..) { match MethodTypeAttribute::parse_if_matching_attribute(&attr, deprecations)? { Some(attr) => found_attrs.push(attr), None => new_attrs.push(attr), } } *attrs = new_attrs; Ok(found_attrs) } const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments"; const RECEIVER_BY_VALUE_ERR: &str = "Python objects are shared, so 'self' cannot be moved out of the Python interpreter. Try `&self`, `&mut self, `slf: PyRef<'_, Self>` or `slf: PyRefMut<'_, Self>`."; fn ensure_signatures_on_valid_method( fn_type: &FnType, signature: Option<&SignatureAttribute>, text_signature: Option<&TextSignatureAttribute>, ) -> syn::Result<()> { if let Some(signature) = signature { match fn_type { FnType::Getter(_) => { bail_spanned!(signature.kw.span() => "`signature` not allowed with `getter`") } FnType::Setter(_) => { bail_spanned!(signature.kw.span() => "`signature` not allowed with `setter`") } FnType::ClassAttribute => { bail_spanned!(signature.kw.span() => "`signature` not allowed with `classattr`") } _ => {} } } if let Some(text_signature) = text_signature { match fn_type { FnType::Getter(_) => { bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `getter`") } FnType::Setter(_) => { bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `setter`") } FnType::ClassAttribute => { bail_spanned!(text_signature.kw.span() => "`text_signature` not allowed with `classattr`") } _ => {} } } Ok(()) } pyo3-macros-backend-0.20.2/src/module.rs000064400000000000000000000144641046102023000161010ustar 00000000000000//! Code generation for the function that initializes a python module and adds classes and function. use crate::{ attributes::{self, take_attributes, take_pyo3_options, CrateAttribute, NameAttribute}, pyfunction::{impl_wrap_pyfunction, PyFunctionOptions}, utils::{get_pyo3_crate, PythonDoc}, }; use proc_macro2::TokenStream; use quote::quote; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, spanned::Spanned, token::Comma, Ident, Path, Result, Visibility, }; #[derive(Default)] pub struct PyModuleOptions { krate: Option, name: Option, } impl PyModuleOptions { pub fn from_attrs(attrs: &mut Vec) -> Result { let mut options: PyModuleOptions = Default::default(); for option in take_pyo3_options(attrs)? { match option { PyModulePyO3Option::Name(name) => options.set_name(name.value.0)?, PyModulePyO3Option::Crate(path) => options.set_crate(path)?, } } Ok(options) } fn set_name(&mut self, name: syn::Ident) -> Result<()> { ensure_spanned!( self.name.is_none(), name.span() => "`name` may only be specified once" ); self.name = Some(name); Ok(()) } fn set_crate(&mut self, path: CrateAttribute) -> Result<()> { ensure_spanned!( self.krate.is_none(), path.span() => "`crate` may only be specified once" ); self.krate = Some(path); Ok(()) } } /// Generates the function that is called by the python interpreter to initialize the native /// module pub fn pymodule_impl( fnname: &Ident, options: PyModuleOptions, doc: PythonDoc, visibility: &Visibility, ) -> TokenStream { let name = options.name.unwrap_or_else(|| fnname.unraw()); let krate = get_pyo3_crate(&options.krate); let pyinit_symbol = format!("PyInit_{}", name); quote! { // Create a module with the same name as the `#[pymodule]` - this way `use ` // will actually bring both the module and the function into scope. #[doc(hidden)] #visibility mod #fnname { pub(crate) struct MakeDef; pub static DEF: #krate::impl_::pymodule::ModuleDef = MakeDef::make_def(); pub const NAME: &'static str = concat!(stringify!(#name), "\0"); /// This autogenerated function is called by the python interpreter when importing /// the module. #[export_name = #pyinit_symbol] pub unsafe extern "C" fn init() -> *mut #krate::ffi::PyObject { #krate::impl_::trampoline::module_init(|py| DEF.make_module(py)) } } // Generate the definition inside an anonymous function in the same scope as the original function - // this avoids complications around the fact that the generated module has a different scope // (and `super` doesn't always refer to the outer scope, e.g. if the `#[pymodule] is // inside a function body) const _: () = { use #krate::impl_::pymodule as impl_; impl #fnname::MakeDef { const fn make_def() -> impl_::ModuleDef { const INITIALIZER: impl_::ModuleInitializer = impl_::ModuleInitializer(#fnname); unsafe { impl_::ModuleDef::new(#fnname::NAME, #doc, INITIALIZER) } } } }; } } /// Finds and takes care of the #[pyfn(...)] in `#[pymodule]` pub fn process_functions_in_module( options: &PyModuleOptions, func: &mut syn::ItemFn, ) -> syn::Result<()> { let mut stmts: Vec = Vec::new(); let krate = get_pyo3_crate(&options.krate); for mut stmt in func.block.stmts.drain(..) { if let syn::Stmt::Item(syn::Item::Fn(func)) = &mut stmt { if let Some(pyfn_args) = get_pyfn_attr(&mut func.attrs)? { let module_name = pyfn_args.modname; let wrapped_function = impl_wrap_pyfunction(func, pyfn_args.options)?; let name = &func.sig.ident; let statements: Vec = syn::parse_quote! { #wrapped_function #module_name.add_function(#krate::impl_::pyfunction::_wrap_pyfunction(&#name::DEF, #module_name)?)?; }; stmts.extend(statements); } }; stmts.push(stmt); } func.block.stmts = stmts; Ok(()) } pub struct PyFnArgs { modname: Path, options: PyFunctionOptions, } impl Parse for PyFnArgs { fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { let modname = input.parse().map_err( |e| err_spanned!(e.span() => "expected module as first argument to #[pyfn()]"), )?; if input.is_empty() { return Ok(Self { modname, options: Default::default(), }); } let _: Comma = input.parse()?; Ok(Self { modname, options: input.parse()?, }) } } /// Extracts the data from the #[pyfn(...)] attribute of a function fn get_pyfn_attr(attrs: &mut Vec) -> syn::Result> { let mut pyfn_args: Option = None; take_attributes(attrs, |attr| { if attr.path().is_ident("pyfn") { ensure_spanned!( pyfn_args.is_none(), attr.span() => "`#[pyfn] may only be specified once" ); pyfn_args = Some(attr.parse_args()?); Ok(true) } else { Ok(false) } })?; if let Some(pyfn_args) = &mut pyfn_args { pyfn_args .options .add_attributes(take_pyo3_options(attrs)?)?; } Ok(pyfn_args) } enum PyModulePyO3Option { Crate(CrateAttribute), Name(NameAttribute), } impl Parse for PyModulePyO3Option { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::name) { input.parse().map(PyModulePyO3Option::Name) } else if lookahead.peek(syn::Token![crate]) { input.parse().map(PyModulePyO3Option::Crate) } else { Err(lookahead.error()) } } } pyo3-macros-backend-0.20.2/src/params.rs000064400000000000000000000205251046102023000160720ustar 00000000000000use crate::{ method::{FnArg, FnSpec}, pyfunction::FunctionSignature, quotes::some_wrap, }; use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned}; use syn::spanned::Spanned; use syn::Result; /// Return true if the argument list is simply (*args, **kwds). pub fn is_forwarded_args(signature: &FunctionSignature<'_>) -> bool { matches!( signature.arguments.as_slice(), [ FnArg { is_varargs: true, .. }, FnArg { is_kwargs: true, .. }, ] ) } pub fn impl_arg_params( spec: &FnSpec<'_>, self_: Option<&syn::Type>, fastcall: bool, ) -> Result<(TokenStream, Vec)> { let args_array = syn::Ident::new("output", Span::call_site()); if !fastcall && is_forwarded_args(&spec.signature) { // In the varargs convention, we can just pass though if the signature // is (*args, **kwds). let arg_convert = spec .signature .arguments .iter() .map(|arg| impl_arg_param(arg, &mut 0, &args_array)) .collect::>()?; return Ok(( quote! { let _args = py.from_borrowed_ptr::<_pyo3::types::PyTuple>(_args); let _kwargs: ::std::option::Option<&_pyo3::types::PyDict> = py.from_borrowed_ptr_or_opt(_kwargs); }, arg_convert, )); }; let positional_parameter_names = &spec.signature.python_signature.positional_parameters; let positional_only_parameters = &spec.signature.python_signature.positional_only_parameters; let required_positional_parameters = &spec .signature .python_signature .required_positional_parameters; let keyword_only_parameters = spec .signature .python_signature .keyword_only_parameters .iter() .map(|(name, required)| { quote! { _pyo3::impl_::extract_argument::KeywordOnlyParameterDescription { name: #name, required: #required, } } }); let num_params = positional_parameter_names.len() + keyword_only_parameters.len(); let mut option_pos = 0; let param_conversion = spec .signature .arguments .iter() .map(|arg| impl_arg_param(arg, &mut option_pos, &args_array)) .collect::>()?; let args_handler = if spec.signature.python_signature.varargs.is_some() { quote! { _pyo3::impl_::extract_argument::TupleVarargs } } else { quote! { _pyo3::impl_::extract_argument::NoVarargs } }; let kwargs_handler = if spec.signature.python_signature.kwargs.is_some() { quote! { _pyo3::impl_::extract_argument::DictVarkeywords } } else { quote! { _pyo3::impl_::extract_argument::NoVarkeywords } }; let cls_name = if let Some(cls) = self_ { quote! { ::std::option::Option::Some(<#cls as _pyo3::type_object::PyTypeInfo>::NAME) } } else { quote! { ::std::option::Option::None } }; let python_name = &spec.python_name; let extract_expression = if fastcall { quote! { DESCRIPTION.extract_arguments_fastcall::<#args_handler, #kwargs_handler>( py, _args, _nargs, _kwnames, &mut #args_array )? } } else { quote! { DESCRIPTION.extract_arguments_tuple_dict::<#args_handler, #kwargs_handler>( py, _args, _kwargs, &mut #args_array )? } }; // create array of arguments, and then parse Ok(( quote! { const DESCRIPTION: _pyo3::impl_::extract_argument::FunctionDescription = _pyo3::impl_::extract_argument::FunctionDescription { cls_name: #cls_name, func_name: stringify!(#python_name), positional_parameter_names: &[#(#positional_parameter_names),*], positional_only_parameters: #positional_only_parameters, required_positional_parameters: #required_positional_parameters, keyword_only_parameters: &[#(#keyword_only_parameters),*], }; let mut #args_array = [::std::option::Option::None; #num_params]; let (_args, _kwargs) = #extract_expression; }, param_conversion, )) } /// Re option_pos: The option slice doesn't contain the py: Python argument, so the argument /// index and the index in option diverge when using py: Python fn impl_arg_param( arg: &FnArg<'_>, option_pos: &mut usize, args_array: &syn::Ident, ) -> Result { // Use this macro inside this function, to ensure that all code generated here is associated // with the function argument macro_rules! quote_arg_span { ($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => $($tokens)*) } } if arg.py { return Ok(quote! { py }); } let name = arg.name; let name_str = name.to_string(); if arg.is_varargs { ensure_spanned!( arg.optional.is_none(), arg.name.span() => "args cannot be optional" ); return Ok(quote_arg_span! { _pyo3::impl_::extract_argument::extract_argument( _args, &mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT }, #name_str )? }); } else if arg.is_kwargs { ensure_spanned!( arg.optional.is_some(), arg.name.span() => "kwargs must be Option<_>" ); return Ok(quote_arg_span! { _pyo3::impl_::extract_argument::extract_optional_argument( _kwargs.map(::std::convert::AsRef::as_ref), &mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT }, #name_str, || ::std::option::Option::None )? }); } let arg_value = quote_arg_span!(#args_array[#option_pos]); *option_pos += 1; let mut default = arg.default.as_ref().map(|expr| quote!(#expr)); // Option arguments have special treatment: the default should be specified _without_ the // Some() wrapper. Maybe this should be changed in future?! if arg.optional.is_some() { default = Some(default.map_or_else(|| quote!(::std::option::Option::None), some_wrap)); } let tokens = if let Some(expr_path) = arg.attrs.from_py_with.as_ref().map(|attr| &attr.value) { if let Some(default) = default { quote_arg_span! { #[allow(clippy::redundant_closure)] _pyo3::impl_::extract_argument::from_py_with_with_default( #arg_value, #name_str, #expr_path, || #default )? } } else { quote_arg_span! { _pyo3::impl_::extract_argument::from_py_with( _pyo3::impl_::extract_argument::unwrap_required_argument(#arg_value), #name_str, #expr_path, )? } } } else if arg.optional.is_some() { quote_arg_span! { #[allow(clippy::redundant_closure)] _pyo3::impl_::extract_argument::extract_optional_argument( #arg_value, &mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT }, #name_str, || #default )? } } else if let Some(default) = default { quote_arg_span! { #[allow(clippy::redundant_closure)] _pyo3::impl_::extract_argument::extract_argument_with_default( #arg_value, &mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT }, #name_str, || #default )? } } else { quote_arg_span! { _pyo3::impl_::extract_argument::extract_argument( _pyo3::impl_::extract_argument::unwrap_required_argument(#arg_value), &mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT }, #name_str )? } }; Ok(tokens) } pyo3-macros-backend-0.20.2/src/pyclass.rs000064400000000000000000001200101046102023000162530ustar 00000000000000use std::borrow::Cow; use crate::attributes::kw::frozen; use crate::attributes::{ self, kw, take_pyo3_options, CrateAttribute, ExtendsAttribute, FreelistAttribute, ModuleAttribute, NameAttribute, NameLitStr, RenameAllAttribute, TextSignatureAttribute, TextSignatureAttributeValue, }; use crate::deprecations::{Deprecation, Deprecations}; use crate::konst::{ConstAttributes, ConstSpec}; use crate::method::FnSpec; use crate::pyimpl::{gen_py_const, PyClassMethodsType}; use crate::pymethod::{ impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType, SlotDef, __INT__, __REPR__, __RICHCMP__, }; use crate::utils::{self, apply_renaming_rule, get_pyo3_crate, PythonDoc}; use crate::PyFunctionOptions; use proc_macro2::{Ident, Span, TokenStream}; use quote::quote; use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::{parse_quote, spanned::Spanned, Result, Token}; /// If the class is derived from a Rust `struct` or `enum`. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum PyClassKind { Struct, Enum, } /// The parsed arguments of the pyclass macro pub struct PyClassArgs { pub class_kind: PyClassKind, pub options: PyClassPyO3Options, pub deprecations: Deprecations, } impl PyClassArgs { fn parse(input: ParseStream<'_>, kind: PyClassKind) -> Result { Ok(PyClassArgs { class_kind: kind, options: PyClassPyO3Options::parse(input)?, deprecations: Deprecations::new(), }) } pub fn parse_stuct_args(input: ParseStream<'_>) -> syn::Result { Self::parse(input, PyClassKind::Struct) } pub fn parse_enum_args(input: ParseStream<'_>) -> syn::Result { Self::parse(input, PyClassKind::Enum) } } #[derive(Default)] pub struct PyClassPyO3Options { pub krate: Option, pub dict: Option, pub extends: Option, pub get_all: Option, pub freelist: Option, pub frozen: Option, pub mapping: Option, pub module: Option, pub name: Option, pub rename_all: Option, pub sequence: Option, pub set_all: Option, pub subclass: Option, pub text_signature: Option, pub unsendable: Option, pub weakref: Option, pub deprecations: Deprecations, } enum PyClassPyO3Option { Crate(CrateAttribute), Dict(kw::dict), Extends(ExtendsAttribute), Freelist(FreelistAttribute), Frozen(kw::frozen), GetAll(kw::get_all), Mapping(kw::mapping), Module(ModuleAttribute), Name(NameAttribute), RenameAll(RenameAllAttribute), Sequence(kw::sequence), SetAll(kw::set_all), Subclass(kw::subclass), TextSignature(TextSignatureAttribute), Unsendable(kw::unsendable), Weakref(kw::weakref), } impl Parse for PyClassPyO3Option { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(Token![crate]) { input.parse().map(PyClassPyO3Option::Crate) } else if lookahead.peek(kw::dict) { input.parse().map(PyClassPyO3Option::Dict) } else if lookahead.peek(kw::extends) { input.parse().map(PyClassPyO3Option::Extends) } else if lookahead.peek(attributes::kw::freelist) { input.parse().map(PyClassPyO3Option::Freelist) } else if lookahead.peek(attributes::kw::frozen) { input.parse().map(PyClassPyO3Option::Frozen) } else if lookahead.peek(attributes::kw::get_all) { input.parse().map(PyClassPyO3Option::GetAll) } else if lookahead.peek(attributes::kw::mapping) { input.parse().map(PyClassPyO3Option::Mapping) } else if lookahead.peek(attributes::kw::module) { input.parse().map(PyClassPyO3Option::Module) } else if lookahead.peek(kw::name) { input.parse().map(PyClassPyO3Option::Name) } else if lookahead.peek(kw::rename_all) { input.parse().map(PyClassPyO3Option::RenameAll) } else if lookahead.peek(attributes::kw::sequence) { input.parse().map(PyClassPyO3Option::Sequence) } else if lookahead.peek(attributes::kw::set_all) { input.parse().map(PyClassPyO3Option::SetAll) } else if lookahead.peek(attributes::kw::subclass) { input.parse().map(PyClassPyO3Option::Subclass) } else if lookahead.peek(attributes::kw::text_signature) { input.parse().map(PyClassPyO3Option::TextSignature) } else if lookahead.peek(attributes::kw::unsendable) { input.parse().map(PyClassPyO3Option::Unsendable) } else if lookahead.peek(attributes::kw::weakref) { input.parse().map(PyClassPyO3Option::Weakref) } else { Err(lookahead.error()) } } } impl PyClassPyO3Options { fn parse(input: ParseStream<'_>) -> syn::Result { let mut options: PyClassPyO3Options = Default::default(); for option in Punctuated::::parse_terminated(input)? { options.set_option(option)?; } Ok(options) } pub fn take_pyo3_options(&mut self, attrs: &mut Vec) -> syn::Result<()> { take_pyo3_options(attrs)? .into_iter() .try_for_each(|option| self.set_option(option)) } fn set_option(&mut self, option: PyClassPyO3Option) -> syn::Result<()> { macro_rules! set_option { ($key:ident) => { { ensure_spanned!( self.$key.is_none(), $key.span() => concat!("`", stringify!($key), "` may only be specified once") ); self.$key = Some($key); } }; } match option { PyClassPyO3Option::Crate(krate) => set_option!(krate), PyClassPyO3Option::Dict(dict) => set_option!(dict), PyClassPyO3Option::Extends(extends) => set_option!(extends), PyClassPyO3Option::Freelist(freelist) => set_option!(freelist), PyClassPyO3Option::Frozen(frozen) => set_option!(frozen), PyClassPyO3Option::GetAll(get_all) => set_option!(get_all), PyClassPyO3Option::Mapping(mapping) => set_option!(mapping), PyClassPyO3Option::Module(module) => set_option!(module), PyClassPyO3Option::Name(name) => set_option!(name), PyClassPyO3Option::RenameAll(rename_all) => set_option!(rename_all), PyClassPyO3Option::Sequence(sequence) => set_option!(sequence), PyClassPyO3Option::SetAll(set_all) => set_option!(set_all), PyClassPyO3Option::Subclass(subclass) => set_option!(subclass), PyClassPyO3Option::TextSignature(text_signature) => { self.deprecations .push(Deprecation::PyClassTextSignature, text_signature.span()); set_option!(text_signature) } PyClassPyO3Option::Unsendable(unsendable) => set_option!(unsendable), PyClassPyO3Option::Weakref(weakref) => set_option!(weakref), } Ok(()) } } pub fn build_py_class( class: &mut syn::ItemStruct, mut args: PyClassArgs, methods_type: PyClassMethodsType, ) -> syn::Result { args.options.take_pyo3_options(&mut class.attrs)?; let doc = utils::get_doc(&class.attrs, None); let krate = get_pyo3_crate(&args.options.krate); if let Some(lt) = class.generics.lifetimes().next() { bail_spanned!( lt.span() => "#[pyclass] cannot have lifetime parameters. \ For an explanation, see https://pyo3.rs/latest/class.html#no-lifetime-parameters" ); } ensure_spanned!( class.generics.params.is_empty(), class.generics.span() => "#[pyclass] cannot have generic parameters. \ For an explanation, see https://pyo3.rs/latest/class.html#no-generic-parameters" ); let mut field_options: Vec<(&syn::Field, FieldPyO3Options)> = match &mut class.fields { syn::Fields::Named(fields) => fields .named .iter_mut() .map(|field| { FieldPyO3Options::take_pyo3_options(&mut field.attrs) .map(move |options| (&*field, options)) }) .collect::>()?, syn::Fields::Unnamed(fields) => fields .unnamed .iter_mut() .map(|field| { FieldPyO3Options::take_pyo3_options(&mut field.attrs) .map(move |options| (&*field, options)) }) .collect::>()?, syn::Fields::Unit => { if let Some(attr) = args.options.set_all { return Err(syn::Error::new_spanned(attr, UNIT_SET)); }; if let Some(attr) = args.options.get_all { return Err(syn::Error::new_spanned(attr, UNIT_GET)); }; // No fields for unit struct Vec::new() } }; if let Some(attr) = args.options.get_all { for (_, FieldPyO3Options { get, .. }) in &mut field_options { if let Some(old_get) = get.replace(Annotated::Struct(attr)) { return Err(syn::Error::new(old_get.span(), DUPE_GET)); } } } if let Some(attr) = args.options.set_all { for (_, FieldPyO3Options { set, .. }) in &mut field_options { if let Some(old_set) = set.replace(Annotated::Struct(attr)) { return Err(syn::Error::new(old_set.span(), DUPE_SET)); } } } impl_class(&class.ident, &args, doc, field_options, methods_type, krate) } enum Annotated { Field(X), Struct(Y), } impl Annotated { fn span(&self) -> Span { match self { Self::Field(x) => x.span(), Self::Struct(y) => y.span(), } } } /// `#[pyo3()]` options for pyclass fields struct FieldPyO3Options { get: Option>, set: Option>, name: Option, } enum FieldPyO3Option { Get(attributes::kw::get), Set(attributes::kw::set), Name(NameAttribute), } impl Parse for FieldPyO3Option { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::get) { input.parse().map(FieldPyO3Option::Get) } else if lookahead.peek(attributes::kw::set) { input.parse().map(FieldPyO3Option::Set) } else if lookahead.peek(attributes::kw::name) { input.parse().map(FieldPyO3Option::Name) } else { Err(lookahead.error()) } } } impl FieldPyO3Options { fn take_pyo3_options(attrs: &mut Vec) -> Result { let mut options = FieldPyO3Options { get: None, set: None, name: None, }; for option in take_pyo3_options(attrs)? { match option { FieldPyO3Option::Get(kw) => { if options.get.replace(Annotated::Field(kw)).is_some() { return Err(syn::Error::new(kw.span(), UNIQUE_GET)); } } FieldPyO3Option::Set(kw) => { if options.set.replace(Annotated::Field(kw)).is_some() { return Err(syn::Error::new(kw.span(), UNIQUE_SET)); } } FieldPyO3Option::Name(name) => { if options.name.replace(name).is_some() { return Err(syn::Error::new(options.name.span(), UNIQUE_NAME)); } } } } Ok(options) } } fn get_class_python_name<'a>(cls: &'a syn::Ident, args: &'a PyClassArgs) -> Cow<'a, syn::Ident> { args.options .name .as_ref() .map(|name_attr| Cow::Borrowed(&name_attr.value.0)) .unwrap_or_else(|| Cow::Owned(cls.unraw())) } fn impl_class( cls: &syn::Ident, args: &PyClassArgs, doc: PythonDoc, field_options: Vec<(&syn::Field, FieldPyO3Options)>, methods_type: PyClassMethodsType, krate: syn::Path, ) -> syn::Result { let pytypeinfo_impl = impl_pytypeinfo(cls, args, Some(&args.options.deprecations)); let py_class_impl = PyClassImplsBuilder::new( cls, args, methods_type, descriptors_to_items( cls, args.options.rename_all.as_ref(), args.options.frozen, field_options, )?, vec![], ) .doc(doc) .impl_all()?; Ok(quote! { const _: () = { use #krate as _pyo3; #pytypeinfo_impl #py_class_impl }; }) } struct PyClassEnumVariant<'a> { ident: &'a syn::Ident, options: EnumVariantPyO3Options, } impl<'a> PyClassEnumVariant<'a> { fn python_name(&self, args: &PyClassArgs) -> Cow<'_, syn::Ident> { self.options .name .as_ref() .map(|name_attr| Cow::Borrowed(&name_attr.value.0)) .unwrap_or_else(|| { let name = self.ident.unraw(); if let Some(attr) = &args.options.rename_all { let new_name = apply_renaming_rule(attr.value.rule, &name.to_string()); Cow::Owned(Ident::new(&new_name, Span::call_site())) } else { Cow::Owned(name) } }) } } struct PyClassEnum<'a> { ident: &'a syn::Ident, // The underlying #[repr] of the enum, used to implement __int__ and __richcmp__. // This matters when the underlying representation may not fit in `isize`. repr_type: syn::Ident, variants: Vec>, } impl<'a> PyClassEnum<'a> { fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result { fn is_numeric_type(t: &syn::Ident) -> bool { [ "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "u128", "i128", "usize", "isize", ] .iter() .any(|&s| t == s) } let ident = &enum_.ident; // According to the [reference](https://doc.rust-lang.org/reference/items/enumerations.html), // "Under the default representation, the specified discriminant is interpreted as an isize // value", so `isize` should be enough by default. let mut repr_type = syn::Ident::new("isize", proc_macro2::Span::call_site()); if let Some(attr) = enum_.attrs.iter().find(|attr| attr.path().is_ident("repr")) { let args = attr.parse_args_with(Punctuated::::parse_terminated)?; if let Some(ident) = args .into_iter() .filter_map(|ts| syn::parse2::(ts).ok()) .find(is_numeric_type) { repr_type = ident; } } let variants = enum_ .variants .iter_mut() .map(extract_variant_data) .collect::>()?; Ok(Self { ident, repr_type, variants, }) } } pub fn build_py_enum( enum_: &mut syn::ItemEnum, mut args: PyClassArgs, method_type: PyClassMethodsType, ) -> syn::Result { args.options.take_pyo3_options(&mut enum_.attrs)?; if let Some(extends) = &args.options.extends { bail_spanned!(extends.span() => "enums can't extend from other classes"); } else if let Some(subclass) = &args.options.subclass { bail_spanned!(subclass.span() => "enums can't be inherited by other classes"); } else if enum_.variants.is_empty() { bail_spanned!(enum_.brace_token.span.join() => "#[pyclass] can't be used on enums without any variants"); } let doc = utils::get_doc(&enum_.attrs, None); let enum_ = PyClassEnum::new(enum_)?; impl_enum(enum_, &args, doc, method_type) } /// `#[pyo3()]` options for pyclass enum variants struct EnumVariantPyO3Options { name: Option, } enum EnumVariantPyO3Option { Name(NameAttribute), } impl Parse for EnumVariantPyO3Option { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::name) { input.parse().map(EnumVariantPyO3Option::Name) } else { Err(lookahead.error()) } } } impl EnumVariantPyO3Options { fn take_pyo3_options(attrs: &mut Vec) -> Result { let mut options = EnumVariantPyO3Options { name: None }; for option in take_pyo3_options(attrs)? { match option { EnumVariantPyO3Option::Name(name) => { ensure_spanned!( options.name.is_none(), name.span() => "`name` may only be specified once" ); options.name = Some(name); } } } Ok(options) } } fn impl_enum( enum_: PyClassEnum<'_>, args: &PyClassArgs, doc: PythonDoc, methods_type: PyClassMethodsType, ) -> Result { let krate = get_pyo3_crate(&args.options.krate); let cls = enum_.ident; let ty: syn::Type = syn::parse_quote!(#cls); let variants = enum_.variants; let pytypeinfo = impl_pytypeinfo(cls, args, None); let (default_repr, default_repr_slot) = { let variants_repr = variants.iter().map(|variant| { let variant_name = variant.ident; // Assuming all variants are unit variants because they are the only type we support. let repr = format!( "{}.{}", get_class_python_name(cls, args), variant.python_name(args), ); quote! { #cls::#variant_name => #repr, } }); let mut repr_impl: syn::ImplItemFn = syn::parse_quote! { fn __pyo3__repr__(&self) -> &'static str { match self { #(#variants_repr)* } } }; let repr_slot = generate_default_protocol_slot(&ty, &mut repr_impl, &__REPR__).unwrap(); (repr_impl, repr_slot) }; let repr_type = &enum_.repr_type; let (default_int, default_int_slot) = { // This implementation allows us to convert &T to #repr_type without implementing `Copy` let variants_to_int = variants.iter().map(|variant| { let variant_name = variant.ident; quote! { #cls::#variant_name => #cls::#variant_name as #repr_type, } }); let mut int_impl: syn::ImplItemFn = syn::parse_quote! { fn __pyo3__int__(&self) -> #repr_type { match self { #(#variants_to_int)* } } }; let int_slot = generate_default_protocol_slot(&ty, &mut int_impl, &__INT__).unwrap(); (int_impl, int_slot) }; let (default_richcmp, default_richcmp_slot) = { let mut richcmp_impl: syn::ImplItemFn = syn::parse_quote! { fn __pyo3__richcmp__( &self, py: _pyo3::Python, other: &_pyo3::PyAny, op: _pyo3::basic::CompareOp ) -> _pyo3::PyResult<_pyo3::PyObject> { use _pyo3::conversion::ToPyObject; use ::core::result::Result::*; match op { _pyo3::basic::CompareOp::Eq => { let self_val = self.__pyo3__int__(); if let Ok(i) = other.extract::<#repr_type>() { return Ok((self_val == i).to_object(py)); } if let Ok(other) = other.extract::<_pyo3::PyRef>() { return Ok((self_val == other.__pyo3__int__()).to_object(py)); } return Ok(py.NotImplemented()); } _pyo3::basic::CompareOp::Ne => { let self_val = self.__pyo3__int__(); if let Ok(i) = other.extract::<#repr_type>() { return Ok((self_val != i).to_object(py)); } if let Ok(other) = other.extract::<_pyo3::PyRef>() { return Ok((self_val != other.__pyo3__int__()).to_object(py)); } return Ok(py.NotImplemented()); } _ => Ok(py.NotImplemented()), } } }; let richcmp_slot = generate_default_protocol_slot(&ty, &mut richcmp_impl, &__RICHCMP__).unwrap(); (richcmp_impl, richcmp_slot) }; let default_slots = vec![default_repr_slot, default_int_slot, default_richcmp_slot]; let pyclass_impls = PyClassImplsBuilder::new( cls, args, methods_type, enum_default_methods(cls, variants.iter().map(|v| (v.ident, v.python_name(args)))), default_slots, ) .doc(doc) .impl_all()?; Ok(quote! { const _: () = { use #krate as _pyo3; #pytypeinfo #pyclass_impls #[doc(hidden)] #[allow(non_snake_case)] impl #cls { #default_repr #default_int #default_richcmp } }; }) } fn generate_default_protocol_slot( cls: &syn::Type, method: &mut syn::ImplItemFn, slot: &SlotDef, ) -> syn::Result { let spec = FnSpec::parse( &mut method.sig, &mut Vec::new(), PyFunctionOptions::default(), ) .unwrap(); let name = spec.name.to_string(); slot.generate_type_slot( &syn::parse_quote!(#cls), &spec, &format!("__default_{}__", name), ) } fn enum_default_methods<'a>( cls: &'a syn::Ident, unit_variant_names: impl IntoIterator)>, ) -> Vec { let cls_type = syn::parse_quote!(#cls); let variant_to_attribute = |var_ident: &syn::Ident, py_ident: &syn::Ident| ConstSpec { rust_ident: var_ident.clone(), attributes: ConstAttributes { is_class_attr: true, name: Some(NameAttribute { kw: syn::parse_quote! { name }, value: NameLitStr(py_ident.clone()), }), deprecations: Default::default(), }, }; unit_variant_names .into_iter() .map(|(var, py_name)| gen_py_const(&cls_type, &variant_to_attribute(var, &py_name))) .collect() } fn extract_variant_data(variant: &mut syn::Variant) -> syn::Result> { use syn::Fields; let ident = match variant.fields { Fields::Unit => &variant.ident, _ => bail_spanned!(variant.span() => "Currently only support unit variants."), }; let options = EnumVariantPyO3Options::take_pyo3_options(&mut variant.attrs)?; Ok(PyClassEnumVariant { ident, options }) } fn descriptors_to_items( cls: &syn::Ident, rename_all: Option<&RenameAllAttribute>, frozen: Option, field_options: Vec<(&syn::Field, FieldPyO3Options)>, ) -> syn::Result> { let ty = syn::parse_quote!(#cls); let mut items = Vec::new(); for (field_index, (field, options)) in field_options.into_iter().enumerate() { if let FieldPyO3Options { name: Some(name), get: None, set: None, } = options { return Err(syn::Error::new_spanned(name, USELESS_NAME)); } if options.get.is_some() { let getter = impl_py_getter_def( &ty, PropertyType::Descriptor { field_index, field, python_name: options.name.as_ref(), renaming_rule: rename_all.map(|rename_all| rename_all.value.rule), }, )?; items.push(getter); } if let Some(set) = options.set { ensure_spanned!(frozen.is_none(), set.span() => "cannot use `#[pyo3(set)]` on a `frozen` class"); let setter = impl_py_setter_def( &ty, PropertyType::Descriptor { field_index, field, python_name: options.name.as_ref(), renaming_rule: rename_all.map(|rename_all| rename_all.value.rule), }, )?; items.push(setter); }; } Ok(items) } fn impl_pytypeinfo( cls: &syn::Ident, attr: &PyClassArgs, deprecations: Option<&Deprecations>, ) -> TokenStream { let cls_name = get_class_python_name(cls, attr).to_string(); let module = if let Some(ModuleAttribute { value, .. }) = &attr.options.module { quote! { ::core::option::Option::Some(#value) } } else { quote! { ::core::option::Option::None } }; quote! { unsafe impl _pyo3::type_object::PyTypeInfo for #cls { type AsRefTarget = _pyo3::PyCell; const NAME: &'static str = #cls_name; const MODULE: ::std::option::Option<&'static str> = #module; #[inline] fn type_object_raw(py: _pyo3::Python<'_>) -> *mut _pyo3::ffi::PyTypeObject { #deprecations <#cls as _pyo3::impl_::pyclass::PyClassImpl>::lazy_type_object() .get_or_init(py) .as_type_ptr() } } } } /// Implements most traits used by `#[pyclass]`. /// /// Specifically, it implements traits that only depend on class name, /// and attributes of `#[pyclass]`, and docstrings. /// Therefore it doesn't implement traits that depends on struct fields and enum variants. struct PyClassImplsBuilder<'a> { cls: &'a syn::Ident, attr: &'a PyClassArgs, methods_type: PyClassMethodsType, default_methods: Vec, default_slots: Vec, doc: Option, } impl<'a> PyClassImplsBuilder<'a> { fn new( cls: &'a syn::Ident, attr: &'a PyClassArgs, methods_type: PyClassMethodsType, default_methods: Vec, default_slots: Vec, ) -> Self { Self { cls, attr, methods_type, default_methods, default_slots, doc: None, } } fn doc(self, doc: PythonDoc) -> Self { Self { doc: Some(doc), ..self } } fn impl_all(&self) -> Result { let tokens = vec![ self.impl_pyclass(), self.impl_extractext(), self.impl_into_py(), self.impl_pyclassimpl()?, self.impl_freelist(), ] .into_iter() .collect(); Ok(tokens) } fn impl_pyclass(&self) -> TokenStream { let cls = self.cls; let frozen = if self.attr.options.frozen.is_some() { quote! { _pyo3::pyclass::boolean_struct::True } } else { quote! { _pyo3::pyclass::boolean_struct::False } }; quote! { impl _pyo3::PyClass for #cls { type Frozen = #frozen; } } } fn impl_extractext(&self) -> TokenStream { let cls = self.cls; if self.attr.options.frozen.is_some() { quote! { impl<'a, 'py> _pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a #cls { type Holder = ::std::option::Option<_pyo3::PyRef<'py, #cls>>; #[inline] fn extract(obj: &'py _pyo3::PyAny, holder: &'a mut Self::Holder) -> _pyo3::PyResult { _pyo3::impl_::extract_argument::extract_pyclass_ref(obj, holder) } } } } else { quote! { impl<'a, 'py> _pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a #cls { type Holder = ::std::option::Option<_pyo3::PyRef<'py, #cls>>; #[inline] fn extract(obj: &'py _pyo3::PyAny, holder: &'a mut Self::Holder) -> _pyo3::PyResult { _pyo3::impl_::extract_argument::extract_pyclass_ref(obj, holder) } } impl<'a, 'py> _pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a mut #cls { type Holder = ::std::option::Option<_pyo3::PyRefMut<'py, #cls>>; #[inline] fn extract(obj: &'py _pyo3::PyAny, holder: &'a mut Self::Holder) -> _pyo3::PyResult { _pyo3::impl_::extract_argument::extract_pyclass_ref_mut(obj, holder) } } } } } fn impl_into_py(&self) -> TokenStream { let cls = self.cls; let attr = self.attr; // If #cls is not extended type, we allow Self->PyObject conversion if attr.options.extends.is_none() { quote! { impl _pyo3::IntoPy<_pyo3::PyObject> for #cls { fn into_py(self, py: _pyo3::Python) -> _pyo3::PyObject { _pyo3::IntoPy::into_py(_pyo3::Py::new(py, self).unwrap(), py) } } } } else { quote! {} } } fn impl_pyclassimpl(&self) -> Result { let cls = self.cls; let doc = self.doc.as_ref().map_or(quote! {"\0"}, |doc| quote! {#doc}); let deprecated_text_signature = match self .attr .options .text_signature .as_ref() .map(|attr| &attr.value) { Some(TextSignatureAttributeValue::Str(s)) => quote!(::std::option::Option::Some(#s)), Some(TextSignatureAttributeValue::Disabled(_)) | None => { quote!(::std::option::Option::None) } }; let is_basetype = self.attr.options.subclass.is_some(); let base = self .attr .options .extends .as_ref() .map(|extends_attr| extends_attr.value.clone()) .unwrap_or_else(|| parse_quote! { _pyo3::PyAny }); let is_subclass = self.attr.options.extends.is_some(); let is_mapping: bool = self.attr.options.mapping.is_some(); let is_sequence: bool = self.attr.options.sequence.is_some(); ensure_spanned!( !(is_mapping && is_sequence), self.cls.span() => "a `#[pyclass]` cannot be both a `mapping` and a `sequence`" ); let dict_offset = if self.attr.options.dict.is_some() { quote! { fn dict_offset() -> ::std::option::Option<_pyo3::ffi::Py_ssize_t> { ::std::option::Option::Some(_pyo3::impl_::pyclass::dict_offset::()) } } } else { TokenStream::new() }; // insert space for weak ref let weaklist_offset = if self.attr.options.weakref.is_some() { quote! { fn weaklist_offset() -> ::std::option::Option<_pyo3::ffi::Py_ssize_t> { ::std::option::Option::Some(_pyo3::impl_::pyclass::weaklist_offset::()) } } } else { TokenStream::new() }; let thread_checker = if self.attr.options.unsendable.is_some() { quote! { _pyo3::impl_::pyclass::ThreadCheckerImpl } } else { quote! { _pyo3::impl_::pyclass::SendablePyClass<#cls> } }; let (pymethods_items, inventory, inventory_class) = match self.methods_type { PyClassMethodsType::Specialization => (quote! { collector.py_methods() }, None, None), PyClassMethodsType::Inventory => { // To allow multiple #[pymethods] block, we define inventory types. let inventory_class_name = syn::Ident::new( &format!("Pyo3MethodsInventoryFor{}", cls.unraw()), Span::call_site(), ); ( quote! { ::std::boxed::Box::new( ::std::iter::Iterator::map( _pyo3::inventory::iter::<::Inventory>(), _pyo3::impl_::pyclass::PyClassInventory::items ) ) }, Some(quote! { type Inventory = #inventory_class_name; }), Some(define_inventory_class(&inventory_class_name)), ) } }; let default_methods = self .default_methods .iter() .map(|meth| &meth.associated_method) .chain( self.default_slots .iter() .map(|meth| &meth.associated_method), ); let default_method_defs = self.default_methods.iter().map(|meth| &meth.method_def); let default_slot_defs = self.default_slots.iter().map(|slot| &slot.slot_def); let freelist_slots = self.freelist_slots(); let deprecations = &self.attr.deprecations; let class_mutability = if self.attr.options.frozen.is_some() { quote! { ImmutableChild } } else { quote! { MutableChild } }; let cls = self.cls; let attr = self.attr; let dict = if attr.options.dict.is_some() { quote! { _pyo3::impl_::pyclass::PyClassDictSlot } } else { quote! { _pyo3::impl_::pyclass::PyClassDummySlot } }; // insert space for weak ref let weakref = if attr.options.weakref.is_some() { quote! { _pyo3::impl_::pyclass::PyClassWeakRefSlot } } else { quote! { _pyo3::impl_::pyclass::PyClassDummySlot } }; let base_nativetype = if attr.options.extends.is_some() { quote! { ::BaseNativeType } } else { quote! { _pyo3::PyAny } }; Ok(quote! { impl _pyo3::impl_::pyclass::PyClassImpl for #cls { const IS_BASETYPE: bool = #is_basetype; const IS_SUBCLASS: bool = #is_subclass; const IS_MAPPING: bool = #is_mapping; const IS_SEQUENCE: bool = #is_sequence; type BaseType = #base; type ThreadChecker = #thread_checker; #inventory type PyClassMutability = <<#base as _pyo3::impl_::pyclass::PyClassBaseType>::PyClassMutability as _pyo3::impl_::pycell::PyClassMutability>::#class_mutability; type Dict = #dict; type WeakRef = #weakref; type BaseNativeType = #base_nativetype; fn items_iter() -> _pyo3::impl_::pyclass::PyClassItemsIter { use _pyo3::impl_::pyclass::*; let collector = PyClassImplCollector::::new(); #deprecations; static INTRINSIC_ITEMS: PyClassItems = PyClassItems { methods: &[#(#default_method_defs),*], slots: &[#(#default_slot_defs),* #(#freelist_slots),*], }; PyClassItemsIter::new(&INTRINSIC_ITEMS, #pymethods_items) } fn doc(py: _pyo3::Python<'_>) -> _pyo3::PyResult<&'static ::std::ffi::CStr> { use _pyo3::impl_::pyclass::*; static DOC: _pyo3::once_cell::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = _pyo3::once_cell::GILOnceCell::new(); DOC.get_or_try_init(py, || { let collector = PyClassImplCollector::::new(); build_pyclass_doc(<#cls as _pyo3::PyTypeInfo>::NAME, #doc, #deprecated_text_signature.or_else(|| collector.new_text_signature())) }).map(::std::ops::Deref::deref) } #dict_offset #weaklist_offset fn lazy_type_object() -> &'static _pyo3::impl_::pyclass::LazyTypeObject { use _pyo3::impl_::pyclass::LazyTypeObject; static TYPE_OBJECT: LazyTypeObject<#cls> = LazyTypeObject::new(); &TYPE_OBJECT } } #[doc(hidden)] #[allow(non_snake_case)] impl #cls { #(#default_methods)* } #inventory_class }) } fn impl_freelist(&self) -> TokenStream { let cls = self.cls; self.attr.options.freelist.as_ref().map_or(quote!{}, |freelist| { let freelist = &freelist.value; quote! { impl _pyo3::impl_::pyclass::PyClassWithFreeList for #cls { #[inline] fn get_free_list(py: _pyo3::Python<'_>) -> &mut _pyo3::impl_::freelist::FreeList<*mut _pyo3::ffi::PyObject> { static mut FREELIST: *mut _pyo3::impl_::freelist::FreeList<*mut _pyo3::ffi::PyObject> = 0 as *mut _; unsafe { if FREELIST.is_null() { FREELIST = ::std::boxed::Box::into_raw(::std::boxed::Box::new( _pyo3::impl_::freelist::FreeList::with_capacity(#freelist))); } &mut *FREELIST } } } } }) } fn freelist_slots(&self) -> Vec { let cls = self.cls; if self.attr.options.freelist.is_some() { vec![ quote! { _pyo3::ffi::PyType_Slot { slot: _pyo3::ffi::Py_tp_alloc, pfunc: _pyo3::impl_::pyclass::alloc_with_freelist::<#cls> as *mut _, } }, quote! { _pyo3::ffi::PyType_Slot { slot: _pyo3::ffi::Py_tp_free, pfunc: _pyo3::impl_::pyclass::free_with_freelist::<#cls> as *mut _, } }, ] } else { Vec::new() } } } fn define_inventory_class(inventory_class_name: &syn::Ident) -> TokenStream { quote! { #[doc(hidden)] pub struct #inventory_class_name { items: _pyo3::impl_::pyclass::PyClassItems, } impl #inventory_class_name { pub const fn new(items: _pyo3::impl_::pyclass::PyClassItems) -> Self { Self { items } } } impl _pyo3::impl_::pyclass::PyClassInventory for #inventory_class_name { fn items(&self) -> &_pyo3::impl_::pyclass::PyClassItems { &self.items } } _pyo3::inventory::collect!(#inventory_class_name); } } const UNIQUE_GET: &str = "`get` may only be specified once"; const UNIQUE_SET: &str = "`set` may only be specified once"; const UNIQUE_NAME: &str = "`name` may only be specified once"; const DUPE_SET: &str = "useless `set` - the struct is already annotated with `set_all`"; const DUPE_GET: &str = "useless `get` - the struct is already annotated with `get_all`"; const UNIT_GET: &str = "`get_all` on an unit struct does nothing, because unit structs have no fields"; const UNIT_SET: &str = "`set_all` on an unit struct does nothing, because unit structs have no fields"; const USELESS_NAME: &str = "`name` is useless without `get` or `set`"; pyo3-macros-backend-0.20.2/src/pyfunction/signature.rs000064400000000000000000000450361046102023000210120ustar 00000000000000use proc_macro2::{Span, TokenStream}; use quote::ToTokens; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, Token, }; use crate::{ attributes::{kw, KeywordAttribute}, method::FnArg, }; pub struct Signature { paren_token: syn::token::Paren, pub items: Punctuated, } impl Parse for Signature { fn parse(input: ParseStream<'_>) -> syn::Result { let content; let paren_token = syn::parenthesized!(content in input); let items = content.parse_terminated(SignatureItem::parse, Token![,])?; Ok(Signature { paren_token, items }) } } impl ToTokens for Signature { fn to_tokens(&self, tokens: &mut TokenStream) { self.paren_token .surround(tokens, |tokens| self.items.to_tokens(tokens)) } } #[derive(Debug, PartialEq, Eq)] pub struct SignatureItemArgument { pub ident: syn::Ident, pub eq_and_default: Option<(Token![=], syn::Expr)>, } #[derive(Debug, PartialEq, Eq)] pub struct SignatureItemPosargsSep { pub slash: Token![/], } #[derive(Debug, PartialEq, Eq)] pub struct SignatureItemVarargsSep { pub asterisk: Token![*], } #[derive(Debug, PartialEq, Eq)] pub struct SignatureItemVarargs { pub sep: SignatureItemVarargsSep, pub ident: syn::Ident, } #[derive(Debug, PartialEq, Eq)] pub struct SignatureItemKwargs { pub asterisks: (Token![*], Token![*]), pub ident: syn::Ident, } #[derive(Debug, PartialEq, Eq)] pub enum SignatureItem { Argument(Box), PosargsSep(SignatureItemPosargsSep), VarargsSep(SignatureItemVarargsSep), Varargs(SignatureItemVarargs), Kwargs(SignatureItemKwargs), } impl Parse for SignatureItem { fn parse(input: ParseStream<'_>) -> syn::Result { let lookahead = input.lookahead1(); if lookahead.peek(Token![*]) { if input.peek2(Token![*]) { input.parse().map(SignatureItem::Kwargs) } else { let sep = input.parse()?; if input.is_empty() || input.peek(Token![,]) { Ok(SignatureItem::VarargsSep(sep)) } else { Ok(SignatureItem::Varargs(SignatureItemVarargs { sep, ident: input.parse()?, })) } } } else if lookahead.peek(Token![/]) { input.parse().map(SignatureItem::PosargsSep) } else { input.parse().map(SignatureItem::Argument) } } } impl ToTokens for SignatureItem { fn to_tokens(&self, tokens: &mut TokenStream) { match self { SignatureItem::Argument(arg) => arg.to_tokens(tokens), SignatureItem::Varargs(varargs) => varargs.to_tokens(tokens), SignatureItem::VarargsSep(sep) => sep.to_tokens(tokens), SignatureItem::Kwargs(kwargs) => kwargs.to_tokens(tokens), SignatureItem::PosargsSep(sep) => sep.to_tokens(tokens), } } } impl Parse for SignatureItemArgument { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self { ident: input.parse()?, eq_and_default: if input.peek(Token![=]) { Some((input.parse()?, input.parse()?)) } else { None }, }) } } impl ToTokens for SignatureItemArgument { fn to_tokens(&self, tokens: &mut TokenStream) { self.ident.to_tokens(tokens); if let Some((eq, default)) = &self.eq_and_default { eq.to_tokens(tokens); default.to_tokens(tokens); } } } impl Parse for SignatureItemVarargsSep { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self { asterisk: input.parse()?, }) } } impl ToTokens for SignatureItemVarargsSep { fn to_tokens(&self, tokens: &mut TokenStream) { self.asterisk.to_tokens(tokens); } } impl Parse for SignatureItemVarargs { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self { sep: input.parse()?, ident: input.parse()?, }) } } impl ToTokens for SignatureItemVarargs { fn to_tokens(&self, tokens: &mut TokenStream) { self.sep.to_tokens(tokens); self.ident.to_tokens(tokens); } } impl Parse for SignatureItemKwargs { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self { asterisks: (input.parse()?, input.parse()?), ident: input.parse()?, }) } } impl ToTokens for SignatureItemKwargs { fn to_tokens(&self, tokens: &mut TokenStream) { self.asterisks.0.to_tokens(tokens); self.asterisks.1.to_tokens(tokens); self.ident.to_tokens(tokens); } } impl Parse for SignatureItemPosargsSep { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self { slash: input.parse()?, }) } } impl ToTokens for SignatureItemPosargsSep { fn to_tokens(&self, tokens: &mut TokenStream) { self.slash.to_tokens(tokens); } } pub type SignatureAttribute = KeywordAttribute; #[derive(Default)] pub struct PythonSignature { pub positional_parameters: Vec, pub positional_only_parameters: usize, pub required_positional_parameters: usize, pub varargs: Option, // Tuples of keyword name and whether it is required pub keyword_only_parameters: Vec<(String, bool)>, pub kwargs: Option, } impl PythonSignature { pub fn has_no_args(&self) -> bool { self.positional_parameters.is_empty() && self.keyword_only_parameters.is_empty() && self.varargs.is_none() && self.kwargs.is_none() } } pub struct FunctionSignature<'a> { pub arguments: Vec>, pub python_signature: PythonSignature, pub attribute: Option, } pub enum ParseState { /// Accepting positional parameters, which might be positional only Positional, /// Accepting positional parameters after '/' PositionalAfterPosargs, /// Accepting keyword-only parameters after '*' or '*args' Keywords, /// After `**kwargs` nothing is allowed Done, } impl ParseState { fn add_argument( &mut self, signature: &mut PythonSignature, name: String, required: bool, span: Span, ) -> syn::Result<()> { match self { ParseState::Positional | ParseState::PositionalAfterPosargs => { signature.positional_parameters.push(name); if required { signature.required_positional_parameters += 1; ensure_spanned!( signature.required_positional_parameters == signature.positional_parameters.len(), span => "cannot have required positional parameter after an optional parameter" ); } Ok(()) } ParseState::Keywords => { signature.keyword_only_parameters.push((name, required)); Ok(()) } ParseState::Done => { bail_spanned!(span => format!("no more arguments are allowed after `**{}`", signature.kwargs.as_deref().unwrap_or(""))) } } } fn add_varargs( &mut self, signature: &mut PythonSignature, varargs: &SignatureItemVarargs, ) -> syn::Result<()> { match self { ParseState::Positional | ParseState::PositionalAfterPosargs => { signature.varargs = Some(varargs.ident.to_string()); *self = ParseState::Keywords; Ok(()) } ParseState::Keywords => { bail_spanned!(varargs.span() => format!("`*{}` not allowed after `*{}`", varargs.ident, signature.varargs.as_deref().unwrap_or(""))) } ParseState::Done => { bail_spanned!(varargs.span() => format!("`*{}` not allowed after `**{}`", varargs.ident, signature.kwargs.as_deref().unwrap_or(""))) } } } fn add_kwargs( &mut self, signature: &mut PythonSignature, kwargs: &SignatureItemKwargs, ) -> syn::Result<()> { match self { ParseState::Positional | ParseState::PositionalAfterPosargs | ParseState::Keywords => { signature.kwargs = Some(kwargs.ident.to_string()); *self = ParseState::Done; Ok(()) } ParseState::Done => { bail_spanned!(kwargs.span() => format!("`**{}` not allowed after `**{}`", kwargs.ident, signature.kwargs.as_deref().unwrap_or(""))) } } } fn finish_pos_only_args( &mut self, signature: &mut PythonSignature, span: Span, ) -> syn::Result<()> { match self { ParseState::Positional => { signature.positional_only_parameters = signature.positional_parameters.len(); *self = ParseState::PositionalAfterPosargs; Ok(()) } ParseState::PositionalAfterPosargs => { bail_spanned!(span => "`/` not allowed after `/`") } ParseState::Keywords => { bail_spanned!(span => format!("`/` not allowed after `*{}`", signature.varargs.as_deref().unwrap_or(""))) } ParseState::Done => { bail_spanned!(span => format!("`/` not allowed after `**{}`", signature.kwargs.as_deref().unwrap_or(""))) } } } fn finish_pos_args(&mut self, signature: &PythonSignature, span: Span) -> syn::Result<()> { match self { ParseState::Positional | ParseState::PositionalAfterPosargs => { *self = ParseState::Keywords; Ok(()) } ParseState::Keywords => { bail_spanned!(span => format!("`*` not allowed after `*{}`", signature.varargs.as_deref().unwrap_or(""))) } ParseState::Done => { bail_spanned!(span => format!("`*` not allowed after `**{}`", signature.kwargs.as_deref().unwrap_or(""))) } } } } impl<'a> FunctionSignature<'a> { pub fn from_arguments_and_attribute( mut arguments: Vec>, attribute: SignatureAttribute, ) -> syn::Result { let mut parse_state = ParseState::Positional; let mut python_signature = PythonSignature::default(); let mut args_iter = arguments.iter_mut(); let mut next_non_py_argument_checked = |name: &syn::Ident| { for fn_arg in args_iter.by_ref() { if fn_arg.py { // If the user incorrectly tried to include py: Python in the // signature, give a useful error as a hint. ensure_spanned!( name != fn_arg.name, name.span() => "arguments of type `Python` must not be part of the signature" ); // Otherwise try next argument. continue; } ensure_spanned!( name == fn_arg.name, name.span() => format!( "expected argument from function definition `{}` but got argument `{}`", fn_arg.name.unraw(), name.unraw(), ) ); return Ok(fn_arg); } bail_spanned!( name.span() => "signature entry does not have a corresponding function argument" ) }; for item in &attribute.value.items { match item { SignatureItem::Argument(arg) => { let fn_arg = next_non_py_argument_checked(&arg.ident)?; parse_state.add_argument( &mut python_signature, arg.ident.unraw().to_string(), arg.eq_and_default.is_none(), arg.span(), )?; if let Some((_, default)) = &arg.eq_and_default { fn_arg.default = Some(default.clone()); } } SignatureItem::VarargsSep(sep) => { parse_state.finish_pos_args(&python_signature, sep.span())? } SignatureItem::Varargs(varargs) => { let fn_arg = next_non_py_argument_checked(&varargs.ident)?; fn_arg.is_varargs = true; parse_state.add_varargs(&mut python_signature, varargs)?; } SignatureItem::Kwargs(kwargs) => { let fn_arg = next_non_py_argument_checked(&kwargs.ident)?; fn_arg.is_kwargs = true; parse_state.add_kwargs(&mut python_signature, kwargs)?; } SignatureItem::PosargsSep(sep) => { parse_state.finish_pos_only_args(&mut python_signature, sep.span())? } }; } // Ensure no non-py arguments remain if let Some(arg) = args_iter.find(|arg| !arg.py) { bail_spanned!( attribute.kw.span() => format!("missing signature entry for argument `{}`", arg.name) ); } Ok(FunctionSignature { arguments, python_signature, attribute: Some(attribute), }) } /// Without `#[pyo3(signature)]` or `#[args]` - just take the Rust function arguments as positional. pub fn from_arguments(arguments: Vec>) -> syn::Result { let mut python_signature = PythonSignature::default(); for arg in &arguments { // Python<'_> arguments don't show in Python signature if arg.py { continue; } if arg.optional.is_none() { // This argument is required, all previous arguments must also have been required ensure_spanned!( python_signature.required_positional_parameters == python_signature.positional_parameters.len(), arg.ty.span() => "required arguments after an `Option<_>` argument are ambiguous\n\ = help: add a `#[pyo3(signature)]` annotation on this function to unambiguously specify the default values for all optional parameters" ); python_signature.required_positional_parameters = python_signature.positional_parameters.len() + 1; } python_signature .positional_parameters .push(arg.name.unraw().to_string()); } Ok(Self { arguments, python_signature, attribute: None, }) } fn default_value_for_parameter(&self, parameter: &str) -> String { let mut default = "...".to_string(); if let Some(fn_arg) = self.arguments.iter().find(|arg| arg.name == parameter) { if let Some(arg_default) = fn_arg.default.as_ref() { match arg_default { // literal values syn::Expr::Lit(syn::ExprLit { lit, .. }) => match lit { syn::Lit::Str(s) => default = s.token().to_string(), syn::Lit::Char(c) => default = c.token().to_string(), syn::Lit::Int(i) => default = i.base10_digits().to_string(), syn::Lit::Float(f) => default = f.base10_digits().to_string(), syn::Lit::Bool(b) => { default = if b.value() { "True".to_string() } else { "False".to_string() } } _ => {} }, // None syn::Expr::Path(syn::ExprPath { qself: None, path, .. }) if path.is_ident("None") => { default = "None".to_string(); } // others, unsupported yet so defaults to `...` _ => {} } } else if fn_arg.optional.is_some() { // functions without a `#[pyo3(signature = (...))]` option // will treat trailing `Option` arguments as having a default of `None` default = "None".to_string(); } } default } pub fn text_signature(&self, self_argument: Option<&str>) -> String { let mut output = String::new(); output.push('('); if let Some(arg) = self_argument { output.push('$'); output.push_str(arg); } let mut maybe_push_comma = { let mut first = self_argument.is_none(); move |output: &mut String| { if !first { output.push_str(", "); } else { first = false; } } }; let py_sig = &self.python_signature; for (i, parameter) in py_sig.positional_parameters.iter().enumerate() { maybe_push_comma(&mut output); output.push_str(parameter); if i >= py_sig.required_positional_parameters { output.push('='); output.push_str(&self.default_value_for_parameter(parameter)); } if py_sig.positional_only_parameters > 0 && i + 1 == py_sig.positional_only_parameters { output.push_str(", /") } } if let Some(varargs) = &py_sig.varargs { maybe_push_comma(&mut output); output.push('*'); output.push_str(varargs); } else if !py_sig.keyword_only_parameters.is_empty() { maybe_push_comma(&mut output); output.push('*'); } for (parameter, required) in &py_sig.keyword_only_parameters { maybe_push_comma(&mut output); output.push_str(parameter); if !required { output.push('='); output.push_str(&self.default_value_for_parameter(parameter)); } } if let Some(kwargs) = &py_sig.kwargs { maybe_push_comma(&mut output); output.push_str("**"); output.push_str(kwargs); } output.push(')'); output } } pyo3-macros-backend-0.20.2/src/pyfunction.rs000064400000000000000000000216211046102023000170030ustar 00000000000000use crate::{ attributes::{ self, get_pyo3_options, take_attributes, take_pyo3_options, CrateAttribute, FromPyWithAttribute, NameAttribute, TextSignatureAttribute, }, deprecations::Deprecations, method::{self, CallingConvention, FnArg}, pymethod::check_generic, utils::{ensure_not_async_fn, get_pyo3_crate}, }; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{ext::IdentExt, spanned::Spanned, Result}; use syn::{ parse::{Parse, ParseStream}, token::Comma, }; mod signature; pub use self::signature::{FunctionSignature, SignatureAttribute}; #[derive(Clone, Debug)] pub struct PyFunctionArgPyO3Attributes { pub from_py_with: Option, } enum PyFunctionArgPyO3Attribute { FromPyWith(FromPyWithAttribute), } impl Parse for PyFunctionArgPyO3Attribute { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::from_py_with) { input.parse().map(PyFunctionArgPyO3Attribute::FromPyWith) } else { Err(lookahead.error()) } } } impl PyFunctionArgPyO3Attributes { /// Parses #[pyo3(from_python_with = "func")] pub fn from_attrs(attrs: &mut Vec) -> syn::Result { let mut attributes = PyFunctionArgPyO3Attributes { from_py_with: None }; take_attributes(attrs, |attr| { if let Some(pyo3_attrs) = get_pyo3_options(attr)? { for attr in pyo3_attrs { match attr { PyFunctionArgPyO3Attribute::FromPyWith(from_py_with) => { ensure_spanned!( attributes.from_py_with.is_none(), from_py_with.span() => "`from_py_with` may only be specified once per argument" ); attributes.from_py_with = Some(from_py_with); } } } Ok(true) } else { Ok(false) } })?; Ok(attributes) } } #[derive(Default)] pub struct PyFunctionOptions { pub pass_module: Option, pub name: Option, pub signature: Option, pub text_signature: Option, pub krate: Option, } impl Parse for PyFunctionOptions { fn parse(input: ParseStream<'_>) -> Result { let mut options = PyFunctionOptions::default(); while !input.is_empty() { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::name) || lookahead.peek(attributes::kw::pass_module) || lookahead.peek(attributes::kw::signature) || lookahead.peek(attributes::kw::text_signature) { options.add_attributes(std::iter::once(input.parse()?))?; if !input.is_empty() { let _: Comma = input.parse()?; } } else if lookahead.peek(syn::Token![crate]) { // TODO needs duplicate check? options.krate = Some(input.parse()?); } else { return Err(lookahead.error()); } } Ok(options) } } pub enum PyFunctionOption { Name(NameAttribute), PassModule(attributes::kw::pass_module), Signature(SignatureAttribute), TextSignature(TextSignatureAttribute), Crate(CrateAttribute), } impl Parse for PyFunctionOption { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::name) { input.parse().map(PyFunctionOption::Name) } else if lookahead.peek(attributes::kw::pass_module) { input.parse().map(PyFunctionOption::PassModule) } else if lookahead.peek(attributes::kw::signature) { input.parse().map(PyFunctionOption::Signature) } else if lookahead.peek(attributes::kw::text_signature) { input.parse().map(PyFunctionOption::TextSignature) } else if lookahead.peek(syn::Token![crate]) { input.parse().map(PyFunctionOption::Crate) } else { Err(lookahead.error()) } } } impl PyFunctionOptions { pub fn from_attrs(attrs: &mut Vec) -> syn::Result { let mut options = PyFunctionOptions::default(); options.add_attributes(take_pyo3_options(attrs)?)?; Ok(options) } pub fn add_attributes( &mut self, attrs: impl IntoIterator, ) -> Result<()> { macro_rules! set_option { ($key:ident) => { { ensure_spanned!( self.$key.is_none(), $key.span() => concat!("`", stringify!($key), "` may only be specified once") ); self.$key = Some($key); } }; } for attr in attrs { match attr { PyFunctionOption::Name(name) => set_option!(name), PyFunctionOption::PassModule(pass_module) => set_option!(pass_module), PyFunctionOption::Signature(signature) => set_option!(signature), PyFunctionOption::TextSignature(text_signature) => set_option!(text_signature), PyFunctionOption::Crate(krate) => set_option!(krate), } } Ok(()) } } pub fn build_py_function( ast: &mut syn::ItemFn, mut options: PyFunctionOptions, ) -> syn::Result { options.add_attributes(take_pyo3_options(&mut ast.attrs)?)?; impl_wrap_pyfunction(ast, options) } /// Generates python wrapper over a function that allows adding it to a python module as a python /// function pub fn impl_wrap_pyfunction( func: &mut syn::ItemFn, options: PyFunctionOptions, ) -> syn::Result { check_generic(&func.sig)?; ensure_not_async_fn(&func.sig)?; let PyFunctionOptions { pass_module, name, signature, text_signature, krate, } = options; let python_name = name.map_or_else(|| func.sig.ident.unraw(), |name| name.value.0); let tp = if pass_module.is_some() { let span = match func.sig.inputs.first() { Some(syn::FnArg::Typed(first_arg)) => first_arg.ty.span(), Some(syn::FnArg::Receiver(_)) | None => bail_spanned!( func.sig.paren_token.span.join() => "expected `&PyModule` or `Py` as first argument with `pass_module`" ), }; method::FnType::FnModule(span) } else { method::FnType::FnStatic }; let arguments = func .sig .inputs .iter_mut() .skip(if tp.skip_first_rust_argument_in_python_signature() { 1 } else { 0 }) .map(FnArg::parse) .collect::>>()?; let signature = if let Some(signature) = signature { FunctionSignature::from_arguments_and_attribute(arguments, signature)? } else { FunctionSignature::from_arguments(arguments)? }; let ty = method::get_return_info(&func.sig.output); let spec = method::FnSpec { tp, name: &func.sig.ident, convention: CallingConvention::from_signature(&signature), python_name, signature, output: ty, text_signature, unsafety: func.sig.unsafety, deprecations: Deprecations::new(), }; let krate = get_pyo3_crate(&krate); let vis = &func.vis; let name = &func.sig.ident; let wrapper_ident = format_ident!("__pyfunction_{}", spec.name); let wrapper = spec.get_wrapper_function(&wrapper_ident, None)?; let methoddef = spec.get_methoddef(wrapper_ident, &spec.get_doc(&func.attrs)); let wrapped_pyfunction = quote! { // Create a module with the same name as the `#[pyfunction]` - this way `use ` // will actually bring both the module and the function into scope. #[doc(hidden)] #vis mod #name { pub(crate) struct MakeDef; pub const DEF: #krate::impl_::pyfunction::PyMethodDef = MakeDef::DEF; } // Generate the definition inside an anonymous function in the same scope as the original function - // this avoids complications around the fact that the generated module has a different scope // (and `super` doesn't always refer to the outer scope, e.g. if the `#[pyfunction] is // inside a function body) const _: () = { use #krate as _pyo3; impl #name::MakeDef { const DEF: #krate::impl_::pyfunction::PyMethodDef = #methoddef; } #[allow(non_snake_case)] #wrapper }; }; Ok(wrapped_pyfunction) } pyo3-macros-backend-0.20.2/src/pyimpl.rs000064400000000000000000000251601046102023000161210ustar 00000000000000use std::collections::HashSet; use crate::{ attributes::{take_pyo3_options, CrateAttribute}, konst::{ConstAttributes, ConstSpec}, pyfunction::PyFunctionOptions, pymethod::{self, is_proto_method, MethodAndMethodDef, MethodAndSlotDef}, utils::get_pyo3_crate, }; use proc_macro2::TokenStream; use pymethod::GeneratedPyMethod; use quote::{format_ident, quote}; use syn::{ parse::{Parse, ParseStream}, spanned::Spanned, Result, }; /// The mechanism used to collect `#[pymethods]` into the type object #[derive(Copy, Clone)] pub enum PyClassMethodsType { Specialization, Inventory, } enum PyImplPyO3Option { Crate(CrateAttribute), } impl Parse for PyImplPyO3Option { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(syn::Token![crate]) { input.parse().map(PyImplPyO3Option::Crate) } else { Err(lookahead.error()) } } } #[derive(Default)] pub struct PyImplOptions { krate: Option, } impl PyImplOptions { pub fn from_attrs(attrs: &mut Vec) -> Result { let mut options: PyImplOptions = Default::default(); for option in take_pyo3_options(attrs)? { match option { PyImplPyO3Option::Crate(path) => options.set_crate(path)?, } } Ok(options) } fn set_crate(&mut self, path: CrateAttribute) -> Result<()> { ensure_spanned!( self.krate.is_none(), path.span() => "`crate` may only be specified once" ); self.krate = Some(path); Ok(()) } } pub fn build_py_methods( ast: &mut syn::ItemImpl, methods_type: PyClassMethodsType, ) -> syn::Result { if let Some((_, path, _)) = &ast.trait_ { bail_spanned!(path.span() => "#[pymethods] cannot be used on trait impl blocks"); } else if ast.generics != Default::default() { bail_spanned!( ast.generics.span() => "#[pymethods] cannot be used with lifetime parameters or generics" ); } else { let options = PyImplOptions::from_attrs(&mut ast.attrs)?; impl_methods(&ast.self_ty, &mut ast.items, methods_type, options) } } pub fn impl_methods( ty: &syn::Type, impls: &mut [syn::ImplItem], methods_type: PyClassMethodsType, options: PyImplOptions, ) -> syn::Result { let mut trait_impls = Vec::new(); let mut proto_impls = Vec::new(); let mut methods = Vec::new(); let mut associated_methods = Vec::new(); let mut implemented_proto_fragments = HashSet::new(); for iimpl in impls { match iimpl { syn::ImplItem::Fn(meth) => { let mut fun_options = PyFunctionOptions::from_attrs(&mut meth.attrs)?; fun_options.krate = fun_options.krate.or_else(|| options.krate.clone()); match pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs, fun_options)? { GeneratedPyMethod::Method(MethodAndMethodDef { associated_method, method_def, }) => { let attrs = get_cfg_attributes(&meth.attrs); associated_methods.push(quote!(#(#attrs)* #associated_method)); methods.push(quote!(#(#attrs)* #method_def)); } GeneratedPyMethod::SlotTraitImpl(method_name, token_stream) => { implemented_proto_fragments.insert(method_name); let attrs = get_cfg_attributes(&meth.attrs); trait_impls.push(quote!(#(#attrs)* #token_stream)); } GeneratedPyMethod::Proto(MethodAndSlotDef { associated_method, slot_def, }) => { let attrs = get_cfg_attributes(&meth.attrs); proto_impls.push(quote!(#(#attrs)* #slot_def)); associated_methods.push(quote!(#(#attrs)* #associated_method)); } } } syn::ImplItem::Const(konst) => { let attributes = ConstAttributes::from_attrs(&mut konst.attrs)?; if attributes.is_class_attr { let spec = ConstSpec { rust_ident: konst.ident.clone(), attributes, }; let attrs = get_cfg_attributes(&konst.attrs); let MethodAndMethodDef { associated_method, method_def, } = gen_py_const(ty, &spec); methods.push(quote!(#(#attrs)* #method_def)); associated_methods.push(quote!(#(#attrs)* #associated_method)); if is_proto_method(&spec.python_name().to_string()) { // If this is a known protocol method e.g. __contains__, then allow this // symbol even though it's not an uppercase constant. konst .attrs .push(syn::parse_quote!(#[allow(non_upper_case_globals)])); } } } syn::ImplItem::Macro(m) => bail_spanned!( m.span() => "macros cannot be used as items in `#[pymethods]` impl blocks\n\ = note: this was previously accepted and ignored" ), _ => {} } } add_shared_proto_slots(ty, &mut proto_impls, implemented_proto_fragments); let krate = get_pyo3_crate(&options.krate); let items = match methods_type { PyClassMethodsType::Specialization => impl_py_methods(ty, methods, proto_impls), PyClassMethodsType::Inventory => submit_methods_inventory(ty, methods, proto_impls), }; Ok(quote! { const _: () = { use #krate as _pyo3; #(#trait_impls)* #items #[doc(hidden)] #[allow(non_snake_case)] impl #ty { #(#associated_methods)* } }; }) } pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec) -> MethodAndMethodDef { let member = &spec.rust_ident; let wrapper_ident = format_ident!("__pymethod_{}__", member); let deprecations = &spec.attributes.deprecations; let python_name = &spec.null_terminated_python_name(); let associated_method = quote! { fn #wrapper_ident(py: _pyo3::Python<'_>) -> _pyo3::PyResult<_pyo3::PyObject> { #deprecations ::std::result::Result::Ok(_pyo3::IntoPy::into_py(#cls::#member, py)) } }; let method_def = quote! { _pyo3::class::PyMethodDefType::ClassAttribute({ _pyo3::class::PyClassAttributeDef::new( #python_name, _pyo3::impl_::pymethods::PyClassAttributeFactory(#cls::#wrapper_ident) ) }) }; MethodAndMethodDef { associated_method, method_def, } } fn impl_py_methods( ty: &syn::Type, methods: Vec, proto_impls: Vec, ) -> TokenStream { quote! { impl _pyo3::impl_::pyclass::PyMethods<#ty> for _pyo3::impl_::pyclass::PyClassImplCollector<#ty> { fn py_methods(self) -> &'static _pyo3::impl_::pyclass::PyClassItems { static ITEMS: _pyo3::impl_::pyclass::PyClassItems = _pyo3::impl_::pyclass::PyClassItems { methods: &[#(#methods),*], slots: &[#(#proto_impls),*] }; &ITEMS } } } } fn add_shared_proto_slots( ty: &syn::Type, proto_impls: &mut Vec, mut implemented_proto_fragments: HashSet, ) { macro_rules! try_add_shared_slot { ($slot:ident, $($fragments:literal),*) => {{ let mut implemented = false; $(implemented |= implemented_proto_fragments.remove($fragments));*; if implemented { proto_impls.push(quote! { _pyo3::impl_::pyclass::$slot!(#ty) }) } }}; } try_add_shared_slot!( generate_pyclass_getattro_slot, "__getattribute__", "__getattr__" ); try_add_shared_slot!(generate_pyclass_setattr_slot, "__setattr__", "__delattr__"); try_add_shared_slot!(generate_pyclass_setdescr_slot, "__set__", "__delete__"); try_add_shared_slot!(generate_pyclass_setitem_slot, "__setitem__", "__delitem__"); try_add_shared_slot!(generate_pyclass_add_slot, "__add__", "__radd__"); try_add_shared_slot!(generate_pyclass_sub_slot, "__sub__", "__rsub__"); try_add_shared_slot!(generate_pyclass_mul_slot, "__mul__", "__rmul__"); try_add_shared_slot!(generate_pyclass_mod_slot, "__mod__", "__rmod__"); try_add_shared_slot!(generate_pyclass_divmod_slot, "__divmod__", "__rdivmod__"); try_add_shared_slot!(generate_pyclass_lshift_slot, "__lshift__", "__rlshift__"); try_add_shared_slot!(generate_pyclass_rshift_slot, "__rshift__", "__rrshift__"); try_add_shared_slot!(generate_pyclass_and_slot, "__and__", "__rand__"); try_add_shared_slot!(generate_pyclass_or_slot, "__or__", "__ror__"); try_add_shared_slot!(generate_pyclass_xor_slot, "__xor__", "__rxor__"); try_add_shared_slot!(generate_pyclass_matmul_slot, "__matmul__", "__rmatmul__"); try_add_shared_slot!(generate_pyclass_truediv_slot, "__truediv__", "__rtruediv__"); try_add_shared_slot!( generate_pyclass_floordiv_slot, "__floordiv__", "__rfloordiv__" ); try_add_shared_slot!(generate_pyclass_pow_slot, "__pow__", "__rpow__"); try_add_shared_slot!( generate_pyclass_richcompare_slot, "__lt__", "__le__", "__eq__", "__ne__", "__gt__", "__ge__" ); // if this assertion trips, a slot fragment has been implemented which has not been added in the // list above assert!(implemented_proto_fragments.is_empty()); } fn submit_methods_inventory( ty: &syn::Type, methods: Vec, proto_impls: Vec, ) -> TokenStream { quote! { _pyo3::inventory::submit! { type Inventory = <#ty as _pyo3::impl_::pyclass::PyClassImpl>::Inventory; Inventory::new(_pyo3::impl_::pyclass::PyClassItems { methods: &[#(#methods),*], slots: &[#(#proto_impls),*] }) } } } fn get_cfg_attributes(attrs: &[syn::Attribute]) -> Vec<&syn::Attribute> { attrs .iter() .filter(|attr| attr.path().is_ident("cfg")) .collect() } pyo3-macros-backend-0.20.2/src/pymethod.rs000064400000000000000000001450031046102023000164370ustar 00000000000000use std::borrow::Cow; use crate::attributes::{NameAttribute, RenamingRule}; use crate::method::{CallingConvention, ExtractErrorMode}; use crate::utils::{ensure_not_async_fn, PythonDoc}; use crate::{ method::{FnArg, FnSpec, FnType, SelfType}, pyfunction::PyFunctionOptions, }; use crate::{quotes, utils}; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; use syn::{ext::IdentExt, spanned::Spanned, Result}; /// Generated code for a single pymethod item. pub struct MethodAndMethodDef { /// The implementation of the Python wrapper for the pymethod pub associated_method: TokenStream, /// The method def which will be used to register this pymethod pub method_def: TokenStream, } /// Generated code for a single pymethod item which is registered by a slot. pub struct MethodAndSlotDef { /// The implementation of the Python wrapper for the pymethod pub associated_method: TokenStream, /// The slot def which will be used to register this pymethod pub slot_def: TokenStream, } pub enum GeneratedPyMethod { Method(MethodAndMethodDef), Proto(MethodAndSlotDef), SlotTraitImpl(String, TokenStream), } pub struct PyMethod<'a> { kind: PyMethodKind, method_name: String, spec: FnSpec<'a>, } enum PyMethodKind { Fn, Proto(PyMethodProtoKind), } impl PyMethodKind { fn from_name(name: &str) -> Self { match name { // Protocol implemented through slots "__str__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__STR__)), "__repr__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__REPR__)), "__hash__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__HASH__)), "__richcmp__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__RICHCMP__)), "__get__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GET__)), "__iter__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ITER__)), "__next__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__NEXT__)), "__await__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__AWAIT__)), "__aiter__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__AITER__)), "__anext__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ANEXT__)), "__len__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__LEN__)), "__contains__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__CONTAINS__)), "__concat__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__CONCAT__)), "__repeat__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__REPEAT__)), "__inplace_concat__" => { PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INPLACE_CONCAT__)) } "__inplace_repeat__" => { PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INPLACE_REPEAT__)) } "__getitem__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GETITEM__)), "__pos__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__POS__)), "__neg__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__NEG__)), "__abs__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ABS__)), "__invert__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INVERT__)), "__index__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INDEX__)), "__int__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__INT__)), "__float__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__FLOAT__)), "__bool__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__BOOL__)), "__iadd__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IADD__)), "__isub__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ISUB__)), "__imul__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMUL__)), "__imatmul__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMATMUL__)), "__itruediv__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ITRUEDIV__)), "__ifloordiv__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IFLOORDIV__)), "__imod__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IMOD__)), "__ipow__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IPOW__)), "__ilshift__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__ILSHIFT__)), "__irshift__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IRSHIFT__)), "__iand__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IAND__)), "__ixor__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IXOR__)), "__ior__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__IOR__)), "__getbuffer__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__GETBUFFER__)), "__releasebuffer__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__RELEASEBUFFER__)), "__clear__" => PyMethodKind::Proto(PyMethodProtoKind::Slot(&__CLEAR__)), // Protocols implemented through traits "__getattribute__" => { PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GETATTRIBUTE__)) } "__getattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GETATTR__)), "__setattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SETATTR__)), "__delattr__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELATTR__)), "__set__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SET__)), "__delete__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELETE__)), "__setitem__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SETITEM__)), "__delitem__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DELITEM__)), "__add__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__ADD__)), "__radd__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RADD__)), "__sub__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__SUB__)), "__rsub__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RSUB__)), "__mul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MUL__)), "__rmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMUL__)), "__matmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MATMUL__)), "__rmatmul__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMATMUL__)), "__floordiv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__FLOORDIV__)), "__rfloordiv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RFLOORDIV__)), "__truediv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__TRUEDIV__)), "__rtruediv__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RTRUEDIV__)), "__divmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__DIVMOD__)), "__rdivmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RDIVMOD__)), "__mod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__MOD__)), "__rmod__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RMOD__)), "__lshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LSHIFT__)), "__rlshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RLSHIFT__)), "__rshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RSHIFT__)), "__rrshift__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RRSHIFT__)), "__and__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__AND__)), "__rand__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RAND__)), "__xor__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__XOR__)), "__rxor__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RXOR__)), "__or__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__OR__)), "__ror__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__ROR__)), "__pow__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__POW__)), "__rpow__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__RPOW__)), "__lt__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LT__)), "__le__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__LE__)), "__eq__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__EQ__)), "__ne__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__NE__)), "__gt__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GT__)), "__ge__" => PyMethodKind::Proto(PyMethodProtoKind::SlotFragment(&__GE__)), // Some tricky protocols which don't fit the pattern of the rest "__call__" => PyMethodKind::Proto(PyMethodProtoKind::Call), "__traverse__" => PyMethodKind::Proto(PyMethodProtoKind::Traverse), // Not a proto _ => PyMethodKind::Fn, } } } enum PyMethodProtoKind { Slot(&'static SlotDef), Call, Traverse, SlotFragment(&'static SlotFragmentDef), } impl<'a> PyMethod<'a> { fn parse( sig: &'a mut syn::Signature, meth_attrs: &mut Vec, options: PyFunctionOptions, ) -> Result { let spec = FnSpec::parse(sig, meth_attrs, options)?; let method_name = spec.python_name.to_string(); let kind = PyMethodKind::from_name(&method_name); Ok(Self { kind, method_name, spec, }) } } pub fn is_proto_method(name: &str) -> bool { match PyMethodKind::from_name(name) { PyMethodKind::Fn => false, PyMethodKind::Proto(_) => true, } } pub fn gen_py_method( cls: &syn::Type, sig: &mut syn::Signature, meth_attrs: &mut Vec, options: PyFunctionOptions, ) -> Result { check_generic(sig)?; ensure_not_async_fn(sig)?; ensure_function_options_valid(&options)?; let method = PyMethod::parse(sig, meth_attrs, options)?; let spec = &method.spec; Ok(match (method.kind, &spec.tp) { // Class attributes go before protos so that class attributes can be used to set proto // method to None. (_, FnType::ClassAttribute) => { GeneratedPyMethod::Method(impl_py_class_attribute(cls, spec)?) } (PyMethodKind::Proto(proto_kind), _) => { ensure_no_forbidden_protocol_attributes(&proto_kind, spec, &method.method_name)?; match proto_kind { PyMethodProtoKind::Slot(slot_def) => { let slot = slot_def.generate_type_slot(cls, spec, &method.method_name)?; GeneratedPyMethod::Proto(slot) } PyMethodProtoKind::Call => { GeneratedPyMethod::Proto(impl_call_slot(cls, method.spec)?) } PyMethodProtoKind::Traverse => { GeneratedPyMethod::Proto(impl_traverse_slot(cls, spec)?) } PyMethodProtoKind::SlotFragment(slot_fragment_def) => { let proto = slot_fragment_def.generate_pyproto_fragment(cls, spec)?; GeneratedPyMethod::SlotTraitImpl(method.method_name, proto) } } } // ordinary functions (with some specialties) (_, FnType::Fn(_)) => GeneratedPyMethod::Method(impl_py_method_def( cls, spec, &spec.get_doc(meth_attrs), None, )?), (_, FnType::FnClass(_)) => GeneratedPyMethod::Method(impl_py_method_def( cls, spec, &spec.get_doc(meth_attrs), Some(quote!(_pyo3::ffi::METH_CLASS)), )?), (_, FnType::FnStatic) => GeneratedPyMethod::Method(impl_py_method_def( cls, spec, &spec.get_doc(meth_attrs), Some(quote!(_pyo3::ffi::METH_STATIC)), )?), // special prototypes (_, FnType::FnNew) | (_, FnType::FnNewClass(_)) => { GeneratedPyMethod::Proto(impl_py_method_def_new(cls, spec)?) } (_, FnType::Getter(self_type)) => GeneratedPyMethod::Method(impl_py_getter_def( cls, PropertyType::Function { self_type, spec, doc: spec.get_doc(meth_attrs), }, )?), (_, FnType::Setter(self_type)) => GeneratedPyMethod::Method(impl_py_setter_def( cls, PropertyType::Function { self_type, spec, doc: spec.get_doc(meth_attrs), }, )?), (_, FnType::FnModule(_)) => { unreachable!("methods cannot be FnModule") } }) } pub fn check_generic(sig: &syn::Signature) -> syn::Result<()> { let err_msg = |typ| format!("Python functions cannot have generic {} parameters", typ); for param in &sig.generics.params { match param { syn::GenericParam::Lifetime(_) => {} syn::GenericParam::Type(_) => bail_spanned!(param.span() => err_msg("type")), syn::GenericParam::Const(_) => bail_spanned!(param.span() => err_msg("const")), } } Ok(()) } fn ensure_function_options_valid(options: &PyFunctionOptions) -> syn::Result<()> { if let Some(pass_module) = &options.pass_module { bail_spanned!(pass_module.span() => "`pass_module` cannot be used on Python methods"); } Ok(()) } fn ensure_no_forbidden_protocol_attributes( proto_kind: &PyMethodProtoKind, spec: &FnSpec<'_>, method_name: &str, ) -> syn::Result<()> { if let Some(signature) = &spec.signature.attribute { // __call__ is allowed to have a signature, but nothing else is. if !matches!(proto_kind, PyMethodProtoKind::Call) { bail_spanned!(signature.kw.span() => format!("`signature` cannot be used with magic method `{}`", method_name)); } } if let Some(text_signature) = &spec.text_signature { bail_spanned!(text_signature.kw.span() => format!("`text_signature` cannot be used with magic method `{}`", method_name)); } Ok(()) } /// Also used by pyfunction. pub fn impl_py_method_def( cls: &syn::Type, spec: &FnSpec<'_>, doc: &PythonDoc, flags: Option, ) -> Result { let wrapper_ident = format_ident!("__pymethod_{}__", spec.python_name); let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls))?; let add_flags = flags.map(|flags| quote!(.flags(#flags))); let methoddef_type = match spec.tp { FnType::FnStatic => quote!(Static), FnType::FnClass(_) => quote!(Class), _ => quote!(Method), }; let methoddef = spec.get_methoddef(quote! { #cls::#wrapper_ident }, doc); let method_def = quote! { _pyo3::class::PyMethodDefType::#methoddef_type(#methoddef #add_flags) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec<'_>) -> Result { let wrapper_ident = syn::Ident::new("__pymethod___new____", Span::call_site()); let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls))?; // Use just the text_signature_call_signature() because the class' Python name // isn't known to `#[pymethods]` - that has to be attached at runtime from the PyClassImpl // trait implementation created by `#[pyclass]`. let text_signature_body = spec.text_signature_call_signature().map_or_else( || quote!(::std::option::Option::None), |text_signature| quote!(::std::option::Option::Some(#text_signature)), ); let deprecations = &spec.deprecations; let slot_def = quote! { _pyo3::ffi::PyType_Slot { slot: _pyo3::ffi::Py_tp_new, pfunc: { unsafe extern "C" fn trampoline( subtype: *mut _pyo3::ffi::PyTypeObject, args: *mut _pyo3::ffi::PyObject, kwargs: *mut _pyo3::ffi::PyObject, ) -> *mut _pyo3::ffi::PyObject { #deprecations use _pyo3::impl_::pyclass::*; impl PyClassNewTextSignature<#cls> for PyClassImplCollector<#cls> { #[inline] fn new_text_signature(self) -> ::std::option::Option<&'static str> { #text_signature_body } } _pyo3::impl_::trampoline::newfunc( subtype, args, kwargs, #cls::#wrapper_ident ) } trampoline } as _pyo3::ffi::newfunc as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } fn impl_call_slot(cls: &syn::Type, mut spec: FnSpec<'_>) -> Result { // HACK: __call__ proto slot must always use varargs calling convention, so change the spec. // Probably indicates there's a refactoring opportunity somewhere. spec.convention = CallingConvention::Varargs; let wrapper_ident = syn::Ident::new("__pymethod___call____", Span::call_site()); let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls))?; let slot_def = quote! { _pyo3::ffi::PyType_Slot { slot: _pyo3::ffi::Py_tp_call, pfunc: { unsafe extern "C" fn trampoline( slf: *mut _pyo3::ffi::PyObject, args: *mut _pyo3::ffi::PyObject, kwargs: *mut _pyo3::ffi::PyObject, ) -> *mut _pyo3::ffi::PyObject { _pyo3::impl_::trampoline::ternaryfunc( slf, args, kwargs, #cls::#wrapper_ident ) } trampoline } as _pyo3::ffi::ternaryfunc as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } fn impl_traverse_slot(cls: &syn::Type, spec: &FnSpec<'_>) -> syn::Result { if let (Some(py_arg), _) = split_off_python_arg(&spec.signature.arguments) { return Err(syn::Error::new_spanned(py_arg.ty, "__traverse__ may not take `Python`. \ Usually, an implementation of `__traverse__` should do nothing but calls to `visit.call`. \ Most importantly, safe access to the GIL is prohibited inside implementations of `__traverse__`, \ i.e. `Python::with_gil` will panic.")); } let rust_fn_ident = spec.name; let associated_method = quote! { pub unsafe extern "C" fn __pymethod_traverse__( slf: *mut _pyo3::ffi::PyObject, visit: _pyo3::ffi::visitproc, arg: *mut ::std::os::raw::c_void, ) -> ::std::os::raw::c_int { _pyo3::impl_::pymethods::_call_traverse::<#cls>(slf, #cls::#rust_fn_ident, visit, arg) } }; let slot_def = quote! { _pyo3::ffi::PyType_Slot { slot: _pyo3::ffi::Py_tp_traverse, pfunc: #cls::__pymethod_traverse__ as _pyo3::ffi::traverseproc as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } fn impl_py_class_attribute(cls: &syn::Type, spec: &FnSpec<'_>) -> syn::Result { let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); ensure_spanned!( args.is_empty(), args[0].ty.span() => "#[classattr] can only have one argument (of type pyo3::Python)" ); let name = &spec.name; let fncall = if py_arg.is_some() { quote!(function(py)) } else { quote!(function()) }; let wrapper_ident = format_ident!("__pymethod_{}__", name); let python_name = spec.null_terminated_python_name(); let body = quotes::ok_wrap(fncall); let associated_method = quote! { fn #wrapper_ident(py: _pyo3::Python<'_>) -> _pyo3::PyResult<_pyo3::PyObject> { let function = #cls::#name; // Shadow the method name to avoid #3017 #body } }; let method_def = quote! { _pyo3::class::PyMethodDefType::ClassAttribute({ _pyo3::class::PyClassAttributeDef::new( #python_name, _pyo3::impl_::pymethods::PyClassAttributeFactory(#cls::#wrapper_ident) ) }) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } fn impl_call_setter( cls: &syn::Type, spec: &FnSpec<'_>, self_type: &SelfType, ) -> syn::Result { let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); let slf = self_type.receiver(cls, ExtractErrorMode::Raise); if args.is_empty() { bail_spanned!(spec.name.span() => "setter function expected to have one argument"); } else if args.len() > 1 { bail_spanned!( args[1].ty.span() => "setter function can have at most two arguments ([pyo3::Python,] and value)" ); } let name = &spec.name; let fncall = if py_arg.is_some() { quote!(#cls::#name(#slf, py, _val)) } else { quote!(#cls::#name(#slf, _val)) }; Ok(fncall) } // Used here for PropertyType::Function, used in pyclass for descriptors. pub fn impl_py_setter_def( cls: &syn::Type, property_type: PropertyType<'_>, ) -> Result { let python_name = property_type.null_terminated_python_name()?; let doc = property_type.doc(); let setter_impl = match property_type { PropertyType::Descriptor { field_index, field, .. } => { let slf = SelfType::Receiver { mutable: true, span: Span::call_site(), } .receiver(cls, ExtractErrorMode::Raise); if let Some(ident) = &field.ident { // named struct field quote!({ #slf.#ident = _val; }) } else { // tuple struct field let index = syn::Index::from(field_index); quote!({ #slf.#index = _val; }) } } PropertyType::Function { spec, self_type, .. } => impl_call_setter(cls, spec, self_type)?, }; let wrapper_ident = match property_type { PropertyType::Descriptor { field: syn::Field { ident: Some(ident), .. }, .. } => { format_ident!("__pymethod_set_{}__", ident) } PropertyType::Descriptor { field_index, .. } => { format_ident!("__pymethod_set_field_{}__", field_index) } PropertyType::Function { spec, .. } => { format_ident!("__pymethod_set_{}__", spec.name) } }; let mut cfg_attrs = TokenStream::new(); if let PropertyType::Descriptor { field, .. } = &property_type { for attr in field .attrs .iter() .filter(|attr| attr.path().is_ident("cfg")) { attr.to_tokens(&mut cfg_attrs); } } let associated_method = quote! { #cfg_attrs unsafe fn #wrapper_ident( py: _pyo3::Python<'_>, _slf: *mut _pyo3::ffi::PyObject, _value: *mut _pyo3::ffi::PyObject, ) -> _pyo3::PyResult<::std::os::raw::c_int> { let _value = py .from_borrowed_ptr_or_opt(_value) .ok_or_else(|| { _pyo3::exceptions::PyAttributeError::new_err("can't delete attribute") })?; let _val = _pyo3::FromPyObject::extract(_value)?; _pyo3::callback::convert(py, #setter_impl) } }; let method_def = quote! { #cfg_attrs _pyo3::class::PyMethodDefType::Setter( _pyo3::class::PySetterDef::new( #python_name, _pyo3::impl_::pymethods::PySetter(#cls::#wrapper_ident), #doc ) ) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } fn impl_call_getter( cls: &syn::Type, spec: &FnSpec<'_>, self_type: &SelfType, ) -> syn::Result { let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); let slf = self_type.receiver(cls, ExtractErrorMode::Raise); ensure_spanned!( args.is_empty(), args[0].ty.span() => "getter function can only have one argument (of type pyo3::Python)" ); let name = &spec.name; let fncall = if py_arg.is_some() { quote!(#cls::#name(#slf, py)) } else { quote!(#cls::#name(#slf)) }; Ok(fncall) } // Used here for PropertyType::Function, used in pyclass for descriptors. pub fn impl_py_getter_def( cls: &syn::Type, property_type: PropertyType<'_>, ) -> Result { let python_name = property_type.null_terminated_python_name()?; let doc = property_type.doc(); let body = match property_type { PropertyType::Descriptor { field_index, field, .. } => { let slf = SelfType::Receiver { mutable: false, span: Span::call_site(), } .receiver(cls, ExtractErrorMode::Raise); let field_token = if let Some(ident) = &field.ident { // named struct field ident.to_token_stream() } else { // tuple struct field syn::Index::from(field_index).to_token_stream() }; quotes::map_result_into_ptr(quotes::ok_wrap(quote! { ::std::clone::Clone::clone(&(#slf.#field_token)) })) } // Forward to `IntoPyCallbackOutput`, to handle `#[getter]`s returning results. PropertyType::Function { spec, self_type, .. } => { let call = impl_call_getter(cls, spec, self_type)?; quote! { _pyo3::callback::convert(py, #call) } } }; let wrapper_ident = match property_type { PropertyType::Descriptor { field: syn::Field { ident: Some(ident), .. }, .. } => { format_ident!("__pymethod_get_{}__", ident) } PropertyType::Descriptor { field_index, .. } => { format_ident!("__pymethod_get_field_{}__", field_index) } PropertyType::Function { spec, .. } => { format_ident!("__pymethod_get_{}__", spec.name) } }; let mut cfg_attrs = TokenStream::new(); if let PropertyType::Descriptor { field, .. } = &property_type { for attr in field .attrs .iter() .filter(|attr| attr.path().is_ident("cfg")) { attr.to_tokens(&mut cfg_attrs); } } let associated_method = quote! { #cfg_attrs unsafe fn #wrapper_ident( py: _pyo3::Python<'_>, _slf: *mut _pyo3::ffi::PyObject ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { #body } }; let method_def = quote! { #cfg_attrs _pyo3::class::PyMethodDefType::Getter( _pyo3::class::PyGetterDef::new( #python_name, _pyo3::impl_::pymethods::PyGetter(#cls::#wrapper_ident), #doc ) ) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } /// Split an argument of pyo3::Python from the front of the arg list, if present fn split_off_python_arg<'a>(args: &'a [FnArg<'a>]) -> (Option<&FnArg<'_>>, &[FnArg<'_>]) { match args { [py, args @ ..] if utils::is_python(py.ty) => (Some(py), args), args => (None, args), } } pub enum PropertyType<'a> { Descriptor { field_index: usize, field: &'a syn::Field, python_name: Option<&'a NameAttribute>, renaming_rule: Option, }, Function { self_type: &'a SelfType, spec: &'a FnSpec<'a>, doc: PythonDoc, }, } impl PropertyType<'_> { fn null_terminated_python_name(&self) -> Result { match self { PropertyType::Descriptor { field, python_name, renaming_rule, .. } => { let name = match (python_name, &field.ident) { (Some(name), _) => name.value.0.to_string(), (None, Some(field_name)) => { let mut name = field_name.unraw().to_string(); if let Some(rule) = renaming_rule { name = utils::apply_renaming_rule(*rule, &name); } name.push('\0'); name } (None, None) => { bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`"); } }; Ok(syn::LitStr::new(&name, field.span())) } PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name()), } } fn doc(&self) -> Cow<'_, PythonDoc> { match self { PropertyType::Descriptor { field, .. } => { Cow::Owned(utils::get_doc(&field.attrs, None)) } PropertyType::Function { doc, .. } => Cow::Borrowed(doc), } } } const __STR__: SlotDef = SlotDef::new("Py_tp_str", "reprfunc"); pub const __REPR__: SlotDef = SlotDef::new("Py_tp_repr", "reprfunc"); const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc") .ret_ty(Ty::PyHashT) .return_conversion(TokenGenerator( || quote! { _pyo3::callback::HashCallbackOutput }, )); pub const __RICHCMP__: SlotDef = SlotDef::new("Py_tp_richcompare", "richcmpfunc") .extract_error_mode(ExtractErrorMode::NotImplemented) .arguments(&[Ty::Object, Ty::CompareOp]); const __GET__: SlotDef = SlotDef::new("Py_tp_descr_get", "descrgetfunc") .arguments(&[Ty::MaybeNullObject, Ty::MaybeNullObject]); const __ITER__: SlotDef = SlotDef::new("Py_tp_iter", "getiterfunc"); const __NEXT__: SlotDef = SlotDef::new("Py_tp_iternext", "iternextfunc").return_conversion( TokenGenerator(|| quote! { _pyo3::class::iter::IterNextOutput::<_, _> }), ); const __AWAIT__: SlotDef = SlotDef::new("Py_am_await", "unaryfunc"); const __AITER__: SlotDef = SlotDef::new("Py_am_aiter", "unaryfunc"); const __ANEXT__: SlotDef = SlotDef::new("Py_am_anext", "unaryfunc").return_conversion( TokenGenerator(|| quote! { _pyo3::class::pyasync::IterANextOutput::<_, _> }), ); const __LEN__: SlotDef = SlotDef::new("Py_mp_length", "lenfunc").ret_ty(Ty::PySsizeT); const __CONTAINS__: SlotDef = SlotDef::new("Py_sq_contains", "objobjproc") .arguments(&[Ty::Object]) .ret_ty(Ty::Int); const __CONCAT__: SlotDef = SlotDef::new("Py_sq_concat", "binaryfunc").arguments(&[Ty::Object]); const __REPEAT__: SlotDef = SlotDef::new("Py_sq_repeat", "ssizeargfunc").arguments(&[Ty::PySsizeT]); const __INPLACE_CONCAT__: SlotDef = SlotDef::new("Py_sq_concat", "binaryfunc").arguments(&[Ty::Object]); const __INPLACE_REPEAT__: SlotDef = SlotDef::new("Py_sq_repeat", "ssizeargfunc").arguments(&[Ty::PySsizeT]); const __GETITEM__: SlotDef = SlotDef::new("Py_mp_subscript", "binaryfunc").arguments(&[Ty::Object]); const __POS__: SlotDef = SlotDef::new("Py_nb_positive", "unaryfunc"); const __NEG__: SlotDef = SlotDef::new("Py_nb_negative", "unaryfunc"); const __ABS__: SlotDef = SlotDef::new("Py_nb_absolute", "unaryfunc"); const __INVERT__: SlotDef = SlotDef::new("Py_nb_invert", "unaryfunc"); const __INDEX__: SlotDef = SlotDef::new("Py_nb_index", "unaryfunc"); pub const __INT__: SlotDef = SlotDef::new("Py_nb_int", "unaryfunc"); const __FLOAT__: SlotDef = SlotDef::new("Py_nb_float", "unaryfunc"); const __BOOL__: SlotDef = SlotDef::new("Py_nb_bool", "inquiry").ret_ty(Ty::Int); const __IADD__: SlotDef = SlotDef::new("Py_nb_inplace_add", "binaryfunc") .arguments(&[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __ISUB__: SlotDef = SlotDef::new("Py_nb_inplace_subtract", "binaryfunc") .arguments(&[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __IMUL__: SlotDef = SlotDef::new("Py_nb_inplace_multiply", "binaryfunc") .arguments(&[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __IMATMUL__: SlotDef = SlotDef::new("Py_nb_inplace_matrix_multiply", "binaryfunc") .arguments(&[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __ITRUEDIV__: SlotDef = SlotDef::new("Py_nb_inplace_true_divide", "binaryfunc") .arguments(&[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __IFLOORDIV__: SlotDef = SlotDef::new("Py_nb_inplace_floor_divide", "binaryfunc") .arguments(&[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __IMOD__: SlotDef = SlotDef::new("Py_nb_inplace_remainder", "binaryfunc") .arguments(&[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __IPOW__: SlotDef = SlotDef::new("Py_nb_inplace_power", "ipowfunc") .arguments(&[Ty::Object, Ty::IPowModulo]) .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __ILSHIFT__: SlotDef = SlotDef::new("Py_nb_inplace_lshift", "binaryfunc") .arguments(&[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __IRSHIFT__: SlotDef = SlotDef::new("Py_nb_inplace_rshift", "binaryfunc") .arguments(&[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __IAND__: SlotDef = SlotDef::new("Py_nb_inplace_and", "binaryfunc") .arguments(&[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __IXOR__: SlotDef = SlotDef::new("Py_nb_inplace_xor", "binaryfunc") .arguments(&[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __IOR__: SlotDef = SlotDef::new("Py_nb_inplace_or", "binaryfunc") .arguments(&[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .return_self(); const __GETBUFFER__: SlotDef = SlotDef::new("Py_bf_getbuffer", "getbufferproc") .arguments(&[Ty::PyBuffer, Ty::Int]) .ret_ty(Ty::Int) .require_unsafe(); const __RELEASEBUFFER__: SlotDef = SlotDef::new("Py_bf_releasebuffer", "releasebufferproc") .arguments(&[Ty::PyBuffer]) .ret_ty(Ty::Void) .require_unsafe(); const __CLEAR__: SlotDef = SlotDef::new("Py_tp_clear", "inquiry") .arguments(&[]) .ret_ty(Ty::Int); #[derive(Clone, Copy)] enum Ty { Object, MaybeNullObject, NonNullObject, IPowModulo, CompareOp, Int, PyHashT, PySsizeT, Void, PyBuffer, } impl Ty { fn ffi_type(self) -> TokenStream { match self { Ty::Object | Ty::MaybeNullObject => quote! { *mut _pyo3::ffi::PyObject }, Ty::NonNullObject => quote! { ::std::ptr::NonNull<_pyo3::ffi::PyObject> }, Ty::IPowModulo => quote! { _pyo3::impl_::pymethods::IPowModulo }, Ty::Int | Ty::CompareOp => quote! { ::std::os::raw::c_int }, Ty::PyHashT => quote! { _pyo3::ffi::Py_hash_t }, Ty::PySsizeT => quote! { _pyo3::ffi::Py_ssize_t }, Ty::Void => quote! { () }, Ty::PyBuffer => quote! { *mut _pyo3::ffi::Py_buffer }, } } fn extract( self, ident: &syn::Ident, arg: &FnArg<'_>, extract_error_mode: ExtractErrorMode, ) -> TokenStream { let name_str = arg.name.unraw().to_string(); match self { Ty::Object => extract_object( extract_error_mode, &name_str, quote! { py.from_borrowed_ptr::<_pyo3::PyAny>(#ident) }, ), Ty::MaybeNullObject => extract_object( extract_error_mode, &name_str, quote! { py.from_borrowed_ptr::<_pyo3::PyAny>( if #ident.is_null() { _pyo3::ffi::Py_None() } else { #ident } ) }, ), Ty::NonNullObject => extract_object( extract_error_mode, &name_str, quote! { py.from_borrowed_ptr::<_pyo3::PyAny>(#ident.as_ptr()) }, ), Ty::IPowModulo => extract_object( extract_error_mode, &name_str, quote! { #ident.to_borrowed_any(py) }, ), Ty::CompareOp => extract_error_mode.handle_error( quote! { _pyo3::class::basic::CompareOp::from_raw(#ident) .ok_or_else(|| _pyo3::exceptions::PyValueError::new_err("invalid comparison operator")) }, ), Ty::PySsizeT => { let ty = arg.ty; extract_error_mode.handle_error( quote! { ::std::convert::TryInto::<#ty>::try_into(#ident).map_err(|e| _pyo3::exceptions::PyValueError::new_err(e.to_string())) }, ) } // Just pass other types through unmodified Ty::PyBuffer | Ty::Int | Ty::PyHashT | Ty::Void => quote! { #ident }, } } } fn extract_object( extract_error_mode: ExtractErrorMode, name: &str, source: TokenStream, ) -> TokenStream { extract_error_mode.handle_error(quote! { _pyo3::impl_::extract_argument::extract_argument( #source, &mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT }, #name ) }) } enum ReturnMode { ReturnSelf, Conversion(TokenGenerator), } impl ReturnMode { fn return_call_output(&self, call: TokenStream) -> TokenStream { match self { ReturnMode::Conversion(conversion) => quote! { let _result: _pyo3::PyResult<#conversion> = #call; _pyo3::callback::convert(py, _result) }, ReturnMode::ReturnSelf => quote! { let _result: _pyo3::PyResult<()> = #call; _result?; _pyo3::ffi::Py_XINCREF(_raw_slf); ::std::result::Result::Ok(_raw_slf) }, } } } pub struct SlotDef { slot: StaticIdent, func_ty: StaticIdent, arguments: &'static [Ty], ret_ty: Ty, extract_error_mode: ExtractErrorMode, return_mode: Option, require_unsafe: bool, } const NO_ARGUMENTS: &[Ty] = &[]; impl SlotDef { const fn new(slot: &'static str, func_ty: &'static str) -> Self { SlotDef { slot: StaticIdent(slot), func_ty: StaticIdent(func_ty), arguments: NO_ARGUMENTS, ret_ty: Ty::Object, extract_error_mode: ExtractErrorMode::Raise, return_mode: None, require_unsafe: false, } } const fn arguments(mut self, arguments: &'static [Ty]) -> Self { self.arguments = arguments; self } const fn ret_ty(mut self, ret_ty: Ty) -> Self { self.ret_ty = ret_ty; self } const fn return_conversion(mut self, return_conversion: TokenGenerator) -> Self { self.return_mode = Some(ReturnMode::Conversion(return_conversion)); self } const fn extract_error_mode(mut self, extract_error_mode: ExtractErrorMode) -> Self { self.extract_error_mode = extract_error_mode; self } const fn return_self(mut self) -> Self { self.return_mode = Some(ReturnMode::ReturnSelf); self } const fn require_unsafe(mut self) -> Self { self.require_unsafe = true; self } pub fn generate_type_slot( &self, cls: &syn::Type, spec: &FnSpec<'_>, method_name: &str, ) -> Result { let SlotDef { slot, func_ty, arguments, extract_error_mode, ret_ty, return_mode, require_unsafe, } = self; if *require_unsafe { ensure_spanned!( spec.unsafety.is_some(), spec.name.span() => format!("`{}` must be `unsafe fn`", method_name) ); } let arg_types: &Vec<_> = &arguments.iter().map(|arg| arg.ffi_type()).collect(); let arg_idents: &Vec<_> = &(0..arguments.len()) .map(|i| format_ident!("arg{}", i)) .collect(); let wrapper_ident = format_ident!("__pymethod_{}__", method_name); let ret_ty = ret_ty.ffi_type(); let body = generate_method_body( cls, spec, arguments, *extract_error_mode, return_mode.as_ref(), )?; let name = spec.name; let associated_method = quote! { unsafe fn #wrapper_ident( py: _pyo3::Python<'_>, _raw_slf: *mut _pyo3::ffi::PyObject, #(#arg_idents: #arg_types),* ) -> _pyo3::PyResult<#ret_ty> { let function = #cls::#name; // Shadow the method name to avoid #3017 let _slf = _raw_slf; #body } }; let slot_def = quote! {{ unsafe extern "C" fn trampoline( _slf: *mut _pyo3::ffi::PyObject, #(#arg_idents: #arg_types),* ) -> #ret_ty { _pyo3::impl_::trampoline:: #func_ty ( _slf, #(#arg_idents,)* #cls::#wrapper_ident ) } _pyo3::ffi::PyType_Slot { slot: _pyo3::ffi::#slot, pfunc: trampoline as _pyo3::ffi::#func_ty as _ } }}; Ok(MethodAndSlotDef { associated_method, slot_def, }) } } fn generate_method_body( cls: &syn::Type, spec: &FnSpec<'_>, arguments: &[Ty], extract_error_mode: ExtractErrorMode, return_mode: Option<&ReturnMode>, ) -> Result { let self_arg = spec.tp.self_arg(Some(cls), extract_error_mode); let rust_name = spec.name; let args = extract_proto_arguments(spec, arguments, extract_error_mode)?; let call = quote! { _pyo3::callback::convert(py, #cls::#rust_name(#self_arg #(#args),*)) }; Ok(if let Some(return_mode) = return_mode { return_mode.return_call_output(call) } else { call }) } struct SlotFragmentDef { fragment: &'static str, arguments: &'static [Ty], extract_error_mode: ExtractErrorMode, ret_ty: Ty, } impl SlotFragmentDef { const fn new(fragment: &'static str, arguments: &'static [Ty]) -> Self { SlotFragmentDef { fragment, arguments, extract_error_mode: ExtractErrorMode::Raise, ret_ty: Ty::Void, } } const fn extract_error_mode(mut self, extract_error_mode: ExtractErrorMode) -> Self { self.extract_error_mode = extract_error_mode; self } const fn ret_ty(mut self, ret_ty: Ty) -> Self { self.ret_ty = ret_ty; self } fn generate_pyproto_fragment(&self, cls: &syn::Type, spec: &FnSpec<'_>) -> Result { let SlotFragmentDef { fragment, arguments, extract_error_mode, ret_ty, } = self; let fragment_trait = format_ident!("PyClass{}SlotFragment", fragment); let method = syn::Ident::new(fragment, Span::call_site()); let wrapper_ident = format_ident!("__pymethod_{}__", fragment); let arg_types: &Vec<_> = &arguments.iter().map(|arg| arg.ffi_type()).collect(); let arg_idents: &Vec<_> = &(0..arguments.len()) .map(|i| format_ident!("arg{}", i)) .collect(); let body = generate_method_body(cls, spec, arguments, *extract_error_mode, None)?; let ret_ty = ret_ty.ffi_type(); Ok(quote! { impl _pyo3::impl_::pyclass::#fragment_trait<#cls> for _pyo3::impl_::pyclass::PyClassImplCollector<#cls> { #[inline] unsafe fn #method( self, py: _pyo3::Python, _raw_slf: *mut _pyo3::ffi::PyObject, #(#arg_idents: #arg_types),* ) -> _pyo3::PyResult<#ret_ty> { impl #cls { unsafe fn #wrapper_ident( py: _pyo3::Python, _raw_slf: *mut _pyo3::ffi::PyObject, #(#arg_idents: #arg_types),* ) -> _pyo3::PyResult<#ret_ty> { let _slf = _raw_slf; #body } } #cls::#wrapper_ident(py, _raw_slf, #(#arg_idents),*) } } }) } } const __GETATTRIBUTE__: SlotFragmentDef = SlotFragmentDef::new("__getattribute__", &[Ty::Object]).ret_ty(Ty::Object); const __GETATTR__: SlotFragmentDef = SlotFragmentDef::new("__getattr__", &[Ty::Object]).ret_ty(Ty::Object); const __SETATTR__: SlotFragmentDef = SlotFragmentDef::new("__setattr__", &[Ty::Object, Ty::NonNullObject]); const __DELATTR__: SlotFragmentDef = SlotFragmentDef::new("__delattr__", &[Ty::Object]); const __SET__: SlotFragmentDef = SlotFragmentDef::new("__set__", &[Ty::Object, Ty::NonNullObject]); const __DELETE__: SlotFragmentDef = SlotFragmentDef::new("__delete__", &[Ty::Object]); const __SETITEM__: SlotFragmentDef = SlotFragmentDef::new("__setitem__", &[Ty::Object, Ty::NonNullObject]); const __DELITEM__: SlotFragmentDef = SlotFragmentDef::new("__delitem__", &[Ty::Object]); macro_rules! binary_num_slot_fragment_def { ($ident:ident, $name:literal) => { const $ident: SlotFragmentDef = SlotFragmentDef::new($name, &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); }; } binary_num_slot_fragment_def!(__ADD__, "__add__"); binary_num_slot_fragment_def!(__RADD__, "__radd__"); binary_num_slot_fragment_def!(__SUB__, "__sub__"); binary_num_slot_fragment_def!(__RSUB__, "__rsub__"); binary_num_slot_fragment_def!(__MUL__, "__mul__"); binary_num_slot_fragment_def!(__RMUL__, "__rmul__"); binary_num_slot_fragment_def!(__MATMUL__, "__matmul__"); binary_num_slot_fragment_def!(__RMATMUL__, "__rmatmul__"); binary_num_slot_fragment_def!(__FLOORDIV__, "__floordiv__"); binary_num_slot_fragment_def!(__RFLOORDIV__, "__rfloordiv__"); binary_num_slot_fragment_def!(__TRUEDIV__, "__truediv__"); binary_num_slot_fragment_def!(__RTRUEDIV__, "__rtruediv__"); binary_num_slot_fragment_def!(__DIVMOD__, "__divmod__"); binary_num_slot_fragment_def!(__RDIVMOD__, "__rdivmod__"); binary_num_slot_fragment_def!(__MOD__, "__mod__"); binary_num_slot_fragment_def!(__RMOD__, "__rmod__"); binary_num_slot_fragment_def!(__LSHIFT__, "__lshift__"); binary_num_slot_fragment_def!(__RLSHIFT__, "__rlshift__"); binary_num_slot_fragment_def!(__RSHIFT__, "__rshift__"); binary_num_slot_fragment_def!(__RRSHIFT__, "__rrshift__"); binary_num_slot_fragment_def!(__AND__, "__and__"); binary_num_slot_fragment_def!(__RAND__, "__rand__"); binary_num_slot_fragment_def!(__XOR__, "__xor__"); binary_num_slot_fragment_def!(__RXOR__, "__rxor__"); binary_num_slot_fragment_def!(__OR__, "__or__"); binary_num_slot_fragment_def!(__ROR__, "__ror__"); const __POW__: SlotFragmentDef = SlotFragmentDef::new("__pow__", &[Ty::Object, Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __RPOW__: SlotFragmentDef = SlotFragmentDef::new("__rpow__", &[Ty::Object, Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __LT__: SlotFragmentDef = SlotFragmentDef::new("__lt__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __LE__: SlotFragmentDef = SlotFragmentDef::new("__le__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __EQ__: SlotFragmentDef = SlotFragmentDef::new("__eq__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __NE__: SlotFragmentDef = SlotFragmentDef::new("__ne__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __GT__: SlotFragmentDef = SlotFragmentDef::new("__gt__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); const __GE__: SlotFragmentDef = SlotFragmentDef::new("__ge__", &[Ty::Object]) .extract_error_mode(ExtractErrorMode::NotImplemented) .ret_ty(Ty::Object); fn extract_proto_arguments( spec: &FnSpec<'_>, proto_args: &[Ty], extract_error_mode: ExtractErrorMode, ) -> Result> { let mut args = Vec::with_capacity(spec.signature.arguments.len()); let mut non_python_args = 0; for arg in &spec.signature.arguments { if arg.py { args.push(quote! { py }); } else { let ident = syn::Ident::new(&format!("arg{}", non_python_args), Span::call_site()); let conversions = proto_args.get(non_python_args) .ok_or_else(|| err_spanned!(arg.ty.span() => format!("Expected at most {} non-python arguments", proto_args.len())))? .extract(&ident, arg, extract_error_mode); non_python_args += 1; args.push(conversions); } } if non_python_args != proto_args.len() { bail_spanned!(spec.name.span() => format!("Expected {} arguments, got {}", proto_args.len(), non_python_args)); } Ok(args) } struct StaticIdent(&'static str); impl ToTokens for StaticIdent { fn to_tokens(&self, tokens: &mut TokenStream) { syn::Ident::new(self.0, Span::call_site()).to_tokens(tokens) } } struct TokenGenerator(fn() -> TokenStream); impl ToTokens for TokenGenerator { fn to_tokens(&self, tokens: &mut TokenStream) { self.0().to_tokens(tokens) } } pyo3-macros-backend-0.20.2/src/quotes.rs000064400000000000000000000010031046102023000161150ustar 00000000000000use proc_macro2::TokenStream; use quote::quote; pub(crate) fn some_wrap(obj: TokenStream) -> TokenStream { quote! { _pyo3::impl_::wrap::SomeWrap::wrap(#obj) } } pub(crate) fn ok_wrap(obj: TokenStream) -> TokenStream { quote! { _pyo3::impl_::wrap::OkWrap::wrap(#obj, py) .map_err(::core::convert::Into::<_pyo3::PyErr>::into) } } pub(crate) fn map_result_into_ptr(result: TokenStream) -> TokenStream { quote! { #result.map(_pyo3::PyObject::into_ptr) } } pyo3-macros-backend-0.20.2/src/utils.rs000064400000000000000000000145761046102023000157600ustar 00000000000000use proc_macro2::{Span, TokenStream}; use quote::ToTokens; use syn::{punctuated::Punctuated, spanned::Spanned, Token}; use crate::attributes::{CrateAttribute, RenamingRule}; /// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span. macro_rules! err_spanned { ($span:expr => $msg:expr) => { syn::Error::new($span, $msg) }; } /// Macro inspired by `anyhow::bail!` to return a compiler error with the given span. macro_rules! bail_spanned { ($span:expr => $msg:expr) => { return Err(err_spanned!($span => $msg)) }; } /// Macro inspired by `anyhow::ensure!` to return a compiler error with the given span if the /// specified condition is not met. macro_rules! ensure_spanned { ($condition:expr, $span:expr => $msg:expr) => { if !($condition) { bail_spanned!($span => $msg); } } } /// Check if the given type `ty` is `pyo3::Python`. pub fn is_python(ty: &syn::Type) -> bool { match unwrap_ty_group(ty) { syn::Type::Path(typath) => typath .path .segments .last() .map(|seg| seg.ident == "Python") .unwrap_or(false), _ => false, } } /// If `ty` is `Option`, return `Some(T)`, else `None`. pub fn option_type_argument(ty: &syn::Type) -> Option<&syn::Type> { if let syn::Type::Path(syn::TypePath { path, .. }) = ty { let seg = path.segments.last().filter(|s| s.ident == "Option")?; if let syn::PathArguments::AngleBracketed(params) = &seg.arguments { if let syn::GenericArgument::Type(ty) = params.args.first()? { return Some(ty); } } } None } /// A syntax tree which evaluates to a nul-terminated docstring for Python. /// /// Typically the tokens will just be that string, but if the original docs included macro /// expressions then the tokens will be a concat!("...", "\n", "\0") expression of the strings and /// macro parts. /// contents such as parse the string contents. #[derive(Clone)] pub struct PythonDoc(TokenStream); /// Collects all #[doc = "..."] attributes into a TokenStream evaluating to a null-terminated string. /// /// If this doc is for a callable, the provided `text_signature` can be passed to prepend /// this to the documentation suitable for Python to extract this into the `__text_signature__` /// attribute. pub fn get_doc(attrs: &[syn::Attribute], mut text_signature: Option) -> PythonDoc { // insert special divider between `__text_signature__` and doc // (assume text_signature is itself well-formed) if let Some(text_signature) = &mut text_signature { text_signature.push_str("\n--\n\n"); } let mut parts = Punctuated::::new(); let mut first = true; let mut current_part = text_signature.unwrap_or_default(); for attr in attrs { if attr.path().is_ident("doc") { if let Ok(nv) = attr.meta.require_name_value() { if !first { current_part.push('\n'); } else { first = false; } if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(lit_str), .. }) = &nv.value { // Strip single left space from literal strings, if needed. // e.g. `/// Hello world` expands to #[doc = " Hello world"] let doc_line = lit_str.value(); current_part.push_str(doc_line.strip_prefix(' ').unwrap_or(&doc_line)); } else { // This is probably a macro doc from Rust 1.54, e.g. #[doc = include_str!(...)] // Reset the string buffer, write that part, and then push this macro part too. parts.push(current_part.to_token_stream()); current_part.clear(); parts.push(nv.value.to_token_stream()); } } } } if !parts.is_empty() { // Doc contained macro pieces - return as `concat!` expression if !current_part.is_empty() { parts.push(current_part.to_token_stream()); } let mut tokens = TokenStream::new(); syn::Ident::new("concat", Span::call_site()).to_tokens(&mut tokens); syn::token::Not(Span::call_site()).to_tokens(&mut tokens); syn::token::Bracket(Span::call_site()).surround(&mut tokens, |tokens| { parts.to_tokens(tokens); syn::token::Comma(Span::call_site()).to_tokens(tokens); syn::LitStr::new("\0", Span::call_site()).to_tokens(tokens); }); PythonDoc(tokens) } else { // Just a string doc - return directly with nul terminator current_part.push('\0'); PythonDoc(current_part.to_token_stream()) } } impl quote::ToTokens for PythonDoc { fn to_tokens(&self, tokens: &mut TokenStream) { self.0.to_tokens(tokens) } } pub fn ensure_not_async_fn(sig: &syn::Signature) -> syn::Result<()> { if let Some(asyncness) = &sig.asyncness { bail_spanned!( asyncness.span() => "`async fn` is not yet supported for Python functions.\n\n\ Additional crates such as `pyo3-asyncio` can be used to integrate async Rust and \ Python. For more information, see https://github.com/PyO3/pyo3/issues/1632" ); }; Ok(()) } pub fn unwrap_ty_group(mut ty: &syn::Type) -> &syn::Type { while let syn::Type::Group(g) = ty { ty = &*g.elem; } ty } /// Extract the path to the pyo3 crate, or use the default (`::pyo3`). pub(crate) fn get_pyo3_crate(attr: &Option) -> syn::Path { attr.as_ref() .map(|p| p.value.0.clone()) .unwrap_or_else(|| syn::parse_str("::pyo3").unwrap()) } pub fn apply_renaming_rule(rule: RenamingRule, name: &str) -> String { use heck::*; match rule { RenamingRule::CamelCase => name.to_lower_camel_case(), RenamingRule::KebabCase => name.to_kebab_case(), RenamingRule::Lowercase => name.to_lowercase(), RenamingRule::PascalCase => name.to_upper_camel_case(), RenamingRule::ScreamingKebabCase => name.to_shouty_kebab_case(), RenamingRule::ScreamingSnakeCase => name.to_shouty_snake_case(), RenamingRule::SnakeCase => name.to_snake_case(), RenamingRule::Uppercase => name.to_uppercase(), } }