pyo3-macros-backend-0.22.2/.cargo_vcs_info.json0000644000000001610000000000100146560ustar { "git": { "sha1": "92b0f1579776952ceaa67e0423d3f94f69faccfe" }, "path_in_vcs": "pyo3-macros-backend" }pyo3-macros-backend-0.22.2/Cargo.toml0000644000000040240000000000100126560ustar # 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.22.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.5" [dependencies.proc-macro2] version = "1.0.60" default-features = false [dependencies.pyo3-build-config] version = "=0.22.2" features = ["resolve-config"] [dependencies.quote] version = "1" default-features = false [dependencies.syn] version = "2.0.59" features = [ "derive", "parsing", "printing", "clone-impls", "full", "extra-traits", ] default-features = false [build-dependencies.pyo3-build-config] version = "=0.22.2" [features] experimental-async = [] gil-refs = [] [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_2021_prelude_collisions = "warn" unused_lifetimes = "warn" [lints.rust.rust_2018_idioms] level = "warn" priority = -1 [lints.rustdoc] bare_urls = "warn" broken_intra_doc_links = "warn" pyo3-macros-backend-0.22.2/Cargo.toml.orig000064400000000000000000000022131046102023000163350ustar 00000000000000[package] name = "pyo3-macros-backend" version = "0.22.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] heck = "0.5" proc-macro2 = { version = "1.0.60", default-features = false } pyo3-build-config = { path = "../pyo3-build-config", version = "=0.22.2", features = ["resolve-config"] } quote = { version = "1", default-features = false } [dependencies.syn] version = "2.0.59" # for `LitCStr` default-features = false features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits"] [build-dependencies] pyo3-build-config = { path = "../pyo3-build-config", version = "=0.22.2" } [lints] workspace = true [features] experimental-async = [] gil-refs = [] pyo3-macros-backend-0.22.2/LICENSE-APACHE000064400000000000000000000250351046102023000154010ustar 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.22.2/LICENSE-MIT000064400000000000000000000021231046102023000151020ustar 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.22.2/build.rs000064400000000000000000000001511046102023000151120ustar 00000000000000fn main() { pyo3_build_config::print_expected_cfgs(); pyo3_build_config::print_feature_cfgs(); } pyo3-macros-backend-0.22.2/src/attributes.rs000064400000000000000000000176231046102023000170040ustar 00000000000000use proc_macro2::TokenStream; use quote::ToTokens; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Expr, ExprPath, Ident, LitStr, Path, Result, Token, }; pub mod kw { syn::custom_keyword!(annotation); syn::custom_keyword!(attribute); syn::custom_keyword!(cancel_handle); syn::custom_keyword!(constructor); syn::custom_keyword!(dict); syn::custom_keyword!(eq); syn::custom_keyword!(eq_int); syn::custom_keyword!(extends); syn::custom_keyword!(freelist); syn::custom_keyword!(from_py_with); syn::custom_keyword!(frozen); syn::custom_keyword!(get); syn::custom_keyword!(get_all); syn::custom_keyword!(hash); 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!(ord); 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!(submodule); 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_with(Ident::parse_any) { 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; pub type SubmoduleAttribute = kw::submodule; 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.22.2/src/deprecations.rs000064400000000000000000000062561046102023000172760ustar 00000000000000use crate::{ method::{FnArg, FnSpec}, utils::Ctx, }; use proc_macro2::{Span, TokenStream}; use quote::{quote_spanned, ToTokens}; pub enum Deprecation { PyMethodsNewDeprecatedForm, } impl Deprecation { fn ident(&self, span: Span) -> syn::Ident { let string = match self { Deprecation::PyMethodsNewDeprecatedForm => "PYMETHODS_NEW_DEPRECATED_FORM", }; syn::Ident::new(string, span) } } pub struct Deprecations<'ctx>(Vec<(Deprecation, Span)>, &'ctx Ctx); impl<'ctx> Deprecations<'ctx> { pub fn new(ctx: &'ctx Ctx) -> Self { Deprecations(Vec::new(), ctx) } pub fn push(&mut self, deprecation: Deprecation, span: Span) { self.0.push((deprecation, span)) } } impl<'ctx> ToTokens for Deprecations<'ctx> { fn to_tokens(&self, tokens: &mut TokenStream) { let Self(deprecations, Ctx { pyo3_path, .. }) = self; for (deprecation, span) in deprecations { let pyo3_path = pyo3_path.to_tokens_spanned(*span); let ident = deprecation.ident(*span); quote_spanned!( *span => #[allow(clippy::let_unit_value)] { let _ = #pyo3_path::impl_::deprecations::#ident; } ) .to_tokens(tokens) } } } pub(crate) fn deprecate_trailing_option_default(spec: &FnSpec<'_>) -> TokenStream { if spec.signature.attribute.is_none() && spec.tp.signature_attribute_allowed() && spec.signature.arguments.iter().any(|arg| { if let FnArg::Regular(arg) = arg { arg.option_wrapped_type.is_some() } else { false } }) { use std::fmt::Write; let mut deprecation_msg = String::from( "this function has implicit defaults for the trailing `Option` arguments \n\ = note: these implicit defaults are being phased out \n\ = help: add `#[pyo3(signature = (", ); spec.signature.arguments.iter().for_each(|arg| { match arg { FnArg::Regular(arg) => { if arg.option_wrapped_type.is_some() { write!(deprecation_msg, "{}=None, ", arg.name) } else { write!(deprecation_msg, "{}, ", arg.name) } } FnArg::VarArgs(arg) => write!(deprecation_msg, "{}, ", arg.name), FnArg::KwArgs(arg) => write!(deprecation_msg, "{}, ", arg.name), FnArg::Py(_) | FnArg::CancelHandle(_) => Ok(()), } .expect("writing to `String` should not fail"); }); //remove trailing space and comma deprecation_msg.pop(); deprecation_msg.pop(); deprecation_msg.push_str( "))]` to this function to silence this warning and keep the current behavior", ); quote_spanned! { spec.name.span() => #[deprecated(note = #deprecation_msg)] #[allow(dead_code)] const SIGNATURE: () = (); const _: () = SIGNATURE; } } else { TokenStream::new() } } pyo3-macros-backend-0.22.2/src/frompyobject.rs000064400000000000000000000666501046102023000173250ustar 00000000000000use crate::attributes::{self, get_pyo3_options, CrateAttribute, FromPyWithAttribute}; use crate::utils::Ctx; use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned}; 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, ctx: &Ctx) -> (TokenStream, TokenStream) { let Ctx { pyo3_path, .. } = ctx; let mut var_extracts = Vec::new(); let mut variant_names = Vec::new(); let mut error_names = Vec::new(); let mut deprecations = TokenStream::new(); for var in &self.variants { let (struct_derive, dep) = var.build(ctx); deprecations.extend(dep); let ext = quote!({ let maybe_ret = || -> #pyo3_path::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_path::impl_::frompyobject::failed_to_extract_enum( obj.py(), #ty_name, &[#(#variant_names),*], &[#(#error_names),*], &errors ) ) ), deprecations, ) } } 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, ctx: &Ctx) -> (TokenStream, TokenStream) { match &self.ty { ContainerType::StructNewtype(ident, from_py_with) => { self.build_newtype_struct(Some(ident), from_py_with, ctx) } ContainerType::TupleNewtype(from_py_with) => { self.build_newtype_struct(None, from_py_with, ctx) } ContainerType::Tuple(tups) => self.build_tuple_struct(tups, ctx), ContainerType::Struct(tups) => self.build_struct(tups, ctx), } } fn build_newtype_struct( &self, field_ident: Option<&Ident>, from_py_with: &Option, ctx: &Ctx, ) -> (TokenStream, TokenStream) { let Ctx { pyo3_path, .. } = ctx; 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_path::impl_::frompyobject::extract_struct_field(obj, #struct_name, #field_name)? }) }, TokenStream::new(), ), Some(FromPyWithAttribute { value: expr_path, .. }) => ( quote! { Ok(#self_ty { #ident: #pyo3_path::impl_::frompyobject::extract_struct_field_with(#expr_path as fn(_) -> _, obj, #struct_name, #field_name)? }) }, quote_spanned! { expr_path.span() => const _: () = { fn check_from_py_with() { let e = #pyo3_path::impl_::deprecations::GilRefs::new(); #pyo3_path::impl_::deprecations::inspect_fn(#expr_path, &e); e.from_py_with_arg(); } }; }, ), } } else { match from_py_with { None => ( quote!( #pyo3_path::impl_::frompyobject::extract_tuple_struct_field(obj, #struct_name, 0).map(#self_ty) ), TokenStream::new(), ), Some(FromPyWithAttribute { value: expr_path, .. }) => ( quote! ( #pyo3_path::impl_::frompyobject::extract_tuple_struct_field_with(#expr_path as fn(_) -> _, obj, #struct_name, 0).map(#self_ty) ), quote_spanned! { expr_path.span() => const _: () = { fn check_from_py_with() { let e = #pyo3_path::impl_::deprecations::GilRefs::new(); #pyo3_path::impl_::deprecations::inspect_fn(#expr_path, &e); e.from_py_with_arg(); } }; }, ), } } } fn build_tuple_struct( &self, struct_fields: &[TupleStructField], ctx: &Ctx, ) -> (TokenStream, TokenStream) { let Ctx { pyo3_path, .. } = ctx; 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_path::impl_::frompyobject::extract_tuple_struct_field(&#ident, #struct_name, #index)? ), Some(FromPyWithAttribute { value: expr_path, .. }) => quote! ( #pyo3_path::impl_::frompyobject::extract_tuple_struct_field_with(#expr_path as fn(_) -> _, &#ident, #struct_name, #index)? ), } }); let deprecations = struct_fields .iter() .filter_map(|field| { let FromPyWithAttribute { value: expr_path, .. } = field.from_py_with.as_ref()?; Some(quote_spanned! { expr_path.span() => const _: () = { fn check_from_py_with() { let e = #pyo3_path::impl_::deprecations::GilRefs::new(); #pyo3_path::impl_::deprecations::inspect_fn(#expr_path, &e); e.from_py_with_arg(); } }; }) }) .collect::(); ( quote!( match #pyo3_path::types::PyAnyMethods::extract(obj) { ::std::result::Result::Ok((#(#field_idents),*)) => ::std::result::Result::Ok(#self_ty(#(#fields),*)), ::std::result::Result::Err(err) => ::std::result::Result::Err(err), } ), deprecations, ) } fn build_struct( &self, struct_fields: &[NamedStructField<'_>], ctx: &Ctx, ) -> (TokenStream, TokenStream) { let Ctx { pyo3_path, .. } = ctx; 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!(#pyo3_path::types::PyAnyMethods::getattr(obj, #pyo3_path::intern!(obj.py(), #name))) } FieldGetter::GetAttr(None) => { quote!(#pyo3_path::types::PyAnyMethods::getattr(obj, #pyo3_path::intern!(obj.py(), #field_name))) } FieldGetter::GetItem(Some(syn::Lit::Str(key))) => { quote!(#pyo3_path::types::PyAnyMethods::get_item(obj, #pyo3_path::intern!(obj.py(), #key))) } FieldGetter::GetItem(Some(key)) => { quote!(#pyo3_path::types::PyAnyMethods::get_item(obj, #key)) } FieldGetter::GetItem(None) => { quote!(#pyo3_path::types::PyAnyMethods::get_item(obj, #pyo3_path::intern!(obj.py(), #field_name))) } }; let extractor = match &field.from_py_with { None => { quote!(#pyo3_path::impl_::frompyobject::extract_struct_field(&#getter?, #struct_name, #field_name)?) } Some(FromPyWithAttribute { value: expr_path, .. }) => { quote! (#pyo3_path::impl_::frompyobject::extract_struct_field_with(#expr_path as fn(_) -> _, &#getter?, #struct_name, #field_name)?) } }; fields.push(quote!(#ident: #extractor)); } let deprecations = struct_fields .iter() .filter_map(|field| { let FromPyWithAttribute { value: expr_path, .. } = field.from_py_with.as_ref()?; Some(quote_spanned! { expr_path.span() => const _: () = { fn check_from_py_with() { let e = #pyo3_path::impl_::deprecations::GilRefs::new(); #pyo3_path::impl_::deprecations::inspect_fn(#expr_path, &e); e.from_py_with_arg(); } }; }) }) .collect::(); ( quote!(::std::result::Result::Ok(#self_ty{#fields})), deprecations, ) } } #[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!('py)); parse_quote!('py) }; 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 ctx = &Ctx::new(&options.krate, None); let Ctx { pyo3_path, .. } = &ctx; let (derives, from_py_with_deprecations) = 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(ctx) } 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(ctx) } syn::Data::Union(_) => bail_spanned!( tokens.span() => "#[derive(FromPyObject)] is not supported for unions" ), }; let ident = &tokens.ident; Ok(quote!( #[automatically_derived] impl #trait_generics #pyo3_path::FromPyObject<#lt_param> for #ident #generics #where_clause { fn extract_bound(obj: &#pyo3_path::Bound<#lt_param, #pyo3_path::PyAny>) -> #pyo3_path::PyResult { #derives } } #from_py_with_deprecations )) } pyo3-macros-backend-0.22.2/src/konst.rs000064400000000000000000000053231046102023000157460ustar 00000000000000use std::borrow::Cow; use std::ffi::CString; use crate::utils::{Ctx, LitCStr}; use crate::{ attributes::{self, get_pyo3_options, take_attributes, NameAttribute}, deprecations::Deprecations, }; use proc_macro2::{Ident, Span}; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, spanned::Spanned, Result, }; pub struct ConstSpec<'ctx> { pub rust_ident: syn::Ident, pub attributes: ConstAttributes<'ctx>, } 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, ctx: &Ctx) -> LitCStr { let name = self.python_name().to_string(); LitCStr::new(CString::new(name).unwrap(), Span::call_site(), ctx) } } pub struct ConstAttributes<'ctx> { pub is_class_attr: bool, pub name: Option, pub deprecations: Deprecations<'ctx>, } 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<'ctx> ConstAttributes<'ctx> { pub fn from_attrs(attrs: &mut Vec, ctx: &'ctx Ctx) -> syn::Result { let mut attributes = ConstAttributes { is_class_attr: false, name: None, deprecations: Deprecations::new(ctx), }; 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.22.2/src/lib.rs000064400000000000000000000015061046102023000153550ustar 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 pyversions; mod quotes; pub use frompyobject::build_derive_from_pyobject; pub use module::{pymodule_function_impl, pymodule_module_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.22.2/src/method.rs000064400000000000000000001304431046102023000160720ustar 00000000000000use std::borrow::Cow; use std::ffi::CString; use std::fmt::Display; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ext::IdentExt, spanned::Spanned, Ident, Result}; use crate::deprecations::deprecate_trailing_option_default; use crate::utils::{Ctx, LitCStr}; use crate::{ attributes::{FromPyWithAttribute, TextSignatureAttribute, TextSignatureAttributeValue}, deprecations::{Deprecation, Deprecations}, params::{impl_arg_params, Holders}, pyfunction::{ FunctionSignature, PyFunctionArgPyO3Attributes, PyFunctionOptions, SignatureAttribute, }, quotes, utils::{self, is_abi3, PythonDoc}, }; #[derive(Clone, Debug)] pub struct RegularArg<'a> { pub name: Cow<'a, syn::Ident>, pub ty: &'a syn::Type, pub from_py_with: Option, pub default_value: Option, pub option_wrapped_type: Option<&'a syn::Type>, } /// Pythons *args argument #[derive(Clone, Debug)] pub struct VarargsArg<'a> { pub name: Cow<'a, syn::Ident>, pub ty: &'a syn::Type, } /// Pythons **kwarg argument #[derive(Clone, Debug)] pub struct KwargsArg<'a> { pub name: Cow<'a, syn::Ident>, pub ty: &'a syn::Type, } #[derive(Clone, Debug)] pub struct CancelHandleArg<'a> { pub name: &'a syn::Ident, pub ty: &'a syn::Type, } #[derive(Clone, Debug)] pub struct PyArg<'a> { pub name: &'a syn::Ident, pub ty: &'a syn::Type, } #[derive(Clone, Debug)] pub enum FnArg<'a> { Regular(RegularArg<'a>), VarArgs(VarargsArg<'a>), KwArgs(KwargsArg<'a>), Py(PyArg<'a>), CancelHandle(CancelHandleArg<'a>), } impl<'a> FnArg<'a> { pub fn name(&self) -> &syn::Ident { match self { FnArg::Regular(RegularArg { name, .. }) => name, FnArg::VarArgs(VarargsArg { name, .. }) => name, FnArg::KwArgs(KwargsArg { name, .. }) => name, FnArg::Py(PyArg { name, .. }) => name, FnArg::CancelHandle(CancelHandleArg { name, .. }) => name, } } pub fn ty(&self) -> &'a syn::Type { match self { FnArg::Regular(RegularArg { ty, .. }) => ty, FnArg::VarArgs(VarargsArg { ty, .. }) => ty, FnArg::KwArgs(KwargsArg { ty, .. }) => ty, FnArg::Py(PyArg { ty, .. }) => ty, FnArg::CancelHandle(CancelHandleArg { ty, .. }) => ty, } } #[allow(clippy::wrong_self_convention)] pub fn from_py_with(&self) -> Option<&FromPyWithAttribute> { if let FnArg::Regular(RegularArg { from_py_with, .. }) = self { from_py_with.as_ref() } else { None } } pub fn to_varargs_mut(&mut self) -> Result<&mut Self> { if let Self::Regular(RegularArg { name, ty, option_wrapped_type: None, .. }) = self { *self = Self::VarArgs(VarargsArg { name: name.clone(), ty, }); Ok(self) } else { bail_spanned!(self.name().span() => "args cannot be optional") } } pub fn to_kwargs_mut(&mut self) -> Result<&mut Self> { if let Self::Regular(RegularArg { name, ty, option_wrapped_type: Some(..), .. }) = self { *self = Self::KwArgs(KwargsArg { name: name.clone(), ty, }); Ok(self) } else { bail_spanned!(self.name().span() => "kwargs must be Option<_>") } } /// 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 PyFunctionArgPyO3Attributes { from_py_with, cancel_handle, } = 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)), }; if utils::is_python(&cap.ty) { return Ok(Self::Py(PyArg { name: ident, ty: &cap.ty, })); } if cancel_handle.is_some() { // `PyFunctionArgPyO3Attributes::from_attrs` validates that // only compatible attributes are specified, either // `cancel_handle` or `from_py_with`, dublicates and any // combination of the two are already rejected. return Ok(Self::CancelHandle(CancelHandleArg { name: ident, ty: &cap.ty, })); } Ok(Self::Regular(RegularArg { name: Cow::Borrowed(ident), ty: &cap.ty, from_py_with, default_value: None, option_wrapped_type: utils::option_type_argument(&cap.ty), })) } } } } 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) } /// Represents what kind of a function a pyfunction or pymethod is #[derive(Clone, Debug)] pub enum FnType { /// Represents a pymethod annotated with `#[getter]` Getter(SelfType), /// Represents a pymethod annotated with `#[setter]` Setter(SelfType), /// Represents a regular pymethod Fn(SelfType), /// Represents a pymethod annotated with `#[new]`, i.e. the `__new__` dunder. FnNew, /// Represents a pymethod annotated with both `#[new]` and `#[classmethod]` (in either order) FnNewClass(Span), /// Represents a pymethod annotated with `#[classmethod]`, like a `@classmethod` FnClass(Span), /// Represents a pyfunction or a pymethod annotated with `#[staticmethod]`, like a `@staticmethod` FnStatic, /// Represents a pyfunction annotated with `#[pyo3(pass_module)] FnModule(Span), /// Represents a pymethod or associated constant annotated with `#[classattr]` 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 signature_attribute_allowed(&self) -> bool { match self { FnType::Fn(_) | FnType::FnNew | FnType::FnStatic | FnType::FnClass(_) | FnType::FnNewClass(_) | FnType::FnModule(_) => true, // Setter, Getter and ClassAttribute all have fixed signatures (either take 0 or 1 // arguments) so cannot have a `signature = (...)` attribute. FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => false, } } pub fn self_arg( &self, cls: Option<&syn::Type>, error_mode: ExtractErrorMode, holders: &mut Holders, ctx: &Ctx, ) -> Option { let Ctx { pyo3_path, .. } = ctx; 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, holders, ctx, ); syn::Token![,](Span::call_site()).to_tokens(&mut receiver); Some(receiver) } FnType::FnClass(span) | FnType::FnNewClass(span) => { let py = syn::Ident::new("py", Span::call_site()); let slf: Ident = syn::Ident::new("_slf_ref", Span::call_site()); let pyo3_path = pyo3_path.to_tokens_spanned(*span); let ret = quote_spanned! { *span => #[allow(clippy::useless_conversion)] ::std::convert::Into::into( #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &*(#slf as *const _ as *const *mut _)) .downcast_unchecked::<#pyo3_path::types::PyType>() ), }; Some(ret) } FnType::FnModule(span) => { let py = syn::Ident::new("py", Span::call_site()); let slf: Ident = syn::Ident::new("_slf_ref", Span::call_site()); let pyo3_path = pyo3_path.to_tokens_spanned(*span); let ret = quote_spanned! { *span => #[allow(clippy::useless_conversion)] ::std::convert::Into::into( #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &*(#slf as *const _ as *const *mut _)) .downcast_unchecked::<#pyo3_path::types::PyModule>() ), }; Some(ret) } FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => None, } } } #[derive(Clone, Debug)] pub enum SelfType { Receiver { mutable: bool, span: Span }, TryFromBoundRef(Span), } #[derive(Clone, Copy)] pub enum ExtractErrorMode { NotImplemented, Raise, } impl ExtractErrorMode { pub fn handle_error(self, extract: TokenStream, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; match self { ExtractErrorMode::Raise => quote! { #extract? }, ExtractErrorMode::NotImplemented => quote! { match #extract { ::std::result::Result::Ok(value) => value, ::std::result::Result::Err(_) => { return #pyo3_path::callback::convert(py, py.NotImplemented()); }, } }, } } } impl SelfType { pub fn receiver( &self, cls: &syn::Type, error_mode: ExtractErrorMode, holders: &mut Holders, ctx: &Ctx, ) -> 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()); let Ctx { pyo3_path, .. } = ctx; 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) }; let holder = holders.push_holder(*span); let pyo3_path = pyo3_path.to_tokens_spanned(*span); error_mode.handle_error( quote_spanned! { *span => #pyo3_path::impl_::extract_argument::#method::<#cls>( #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &#slf).0, &mut #holder, ) }, ctx, ) } SelfType::TryFromBoundRef(span) => { let pyo3_path = pyo3_path.to_tokens_spanned(*span); error_mode.handle_error( quote_spanned! { *span => #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &#slf).downcast::<#cls>() .map_err(::std::convert::Into::<#pyo3_path::PyErr>::into) .and_then( #[allow(unknown_lints, clippy::unnecessary_fallible_conversions)] // In case slf is Py (unknown_lints can be removed when MSRV is 1.75+) |bound| ::std::convert::TryFrom::try_from(bound).map_err(::std::convert::Into::into) ) }, ctx ) } } } } /// 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 !is_abi3() { // FIXME: available in the stable ABI since 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 convention: CallingConvention, pub text_signature: Option, pub asyncness: Option, pub unsafety: Option, pub deprecations: Deprecations<'a>, } 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::TryFromBoundRef(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, ctx: &'a Ctx, ) -> Result> { let PyFunctionOptions { text_signature, name, signature, .. } = options; let mut python_name = name.map(|name| name.value.0); let mut deprecations = Deprecations::new(ctx); 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 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, text_signature, asyncness: sig.asyncness, unsafety: sig.unsafety, deprecations, }) } pub fn null_terminated_python_name(&self, ctx: &Ctx) -> LitCStr { let name = self.python_name.to_string(); let name = CString::new(name).unwrap(); LitCStr::new(name, self.python_name.span(), ctx) } 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 `&Bound` 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>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, output_span, } = ctx; let mut cancel_handle_iter = self .signature .arguments .iter() .filter(|arg| matches!(arg, FnArg::CancelHandle(..))); let cancel_handle = cancel_handle_iter.next(); if let Some(FnArg::CancelHandle(CancelHandleArg { name, .. })) = cancel_handle { ensure_spanned!(self.asyncness.is_some(), name.span() => "`cancel_handle` attribute can only be used with `async fn`"); if let Some(FnArg::CancelHandle(CancelHandleArg { name, .. })) = cancel_handle_iter.next() { bail_spanned!(name.span() => "`cancel_handle` may only be specified once"); } } if self.asyncness.is_some() { ensure_spanned!( cfg!(feature = "experimental-async"), self.asyncness.span() => "async functions are only supported with the `experimental-async` feature" ); } let rust_call = |args: Vec, holders: &mut Holders| { let mut self_arg = || self.tp.self_arg(cls, ExtractErrorMode::Raise, holders, ctx); let call = if self.asyncness.is_some() { let throw_callback = if cancel_handle.is_some() { quote! { Some(__throw_callback) } } else { quote! { None } }; let python_name = &self.python_name; let qualname_prefix = match cls { Some(cls) => quote!(Some(<#cls as #pyo3_path::PyTypeInfo>::NAME)), None => quote!(None), }; let arg_names = (0..args.len()) .map(|i| format_ident!("arg_{}", i)) .collect::>(); let future = match self.tp { FnType::Fn(SelfType::Receiver { mutable: false, .. }) => { quote! {{ #(let #arg_names = #args;)* let __guard = #pyo3_path::impl_::coroutine::RefGuard::<#cls>::new(&#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf))?; async move { function(&__guard, #(#arg_names),*).await } }} } FnType::Fn(SelfType::Receiver { mutable: true, .. }) => { quote! {{ #(let #arg_names = #args;)* let mut __guard = #pyo3_path::impl_::coroutine::RefMutGuard::<#cls>::new(&#pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &_slf))?; async move { function(&mut __guard, #(#arg_names),*).await } }} } _ => { if let Some(self_arg) = self_arg() { let self_checker = holders.push_gil_refs_checker(self_arg.span()); quote! { function( // NB #self_arg includes a comma, so none inserted here #pyo3_path::impl_::deprecations::inspect_type(#self_arg &#self_checker), #(#args),* ) } } else { quote! { function(#(#args),*) } } } }; let mut call = quote! {{ let future = #future; #pyo3_path::impl_::coroutine::new_coroutine( #pyo3_path::intern!(py, stringify!(#python_name)), #qualname_prefix, #throw_callback, async move { #pyo3_path::impl_::wrap::OkWrap::wrap(future.await) }, ) }}; if cancel_handle.is_some() { call = quote! {{ let __cancel_handle = #pyo3_path::coroutine::CancelHandle::new(); let __throw_callback = __cancel_handle.throw_callback(); #call }}; } call } else if let Some(self_arg) = self_arg() { let self_checker = holders.push_gil_refs_checker(self_arg.span()); quote! { function( // NB #self_arg includes a comma, so none inserted here #pyo3_path::impl_::deprecations::inspect_type(#self_arg &#self_checker), #(#args),* ) } } else { quote! { function(#(#args),*) } }; // We must assign the output_span to the return value of the call, // but *not* of the call itself otherwise the spans get really weird let ret_expr = quote! { let ret = #call; }; let ret_var = quote_spanned! {*output_span=> ret }; let return_conversion = quotes::map_result_into_ptr(quotes::ok_wrap(ret_var, ctx), ctx); quote! { { #ret_expr #return_conversion } } }; let func_name = &self.name; let rust_name = if let Some(cls) = cls { quote!(#cls::#func_name) } else { quote!(#func_name) }; let deprecation = deprecate_trailing_option_default(self); Ok(match self.convention { CallingConvention::Noargs => { let mut holders = Holders::new(); let args = self .signature .arguments .iter() .map(|arg| match arg { FnArg::Py(..) => quote!(py), FnArg::CancelHandle(..) => quote!(__cancel_handle), _ => unreachable!("`CallingConvention::Noargs` should not contain any arguments (reaching Python) except for `self`, which is handled below."), }) .collect(); let call = rust_call(args, &mut holders); let check_gil_refs = holders.check_gil_refs(); let init_holders = holders.init_holders(ctx); quote! { unsafe fn #ident<'py>( py: #pyo3_path::Python<'py>, _slf: *mut #pyo3_path::ffi::PyObject, ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { #deprecation let _slf_ref = &_slf; let function = #rust_name; // Shadow the function name to avoid #3017 #init_holders let result = #call; #check_gil_refs result } } } CallingConvention::Fastcall => { let mut holders = Holders::new(); let (arg_convert, args) = impl_arg_params(self, cls, true, &mut holders, ctx); let call = rust_call(args, &mut holders); let init_holders = holders.init_holders(ctx); let check_gil_refs = holders.check_gil_refs(); quote! { unsafe fn #ident<'py>( py: #pyo3_path::Python<'py>, _slf: *mut #pyo3_path::ffi::PyObject, _args: *const *mut #pyo3_path::ffi::PyObject, _nargs: #pyo3_path::ffi::Py_ssize_t, _kwnames: *mut #pyo3_path::ffi::PyObject ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { #deprecation let _slf_ref = &_slf; let function = #rust_name; // Shadow the function name to avoid #3017 #arg_convert #init_holders let result = #call; #check_gil_refs result } } } CallingConvention::Varargs => { let mut holders = Holders::new(); let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx); let call = rust_call(args, &mut holders); let init_holders = holders.init_holders(ctx); let check_gil_refs = holders.check_gil_refs(); quote! { unsafe fn #ident<'py>( py: #pyo3_path::Python<'py>, _slf: *mut #pyo3_path::ffi::PyObject, _args: *mut #pyo3_path::ffi::PyObject, _kwargs: *mut #pyo3_path::ffi::PyObject ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { #deprecation let _slf_ref = &_slf; let function = #rust_name; // Shadow the function name to avoid #3017 #arg_convert #init_holders let result = #call; #check_gil_refs result } } } CallingConvention::TpNew => { let mut holders = Holders::new(); let (arg_convert, args) = impl_arg_params(self, cls, false, &mut holders, ctx); let self_arg = self .tp .self_arg(cls, ExtractErrorMode::Raise, &mut holders, ctx); let call = quote_spanned! {*output_span=> #rust_name(#self_arg #(#args),*) }; let init_holders = holders.init_holders(ctx); let check_gil_refs = holders.check_gil_refs(); quote! { unsafe fn #ident( py: #pyo3_path::Python<'_>, _slf: *mut #pyo3_path::ffi::PyTypeObject, _args: *mut #pyo3_path::ffi::PyObject, _kwargs: *mut #pyo3_path::ffi::PyObject ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { use #pyo3_path::callback::IntoPyCallbackOutput; #deprecation let _slf_ref = &_slf; let function = #rust_name; // Shadow the function name to avoid #3017 #arg_convert #init_holders let result = #call; let initializer: #pyo3_path::PyClassInitializer::<#cls> = result.convert(py)?; #check_gil_refs #pyo3_path::impl_::pymethods::tp_new_impl(py, initializer, _slf) } } } }) } /// Return a `PyMethodDef` constructor for this function, matching the selected /// calling convention. pub fn get_methoddef(&self, wrapper: impl ToTokens, doc: &PythonDoc, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let python_name = self.null_terminated_python_name(ctx); match self.convention { CallingConvention::Noargs => quote! { #pyo3_path::impl_::pymethods::PyMethodDef::noargs( #python_name, { unsafe extern "C" fn trampoline( _slf: *mut #pyo3_path::ffi::PyObject, _args: *mut #pyo3_path::ffi::PyObject, ) -> *mut #pyo3_path::ffi::PyObject { #pyo3_path::impl_::trampoline::noargs( _slf, _args, #wrapper ) } trampoline }, #doc, ) }, CallingConvention::Fastcall => quote! { #pyo3_path::impl_::pymethods::PyMethodDef::fastcall_cfunction_with_keywords( #python_name, { unsafe extern "C" fn trampoline( _slf: *mut #pyo3_path::ffi::PyObject, _args: *const *mut #pyo3_path::ffi::PyObject, _nargs: #pyo3_path::ffi::Py_ssize_t, _kwnames: *mut #pyo3_path::ffi::PyObject ) -> *mut #pyo3_path::ffi::PyObject { #pyo3_path::impl_::trampoline::fastcall_with_keywords( _slf, _args, _nargs, _kwnames, #wrapper ) } trampoline }, #doc, ) }, CallingConvention::Varargs => quote! { #pyo3_path::impl_::pymethods::PyMethodDef::cfunction_with_keywords( #python_name, { unsafe extern "C" fn trampoline( _slf: *mut #pyo3_path::ffi::PyObject, _args: *mut #pyo3_path::ffi::PyObject, _kwargs: *mut #pyo3_path::ffi::PyObject, ) -> *mut #pyo3_path::ffi::PyObject { #pyo3_path::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], ctx: &Ctx) -> PythonDoc { let text_signature = self .text_signature_call_signature() .map(|sig| format!("{}{}", self.python_name, sig)); utils::get_doc(attrs, text_signature, ctx) } /// 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(_) => { debug_assert!(!fn_type.signature_attribute_allowed()); bail_spanned!(signature.kw.span() => "`signature` not allowed with `getter`") } FnType::Setter(_) => { debug_assert!(!fn_type.signature_attribute_allowed()); bail_spanned!(signature.kw.span() => "`signature` not allowed with `setter`") } FnType::ClassAttribute => { debug_assert!(!fn_type.signature_attribute_allowed()); bail_spanned!(signature.kw.span() => "`signature` not allowed with `classattr`") } _ => debug_assert!(fn_type.signature_attribute_allowed()), } } 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.22.2/src/module.rs000064400000000000000000000572611046102023000161050ustar 00000000000000//! Code generation for the function that initializes a python module and adds classes and function. use crate::{ attributes::{ self, kw, take_attributes, take_pyo3_options, CrateAttribute, ModuleAttribute, NameAttribute, SubmoduleAttribute, }, get_doc, pyclass::PyClassPyO3Option, pyfunction::{impl_wrap_pyfunction, PyFunctionOptions}, utils::{Ctx, LitCStr, PyO3CratePath}, }; use proc_macro2::{Span, TokenStream}; use quote::quote; use std::ffi::CString; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, parse_quote, parse_quote_spanned, punctuated::Punctuated, spanned::Spanned, token::Comma, Item, Meta, Path, Result, }; #[derive(Default)] pub struct PyModuleOptions { krate: Option, name: Option, module: Option, submodule: Option, } impl Parse for PyModuleOptions { fn parse(input: ParseStream<'_>) -> syn::Result { let mut options: PyModuleOptions = Default::default(); options.add_attributes( Punctuated::::parse_terminated(input)?, )?; Ok(options) } } impl PyModuleOptions { fn take_pyo3_options(&mut self, attrs: &mut Vec) -> Result<()> { self.add_attributes(take_pyo3_options(attrs)?) } 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 { PyModulePyO3Option::Crate(krate) => set_option!(krate), PyModulePyO3Option::Name(name) => set_option!(name), PyModulePyO3Option::Module(module) => set_option!(module), PyModulePyO3Option::Submodule(submodule) => set_option!(submodule), } } Ok(()) } } pub fn pymodule_module_impl( module: &mut syn::ItemMod, mut options: PyModuleOptions, ) -> Result { let syn::ItemMod { attrs, vis, unsafety: _, ident, mod_token, content, semi: _, } = module; let items = if let Some((_, items)) = content { items } else { bail_spanned!(mod_token.span() => "`#[pymodule]` can only be used on inline modules") }; options.take_pyo3_options(attrs)?; let ctx = &Ctx::new(&options.krate, None); let Ctx { pyo3_path, .. } = ctx; let doc = get_doc(attrs, None, ctx); let name = options .name .map_or_else(|| ident.unraw(), |name| name.value.0); let full_name = if let Some(module) = &options.module { format!("{}.{}", module.value.value(), name) } else { name.to_string() }; let mut module_items = Vec::new(); let mut module_items_cfg_attrs = Vec::new(); fn extract_use_items( source: &syn::UseTree, cfg_attrs: &[syn::Attribute], target_items: &mut Vec, target_cfg_attrs: &mut Vec>, ) -> Result<()> { match source { syn::UseTree::Name(name) => { target_items.push(name.ident.clone()); target_cfg_attrs.push(cfg_attrs.to_vec()); } syn::UseTree::Path(path) => { extract_use_items(&path.tree, cfg_attrs, target_items, target_cfg_attrs)? } syn::UseTree::Group(group) => { for tree in &group.items { extract_use_items(tree, cfg_attrs, target_items, target_cfg_attrs)? } } syn::UseTree::Glob(glob) => { bail_spanned!(glob.span() => "#[pymodule] cannot import glob statements") } syn::UseTree::Rename(rename) => { target_items.push(rename.rename.clone()); target_cfg_attrs.push(cfg_attrs.to_vec()); } } Ok(()) } let mut pymodule_init = None; for item in &mut *items { match item { Item::Use(item_use) => { let is_pymodule_export = find_and_remove_attribute(&mut item_use.attrs, "pymodule_export"); if is_pymodule_export { let cfg_attrs = get_cfg_attributes(&item_use.attrs); extract_use_items( &item_use.tree, &cfg_attrs, &mut module_items, &mut module_items_cfg_attrs, )?; } } Item::Fn(item_fn) => { ensure_spanned!( !has_attribute(&item_fn.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` statements" ); let is_pymodule_init = find_and_remove_attribute(&mut item_fn.attrs, "pymodule_init"); let ident = &item_fn.sig.ident; if is_pymodule_init { ensure_spanned!( !has_attribute(&item_fn.attrs, "pyfunction"), item_fn.span() => "`#[pyfunction]` cannot be used alongside `#[pymodule_init]`" ); ensure_spanned!(pymodule_init.is_none(), item_fn.span() => "only one `#[pymodule_init]` may be specified"); pymodule_init = Some(quote! { #ident(module)?; }); } else if has_attribute(&item_fn.attrs, "pyfunction") || has_attribute_with_namespace( &item_fn.attrs, Some(pyo3_path), &["pyfunction"], ) || has_attribute_with_namespace( &item_fn.attrs, Some(pyo3_path), &["prelude", "pyfunction"], ) { module_items.push(ident.clone()); module_items_cfg_attrs.push(get_cfg_attributes(&item_fn.attrs)); } } Item::Struct(item_struct) => { ensure_spanned!( !has_attribute(&item_struct.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` statements" ); if has_attribute(&item_struct.attrs, "pyclass") || has_attribute_with_namespace( &item_struct.attrs, Some(pyo3_path), &["pyclass"], ) || has_attribute_with_namespace( &item_struct.attrs, Some(pyo3_path), &["prelude", "pyclass"], ) { module_items.push(item_struct.ident.clone()); module_items_cfg_attrs.push(get_cfg_attributes(&item_struct.attrs)); if !has_pyo3_module_declared::( &item_struct.attrs, "pyclass", |option| matches!(option, PyClassPyO3Option::Module(_)), )? { set_module_attribute(&mut item_struct.attrs, &full_name); } } } Item::Enum(item_enum) => { ensure_spanned!( !has_attribute(&item_enum.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` statements" ); if has_attribute(&item_enum.attrs, "pyclass") || has_attribute_with_namespace(&item_enum.attrs, Some(pyo3_path), &["pyclass"]) || has_attribute_with_namespace( &item_enum.attrs, Some(pyo3_path), &["prelude", "pyclass"], ) { module_items.push(item_enum.ident.clone()); module_items_cfg_attrs.push(get_cfg_attributes(&item_enum.attrs)); if !has_pyo3_module_declared::( &item_enum.attrs, "pyclass", |option| matches!(option, PyClassPyO3Option::Module(_)), )? { set_module_attribute(&mut item_enum.attrs, &full_name); } } } Item::Mod(item_mod) => { ensure_spanned!( !has_attribute(&item_mod.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` statements" ); if has_attribute(&item_mod.attrs, "pymodule") || has_attribute_with_namespace(&item_mod.attrs, Some(pyo3_path), &["pymodule"]) || has_attribute_with_namespace( &item_mod.attrs, Some(pyo3_path), &["prelude", "pymodule"], ) { module_items.push(item_mod.ident.clone()); module_items_cfg_attrs.push(get_cfg_attributes(&item_mod.attrs)); if !has_pyo3_module_declared::( &item_mod.attrs, "pymodule", |option| matches!(option, PyModulePyO3Option::Module(_)), )? { set_module_attribute(&mut item_mod.attrs, &full_name); } } } Item::ForeignMod(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` statements" ); } Item::Trait(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` statements" ); } Item::Const(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` statements" ); } Item::Static(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` statements" ); } Item::Macro(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` statements" ); } Item::ExternCrate(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` statements" ); } Item::Impl(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` statements" ); } Item::TraitAlias(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` statements" ); } Item::Type(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` statements" ); } Item::Union(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` statements" ); } _ => (), } } let module_def = quote! {{ use #pyo3_path::impl_::pymodule as impl_; const INITIALIZER: impl_::ModuleInitializer = impl_::ModuleInitializer(__pyo3_pymodule); unsafe { impl_::ModuleDef::new( __PYO3_NAME, #doc, INITIALIZER ) } }}; let initialization = module_initialization(&name, ctx, module_def, options.submodule.is_some()); Ok(quote!( #(#attrs)* #vis #mod_token #ident { #(#items)* #initialization fn __pyo3_pymodule(module: &#pyo3_path::Bound<'_, #pyo3_path::types::PyModule>) -> #pyo3_path::PyResult<()> { use #pyo3_path::impl_::pymodule::PyAddToModule; #( #(#module_items_cfg_attrs)* #module_items::_PYO3_DEF.add_to_module(module)?; )* #pymodule_init Ok(()) } } )) } /// Generates the function that is called by the python interpreter to initialize the native /// module pub fn pymodule_function_impl( function: &mut syn::ItemFn, mut options: PyModuleOptions, ) -> Result { options.take_pyo3_options(&mut function.attrs)?; process_functions_in_module(&options, function)?; let ctx = &Ctx::new(&options.krate, None); let stmts = std::mem::take(&mut function.block.stmts); let Ctx { pyo3_path, .. } = ctx; let ident = &function.sig.ident; let name = options .name .map_or_else(|| ident.unraw(), |name| name.value.0); let vis = &function.vis; let doc = get_doc(&function.attrs, None, ctx); let initialization = module_initialization(&name, ctx, quote! { MakeDef::make_def() }, false); // Module function called with optional Python<'_> marker as first arg, followed by the module. let mut module_args = Vec::new(); if function.sig.inputs.len() == 2 { module_args.push(quote!(module.py())); } module_args .push(quote!(::std::convert::Into::into(#pyo3_path::impl_::pymethods::BoundRef(module)))); let extractors = function .sig .inputs .iter() .filter_map(|param| { if let syn::FnArg::Typed(pat_type) = param { if let syn::Pat::Ident(pat_ident) = &*pat_type.pat { let ident: &syn::Ident = &pat_ident.ident; return Some([ parse_quote!{ let check_gil_refs = #pyo3_path::impl_::deprecations::GilRefs::new(); }, parse_quote! { let #ident = #pyo3_path::impl_::deprecations::inspect_type(#ident, &check_gil_refs); }, parse_quote_spanned! { pat_type.span() => check_gil_refs.function_arg(); }, ]); } } None }) .flatten(); function.block.stmts = extractors.chain(stmts).collect(); function .attrs .push(parse_quote!(#[allow(clippy::used_underscore_binding)])); Ok(quote! { #[doc(hidden)] #vis mod #ident { #initialization } // 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) #[allow(unknown_lints, non_local_definitions)] impl #ident::MakeDef { const fn make_def() -> #pyo3_path::impl_::pymodule::ModuleDef { fn __pyo3_pymodule(module: &#pyo3_path::Bound<'_, #pyo3_path::types::PyModule>) -> #pyo3_path::PyResult<()> { #ident(#(#module_args),*) } const INITIALIZER: #pyo3_path::impl_::pymodule::ModuleInitializer = #pyo3_path::impl_::pymodule::ModuleInitializer(__pyo3_pymodule); unsafe { #pyo3_path::impl_::pymodule::ModuleDef::new( #ident::__PYO3_NAME, #doc, INITIALIZER ) } } } }) } fn module_initialization( name: &syn::Ident, ctx: &Ctx, module_def: TokenStream, is_submodule: bool, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let pyinit_symbol = format!("PyInit_{}", name); let name = name.to_string(); let pyo3_name = LitCStr::new(CString::new(name).unwrap(), Span::call_site(), ctx); let mut result = quote! { #[doc(hidden)] pub const __PYO3_NAME: &'static ::std::ffi::CStr = #pyo3_name; pub(super) struct MakeDef; #[doc(hidden)] pub static _PYO3_DEF: #pyo3_path::impl_::pymodule::ModuleDef = #module_def; }; if !is_submodule { result.extend(quote! { /// This autogenerated function is called by the python interpreter when importing /// the module. #[doc(hidden)] #[export_name = #pyinit_symbol] pub unsafe extern "C" fn __pyo3_init() -> *mut #pyo3_path::ffi::PyObject { #pyo3_path::impl_::trampoline::module_init(|py| _PYO3_DEF.make_module(py)) } }); } result } /// Finds and takes care of the #[pyfn(...)] in `#[pymodule]` fn process_functions_in_module(options: &PyModuleOptions, func: &mut syn::ItemFn) -> Result<()> { let ctx = &Ctx::new(&options.krate, None); let Ctx { pyo3_path, .. } = ctx; let mut stmts: Vec = Vec::new(); #[cfg(feature = "gil-refs")] let imports = quote!(use #pyo3_path::{PyNativeType, types::PyModuleMethods};); #[cfg(not(feature = "gil-refs"))] let imports = quote!(use #pyo3_path::types::PyModuleMethods;); for mut stmt in func.block.stmts.drain(..) { if let syn::Stmt::Item(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 { #[allow(unknown_lints, unused_imports, redundant_imports)] #imports #module_name.as_borrowed().add_function(#pyo3_path::wrap_pyfunction!(#name, #module_name.as_borrowed())?)?; } }; 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) } fn get_cfg_attributes(attrs: &[syn::Attribute]) -> Vec { attrs .iter() .filter(|attr| attr.path().is_ident("cfg")) .cloned() .collect() } fn find_and_remove_attribute(attrs: &mut Vec, ident: &str) -> bool { let mut found = false; attrs.retain(|attr| { if attr.path().is_ident(ident) { found = true; false } else { true } }); found } enum IdentOrStr<'a> { Str(&'a str), Ident(syn::Ident), } impl<'a> PartialEq for IdentOrStr<'a> { fn eq(&self, other: &syn::Ident) -> bool { match self { IdentOrStr::Str(s) => other == s, IdentOrStr::Ident(i) => other == i, } } } fn has_attribute(attrs: &[syn::Attribute], ident: &str) -> bool { has_attribute_with_namespace(attrs, None, &[ident]) } fn has_attribute_with_namespace( attrs: &[syn::Attribute], crate_path: Option<&PyO3CratePath>, idents: &[&str], ) -> bool { let mut segments = vec![]; if let Some(c) = crate_path { match c { PyO3CratePath::Given(paths) => { for p in &paths.segments { segments.push(IdentOrStr::Ident(p.ident.clone())); } } PyO3CratePath::Default => segments.push(IdentOrStr::Str("pyo3")), } }; for i in idents { segments.push(IdentOrStr::Str(i)); } attrs.iter().any(|attr| { segments .iter() .eq(attr.path().segments.iter().map(|v| &v.ident)) }) } fn set_module_attribute(attrs: &mut Vec, module_name: &str) { attrs.push(parse_quote!(#[pyo3(module = #module_name)])); } fn has_pyo3_module_declared( attrs: &[syn::Attribute], root_attribute_name: &str, is_module_option: impl Fn(&T) -> bool + Copy, ) -> Result { for attr in attrs { if (attr.path().is_ident("pyo3") || attr.path().is_ident(root_attribute_name)) && matches!(attr.meta, Meta::List(_)) { for option in &attr.parse_args_with(Punctuated::::parse_terminated)? { if is_module_option(option) { return Ok(true); } } } } Ok(false) } enum PyModulePyO3Option { Submodule(SubmoduleAttribute), Crate(CrateAttribute), Name(NameAttribute), Module(ModuleAttribute), } 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 if lookahead.peek(attributes::kw::module) { input.parse().map(PyModulePyO3Option::Module) } else if lookahead.peek(attributes::kw::submodule) { input.parse().map(PyModulePyO3Option::Submodule) } else { Err(lookahead.error()) } } } pyo3-macros-backend-0.22.2/src/params.rs000064400000000000000000000311071046102023000160720ustar 00000000000000use crate::utils::Ctx; use crate::{ method::{FnArg, FnSpec, RegularArg}, pyfunction::FunctionSignature, quotes::some_wrap, }; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned}; use syn::spanned::Spanned; pub struct Holders { holders: Vec, gil_refs_checkers: Vec, } impl Holders { pub fn new() -> Self { Holders { holders: Vec::new(), gil_refs_checkers: Vec::new(), } } pub fn push_holder(&mut self, span: Span) -> syn::Ident { let holder = syn::Ident::new(&format!("holder_{}", self.holders.len()), span); self.holders.push(holder.clone()); holder } pub fn push_gil_refs_checker(&mut self, span: Span) -> syn::Ident { let gil_refs_checker = syn::Ident::new( &format!("gil_refs_checker_{}", self.gil_refs_checkers.len()), span, ); self.gil_refs_checkers .push(GilRefChecker::FunctionArg(gil_refs_checker.clone())); gil_refs_checker } pub fn push_from_py_with_checker(&mut self, span: Span) -> syn::Ident { let gil_refs_checker = syn::Ident::new( &format!("gil_refs_checker_{}", self.gil_refs_checkers.len()), span, ); self.gil_refs_checkers .push(GilRefChecker::FromPyWith(gil_refs_checker.clone())); gil_refs_checker } pub fn init_holders(&self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let holders = &self.holders; let gil_refs_checkers = self.gil_refs_checkers.iter().map(|checker| match checker { GilRefChecker::FunctionArg(ident) => ident, GilRefChecker::FromPyWith(ident) => ident, }); quote! { #[allow(clippy::let_unit_value)] #(let mut #holders = #pyo3_path::impl_::extract_argument::FunctionArgumentHolder::INIT;)* #(let #gil_refs_checkers = #pyo3_path::impl_::deprecations::GilRefs::new();)* } } pub fn check_gil_refs(&self) -> TokenStream { self.gil_refs_checkers .iter() .map(|checker| match checker { GilRefChecker::FunctionArg(ident) => { quote_spanned! { ident.span() => #ident.function_arg(); } } GilRefChecker::FromPyWith(ident) => { quote_spanned! { ident.span() => #ident.from_py_with_arg(); } } }) .collect() } } enum GilRefChecker { FunctionArg(syn::Ident), FromPyWith(syn::Ident), } /// Return true if the argument list is simply (*args, **kwds). pub fn is_forwarded_args(signature: &FunctionSignature<'_>) -> bool { matches!( signature.arguments.as_slice(), [FnArg::VarArgs(..), FnArg::KwArgs(..),] ) } pub(crate) fn check_arg_for_gil_refs( tokens: TokenStream, gil_refs_checker: syn::Ident, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; quote! { #pyo3_path::impl_::deprecations::inspect_type(#tokens, &#gil_refs_checker) } } pub fn impl_arg_params( spec: &FnSpec<'_>, self_: Option<&syn::Type>, fastcall: bool, holders: &mut Holders, ctx: &Ctx, ) -> (TokenStream, Vec) { let args_array = syn::Ident::new("output", Span::call_site()); let Ctx { pyo3_path, .. } = ctx; let from_py_with = spec .signature .arguments .iter() .enumerate() .filter_map(|(i, arg)| { let from_py_with = &arg.from_py_with()?.value; let from_py_with_holder = format_ident!("from_py_with_{}", i); Some(quote_spanned! { from_py_with.span() => let e = #pyo3_path::impl_::deprecations::GilRefs::new(); let #from_py_with_holder = #pyo3_path::impl_::deprecations::inspect_fn(#from_py_with, &e); e.from_py_with_arg(); }) }) .collect::(); 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() .enumerate() .map(|(i, arg)| impl_arg_param(arg, i, &mut 0, holders, ctx)) .collect(); return ( quote! { let _args = #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &_args); let _kwargs = #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr_or_opt(py, &_kwargs); #from_py_with }, 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_path::impl_::extract_argument::KeywordOnlyParameterDescription { name: #name, required: #required, } } }); let num_params = positional_parameter_names.len() + keyword_only_parameters.len(); let mut option_pos = 0usize; let param_conversion = spec .signature .arguments .iter() .enumerate() .map(|(i, arg)| impl_arg_param(arg, i, &mut option_pos, holders, ctx)) .collect(); let args_handler = if spec.signature.python_signature.varargs.is_some() { quote! { #pyo3_path::impl_::extract_argument::TupleVarargs } } else { quote! { #pyo3_path::impl_::extract_argument::NoVarargs } }; let kwargs_handler = if spec.signature.python_signature.kwargs.is_some() { quote! { #pyo3_path::impl_::extract_argument::DictVarkeywords } } else { quote! { #pyo3_path::impl_::extract_argument::NoVarkeywords } }; let cls_name = if let Some(cls) = self_ { quote! { ::std::option::Option::Some(<#cls as #pyo3_path::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 ( quote! { const DESCRIPTION: #pyo3_path::impl_::extract_argument::FunctionDescription = #pyo3_path::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; #from_py_with }, param_conversion, ) } fn impl_arg_param( arg: &FnArg<'_>, pos: usize, option_pos: &mut usize, holders: &mut Holders, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let args_array = syn::Ident::new("output", Span::call_site()); match arg { FnArg::Regular(arg) => { let from_py_with = format_ident!("from_py_with_{}", pos); let arg_value = quote!(#args_array[#option_pos].as_deref()); *option_pos += 1; let tokens = impl_regular_arg_param(arg, from_py_with, arg_value, holders, ctx); check_arg_for_gil_refs(tokens, holders.push_gil_refs_checker(arg.ty.span()), ctx) } FnArg::VarArgs(arg) => { let holder = holders.push_holder(arg.name.span()); let name_str = arg.name.to_string(); quote_spanned! { arg.name.span() => #pyo3_path::impl_::extract_argument::extract_argument( &_args, &mut #holder, #name_str )? } } FnArg::KwArgs(arg) => { let holder = holders.push_holder(arg.name.span()); let name_str = arg.name.to_string(); quote_spanned! { arg.name.span() => #pyo3_path::impl_::extract_argument::extract_optional_argument( _kwargs.as_deref(), &mut #holder, #name_str, || ::std::option::Option::None )? } } FnArg::Py(..) => quote! { py }, FnArg::CancelHandle(..) => quote! { __cancel_handle }, } } /// 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 pub(crate) fn impl_regular_arg_param( arg: &RegularArg<'_>, from_py_with: syn::Ident, arg_value: TokenStream, // expected type: Option<&'a Bound<'py, PyAny>> holders: &mut Holders, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let pyo3_path = pyo3_path.to_tokens_spanned(arg.ty.span()); // 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)*) } } let name_str = arg.name.to_string(); let mut default = arg.default_value.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.option_wrapped_type.is_some() { default = Some(default.map_or_else( || quote!(::std::option::Option::None), |tokens| some_wrap(tokens, ctx), )); } if arg.from_py_with.is_some() { if let Some(default) = default { quote_arg_span! { #pyo3_path::impl_::extract_argument::from_py_with_with_default( #arg_value, #name_str, #from_py_with as fn(_) -> _, #[allow(clippy::redundant_closure)] { || #default } )? } } else { quote_arg_span! { #pyo3_path::impl_::extract_argument::from_py_with( #pyo3_path::impl_::extract_argument::unwrap_required_argument(#arg_value), #name_str, #from_py_with as fn(_) -> _, )? } } } else if arg.option_wrapped_type.is_some() { let holder = holders.push_holder(arg.name.span()); quote_arg_span! { #pyo3_path::impl_::extract_argument::extract_optional_argument( #arg_value, &mut #holder, #name_str, #[allow(clippy::redundant_closure)] { || #default } )? } } else if let Some(default) = default { let holder = holders.push_holder(arg.name.span()); quote_arg_span! { #pyo3_path::impl_::extract_argument::extract_argument_with_default( #arg_value, &mut #holder, #name_str, #[allow(clippy::redundant_closure)] { || #default } )? } } else { let holder = holders.push_holder(arg.name.span()); quote_arg_span! { #pyo3_path::impl_::extract_argument::extract_argument( #pyo3_path::impl_::extract_argument::unwrap_required_argument(#arg_value), &mut #holder, #name_str )? } } } pyo3-macros-backend-0.22.2/src/pyclass.rs000064400000000000000000002304541046102023000162730ustar 00000000000000use std::borrow::Cow; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::ext::IdentExt; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::{parse_quote, parse_quote_spanned, spanned::Spanned, Result, Token}; use crate::attributes::kw::frozen; use crate::attributes::{ self, kw, take_pyo3_options, CrateAttribute, ExtendsAttribute, FreelistAttribute, ModuleAttribute, NameAttribute, NameLitStr, RenameAllAttribute, }; use crate::deprecations::Deprecations; use crate::konst::{ConstAttributes, ConstSpec}; use crate::method::{FnArg, FnSpec, PyArg, RegularArg}; use crate::pyfunction::ConstructorAttribute; use crate::pyimpl::{gen_py_const, PyClassMethodsType}; use crate::pymethod::{ impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType, SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __REPR__, __RICHCMP__, }; use crate::pyversions; use crate::utils::{self, apply_renaming_rule, LitCStr, PythonDoc}; use crate::utils::{is_abi3, Ctx}; use crate::PyFunctionOptions; /// 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 #[derive(Clone)] pub struct PyClassArgs { pub class_kind: PyClassKind, pub options: PyClassPyO3Options, } impl PyClassArgs { fn parse(input: ParseStream<'_>, kind: PyClassKind) -> Result { Ok(PyClassArgs { class_kind: kind, options: PyClassPyO3Options::parse(input)?, }) } 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(Clone, Default)] pub struct PyClassPyO3Options { pub krate: Option, pub dict: Option, pub eq: Option, pub eq_int: Option, pub extends: Option, pub get_all: Option, pub freelist: Option, pub frozen: Option, pub hash: Option, pub mapping: Option, pub module: Option, pub name: Option, pub ord: Option, pub rename_all: Option, pub sequence: Option, pub set_all: Option, pub subclass: Option, pub unsendable: Option, pub weakref: Option, } pub enum PyClassPyO3Option { Crate(CrateAttribute), Dict(kw::dict), Eq(kw::eq), EqInt(kw::eq_int), Extends(ExtendsAttribute), Freelist(FreelistAttribute), Frozen(kw::frozen), GetAll(kw::get_all), Hash(kw::hash), Mapping(kw::mapping), Module(ModuleAttribute), Name(NameAttribute), Ord(kw::ord), RenameAll(RenameAllAttribute), Sequence(kw::sequence), SetAll(kw::set_all), Subclass(kw::subclass), 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::eq) { input.parse().map(PyClassPyO3Option::Eq) } else if lookahead.peek(kw::eq_int) { input.parse().map(PyClassPyO3Option::EqInt) } 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::hash) { input.parse().map(PyClassPyO3Option::Hash) } 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(attributes::kw::ord) { input.parse().map(PyClassPyO3Option::Ord) } 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::unsendable) { input.parse().map(PyClassPyO3Option::Unsendable) } else if lookahead.peek(attributes::kw::weakref) { input.parse().map(PyClassPyO3Option::Weakref) } else { Err(lookahead.error()) } } } impl Parse for 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) } } impl PyClassPyO3Options { 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); } }; } let python_version = pyo3_build_config::get().version; match option { PyClassPyO3Option::Crate(krate) => set_option!(krate), PyClassPyO3Option::Dict(dict) => { ensure_spanned!( python_version >= pyversions::PY_3_9 || !is_abi3(), dict.span() => "`dict` requires Python >= 3.9 when using the `abi3` feature" ); set_option!(dict); } PyClassPyO3Option::Eq(eq) => set_option!(eq), PyClassPyO3Option::EqInt(eq_int) => set_option!(eq_int), 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::Hash(hash) => set_option!(hash), PyClassPyO3Option::Mapping(mapping) => set_option!(mapping), PyClassPyO3Option::Module(module) => set_option!(module), PyClassPyO3Option::Name(name) => set_option!(name), PyClassPyO3Option::Ord(ord) => set_option!(ord), 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::Unsendable(unsendable) => set_option!(unsendable), PyClassPyO3Option::Weakref(weakref) => { ensure_spanned!( python_version >= pyversions::PY_3_9 || !is_abi3(), weakref.span() => "`weakref` requires Python >= 3.9 when using the `abi3` feature" ); 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 ctx = &Ctx::new(&args.options.krate, None); let doc = utils::get_doc(&class.attrs, None, ctx); if let Some(lt) = class.generics.lifetimes().next() { bail_spanned!( lt.span() => concat!( "#[pyclass] cannot have lifetime parameters. For an explanation, see \ https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/class.html#no-lifetime-parameters" ) ); } ensure_spanned!( class.generics.params.is_empty(), class.generics.span() => concat!( "#[pyclass] cannot have generic parameters. For an explanation, see \ https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/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, ctx) } 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, ctx: &Ctx, ) -> syn::Result { let Ctx { pyo3_path, .. } = ctx; let pytypeinfo_impl = impl_pytypeinfo(cls, args, None, ctx); let (default_richcmp, default_richcmp_slot) = pyclass_richcmp(&args.options, &syn::parse_quote!(#cls), ctx)?; let (default_hash, default_hash_slot) = pyclass_hash(&args.options, &syn::parse_quote!(#cls), ctx)?; let mut slots = Vec::new(); slots.extend(default_richcmp_slot); slots.extend(default_hash_slot); 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, ctx, )?, slots, ) .doc(doc) .impl_all(ctx)?; Ok(quote! { impl #pyo3_path::types::DerefToPyAny for #cls {} #pytypeinfo_impl #py_class_impl #[doc(hidden)] #[allow(non_snake_case)] impl #cls { #default_richcmp #default_hash } }) } enum PyClassEnum<'a> { Simple(PyClassSimpleEnum<'a>), Complex(PyClassComplexEnum<'a>), } impl<'a> PyClassEnum<'a> { fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result { let has_only_unit_variants = enum_ .variants .iter() .all(|variant| matches!(variant.fields, syn::Fields::Unit)); Ok(if has_only_unit_variants { let simple_enum = PyClassSimpleEnum::new(enum_)?; Self::Simple(simple_enum) } else { let complex_enum = PyClassComplexEnum::new(enum_)?; Self::Complex(complex_enum) }) } } 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)?; let ctx = &Ctx::new(&args.options.krate, None); 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, ctx); let enum_ = PyClassEnum::new(enum_)?; impl_enum(enum_, &args, doc, method_type, ctx) } struct PyClassSimpleEnum<'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> PyClassSimpleEnum<'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) } fn extract_unit_variant_data( variant: &mut syn::Variant, ) -> syn::Result> { use syn::Fields; let ident = match &variant.fields { Fields::Unit => &variant.ident, _ => bail_spanned!(variant.span() => "Must be a unit variant."), }; let options = EnumVariantPyO3Options::take_pyo3_options(&mut variant.attrs)?; Ok(PyClassEnumUnitVariant { ident, options }) } 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: Vec<_> = enum_ .variants .iter_mut() .map(extract_unit_variant_data) .collect::>()?; Ok(Self { ident, repr_type, variants, }) } } struct PyClassComplexEnum<'a> { ident: &'a syn::Ident, variants: Vec>, } impl<'a> PyClassComplexEnum<'a> { fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result { let witness = enum_ .variants .iter() .find(|variant| !matches!(variant.fields, syn::Fields::Unit)) .expect("complex enum has a non-unit variant") .ident .to_owned(); let extract_variant_data = |variant: &'a mut syn::Variant| -> syn::Result> { use syn::Fields; let ident = &variant.ident; let options = EnumVariantPyO3Options::take_pyo3_options(&mut variant.attrs)?; let variant = match &variant.fields { Fields::Unit => { bail_spanned!(variant.span() => format!( "Unit variant `{ident}` is not yet supported in a complex enum\n\ = help: change to an empty tuple variant instead: `{ident}()`\n\ = note: the enum is complex because of non-unit variant `{witness}`", ident=ident, witness=witness)) } Fields::Named(fields) => { let fields = fields .named .iter() .map(|field| PyClassEnumVariantNamedField { ident: field.ident.as_ref().expect("named field has an identifier"), ty: &field.ty, span: field.span(), }) .collect(); PyClassEnumVariant::Struct(PyClassEnumStructVariant { ident, fields, options, }) } Fields::Unnamed(types) => { let fields = types .unnamed .iter() .map(|field| PyClassEnumVariantUnnamedField { ty: &field.ty, span: field.span(), }) .collect(); PyClassEnumVariant::Tuple(PyClassEnumTupleVariant { ident, fields, options, }) } }; Ok(variant) }; let ident = &enum_.ident; let variants: Vec<_> = enum_ .variants .iter_mut() .map(extract_variant_data) .collect::>()?; Ok(Self { ident, variants }) } } enum PyClassEnumVariant<'a> { // TODO(mkovaxx): Unit(PyClassEnumUnitVariant<'a>), Struct(PyClassEnumStructVariant<'a>), Tuple(PyClassEnumTupleVariant<'a>), } trait EnumVariant { fn get_ident(&self) -> &syn::Ident; fn get_options(&self) -> &EnumVariantPyO3Options; fn get_python_name(&self, args: &PyClassArgs) -> Cow<'_, syn::Ident> { self.get_options() .name .as_ref() .map(|name_attr| Cow::Borrowed(&name_attr.value.0)) .unwrap_or_else(|| { let name = self.get_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) } }) } } impl<'a> EnumVariant for PyClassEnumVariant<'a> { fn get_ident(&self) -> &syn::Ident { match self { PyClassEnumVariant::Struct(struct_variant) => struct_variant.ident, PyClassEnumVariant::Tuple(tuple_variant) => tuple_variant.ident, } } fn get_options(&self) -> &EnumVariantPyO3Options { match self { PyClassEnumVariant::Struct(struct_variant) => &struct_variant.options, PyClassEnumVariant::Tuple(tuple_variant) => &tuple_variant.options, } } } /// A unit variant has no fields struct PyClassEnumUnitVariant<'a> { ident: &'a syn::Ident, options: EnumVariantPyO3Options, } impl<'a> EnumVariant for PyClassEnumUnitVariant<'a> { fn get_ident(&self) -> &syn::Ident { self.ident } fn get_options(&self) -> &EnumVariantPyO3Options { &self.options } } /// A struct variant has named fields struct PyClassEnumStructVariant<'a> { ident: &'a syn::Ident, fields: Vec>, options: EnumVariantPyO3Options, } struct PyClassEnumTupleVariant<'a> { ident: &'a syn::Ident, fields: Vec>, options: EnumVariantPyO3Options, } struct PyClassEnumVariantNamedField<'a> { ident: &'a syn::Ident, ty: &'a syn::Type, span: Span, } struct PyClassEnumVariantUnnamedField<'a> { ty: &'a syn::Type, span: Span, } /// `#[pyo3()]` options for pyclass enum variants #[derive(Clone, Default)] struct EnumVariantPyO3Options { name: Option, constructor: Option, } enum EnumVariantPyO3Option { Name(NameAttribute), Constructor(ConstructorAttribute), } 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 if lookahead.peek(attributes::kw::constructor) { input.parse().map(EnumVariantPyO3Option::Constructor) } else { Err(lookahead.error()) } } } impl EnumVariantPyO3Options { fn take_pyo3_options(attrs: &mut Vec) -> Result { let mut options = EnumVariantPyO3Options::default(); take_pyo3_options(attrs)? .into_iter() .try_for_each(|option| options.set_option(option))?; Ok(options) } fn set_option(&mut self, option: EnumVariantPyO3Option) -> 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 { EnumVariantPyO3Option::Constructor(constructor) => set_option!(constructor), EnumVariantPyO3Option::Name(name) => set_option!(name), } Ok(()) } } fn impl_enum( enum_: PyClassEnum<'_>, args: &PyClassArgs, doc: PythonDoc, methods_type: PyClassMethodsType, ctx: &Ctx, ) -> Result { match enum_ { PyClassEnum::Simple(simple_enum) => { impl_simple_enum(simple_enum, args, doc, methods_type, ctx) } PyClassEnum::Complex(complex_enum) => { impl_complex_enum(complex_enum, args, doc, methods_type, ctx) } } } fn impl_simple_enum( simple_enum: PyClassSimpleEnum<'_>, args: &PyClassArgs, doc: PythonDoc, methods_type: PyClassMethodsType, ctx: &Ctx, ) -> Result { let cls = simple_enum.ident; let ty: syn::Type = syn::parse_quote!(#cls); let variants = simple_enum.variants; let pytypeinfo = impl_pytypeinfo(cls, args, None, ctx); for variant in &variants { ensure_spanned!(variant.options.constructor.is_none(), variant.options.constructor.span() => "`constructor` can't be used on a simple enum variant"); } 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.get_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__, ctx).unwrap(); (repr_impl, repr_slot) }; let repr_type = &simple_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__, ctx).unwrap(); (int_impl, int_slot) }; let (default_richcmp, default_richcmp_slot) = pyclass_richcmp_simple_enum(&args.options, &ty, repr_type, ctx)?; let (default_hash, default_hash_slot) = pyclass_hash(&args.options, &ty, ctx)?; let mut default_slots = vec![default_repr_slot, default_int_slot]; default_slots.extend(default_richcmp_slot); default_slots.extend(default_hash_slot); let pyclass_impls = PyClassImplsBuilder::new( cls, args, methods_type, simple_enum_default_methods( cls, variants.iter().map(|v| (v.ident, v.get_python_name(args))), ctx, ), default_slots, ) .doc(doc) .impl_all(ctx)?; Ok(quote! { #pytypeinfo #pyclass_impls #[doc(hidden)] #[allow(non_snake_case)] impl #cls { #default_repr #default_int #default_richcmp #default_hash } }) } fn impl_complex_enum( complex_enum: PyClassComplexEnum<'_>, args: &PyClassArgs, doc: PythonDoc, methods_type: PyClassMethodsType, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let cls = complex_enum.ident; let ty: syn::Type = syn::parse_quote!(#cls); // Need to rig the enum PyClass options let args = { let mut rigged_args = args.clone(); // Needs to be frozen to disallow `&mut self` methods, which could break a runtime invariant rigged_args.options.frozen = parse_quote!(frozen); // Needs to be subclassable by the variant PyClasses rigged_args.options.subclass = parse_quote!(subclass); rigged_args }; let ctx = &Ctx::new(&args.options.krate, None); let cls = complex_enum.ident; let variants = complex_enum.variants; let pytypeinfo = impl_pytypeinfo(cls, &args, None, ctx); let (default_richcmp, default_richcmp_slot) = pyclass_richcmp(&args.options, &ty, ctx)?; let (default_hash, default_hash_slot) = pyclass_hash(&args.options, &ty, ctx)?; let mut default_slots = vec![]; default_slots.extend(default_richcmp_slot); default_slots.extend(default_hash_slot); let impl_builder = PyClassImplsBuilder::new( cls, &args, methods_type, complex_enum_default_methods( cls, variants .iter() .map(|v| (v.get_ident(), v.get_python_name(&args))), ctx, ), default_slots, ) .doc(doc); // Need to customize the into_py impl so that it returns the variant PyClass let enum_into_py_impl = { let match_arms: Vec = variants .iter() .map(|variant| { let variant_ident = variant.get_ident(); let variant_cls = gen_complex_enum_variant_class_ident(cls, variant.get_ident()); quote! { #cls::#variant_ident { .. } => { let pyclass_init = #pyo3_path::PyClassInitializer::from(self).add_subclass(#variant_cls); let variant_value = #pyo3_path::Py::new(py, pyclass_init).unwrap(); #pyo3_path::IntoPy::into_py(variant_value, py) } } }) .collect(); quote! { impl #pyo3_path::IntoPy<#pyo3_path::PyObject> for #cls { fn into_py(self, py: #pyo3_path::Python) -> #pyo3_path::PyObject { match self { #(#match_arms)* } } } } }; let pyclass_impls: TokenStream = [ impl_builder.impl_pyclass(ctx), impl_builder.impl_extractext(ctx), enum_into_py_impl, impl_builder.impl_pyclassimpl(ctx)?, impl_builder.impl_add_to_module(ctx), impl_builder.impl_freelist(ctx), ] .into_iter() .collect(); let mut variant_cls_zsts = vec![]; let mut variant_cls_pytypeinfos = vec![]; let mut variant_cls_pyclass_impls = vec![]; let mut variant_cls_impls = vec![]; for variant in variants { let variant_cls = gen_complex_enum_variant_class_ident(cls, variant.get_ident()); let variant_cls_zst = quote! { #[doc(hidden)] #[allow(non_camel_case_types)] struct #variant_cls; }; variant_cls_zsts.push(variant_cls_zst); let variant_args = PyClassArgs { class_kind: PyClassKind::Struct, // TODO(mkovaxx): propagate variant.options options: { let mut rigged_options: PyClassPyO3Options = parse_quote!(extends = #cls, frozen); // If a specific module was given to the base class, use it for all variants. rigged_options.module.clone_from(&args.options.module); rigged_options }, }; let variant_cls_pytypeinfo = impl_pytypeinfo(&variant_cls, &variant_args, None, ctx); variant_cls_pytypeinfos.push(variant_cls_pytypeinfo); let (variant_cls_impl, field_getters, mut slots) = impl_complex_enum_variant_cls(cls, &variant, ctx)?; variant_cls_impls.push(variant_cls_impl); let variant_new = complex_enum_variant_new(cls, variant, ctx)?; slots.push(variant_new); let pyclass_impl = PyClassImplsBuilder::new( &variant_cls, &variant_args, methods_type, field_getters, slots, ) .impl_all(ctx)?; variant_cls_pyclass_impls.push(pyclass_impl); } Ok(quote! { #pytypeinfo #pyclass_impls #[doc(hidden)] #[allow(non_snake_case)] impl #cls { #default_richcmp #default_hash } #(#variant_cls_zsts)* #(#variant_cls_pytypeinfos)* #(#variant_cls_pyclass_impls)* #(#variant_cls_impls)* }) } fn impl_complex_enum_variant_cls( enum_name: &syn::Ident, variant: &PyClassEnumVariant<'_>, ctx: &Ctx, ) -> Result<(TokenStream, Vec, Vec)> { match variant { PyClassEnumVariant::Struct(struct_variant) => { impl_complex_enum_struct_variant_cls(enum_name, struct_variant, ctx) } PyClassEnumVariant::Tuple(tuple_variant) => { impl_complex_enum_tuple_variant_cls(enum_name, tuple_variant, ctx) } } } fn impl_complex_enum_variant_match_args( ctx: &Ctx, variant_cls_type: &syn::Type, field_names: &mut Vec, ) -> (MethodAndMethodDef, syn::ImplItemConst) { let match_args_const_impl: syn::ImplItemConst = { let args_tp = field_names.iter().map(|_| { quote! { &'static str } }); parse_quote! { const __match_args__: ( #(#args_tp,)* ) = ( #(stringify!(#field_names),)* ); } }; let spec = ConstSpec { rust_ident: format_ident!("__match_args__"), attributes: ConstAttributes { is_class_attr: true, name: None, deprecations: Deprecations::new(ctx), }, }; let variant_match_args = gen_py_const(variant_cls_type, &spec, ctx); (variant_match_args, match_args_const_impl) } fn impl_complex_enum_struct_variant_cls( enum_name: &syn::Ident, variant: &PyClassEnumStructVariant<'_>, ctx: &Ctx, ) -> Result<(TokenStream, Vec, Vec)> { let Ctx { pyo3_path, .. } = ctx; let variant_ident = &variant.ident; let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); let variant_cls_type = parse_quote!(#variant_cls); let mut field_names: Vec = vec![]; let mut fields_with_types: Vec = vec![]; let mut field_getters = vec![]; let mut field_getter_impls: Vec = vec![]; for field in &variant.fields { let field_name = field.ident; let field_type = field.ty; let field_with_type = quote! { #field_name: #field_type }; let field_getter = complex_enum_variant_field_getter(&variant_cls_type, field_name, field.span, ctx)?; let field_getter_impl = quote! { fn #field_name(slf: #pyo3_path::PyRef) -> #pyo3_path::PyResult<#field_type> { match &*slf.into_super() { #enum_name::#variant_ident { #field_name, .. } => Ok(#field_name.clone()), _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), } } }; field_names.push(field_name.clone()); fields_with_types.push(field_with_type); field_getters.push(field_getter); field_getter_impls.push(field_getter_impl); } let (variant_match_args, match_args_const_impl) = impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &mut field_names); field_getters.push(variant_match_args); let cls_impl = quote! { #[doc(hidden)] #[allow(non_snake_case)] impl #variant_cls { fn __pymethod_constructor__(py: #pyo3_path::Python<'_>, #(#fields_with_types,)*) -> #pyo3_path::PyClassInitializer<#variant_cls> { let base_value = #enum_name::#variant_ident { #(#field_names,)* }; #pyo3_path::PyClassInitializer::from(base_value).add_subclass(#variant_cls) } #match_args_const_impl #(#field_getter_impls)* } }; Ok((cls_impl, field_getters, Vec::new())) } fn impl_complex_enum_tuple_variant_field_getters( ctx: &Ctx, variant: &PyClassEnumTupleVariant<'_>, enum_name: &syn::Ident, variant_cls_type: &syn::Type, variant_ident: &&Ident, field_names: &mut Vec, fields_types: &mut Vec, ) -> Result<(Vec, Vec)> { let Ctx { pyo3_path, .. } = ctx; let mut field_getters = vec![]; let mut field_getter_impls = vec![]; for (index, field) in variant.fields.iter().enumerate() { let field_name = format_ident!("_{}", index); let field_type = field.ty; let field_getter = complex_enum_variant_field_getter(variant_cls_type, &field_name, field.span, ctx)?; // Generate the match arms needed to destructure the tuple and access the specific field let field_access_tokens: Vec<_> = (0..variant.fields.len()) .map(|i| { if i == index { quote! { val } } else { quote! { _ } } }) .collect(); let field_getter_impl: syn::ImplItemFn = parse_quote! { fn #field_name(slf: #pyo3_path::PyRef) -> #pyo3_path::PyResult<#field_type> { match &*slf.into_super() { #enum_name::#variant_ident ( #(#field_access_tokens), *) => Ok(val.clone()), _ => unreachable!("Wrong complex enum variant found in variant wrapper PyClass"), } } }; field_names.push(field_name); fields_types.push(field_type.clone()); field_getters.push(field_getter); field_getter_impls.push(field_getter_impl); } Ok((field_getters, field_getter_impls)) } fn impl_complex_enum_tuple_variant_len( ctx: &Ctx, variant_cls_type: &syn::Type, num_fields: usize, ) -> Result<(MethodAndSlotDef, syn::ImplItemFn)> { let Ctx { pyo3_path, .. } = ctx; let mut len_method_impl: syn::ImplItemFn = parse_quote! { fn __len__(slf: #pyo3_path::PyRef) -> #pyo3_path::PyResult { Ok(#num_fields) } }; let variant_len = generate_default_protocol_slot(variant_cls_type, &mut len_method_impl, &__LEN__, ctx)?; Ok((variant_len, len_method_impl)) } fn impl_complex_enum_tuple_variant_getitem( ctx: &Ctx, variant_cls: &syn::Ident, variant_cls_type: &syn::Type, num_fields: usize, ) -> Result<(MethodAndSlotDef, syn::ImplItemFn)> { let Ctx { pyo3_path, .. } = ctx; let match_arms: Vec<_> = (0..num_fields) .map(|i| { let field_access = format_ident!("_{}", i); quote! { #i => Ok( #pyo3_path::IntoPy::into_py( #variant_cls::#field_access(slf)? , py) ) } }) .collect(); let mut get_item_method_impl: syn::ImplItemFn = parse_quote! { fn __getitem__(slf: #pyo3_path::PyRef, idx: usize) -> #pyo3_path::PyResult< #pyo3_path::PyObject> { let py = slf.py(); match idx { #( #match_arms, )* _ => Err(pyo3::exceptions::PyIndexError::new_err("tuple index out of range")), } } }; let variant_getitem = generate_default_protocol_slot( variant_cls_type, &mut get_item_method_impl, &__GETITEM__, ctx, )?; Ok((variant_getitem, get_item_method_impl)) } fn impl_complex_enum_tuple_variant_cls( enum_name: &syn::Ident, variant: &PyClassEnumTupleVariant<'_>, ctx: &Ctx, ) -> Result<(TokenStream, Vec, Vec)> { let Ctx { pyo3_path, .. } = ctx; let variant_ident = &variant.ident; let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); let variant_cls_type = parse_quote!(#variant_cls); let mut slots = vec![]; // represents the index of the field let mut field_names: Vec = vec![]; let mut field_types: Vec = vec![]; let (mut field_getters, field_getter_impls) = impl_complex_enum_tuple_variant_field_getters( ctx, variant, enum_name, &variant_cls_type, variant_ident, &mut field_names, &mut field_types, )?; let num_fields = variant.fields.len(); let (variant_len, len_method_impl) = impl_complex_enum_tuple_variant_len(ctx, &variant_cls_type, num_fields)?; slots.push(variant_len); let (variant_getitem, getitem_method_impl) = impl_complex_enum_tuple_variant_getitem(ctx, &variant_cls, &variant_cls_type, num_fields)?; slots.push(variant_getitem); let (variant_match_args, match_args_method_impl) = impl_complex_enum_variant_match_args(ctx, &variant_cls_type, &mut field_names); field_getters.push(variant_match_args); let cls_impl = quote! { #[doc(hidden)] #[allow(non_snake_case)] impl #variant_cls { fn __pymethod_constructor__(py: #pyo3_path::Python<'_>, #(#field_names : #field_types,)*) -> #pyo3_path::PyClassInitializer<#variant_cls> { let base_value = #enum_name::#variant_ident ( #(#field_names,)* ); #pyo3_path::PyClassInitializer::from(base_value).add_subclass(#variant_cls) } #len_method_impl #getitem_method_impl #match_args_method_impl #(#field_getter_impls)* } }; Ok((cls_impl, field_getters, slots)) } fn gen_complex_enum_variant_class_ident(enum_: &syn::Ident, variant: &syn::Ident) -> syn::Ident { format_ident!("{}_{}", enum_, variant) } fn generate_protocol_slot( cls: &syn::Type, method: &mut syn::ImplItemFn, slot: &SlotDef, name: &str, ctx: &Ctx, ) -> syn::Result { let spec = FnSpec::parse( &mut method.sig, &mut Vec::new(), PyFunctionOptions::default(), ctx, ) .unwrap(); slot.generate_type_slot(&syn::parse_quote!(#cls), &spec, name, ctx) } fn generate_default_protocol_slot( cls: &syn::Type, method: &mut syn::ImplItemFn, slot: &SlotDef, ctx: &Ctx, ) -> syn::Result { let spec = FnSpec::parse( &mut method.sig, &mut Vec::new(), PyFunctionOptions::default(), ctx, ) .unwrap(); let name = spec.name.to_string(); slot.generate_type_slot( &syn::parse_quote!(#cls), &spec, &format!("__default_{}__", name), ctx, ) } fn simple_enum_default_methods<'a>( cls: &'a syn::Ident, unit_variant_names: impl IntoIterator)>, ctx: &Ctx, ) -> 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: Deprecations::new(ctx), }, }; unit_variant_names .into_iter() .map(|(var, py_name)| gen_py_const(&cls_type, &variant_to_attribute(var, &py_name), ctx)) .collect() } fn complex_enum_default_methods<'a>( cls: &'a syn::Ident, variant_names: impl IntoIterator)>, ctx: &Ctx, ) -> 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: Deprecations::new(ctx), }, }; variant_names .into_iter() .map(|(var, py_name)| { gen_complex_enum_variant_attr(cls, &cls_type, &variant_to_attribute(var, &py_name), ctx) }) .collect() } pub fn gen_complex_enum_variant_attr( cls: &syn::Ident, cls_type: &syn::Type, spec: &ConstSpec<'_>, ctx: &Ctx, ) -> MethodAndMethodDef { let Ctx { pyo3_path, .. } = ctx; let member = &spec.rust_ident; let wrapper_ident = format_ident!("__pymethod_variant_cls_{}__", member); let deprecations = &spec.attributes.deprecations; let python_name = spec.null_terminated_python_name(ctx); let variant_cls = format_ident!("{}_{}", cls, member); let associated_method = quote! { fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> { #deprecations ::std::result::Result::Ok(py.get_type_bound::<#variant_cls>().into_any().unbind()) } }; let method_def = quote! { #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static( #pyo3_path::class::PyMethodDefType::ClassAttribute({ #pyo3_path::class::PyClassAttributeDef::new( #python_name, #cls_type::#wrapper_ident ) }) ) }; MethodAndMethodDef { associated_method, method_def, } } fn complex_enum_variant_new<'a>( cls: &'a syn::Ident, variant: PyClassEnumVariant<'a>, ctx: &Ctx, ) -> Result { match variant { PyClassEnumVariant::Struct(struct_variant) => { complex_enum_struct_variant_new(cls, struct_variant, ctx) } PyClassEnumVariant::Tuple(tuple_variant) => { complex_enum_tuple_variant_new(cls, tuple_variant, ctx) } } } fn complex_enum_struct_variant_new<'a>( cls: &'a syn::Ident, variant: PyClassEnumStructVariant<'a>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let variant_cls = format_ident!("{}_{}", cls, variant.ident); let variant_cls_type: syn::Type = parse_quote!(#variant_cls); let arg_py_ident: syn::Ident = parse_quote!(py); let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>); let args = { let mut args = vec![ // py: Python<'_> FnArg::Py(PyArg { name: &arg_py_ident, ty: &arg_py_type, }), ]; for field in &variant.fields { args.push(FnArg::Regular(RegularArg { name: Cow::Borrowed(field.ident), ty: field.ty, from_py_with: None, default_value: None, option_wrapped_type: None, })); } args }; let signature = if let Some(constructor) = variant.options.constructor { crate::pyfunction::FunctionSignature::from_arguments_and_attribute( args, constructor.into_signature(), )? } else { crate::pyfunction::FunctionSignature::from_arguments(args)? }; let spec = FnSpec { tp: crate::method::FnType::FnNew, name: &format_ident!("__pymethod_constructor__"), python_name: format_ident!("__new__"), signature, convention: crate::method::CallingConvention::TpNew, text_signature: None, asyncness: None, unsafety: None, deprecations: Deprecations::new(ctx), }; crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx) } fn complex_enum_tuple_variant_new<'a>( cls: &'a syn::Ident, variant: PyClassEnumTupleVariant<'a>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let variant_cls: Ident = format_ident!("{}_{}", cls, variant.ident); let variant_cls_type: syn::Type = parse_quote!(#variant_cls); let arg_py_ident: syn::Ident = parse_quote!(py); let arg_py_type: syn::Type = parse_quote!(#pyo3_path::Python<'_>); let args = { let mut args = vec![FnArg::Py(PyArg { name: &arg_py_ident, ty: &arg_py_type, })]; for (i, field) in variant.fields.iter().enumerate() { args.push(FnArg::Regular(RegularArg { name: std::borrow::Cow::Owned(format_ident!("_{}", i)), ty: field.ty, from_py_with: None, default_value: None, option_wrapped_type: None, })); } args }; let signature = if let Some(constructor) = variant.options.constructor { crate::pyfunction::FunctionSignature::from_arguments_and_attribute( args, constructor.into_signature(), )? } else { crate::pyfunction::FunctionSignature::from_arguments(args)? }; let spec = FnSpec { tp: crate::method::FnType::FnNew, name: &format_ident!("__pymethod_constructor__"), python_name: format_ident!("__new__"), signature, convention: crate::method::CallingConvention::TpNew, text_signature: None, asyncness: None, unsafety: None, deprecations: Deprecations::new(ctx), }; crate::pymethod::impl_py_method_def_new(&variant_cls_type, &spec, ctx) } fn complex_enum_variant_field_getter<'a>( variant_cls_type: &'a syn::Type, field_name: &'a syn::Ident, field_span: Span, ctx: &Ctx, ) -> Result { let signature = crate::pyfunction::FunctionSignature::from_arguments(vec![])?; let self_type = crate::method::SelfType::TryFromBoundRef(field_span); let spec = FnSpec { tp: crate::method::FnType::Getter(self_type.clone()), name: field_name, python_name: field_name.clone(), signature, convention: crate::method::CallingConvention::Noargs, text_signature: None, asyncness: None, unsafety: None, deprecations: Deprecations::new(ctx), }; let property_type = crate::pymethod::PropertyType::Function { self_type: &self_type, spec: &spec, doc: crate::get_doc(&[], None, ctx), }; let getter = crate::pymethod::impl_py_getter_def(variant_cls_type, property_type, ctx)?; Ok(getter) } fn descriptors_to_items( cls: &syn::Ident, rename_all: Option<&RenameAllAttribute>, frozen: Option, field_options: Vec<(&syn::Field, FieldPyO3Options)>, ctx: &Ctx, ) -> 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), }, ctx, )?; 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), }, ctx, )?; items.push(setter); }; } Ok(items) } fn impl_pytypeinfo( cls: &syn::Ident, attr: &PyClassArgs, deprecations: Option<&Deprecations<'_>>, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; 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 } }; #[cfg(feature = "gil-refs")] let has_py_gil_ref = quote! { #[allow(deprecated)] unsafe impl #pyo3_path::type_object::HasPyGilRef for #cls { type AsRefTarget = #pyo3_path::PyCell; } }; #[cfg(not(feature = "gil-refs"))] let has_py_gil_ref = TokenStream::new(); quote! { #has_py_gil_ref unsafe impl #pyo3_path::type_object::PyTypeInfo for #cls { const NAME: &'static str = #cls_name; const MODULE: ::std::option::Option<&'static str> = #module; #[inline] fn type_object_raw(py: #pyo3_path::Python<'_>) -> *mut #pyo3_path::ffi::PyTypeObject { use #pyo3_path::prelude::PyTypeMethods; #deprecations <#cls as #pyo3_path::impl_::pyclass::PyClassImpl>::lazy_type_object() .get_or_init(py) .as_type_ptr() } } } } fn pyclass_richcmp_arms( options: &PyClassPyO3Options, ctx: &Ctx, ) -> std::result::Result { let Ctx { pyo3_path, .. } = ctx; let eq_arms = options .eq .map(|eq| eq.span) .or(options.eq_int.map(|eq_int| eq_int.span)) .map(|span| { quote_spanned! { span => #pyo3_path::pyclass::CompareOp::Eq => { ::std::result::Result::Ok(#pyo3_path::conversion::IntoPy::into_py(self_val == other, py)) }, #pyo3_path::pyclass::CompareOp::Ne => { ::std::result::Result::Ok(#pyo3_path::conversion::IntoPy::into_py(self_val != other, py)) }, } }) .unwrap_or_default(); if let Some(ord) = options.ord { ensure_spanned!(options.eq.is_some(), ord.span() => "The `ord` option requires the `eq` option."); } let ord_arms = options .ord .map(|ord| { quote_spanned! { ord.span() => #pyo3_path::pyclass::CompareOp::Gt => { ::std::result::Result::Ok(#pyo3_path::conversion::IntoPy::into_py(self_val > other, py)) }, #pyo3_path::pyclass::CompareOp::Lt => { ::std::result::Result::Ok(#pyo3_path::conversion::IntoPy::into_py(self_val < other, py)) }, #pyo3_path::pyclass::CompareOp::Le => { ::std::result::Result::Ok(#pyo3_path::conversion::IntoPy::into_py(self_val <= other, py)) }, #pyo3_path::pyclass::CompareOp::Ge => { ::std::result::Result::Ok(#pyo3_path::conversion::IntoPy::into_py(self_val >= other, py)) }, } }) .unwrap_or_else(|| quote! { _ => ::std::result::Result::Ok(py.NotImplemented()) }); Ok(quote! { #eq_arms #ord_arms }) } fn pyclass_richcmp_simple_enum( options: &PyClassPyO3Options, cls: &syn::Type, repr_type: &syn::Ident, ctx: &Ctx, ) -> Result<(Option, Option)> { let Ctx { pyo3_path, .. } = ctx; if let Some(eq_int) = options.eq_int { ensure_spanned!(options.eq.is_some(), eq_int.span() => "The `eq_int` option requires the `eq` option."); } let deprecation = (options.eq_int.is_none() && options.eq.is_none()) .then(|| { quote! { #[deprecated( since = "0.22.0", note = "Implicit equality for simple enums is deprecated. Use `#[pyclass(eq, eq_int)` to keep the current behavior." )] const DEPRECATION: () = (); const _: () = DEPRECATION; } }) .unwrap_or_default(); let mut options = options.clone(); if options.eq.is_none() { options.eq_int = Some(parse_quote!(eq_int)); } if options.eq.is_none() && options.eq_int.is_none() { return Ok((None, None)); } let arms = pyclass_richcmp_arms(&options, ctx)?; let eq = options.eq.map(|eq| { quote_spanned! { eq.span() => let self_val = self; if let ::std::result::Result::Ok(other) = #pyo3_path::types::PyAnyMethods::downcast::(other) { let other = &*other.borrow(); return match op { #arms } } } }); let eq_int = options.eq_int.map(|eq_int| { quote_spanned! { eq_int.span() => let self_val = self.__pyo3__int__(); if let ::std::result::Result::Ok(other) = #pyo3_path::types::PyAnyMethods::extract::<#repr_type>(other).or_else(|_| { #pyo3_path::types::PyAnyMethods::downcast::(other).map(|o| o.borrow().__pyo3__int__()) }) { return match op { #arms } } } }); let mut richcmp_impl = parse_quote! { fn __pyo3__generated____richcmp__( &self, py: #pyo3_path::Python, other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>, op: #pyo3_path::pyclass::CompareOp ) -> #pyo3_path::PyResult<#pyo3_path::PyObject> { #deprecation #eq #eq_int ::std::result::Result::Ok(py.NotImplemented()) } }; let richcmp_slot = if options.eq.is_some() { generate_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, "__richcmp__", ctx).unwrap() } else { generate_default_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, ctx).unwrap() }; Ok((Some(richcmp_impl), Some(richcmp_slot))) } fn pyclass_richcmp( options: &PyClassPyO3Options, cls: &syn::Type, ctx: &Ctx, ) -> Result<(Option, Option)> { let Ctx { pyo3_path, .. } = ctx; if let Some(eq_int) = options.eq_int { bail_spanned!(eq_int.span() => "`eq_int` can only be used on simple enums.") } let arms = pyclass_richcmp_arms(options, ctx)?; if options.eq.is_some() { let mut richcmp_impl = parse_quote! { fn __pyo3__generated____richcmp__( &self, py: #pyo3_path::Python, other: &#pyo3_path::Bound<'_, #pyo3_path::PyAny>, op: #pyo3_path::pyclass::CompareOp ) -> #pyo3_path::PyResult<#pyo3_path::PyObject> { let self_val = self; if let Ok(other) = #pyo3_path::types::PyAnyMethods::downcast::(other) { let other = &*other.borrow(); match op { #arms } } else { ::std::result::Result::Ok(py.NotImplemented()) } } }; let richcmp_slot = generate_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, "__richcmp__", ctx) .unwrap(); Ok((Some(richcmp_impl), Some(richcmp_slot))) } else { Ok((None, None)) } } fn pyclass_hash( options: &PyClassPyO3Options, cls: &syn::Type, ctx: &Ctx, ) -> Result<(Option, Option)> { if options.hash.is_some() { ensure_spanned!( options.frozen.is_some(), options.hash.span() => "The `hash` option requires the `frozen` option."; options.eq.is_some(), options.hash.span() => "The `hash` option requires the `eq` option."; ); } // FIXME: Use hash.map(...).unzip() on MSRV >= 1.66 match options.hash { Some(opt) => { let mut hash_impl = parse_quote_spanned! { opt.span() => fn __pyo3__generated____hash__(&self) -> u64 { let mut s = ::std::collections::hash_map::DefaultHasher::new(); ::std::hash::Hash::hash(self, &mut s); ::std::hash::Hasher::finish(&s) } }; let hash_slot = generate_protocol_slot(cls, &mut hash_impl, &__HASH__, "__hash__", ctx).unwrap(); Ok((Some(hash_impl), Some(hash_slot))) } None => Ok((None, None)), } } /// 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, ctx: &Ctx) -> Result { let tokens = [ self.impl_pyclass(ctx), self.impl_extractext(ctx), self.impl_into_py(ctx), self.impl_pyclassimpl(ctx)?, self.impl_add_to_module(ctx), self.impl_freelist(ctx), ] .into_iter() .collect(); Ok(tokens) } fn impl_pyclass(&self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let cls = self.cls; let frozen = if self.attr.options.frozen.is_some() { quote! { #pyo3_path::pyclass::boolean_struct::True } } else { quote! { #pyo3_path::pyclass::boolean_struct::False } }; quote! { impl #pyo3_path::PyClass for #cls { type Frozen = #frozen; } } } fn impl_extractext(&self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let cls = self.cls; if self.attr.options.frozen.is_some() { quote! { impl<'a, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a #cls { type Holder = ::std::option::Option<#pyo3_path::PyRef<'py, #cls>>; #[inline] fn extract(obj: &'a #pyo3_path::Bound<'py, #pyo3_path::PyAny>, holder: &'a mut Self::Holder) -> #pyo3_path::PyResult { #pyo3_path::impl_::extract_argument::extract_pyclass_ref(obj, holder) } } } } else { quote! { impl<'a, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a #cls { type Holder = ::std::option::Option<#pyo3_path::PyRef<'py, #cls>>; #[inline] fn extract(obj: &'a #pyo3_path::Bound<'py, #pyo3_path::PyAny>, holder: &'a mut Self::Holder) -> #pyo3_path::PyResult { #pyo3_path::impl_::extract_argument::extract_pyclass_ref(obj, holder) } } impl<'a, 'py> #pyo3_path::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a mut #cls { type Holder = ::std::option::Option<#pyo3_path::PyRefMut<'py, #cls>>; #[inline] fn extract(obj: &'a #pyo3_path::Bound<'py, #pyo3_path::PyAny>, holder: &'a mut Self::Holder) -> #pyo3_path::PyResult { #pyo3_path::impl_::extract_argument::extract_pyclass_ref_mut(obj, holder) } } } } } fn impl_into_py(&self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; 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_path::IntoPy<#pyo3_path::PyObject> for #cls { fn into_py(self, py: #pyo3_path::Python) -> #pyo3_path::PyObject { #pyo3_path::IntoPy::into_py(#pyo3_path::Py::new(py, self).unwrap(), py) } } } } else { quote! {} } } fn impl_pyclassimpl(&self, ctx: &Ctx) -> Result { let Ctx { pyo3_path, .. } = ctx; let cls = self.cls; let doc = self.doc.as_ref().map_or( LitCStr::empty(ctx).to_token_stream(), PythonDoc::to_token_stream, ); let is_basetype = self.attr.options.subclass.is_some(); let base = match &self.attr.options.extends { Some(extends_attr) => extends_attr.value.clone(), None => parse_quote! { #pyo3_path::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_path::ffi::Py_ssize_t> { ::std::option::Option::Some(#pyo3_path::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_path::ffi::Py_ssize_t> { ::std::option::Option::Some(#pyo3_path::impl_::pyclass::weaklist_offset::()) } } } else { TokenStream::new() }; let thread_checker = if self.attr.options.unsendable.is_some() { quote! { #pyo3_path::impl_::pyclass::ThreadCheckerImpl } } else { quote! { #pyo3_path::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_path::inventory::iter::<::Inventory>(), #pyo3_path::impl_::pyclass::PyClassInventory::items ) ) }, Some(quote! { type Inventory = #inventory_class_name; }), Some(define_inventory_class(&inventory_class_name, ctx)), ) } }; 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(ctx); 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_path::impl_::pyclass::PyClassDictSlot } } else { quote! { #pyo3_path::impl_::pyclass::PyClassDummySlot } }; // insert space for weak ref let weakref = if attr.options.weakref.is_some() { quote! { #pyo3_path::impl_::pyclass::PyClassWeakRefSlot } } else { quote! { #pyo3_path::impl_::pyclass::PyClassDummySlot } }; let base_nativetype = if attr.options.extends.is_some() { quote! { ::BaseNativeType } } else { quote! { #pyo3_path::PyAny } }; Ok(quote! { impl #pyo3_path::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_path::impl_::pyclass::PyClassBaseType>::PyClassMutability as #pyo3_path::impl_::pycell::PyClassMutability>::#class_mutability; type Dict = #dict; type WeakRef = #weakref; type BaseNativeType = #base_nativetype; fn items_iter() -> #pyo3_path::impl_::pyclass::PyClassItemsIter { use #pyo3_path::impl_::pyclass::*; let collector = PyClassImplCollector::::new(); 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_path::Python<'_>) -> #pyo3_path::PyResult<&'static ::std::ffi::CStr> { use #pyo3_path::impl_::pyclass::*; static DOC: #pyo3_path::sync::GILOnceCell<::std::borrow::Cow<'static, ::std::ffi::CStr>> = #pyo3_path::sync::GILOnceCell::new(); DOC.get_or_try_init(py, || { let collector = PyClassImplCollector::::new(); build_pyclass_doc(<#cls as #pyo3_path::PyTypeInfo>::NAME, #doc, collector.new_text_signature()) }).map(::std::ops::Deref::deref) } #dict_offset #weaklist_offset fn lazy_type_object() -> &'static #pyo3_path::impl_::pyclass::LazyTypeObject { use #pyo3_path::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_add_to_module(&self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let cls = self.cls; quote! { impl #cls { #[doc(hidden)] pub const _PYO3_DEF: #pyo3_path::impl_::pymodule::AddClassToModule = #pyo3_path::impl_::pymodule::AddClassToModule::new(); } } } fn impl_freelist(&self, ctx: &Ctx) -> TokenStream { let cls = self.cls; let Ctx { pyo3_path, .. } = ctx; self.attr.options.freelist.as_ref().map_or(quote!{}, |freelist| { let freelist = &freelist.value; quote! { impl #pyo3_path::impl_::pyclass::PyClassWithFreeList for #cls { #[inline] fn get_free_list(py: #pyo3_path::Python<'_>) -> &mut #pyo3_path::impl_::freelist::FreeList<*mut #pyo3_path::ffi::PyObject> { static mut FREELIST: *mut #pyo3_path::impl_::freelist::FreeList<*mut #pyo3_path::ffi::PyObject> = 0 as *mut _; unsafe { if FREELIST.is_null() { FREELIST = ::std::boxed::Box::into_raw(::std::boxed::Box::new( #pyo3_path::impl_::freelist::FreeList::with_capacity(#freelist))); } &mut *FREELIST } } } } }) } fn freelist_slots(&self, ctx: &Ctx) -> Vec { let Ctx { pyo3_path, .. } = ctx; let cls = self.cls; if self.attr.options.freelist.is_some() { vec![ quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_alloc, pfunc: #pyo3_path::impl_::pyclass::alloc_with_freelist::<#cls> as *mut _, } }, quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_free, pfunc: #pyo3_path::impl_::pyclass::free_with_freelist::<#cls> as *mut _, } }, ] } else { Vec::new() } } } fn define_inventory_class(inventory_class_name: &syn::Ident, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; quote! { #[doc(hidden)] pub struct #inventory_class_name { items: #pyo3_path::impl_::pyclass::PyClassItems, } impl #inventory_class_name { pub const fn new(items: #pyo3_path::impl_::pyclass::PyClassItems) -> Self { Self { items } } } impl #pyo3_path::impl_::pyclass::PyClassInventory for #inventory_class_name { fn items(&self) -> &#pyo3_path::impl_::pyclass::PyClassItems { &self.items } } #pyo3_path::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.22.2/src/pyfunction/signature.rs000064400000000000000000000511271046102023000210120ustar 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, RegularArg}, }; #[derive(Clone)] 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(Clone, Debug, PartialEq, Eq)] pub struct SignatureItemArgument { pub ident: syn::Ident, pub eq_and_default: Option<(Token![=], syn::Expr)>, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignatureItemPosargsSep { pub slash: Token![/], } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignatureItemVarargsSep { pub asterisk: Token![*], } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignatureItemVarargs { pub sep: SignatureItemVarargsSep, pub ident: syn::Ident, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignatureItemKwargs { pub asterisks: (Token![*], Token![*]), pub ident: syn::Ident, } #[derive(Clone, 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; pub type ConstructorAttribute = KeywordAttribute; impl ConstructorAttribute { pub fn into_signature(self) -> SignatureAttribute { SignatureAttribute { kw: kw::signature(self.kw.span), value: self.value, } } } #[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() { match fn_arg { crate::method::FnArg::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; } crate::method::FnArg::CancelHandle(..) => { // If the user incorrectly tried to include cancel: CoroutineCancel in the // signature, give a useful error as a hint. ensure_spanned!( name != fn_arg.name(), name.span() => "`cancel_handle` argument 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 { if let FnArg::Regular(arg) = fn_arg { arg.default_value = Some(default.clone()); } else { unreachable!( "`Python` and `CancelHandle` are already handled above and `*args`/`**kwargs` are \ parsed and transformed below. Because the have to come last and are only allowed \ once, this has to be a regular argument." ); } } } 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.to_varargs_mut()?; parse_state.add_varargs(&mut python_signature, varargs)?; } SignatureItem::Kwargs(kwargs) => { let fn_arg = next_non_py_argument_checked(&kwargs.ident)?; fn_arg.to_kwargs_mut()?; 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| !matches!(arg, FnArg::Py(..) | FnArg::CancelHandle(..))) { 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 matches!(arg, FnArg::Py(..) | FnArg::CancelHandle(..)) { continue; } if let FnArg::Regular(RegularArg { ty, option_wrapped_type: None, .. }) = arg { // This argument is required, all previous arguments must also have been required ensure_spanned!( python_signature.required_positional_parameters == python_signature.positional_parameters.len(), 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 FnArg::Regular(RegularArg { default_value: Some(arg_default), .. }) = fn_arg { 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 let FnArg::Regular(RegularArg { option_wrapped_type: Some(..), .. }) = fn_arg { // 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.22.2/src/pyfunction.rs000064400000000000000000000236561046102023000170170ustar 00000000000000use crate::utils::Ctx; use crate::{ attributes::{ self, get_pyo3_options, take_attributes, take_pyo3_options, CrateAttribute, FromPyWithAttribute, NameAttribute, TextSignatureAttribute, }, deprecations::Deprecations, method::{self, CallingConvention, FnArg}, pymethod::check_generic, }; 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::{ConstructorAttribute, FunctionSignature, SignatureAttribute}; #[derive(Clone, Debug)] pub struct PyFunctionArgPyO3Attributes { pub from_py_with: Option, pub cancel_handle: Option, } enum PyFunctionArgPyO3Attribute { FromPyWith(FromPyWithAttribute), CancelHandle(attributes::kw::cancel_handle), } impl Parse for PyFunctionArgPyO3Attribute { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::cancel_handle) { input.parse().map(PyFunctionArgPyO3Attribute::CancelHandle) } else 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, cancel_handle: 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); } PyFunctionArgPyO3Attribute::CancelHandle(cancel_handle) => { ensure_spanned!( attributes.cancel_handle.is_none(), cancel_handle.span() => "`cancel_handle` may only be specified once per argument" ); attributes.cancel_handle = Some(cancel_handle); } } ensure_spanned!( attributes.from_py_with.is_none() || attributes.cancel_handle.is_none(), attributes.cancel_handle.unwrap().span() => "`from_py_with` and `cancel_handle` cannot be specified together" ); } 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)?; let PyFunctionOptions { pass_module, name, signature, text_signature, krate, } = options; let ctx = &Ctx::new(&krate, Some(&func.sig)); let Ctx { pyo3_path, .. } = &ctx; let python_name = name .as_ref() .map_or_else(|| &func.sig.ident, |name| &name.value.0) .unraw(); 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 spec = method::FnSpec { tp, name: &func.sig.ident, convention: CallingConvention::from_signature(&signature), python_name, signature, text_signature, asyncness: func.sig.asyncness, unsafety: func.sig.unsafety, deprecations: Deprecations::new(ctx), }; 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, ctx)?; let methoddef = spec.get_methoddef(wrapper_ident, &spec.get_doc(&func.attrs, ctx), ctx); 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 _PYO3_DEF: #pyo3_path::impl_::pymethods::PyMethodDef = MakeDef::_PYO3_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) #[allow(unknown_lints, non_local_definitions)] impl #name::MakeDef { const _PYO3_DEF: #pyo3_path::impl_::pymethods::PyMethodDef = #methoddef; } #[allow(non_snake_case)] #wrapper }; Ok(wrapped_pyfunction) } pyo3-macros-backend-0.22.2/src/pyimpl.rs000064400000000000000000000261011046102023000161170ustar 00000000000000use std::collections::HashSet; use crate::utils::Ctx; use crate::{ attributes::{take_pyo3_options, CrateAttribute}, konst::{ConstAttributes, ConstSpec}, pyfunction::PyFunctionOptions, pymethod::{self, is_proto_method, MethodAndMethodDef, MethodAndSlotDef}, }; 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 ctx = &Ctx::new(&options.krate, Some(&meth.sig)); 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, ctx)? { 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 ctx = &Ctx::new(&options.krate, None); let attributes = ConstAttributes::from_attrs(&mut konst.attrs, ctx)?; 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, ctx); 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" ), _ => {} } } let ctx = &Ctx::new(&options.krate, None); add_shared_proto_slots(ty, &mut proto_impls, implemented_proto_fragments, ctx); let items = match methods_type { PyClassMethodsType::Specialization => impl_py_methods(ty, methods, proto_impls, ctx), PyClassMethodsType::Inventory => submit_methods_inventory(ty, methods, proto_impls, ctx), }; Ok(quote! { #(#trait_impls)* #items #[doc(hidden)] #[allow(non_snake_case)] impl #ty { #(#associated_methods)* } }) } pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec<'_>, ctx: &Ctx) -> 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(ctx); let Ctx { pyo3_path, .. } = ctx; let associated_method = quote! { fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> { #deprecations ::std::result::Result::Ok(#pyo3_path::IntoPy::into_py(#cls::#member, py)) } }; let method_def = quote! { #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static( #pyo3_path::class::PyMethodDefType::ClassAttribute({ #pyo3_path::class::PyClassAttributeDef::new( #python_name, #cls::#wrapper_ident ) }) ) }; MethodAndMethodDef { associated_method, method_def, } } fn impl_py_methods( ty: &syn::Type, methods: Vec, proto_impls: Vec, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; quote! { #[allow(unknown_lints, non_local_definitions)] impl #pyo3_path::impl_::pyclass::PyMethods<#ty> for #pyo3_path::impl_::pyclass::PyClassImplCollector<#ty> { fn py_methods(self) -> &'static #pyo3_path::impl_::pyclass::PyClassItems { static ITEMS: #pyo3_path::impl_::pyclass::PyClassItems = #pyo3_path::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, ctx: &Ctx, ) { let Ctx { pyo3_path, .. } = ctx; 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_path::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, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; quote! { #pyo3_path::inventory::submit! { type Inventory = <#ty as #pyo3_path::impl_::pyclass::PyClassImpl>::Inventory; Inventory::new(#pyo3_path::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.22.2/src/pymethod.rs000064400000000000000000001703131046102023000164430ustar 00000000000000use std::borrow::Cow; use std::ffi::CString; use crate::attributes::{NameAttribute, RenamingRule}; use crate::deprecations::deprecate_trailing_option_default; use crate::method::{CallingConvention, ExtractErrorMode, PyArg}; use crate::params::{check_arg_for_gil_refs, impl_regular_arg_param, Holders}; use crate::utils::PythonDoc; use crate::utils::{Ctx, LitCStr}; use crate::{ method::{FnArg, FnSpec, FnType, SelfType}, pyfunction::PyFunctionOptions, }; use crate::{quotes, utils}; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, 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, ctx: &'a Ctx, ) -> Result { let spec = FnSpec::parse(sig, meth_attrs, options, ctx)?; 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, ctx: &Ctx, ) -> Result { check_generic(sig)?; ensure_function_options_valid(&options)?; let method = PyMethod::parse(sig, meth_attrs, options, ctx)?; let spec = &method.spec; let Ctx { pyo3_path, .. } = ctx; 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, ctx)?) } (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, ctx)?; GeneratedPyMethod::Proto(slot) } PyMethodProtoKind::Call => { GeneratedPyMethod::Proto(impl_call_slot(cls, method.spec, ctx)?) } PyMethodProtoKind::Traverse => { GeneratedPyMethod::Proto(impl_traverse_slot(cls, spec, ctx)?) } PyMethodProtoKind::SlotFragment(slot_fragment_def) => { let proto = slot_fragment_def.generate_pyproto_fragment(cls, spec, ctx)?; 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, ctx), None, ctx, )?), (_, FnType::FnClass(_)) => GeneratedPyMethod::Method(impl_py_method_def( cls, spec, &spec.get_doc(meth_attrs, ctx), Some(quote!(#pyo3_path::ffi::METH_CLASS)), ctx, )?), (_, FnType::FnStatic) => GeneratedPyMethod::Method(impl_py_method_def( cls, spec, &spec.get_doc(meth_attrs, ctx), Some(quote!(#pyo3_path::ffi::METH_STATIC)), ctx, )?), // special prototypes (_, FnType::FnNew) | (_, FnType::FnNewClass(_)) => { GeneratedPyMethod::Proto(impl_py_method_def_new(cls, spec, ctx)?) } (_, FnType::Getter(self_type)) => GeneratedPyMethod::Method(impl_py_getter_def( cls, PropertyType::Function { self_type, spec, doc: spec.get_doc(meth_attrs, ctx), }, ctx, )?), (_, FnType::Setter(self_type)) => GeneratedPyMethod::Method(impl_py_setter_def( cls, PropertyType::Function { self_type, spec, doc: spec.get_doc(meth_attrs, ctx), }, ctx, )?), (_, 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, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let wrapper_ident = format_ident!("__pymethod_{}__", spec.python_name); let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?; 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, ctx); let method_def = quote! { #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static( #pyo3_path::class::PyMethodDefType::#methoddef_type(#methoddef #add_flags) ) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } /// Also used by pyclass. pub fn impl_py_method_def_new( cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let wrapper_ident = syn::Ident::new("__pymethod___new____", Span::call_site()); let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?; // 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_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_new, pfunc: { unsafe extern "C" fn trampoline( subtype: *mut #pyo3_path::ffi::PyTypeObject, args: *mut #pyo3_path::ffi::PyObject, kwargs: *mut #pyo3_path::ffi::PyObject, ) -> *mut #pyo3_path::ffi::PyObject { #deprecations use #pyo3_path::impl_::pyclass::*; #[allow(unknown_lints, non_local_definitions)] impl PyClassNewTextSignature<#cls> for PyClassImplCollector<#cls> { #[inline] fn new_text_signature(self) -> ::std::option::Option<&'static str> { #text_signature_body } } #pyo3_path::impl_::trampoline::newfunc( subtype, args, kwargs, #cls::#wrapper_ident ) } trampoline } as #pyo3_path::ffi::newfunc as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } fn impl_call_slot(cls: &syn::Type, mut spec: FnSpec<'_>, ctx: &Ctx) -> Result { let Ctx { pyo3_path, .. } = ctx; // 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), ctx)?; let slot_def = quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_call, pfunc: { unsafe extern "C" fn trampoline( slf: *mut #pyo3_path::ffi::PyObject, args: *mut #pyo3_path::ffi::PyObject, kwargs: *mut #pyo3_path::ffi::PyObject, ) -> *mut #pyo3_path::ffi::PyObject { #pyo3_path::impl_::trampoline::ternaryfunc( slf, args, kwargs, #cls::#wrapper_ident ) } trampoline } as #pyo3_path::ffi::ternaryfunc as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } fn impl_traverse_slot( cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx, ) -> syn::Result { let Ctx { pyo3_path, .. } = ctx; 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__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \ 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.")); } // check that the receiver does not try to smuggle an (implicit) `Python` token into here if let FnType::Fn(SelfType::TryFromBoundRef(span)) | FnType::Fn(SelfType::Receiver { mutable: true, span, }) = spec.tp { bail_spanned! { span => "__traverse__ may not take a receiver other than `&self`. Usually, an implementation of \ `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \ 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_path::ffi::PyObject, visit: #pyo3_path::ffi::visitproc, arg: *mut ::std::os::raw::c_void, ) -> ::std::os::raw::c_int { #pyo3_path::impl_::pymethods::_call_traverse::<#cls>(slf, #cls::#rust_fn_ident, visit, arg) } }; let slot_def = quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_traverse, pfunc: #cls::__pymethod_traverse__ as #pyo3_path::ffi::traverseproc as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } fn impl_py_class_attribute( cls: &syn::Type, spec: &FnSpec<'_>, ctx: &Ctx, ) -> syn::Result { let Ctx { pyo3_path, .. } = ctx; 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(ctx); let body = quotes::ok_wrap(fncall, ctx); let associated_method = quote! { fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> { let function = #cls::#name; // Shadow the method name to avoid #3017 #pyo3_path::impl_::wrap::map_result_into_py(py, #body) } }; let method_def = quote! { #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static( #pyo3_path::class::PyMethodDefType::ClassAttribute({ #pyo3_path::class::PyClassAttributeDef::new( #python_name, #cls::#wrapper_ident ) }) ) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } fn impl_call_setter( cls: &syn::Type, spec: &FnSpec<'_>, self_type: &SelfType, holders: &mut Holders, ctx: &Ctx, ) -> syn::Result { let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); let slf = self_type.receiver(cls, ExtractErrorMode::Raise, holders, ctx); 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<'_>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let python_name = property_type.null_terminated_python_name(ctx)?; let doc = property_type.doc(ctx); let mut holders = Holders::new(); 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, &mut holders, ctx); 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, &mut holders, ctx)?, }; 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 extract = match &property_type { PropertyType::Function { spec, .. } => { let (_, args) = split_off_python_arg(&spec.signature.arguments); let value_arg = &args[0]; let (from_py_with, ident) = if let Some(from_py_with) = &value_arg.from_py_with().as_ref().map(|f| &f.value) { let ident = syn::Ident::new("from_py_with", from_py_with.span()); ( quote_spanned! { from_py_with.span() => let e = #pyo3_path::impl_::deprecations::GilRefs::new(); let #ident = #pyo3_path::impl_::deprecations::inspect_fn(#from_py_with, &e); e.from_py_with_arg(); }, ident, ) } else { (quote!(), syn::Ident::new("dummy", Span::call_site())) }; let arg = if let FnArg::Regular(arg) = &value_arg { arg } else { bail_spanned!(value_arg.name().span() => "The #[setter] value argument can't be *args, **kwargs or `cancel_handle`."); }; let tokens = impl_regular_arg_param( arg, ident, quote!(::std::option::Option::Some(_value.into())), &mut holders, ctx, ); let extract = check_arg_for_gil_refs(tokens, holders.push_gil_refs_checker(arg.ty.span()), ctx); let deprecation = deprecate_trailing_option_default(spec); quote! { #deprecation #from_py_with let _val = #extract; } } PropertyType::Descriptor { field, .. } => { let span = field.ty.span(); let name = field .ident .as_ref() .map(|i| i.to_string()) .unwrap_or_default(); let holder = holders.push_holder(span); let gil_refs_checker = holders.push_gil_refs_checker(span); quote! { let _val = #pyo3_path::impl_::deprecations::inspect_type( #pyo3_path::impl_::extract_argument::extract_argument(_value.into(), &mut #holder, #name)?, &#gil_refs_checker ); } } }; 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 init_holders = holders.init_holders(ctx); let check_gil_refs = holders.check_gil_refs(); let associated_method = quote! { #cfg_attrs unsafe fn #wrapper_ident( py: #pyo3_path::Python<'_>, _slf: *mut #pyo3_path::ffi::PyObject, _value: *mut #pyo3_path::ffi::PyObject, ) -> #pyo3_path::PyResult<::std::os::raw::c_int> { use ::std::convert::Into; let _value = #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr_or_opt(py, &_value) .ok_or_else(|| { #pyo3_path::exceptions::PyAttributeError::new_err("can't delete attribute") })?; #init_holders #extract let result = #setter_impl; #check_gil_refs #pyo3_path::callback::convert(py, result) } }; let method_def = quote! { #cfg_attrs #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static( #pyo3_path::class::PyMethodDefType::Setter( #pyo3_path::class::PySetterDef::new( #python_name, #cls::#wrapper_ident, #doc ) ) ) }; Ok(MethodAndMethodDef { associated_method, method_def, }) } fn impl_call_getter( cls: &syn::Type, spec: &FnSpec<'_>, self_type: &SelfType, holders: &mut Holders, ctx: &Ctx, ) -> syn::Result { let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); let slf = self_type.receiver(cls, ExtractErrorMode::Raise, holders, ctx); 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<'_>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let python_name = property_type.null_terminated_python_name(ctx)?; let doc = property_type.doc(ctx); 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 mut holders = Holders::new(); match property_type { PropertyType::Descriptor { field_index, field, .. } => { let ty = &field.ty; let field = if let Some(ident) = &field.ident { ident.to_token_stream() } else { syn::Index::from(field_index).to_token_stream() }; // TODO: on MSRV 1.77+, we can use `::std::mem::offset_of!` here, and it should // make it possible for the `MaybeRuntimePyMethodDef` to be a `Static` variant. let method_def = quote_spanned! {ty.span()=> #cfg_attrs { #[allow(unused_imports)] // might not be used if all probes are positve use #pyo3_path::impl_::pyclass::Probe; struct Offset; unsafe impl #pyo3_path::impl_::pyclass::OffsetCalculator<#cls, #ty> for Offset { fn offset() -> usize { #pyo3_path::impl_::pyclass::class_offset::<#cls>() + #pyo3_path::impl_::pyclass::offset_of!(#cls, #field) } } const GENERATOR: #pyo3_path::impl_::pyclass::PyClassGetterGenerator::< #cls, #ty, Offset, { #pyo3_path::impl_::pyclass::IsPyT::<#ty>::VALUE }, { #pyo3_path::impl_::pyclass::IsToPyObject::<#ty>::VALUE }, > = unsafe { #pyo3_path::impl_::pyclass::PyClassGetterGenerator::new() }; #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Runtime( || GENERATOR.generate(#python_name, #doc) ) } }; Ok(MethodAndMethodDef { associated_method: quote! {}, method_def, }) } // Forward to `IntoPyCallbackOutput`, to handle `#[getter]`s returning results. PropertyType::Function { spec, self_type, .. } => { let wrapper_ident = format_ident!("__pymethod_get_{}__", spec.name); let call = impl_call_getter(cls, spec, self_type, &mut holders, ctx)?; let body = quote! { #pyo3_path::callback::convert(py, #call) }; let init_holders = holders.init_holders(ctx); let check_gil_refs = holders.check_gil_refs(); let associated_method = quote! { #cfg_attrs unsafe fn #wrapper_ident( py: #pyo3_path::Python<'_>, _slf: *mut #pyo3_path::ffi::PyObject ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { #init_holders let result = #body; #check_gil_refs result } }; let method_def = quote! { #cfg_attrs #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static( #pyo3_path::class::PyMethodDefType::Getter( #pyo3_path::class::PyGetterDef::new( #python_name, #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<&PyArg<'_>>, &[FnArg<'_>]) { match args { [FnArg::Py(py), args @ ..] => (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, ctx: &Ctx) -> 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 } (None, None) => { bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`"); } }; let name = CString::new(name).unwrap(); Ok(LitCStr::new(name, field.span(), ctx)) } PropertyType::Function { spec, .. } => Ok(spec.null_terminated_python_name(ctx)), } } fn doc(&self, ctx: &Ctx) -> Cow<'_, PythonDoc> { match self { PropertyType::Descriptor { field, .. } => { Cow::Owned(utils::get_doc(&field.attrs, None, ctx)) } 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"); pub const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc") .ret_ty(Ty::PyHashT) .return_conversion(TokenGenerator( |Ctx { pyo3_path, .. }: &Ctx| quote! { #pyo3_path::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_specialized_conversion( TokenGenerator(|_| quote! { IterBaseKind, IterOptionKind, IterResultOptionKind }), TokenGenerator(|_| quote! { iter_tag }), ); 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_specialized_conversion( TokenGenerator( |_| quote! { AsyncIterBaseKind, AsyncIterOptionKind, AsyncIterResultOptionKind }, ), TokenGenerator(|_| quote! { async_iter_tag }), ); pub 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]); pub 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, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, output_span, } = ctx; let pyo3_path = pyo3_path.to_tokens_spanned(*output_span); match self { Ty::Object | Ty::MaybeNullObject => quote! { *mut #pyo3_path::ffi::PyObject }, Ty::NonNullObject => quote! { ::std::ptr::NonNull<#pyo3_path::ffi::PyObject> }, Ty::IPowModulo => quote! { #pyo3_path::impl_::pymethods::IPowModulo }, Ty::Int | Ty::CompareOp => quote! { ::std::os::raw::c_int }, Ty::PyHashT => quote! { #pyo3_path::ffi::Py_hash_t }, Ty::PySsizeT => quote! { #pyo3_path::ffi::Py_ssize_t }, Ty::Void => quote! { () }, Ty::PyBuffer => quote! { *mut #pyo3_path::ffi::Py_buffer }, } } fn extract( self, ident: &syn::Ident, arg: &FnArg<'_>, extract_error_mode: ExtractErrorMode, holders: &mut Holders, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; match self { Ty::Object => extract_object( extract_error_mode, holders, arg, quote! { #ident }, ctx ), Ty::MaybeNullObject => extract_object( extract_error_mode, holders, arg, quote! { if #ident.is_null() { #pyo3_path::ffi::Py_None() } else { #ident } }, ctx ), Ty::NonNullObject => extract_object( extract_error_mode, holders, arg, quote! { #ident.as_ptr() }, ctx ), Ty::IPowModulo => extract_object( extract_error_mode, holders, arg, quote! { #ident.as_ptr() }, ctx ), Ty::CompareOp => extract_error_mode.handle_error( quote! { #pyo3_path::class::basic::CompareOp::from_raw(#ident) .ok_or_else(|| #pyo3_path::exceptions::PyValueError::new_err("invalid comparison operator")) }, ctx ), Ty::PySsizeT => { let ty = arg.ty(); extract_error_mode.handle_error( quote! { ::std::convert::TryInto::<#ty>::try_into(#ident).map_err(|e| #pyo3_path::exceptions::PyValueError::new_err(e.to_string())) }, ctx ) } // Just pass other types through unmodified Ty::PyBuffer | Ty::Int | Ty::PyHashT | Ty::Void => quote! { #ident }, } } } fn extract_object( extract_error_mode: ExtractErrorMode, holders: &mut Holders, arg: &FnArg<'_>, source_ptr: TokenStream, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let gil_refs_checker = holders.push_gil_refs_checker(arg.ty().span()); let name = arg.name().unraw().to_string(); let extract = if let Some(from_py_with) = arg.from_py_with().map(|from_py_with| &from_py_with.value) { let from_py_with_checker = holders.push_from_py_with_checker(from_py_with.span()); quote! { #pyo3_path::impl_::extract_argument::from_py_with( #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &#source_ptr).0, #name, #pyo3_path::impl_::deprecations::inspect_fn(#from_py_with, &#from_py_with_checker) as fn(_) -> _, ) } } else { let holder = holders.push_holder(Span::call_site()); quote! { #pyo3_path::impl_::extract_argument::extract_argument( #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(py, &#source_ptr).0, &mut #holder, #name ) } }; let extracted = extract_error_mode.handle_error(extract, ctx); quote! { #pyo3_path::impl_::deprecations::inspect_type(#extracted, &#gil_refs_checker) } } enum ReturnMode { ReturnSelf, Conversion(TokenGenerator), SpecializedConversion(TokenGenerator, TokenGenerator), } impl ReturnMode { fn return_call_output(&self, call: TokenStream, ctx: &Ctx, holders: &Holders) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let check_gil_refs = holders.check_gil_refs(); match self { ReturnMode::Conversion(conversion) => { let conversion = TokenGeneratorCtx(*conversion, ctx); quote! { let _result: #pyo3_path::PyResult<#conversion> = #pyo3_path::callback::convert(py, #call); #check_gil_refs #pyo3_path::callback::convert(py, _result) } } ReturnMode::SpecializedConversion(traits, tag) => { let traits = TokenGeneratorCtx(*traits, ctx); let tag = TokenGeneratorCtx(*tag, ctx); quote! { let _result = #call; use #pyo3_path::impl_::pymethods::{#traits}; #check_gil_refs (&_result).#tag().convert(py, _result) } } ReturnMode::ReturnSelf => quote! { let _result: #pyo3_path::PyResult<()> = #pyo3_path::callback::convert(py, #call); _result?; #check_gil_refs #pyo3_path::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 return_specialized_conversion( mut self, traits: TokenGenerator, tag: TokenGenerator, ) -> Self { self.return_mode = Some(ReturnMode::SpecializedConversion(traits, tag)); 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, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; 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(ctx)).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(ctx); let mut holders = Holders::new(); let body = generate_method_body( cls, spec, arguments, *extract_error_mode, &mut holders, return_mode.as_ref(), ctx, )?; let name = spec.name; let holders = holders.init_holders(ctx); let associated_method = quote! { unsafe fn #wrapper_ident( py: #pyo3_path::Python<'_>, _raw_slf: *mut #pyo3_path::ffi::PyObject, #(#arg_idents: #arg_types),* ) -> #pyo3_path::PyResult<#ret_ty> { let function = #cls::#name; // Shadow the method name to avoid #3017 let _slf = _raw_slf; #holders #body } }; let slot_def = quote! {{ unsafe extern "C" fn trampoline( _slf: *mut #pyo3_path::ffi::PyObject, #(#arg_idents: #arg_types),* ) -> #ret_ty { #pyo3_path::impl_::trampoline:: #func_ty ( _slf, #(#arg_idents,)* #cls::#wrapper_ident ) } #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::#slot, pfunc: trampoline as #pyo3_path::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, holders: &mut Holders, return_mode: Option<&ReturnMode>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; let self_arg = spec .tp .self_arg(Some(cls), extract_error_mode, holders, ctx); let rust_name = spec.name; let args = extract_proto_arguments(spec, arguments, extract_error_mode, holders, ctx)?; let call = quote! { #cls::#rust_name(#self_arg #(#args),*) }; Ok(if let Some(return_mode) = return_mode { return_mode.return_call_output(call, ctx, holders) } else { let check_gil_refs = holders.check_gil_refs(); quote! { let result = #call; #check_gil_refs; #pyo3_path::callback::convert(py, result) } }) } 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<'_>, ctx: &Ctx, ) -> Result { let Ctx { pyo3_path, .. } = ctx; 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(ctx)).collect(); let arg_idents: &Vec<_> = &(0..arguments.len()) .map(|i| format_ident!("arg{}", i)) .collect(); let mut holders = Holders::new(); let body = generate_method_body( cls, spec, arguments, *extract_error_mode, &mut holders, None, ctx, )?; let ret_ty = ret_ty.ffi_type(ctx); let holders = holders.init_holders(ctx); Ok(quote! { impl #cls { unsafe fn #wrapper_ident( py: #pyo3_path::Python, _raw_slf: *mut #pyo3_path::ffi::PyObject, #(#arg_idents: #arg_types),* ) -> #pyo3_path::PyResult<#ret_ty> { let _slf = _raw_slf; #holders #body } } impl #pyo3_path::impl_::pyclass::#fragment_trait<#cls> for #pyo3_path::impl_::pyclass::PyClassImplCollector<#cls> { #[inline] unsafe fn #method( self, py: #pyo3_path::Python, _raw_slf: *mut #pyo3_path::ffi::PyObject, #(#arg_idents: #arg_types),* ) -> #pyo3_path::PyResult<#ret_ty> { #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, holders: &mut Holders, ctx: &Ctx, ) -> Result> { let mut args = Vec::with_capacity(spec.signature.arguments.len()); let mut non_python_args = 0; for arg in &spec.signature.arguments { if let FnArg::Py(..) = arg { 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, holders, ctx); 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) } } #[derive(Clone, Copy)] struct TokenGenerator(fn(&Ctx) -> TokenStream); struct TokenGeneratorCtx<'ctx>(TokenGenerator, &'ctx Ctx); impl ToTokens for TokenGeneratorCtx<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { let Self(TokenGenerator(gen), ctx) = self; (gen)(ctx).to_tokens(tokens) } } pyo3-macros-backend-0.22.2/src/pyversions.rs000064400000000000000000000001571046102023000170310ustar 00000000000000use pyo3_build_config::PythonVersion; pub const PY_3_9: PythonVersion = PythonVersion { major: 3, minor: 9 }; pyo3-macros-backend-0.22.2/src/quotes.rs000064400000000000000000000016631046102023000161330ustar 00000000000000use crate::utils::Ctx; use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; pub(crate) fn some_wrap(obj: TokenStream, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; quote! { #pyo3_path::impl_::wrap::SomeWrap::wrap(#obj) } } pub(crate) fn ok_wrap(obj: TokenStream, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, output_span, } = ctx; let pyo3_path = pyo3_path.to_tokens_spanned(*output_span); quote_spanned! {*output_span=> #pyo3_path::impl_::wrap::OkWrap::wrap(#obj) .map_err(::core::convert::Into::<#pyo3_path::PyErr>::into) } } pub(crate) fn map_result_into_ptr(result: TokenStream, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, output_span, } = ctx; let pyo3_path = pyo3_path.to_tokens_spanned(*output_span); quote_spanned! {*output_span=> #pyo3_path::impl_::wrap::map_result_into_ptr(py, #result) } } pyo3-macros-backend-0.22.2/src/utils.rs000064400000000000000000000220141046102023000157440ustar 00000000000000use crate::attributes::{CrateAttribute, RenamingRule}; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use std::ffi::CString; use syn::spanned::Spanned; use syn::{punctuated::Punctuated, Token}; /// 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); } }; ($($condition:expr, $span:expr => $msg:expr;)*) => { if let Some(e) = [$( (!($condition)).then(|| err_spanned!($span => $msg)), )*] .into_iter() .flatten() .reduce(|mut acc, e| { acc.combine(e); acc }) { return Err(e); } }; } /// 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 } // TODO: Replace usage of this by [`syn::LitCStr`] when on MSRV 1.77 #[derive(Clone)] pub struct LitCStr { lit: CString, span: Span, pyo3_path: PyO3CratePath, } impl LitCStr { pub fn new(lit: CString, span: Span, ctx: &Ctx) -> Self { Self { lit, span, pyo3_path: ctx.pyo3_path.clone(), } } pub fn empty(ctx: &Ctx) -> Self { Self { lit: CString::new("").unwrap(), span: Span::call_site(), pyo3_path: ctx.pyo3_path.clone(), } } } impl quote::ToTokens for LitCStr { fn to_tokens(&self, tokens: &mut TokenStream) { if cfg!(c_str_lit) { syn::LitCStr::new(&self.lit, self.span).to_tokens(tokens); } else { let pyo3_path = &self.pyo3_path; let lit = self.lit.to_str().unwrap(); tokens.extend(quote::quote_spanned!(self.span => #pyo3_path::ffi::c_str!(#lit))); } } } /// 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(PythonDocKind); #[derive(Clone)] enum PythonDocKind { LitCStr(LitCStr), // There is currently no way to `concat!` c-string literals, we fallback to the `c_str!` macro in // this case. Tokens(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, ctx: &Ctx, ) -> PythonDoc { let Ctx { pyo3_path, .. } = ctx; // 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); }); PythonDoc(PythonDocKind::Tokens( quote!(#pyo3_path::ffi::c_str!(#tokens)), )) } else { // Just a string doc - return directly with nul terminator let docs = CString::new(current_part).unwrap(); PythonDoc(PythonDocKind::LitCStr(LitCStr::new( docs, Span::call_site(), ctx, ))) } } impl quote::ToTokens for PythonDoc { fn to_tokens(&self, tokens: &mut TokenStream) { match &self.0 { PythonDocKind::LitCStr(lit) => lit.to_tokens(tokens), PythonDocKind::Tokens(toks) => toks.to_tokens(tokens), } } } pub fn unwrap_ty_group(mut ty: &syn::Type) -> &syn::Type { while let syn::Type::Group(g) = ty { ty = &*g.elem; } ty } pub struct Ctx { /// Where we can find the pyo3 crate pub pyo3_path: PyO3CratePath, /// If we are in a pymethod or pyfunction, /// this will be the span of the return type pub output_span: Span, } impl Ctx { pub(crate) fn new(attr: &Option, signature: Option<&syn::Signature>) -> Self { let pyo3_path = match attr { Some(attr) => PyO3CratePath::Given(attr.value.0.clone()), None => PyO3CratePath::Default, }; let output_span = if let Some(syn::Signature { output: syn::ReturnType::Type(_, output_type), .. }) = &signature { output_type.span() } else { Span::call_site() }; Self { pyo3_path, output_span, } } } #[derive(Clone)] pub enum PyO3CratePath { Given(syn::Path), Default, } impl PyO3CratePath { pub fn to_tokens_spanned(&self, span: Span) -> TokenStream { match self { Self::Given(path) => quote::quote_spanned! { span => #path }, Self::Default => quote::quote_spanned! { span => ::pyo3 }, } } } impl quote::ToTokens for PyO3CratePath { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::Given(path) => path.to_tokens(tokens), Self::Default => quote::quote! { ::pyo3 }.to_tokens(tokens), } } } 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(), } } pub(crate) fn is_abi3() -> bool { pyo3_build_config::get().abi3 }