pyo3-macros-backend-0.27.2/.cargo_vcs_info.json0000644000000001610000000000100146630ustar { "git": { "sha1": "117102d832d7bd7e66e75501e5722976b28b1a88" }, "path_in_vcs": "pyo3-macros-backend" }pyo3-macros-backend-0.27.2/Cargo.lock0000644000000033700000000000100126430ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3-build-config" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-macros-backend" version = "0.27.2" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", "syn", ] [[package]] name = "quote" version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "target-lexicon" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" pyo3-macros-backend-0.27.2/Cargo.toml0000644000000045020000000000100126640ustar # 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" rust-version = "1.74" name = "pyo3-macros-backend" version = "0.27.2" authors = ["PyO3 Project and Contributors "] build = "build.rs" autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Code generation for PyO3 package" homepage = "https://github.com/pyo3/pyo3" readme = false keywords = [ "pyo3", "python", "cpython", "ffi", ] categories = [ "api-bindings", "development-tools::ffi", ] license = "MIT OR Apache-2.0" repository = "https://github.com/pyo3/pyo3" [features] experimental-async = [] experimental-inspect = [] [lib] name = "pyo3_macros_backend" path = "src/lib.rs" [dependencies.heck] version = "0.5" [dependencies.proc-macro2] version = "1.0.60" default-features = false [dependencies.pyo3-build-config] version = "=0.27.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", "visit-mut", ] default-features = false [build-dependencies.pyo3-build-config] version = "=0.27.2" [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" undocumented_unsafe_blocks = "allow" 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" unsafe_op_in_unsafe_fn = "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.27.2/Cargo.toml.orig000064400000000000000000000023111046102023000163410ustar 00000000000000[package] name = "pyo3-macros-backend" version = "0.27.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" rust-version.workspace = true # 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.27.2", features = ["resolve-config"] } quote = { version = "1", default-features = false } [dependencies.syn] # 2.0.59 for `LitCStr` version = "2.0.59" default-features = false features = ["derive", "parsing", "printing", "clone-impls", "full", "extra-traits", "visit-mut"] [build-dependencies] pyo3-build-config = { path = "../pyo3-build-config", version = "=0.27.2" } [lints] workspace = true [features] experimental-async = [] experimental-inspect = [] pyo3-macros-backend-0.27.2/LICENSE-APACHE000064400000000000000000000231171046102023000154050ustar 00000000000000Copyright (c) 2017-present PyO3 Project and Contributors. https://github.com/PyO3 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. END OF TERMS AND CONDITIONS pyo3-macros-backend-0.27.2/LICENSE-MIT000064400000000000000000000021231046102023000151070ustar 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.27.2/build.rs000064400000000000000000000001511046102023000151170ustar 00000000000000fn main() { pyo3_build_config::print_expected_cfgs(); pyo3_build_config::print_feature_cfgs(); } pyo3-macros-backend-0.27.2/src/attributes.rs000064400000000000000000000324751046102023000170130ustar 00000000000000use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::parse::Parser; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Expr, ExprPath, Ident, Index, LitBool, LitStr, Member, Path, Result, Token, }; use crate::combine_errors::CombineErrors; 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!(into_py_with); syn::custom_keyword!(item); syn::custom_keyword!(immutable_type); 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!(str); 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); syn::custom_keyword!(generic); syn::custom_keyword!(gil_used); syn::custom_keyword!(warn); syn::custom_keyword!(message); syn::custom_keyword!(category); syn::custom_keyword!(from_py_object); syn::custom_keyword!(skip_from_py_object); } fn take_int(read: &mut &str, tracker: &mut usize) -> String { let mut int = String::new(); for (i, ch) in read.char_indices() { match ch { '0'..='9' => { *tracker += 1; int.push(ch) } _ => { *read = &read[i..]; break; } } } int } fn take_ident(read: &mut &str, tracker: &mut usize) -> Ident { let mut ident = String::new(); if read.starts_with("r#") { ident.push_str("r#"); *tracker += 2; *read = &read[2..]; } for (i, ch) in read.char_indices() { match ch { 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => { *tracker += 1; ident.push(ch) } _ => { *read = &read[i..]; break; } } } Ident::parse_any.parse_str(&ident).unwrap() } // shorthand parsing logic inspiration taken from https://github.com/dtolnay/thiserror/blob/master/impl/src/fmt.rs fn parse_shorthand_format(fmt: LitStr) -> Result<(LitStr, Vec)> { let span = fmt.span(); let token = fmt.token(); let value = fmt.value(); let mut read = value.as_str(); let mut out = String::new(); let mut members = Vec::new(); let mut tracker = 1; while let Some(brace) = read.find('{') { tracker += brace; out += &read[..brace + 1]; read = &read[brace + 1..]; if read.starts_with('{') { out.push('{'); read = &read[1..]; tracker += 2; continue; } let next = match read.chars().next() { Some(next) => next, None => break, }; tracker += 1; let member = match next { '0'..='9' => { let start = tracker; let index = take_int(&mut read, &mut tracker).parse::().unwrap(); let end = tracker; let subspan = token.subspan(start..end).unwrap_or(span); let idx = Index { index, span: subspan, }; Member::Unnamed(idx) } 'a'..='z' | 'A'..='Z' | '_' => { let start = tracker; let mut ident = take_ident(&mut read, &mut tracker); let end = tracker; let subspan = token.subspan(start..end).unwrap_or(span); ident.set_span(subspan); Member::Named(ident) } '}' | ':' => { let start = tracker; tracker += 1; let end = tracker; let subspan = token.subspan(start..end).unwrap_or(span); // we found a closing bracket or formatting ':' without finding a member, we assume the user wants the instance formatted here bail_spanned!(subspan.span() => "No member found, you must provide a named or positionally specified member.") } _ => continue, }; members.push(member); } out += read; Ok((LitStr::new(&out, span), members)) } #[derive(Clone, Debug)] pub struct StringFormatter { pub fmt: LitStr, pub args: Vec, } impl Parse for crate::attributes::StringFormatter { fn parse(input: ParseStream<'_>) -> Result { let (fmt, args) = parse_shorthand_format(input.parse()?)?; Ok(Self { fmt, args }) } } impl ToTokens for crate::attributes::StringFormatter { fn to_tokens(&self, tokens: &mut TokenStream) { self.fmt.to_tokens(tokens); tokens.extend(quote! {self.args}) } } #[derive(Clone, Debug)] pub struct KeywordAttribute { pub kw: K, pub value: V, } #[derive(Clone, Debug)] pub struct OptionalKeywordAttribute { pub kw: K, pub value: Option, } /// 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 signature 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 StrFormatterAttribute = OptionalKeywordAttribute; pub type TextSignatureAttribute = KeywordAttribute; pub type SubmoduleAttribute = kw::submodule; pub type GILUsedAttribute = KeywordAttribute; impl Parse for KeywordAttribute { fn parse(input: ParseStream<'_>) -> Result { let kw: K = input.parse()?; let _: Token![=] = input.parse()?; let value = input.parse()?; Ok(KeywordAttribute { kw, value }) } } impl ToTokens for KeywordAttribute { fn to_tokens(&self, tokens: &mut TokenStream) { self.kw.to_tokens(tokens); Token![=](self.kw.span()).to_tokens(tokens); self.value.to_tokens(tokens); } } impl Parse for OptionalKeywordAttribute { fn parse(input: ParseStream<'_>) -> Result { let kw: K = input.parse()?; let value = match input.parse::() { Ok(_) => Some(input.parse()?), Err(_) => None, }; Ok(OptionalKeywordAttribute { kw, value }) } } impl ToTokens for OptionalKeywordAttribute { fn to_tokens(&self, tokens: &mut TokenStream) { self.kw.to_tokens(tokens); if self.value.is_some() { Token![=](self.kw.span()).to_tokens(tokens); self.value.to_tokens(tokens); } } } pub type FromPyWithAttribute = KeywordAttribute; pub type IntoPyWithAttribute = KeywordAttribute; pub type DefaultAttribute = OptionalKeywordAttribute; /// 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| match get_pyo3_options(attr) { Ok(result) => { if let Some(options) = result { out.extend(options.into_iter().map(|a| Ok(a))); Ok(true) } else { Ok(false) } } Err(err) => { out.push(Err(err)); Ok(true) } })?; let out: Vec = out.into_iter().try_combine_syn_errors()?; Ok(out) } pyo3-macros-backend-0.27.2/src/combine_errors.rs000064400000000000000000000015201046102023000176200ustar 00000000000000pub(crate) trait CombineErrors: Iterator { type Ok; fn try_combine_syn_errors(self) -> syn::Result>; } impl CombineErrors for I where I: Iterator>, { type Ok = T; fn try_combine_syn_errors(self) -> syn::Result> { let mut oks: Vec = Vec::new(); let mut errors: Vec = Vec::new(); for res in self { match res { Ok(val) => oks.push(val), Err(e) => errors.push(e), } } let mut err_iter = errors.into_iter(); let mut err = match err_iter.next() { // There are no errors None => return Ok(oks), Some(e) => e, }; for e in err_iter { err.combine(e); } Err(err) } } pyo3-macros-backend-0.27.2/src/derive_attributes.rs000064400000000000000000000205411046102023000203400ustar 00000000000000use crate::attributes::{ self, get_pyo3_options, CrateAttribute, DefaultAttribute, FromPyWithAttribute, IntoPyWithAttribute, RenameAllAttribute, }; use proc_macro2::Span; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; use syn::{parenthesized, Attribute, LitStr, Result, Token}; /// Attributes for deriving `FromPyObject`/`IntoPyObject` scoped on containers. pub enum ContainerAttribute { /// Treat the Container as a Wrapper, operate directly on its field 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), /// Converts the field idents according to the [RenamingRule](attributes::RenamingRule) before extraction RenameAll(RenameAllAttribute), } impl Parse for ContainerAttribute { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::transparent) { let kw: attributes::kw::transparent = input.parse()?; Ok(ContainerAttribute::Transparent(kw)) } else if lookahead.peek(attributes::kw::from_item_all) { let kw: attributes::kw::from_item_all = input.parse()?; Ok(ContainerAttribute::ItemAll(kw)) } else if lookahead.peek(attributes::kw::annotation) { let _: attributes::kw::annotation = input.parse()?; let _: Token![=] = input.parse()?; input.parse().map(ContainerAttribute::ErrorAnnotation) } else if lookahead.peek(Token![crate]) { input.parse().map(ContainerAttribute::Crate) } else if lookahead.peek(attributes::kw::rename_all) { input.parse().map(ContainerAttribute::RenameAll) } else { Err(lookahead.error()) } } } #[derive(Default, Clone)] pub struct ContainerAttributes { /// Treat the Container as a Wrapper, operate directly on its field pub transparent: Option, /// Force every field to be extracted from item of source Python object. pub from_item_all: Option, /// Change the name of an enum variant in the generated error message. pub annotation: Option, /// Change the path for the pyo3 crate pub krate: Option, /// Converts the field idents according to the [RenamingRule](attributes::RenamingRule) before extraction pub rename_all: Option, } impl ContainerAttributes { pub fn from_attrs(attrs: &[Attribute]) -> Result { let mut options = ContainerAttributes::default(); for attr in attrs { if let Some(pyo3_attrs) = get_pyo3_options(attr)? { pyo3_attrs .into_iter() .try_for_each(|opt| options.set_option(opt))?; } } Ok(options) } fn set_option(&mut self, option: ContainerAttribute) -> 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 { ContainerAttribute::Transparent(transparent) => set_option!(transparent), ContainerAttribute::ItemAll(from_item_all) => set_option!(from_item_all), ContainerAttribute::ErrorAnnotation(annotation) => set_option!(annotation), ContainerAttribute::Crate(krate) => set_option!(krate), ContainerAttribute::RenameAll(rename_all) => set_option!(rename_all), } Ok(()) } } #[derive(Clone, Debug)] pub enum FieldGetter { GetItem(attributes::kw::item, Option), GetAttr(attributes::kw::attribute, Option), } impl FieldGetter { pub fn span(&self) -> Span { match self { FieldGetter::GetItem(item, _) => item.span, FieldGetter::GetAttr(attribute, _) => attribute.span, } } } pub enum FieldAttribute { Getter(FieldGetter), FromPyWith(FromPyWithAttribute), IntoPyWith(IntoPyWithAttribute), Default(DefaultAttribute), } impl Parse for FieldAttribute { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::attribute) { let attr_kw: 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(Self::Getter(FieldGetter::GetAttr(attr_kw, Some(attr_name)))) } else { Ok(Self::Getter(FieldGetter::GetAttr(attr_kw, None))) } } else if lookahead.peek(attributes::kw::item) { let item_kw: 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(Self::Getter(FieldGetter::GetItem(item_kw, Some(key)))) } else { Ok(Self::Getter(FieldGetter::GetItem(item_kw, None))) } } else if lookahead.peek(attributes::kw::from_py_with) { input.parse().map(Self::FromPyWith) } else if lookahead.peek(attributes::kw::into_py_with) { input.parse().map(FieldAttribute::IntoPyWith) } else if lookahead.peek(Token![default]) { input.parse().map(Self::Default) } else { Err(lookahead.error()) } } } #[derive(Clone, Debug, Default)] pub struct FieldAttributes { pub getter: Option, pub from_py_with: Option, pub into_py_with: Option, pub default: Option, } impl FieldAttributes { /// Extract the field attributes. pub fn from_attrs(attrs: &[Attribute]) -> Result { let mut options = FieldAttributes::default(); for attr in attrs { if let Some(pyo3_attrs) = get_pyo3_options(attr)? { pyo3_attrs .into_iter() .try_for_each(|opt| options.set_option(opt))?; } } Ok(options) } fn set_option(&mut self, option: FieldAttribute) -> syn::Result<()> { macro_rules! set_option { ($key:ident) => { set_option!($key, concat!("`", stringify!($key), "` may only be specified once")) }; ($key:ident, $msg: expr) => {{ ensure_spanned!( self.$key.is_none(), $key.span() => $msg ); self.$key = Some($key); }} } match option { FieldAttribute::Getter(getter) => { set_option!(getter, "only one of `attribute` or `item` can be provided") } FieldAttribute::FromPyWith(from_py_with) => set_option!(from_py_with), FieldAttribute::IntoPyWith(into_py_with) => set_option!(into_py_with), FieldAttribute::Default(default) => set_option!(default), } Ok(()) } } pyo3-macros-backend-0.27.2/src/frompyobject.rs000064400000000000000000000610561046102023000173250ustar 00000000000000use crate::attributes::{DefaultAttribute, FromPyWithAttribute, RenamingRule}; use crate::derive_attributes::{ContainerAttributes, FieldAttributes, FieldGetter}; #[cfg(feature = "experimental-inspect")] use crate::introspection::{elide_lifetimes, ConcatenationBuilder}; use crate::utils::{self, Ctx}; use proc_macro2::TokenStream; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ ext::IdentExt, parse_quote, punctuated::Punctuated, spanned::Spanned, DataEnum, DeriveInput, Fields, Ident, 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, options: ContainerAttributes, ) -> 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 mut variant_options = ContainerAttributes::from_attrs(&variant.attrs)?; if let Some(rename_all) = &options.rename_all { ensure_spanned!( variant_options.rename_all.is_none(), variant_options.rename_all.span() => "Useless variant `rename_all` - enum is already annotated with `rename_all" ); variant_options.rename_all = Some(rename_all.clone()); } let var_ident = &variant.ident; Container::new( &variant.fields, parse_quote!(#ident::#var_ident), variant_options, ) }) .collect::>>()?; Ok(Enum { enum_ident: ident, variants, }) } /// Build derivation body for enums. fn build(&self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let mut var_extracts = Vec::new(); let mut variant_names = Vec::new(); let mut error_names = Vec::new(); for var in &self.variants { let struct_derive = var.build(ctx); 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 ) ) ) } #[cfg(feature = "experimental-inspect")] fn write_input_type(&self, builder: &mut ConcatenationBuilder, ctx: &Ctx) { for (i, var) in self.variants.iter().enumerate() { if i > 0 { builder.push_str(" | "); } var.write_input_type(builder, ctx); } } } struct NamedStructField<'a> { ident: &'a syn::Ident, getter: Option, from_py_with: Option, default: Option, ty: &'a syn::Type, } struct TupleStructField { from_py_with: Option, ty: syn::Type, } /// 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. #[cfg_attr(not(feature = "experimental-inspect"), allow(unused))] StructNewtype(&'a syn::Ident, Option, &'a syn::Type), /// 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. #[cfg_attr(not(feature = "experimental-inspect"), allow(unused))] TupleNewtype(Option, Box), } /// Data container /// /// Either describes a struct or an enum variant. struct Container<'a> { path: syn::Path, ty: ContainerType<'a>, err_name: String, rename_rule: Option, } 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: ContainerAttributes) -> Result { let style = match fields { Fields::Unnamed(unnamed) if !unnamed.unnamed.is_empty() => { ensure_spanned!( options.rename_all.is_none(), options.rename_all.span() => "`rename_all` is useless on tuple structs and variants." ); let mut tuple_fields = unnamed .unnamed .iter() .map(|field| { let attrs = FieldAttributes::from_attrs(&field.attrs)?; ensure_spanned!( attrs.getter.is_none(), field.span() => "`getter` is not permitted on tuple struct elements." ); ensure_spanned!( attrs.default.is_none(), field.span() => "`default` is not permitted on tuple struct elements." ); Ok(TupleStructField { from_py_with: attrs.from_py_with, ty: field.ty.clone(), }) }) .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, Box::new(field.ty)) } else if options.transparent.is_some() { 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 = FieldAttributes::from_attrs(&field.attrs)?; if let Some(ref from_item_all) = options.from_item_all { if let Some(replaced) = attrs.getter.replace(FieldGetter::GetItem(parse_quote!(item), None)) { match replaced { FieldGetter::GetItem(item, Some(item_name)) => { attrs.getter = Some(FieldGetter::GetItem(item, 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, default: attrs.default, ty: &field.ty, }) }) .collect::>>()?; if struct_fields.iter().all(|field| field.default.is_some()) { bail_spanned!( fields.span() => "cannot derive FromPyObject for structs and variants with only default values" ) } else if options.transparent.is_some() { ensure_spanned!( struct_fields.len() == 1, fields.span() => "transparent structs and variants can only have 1 field" ); ensure_spanned!( options.rename_all.is_none(), options.rename_all.span() => "`rename_all` is not permitted on `transparent` structs and variants" ); 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, field.ty) } 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, rename_rule: options.rename_all.map(|v| v.value.rule), }; 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 { 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 { 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(); if let Some(FromPyWithAttribute { kw, value: expr_path, }) = from_py_with { let extractor = quote_spanned! { kw.span => { let from_py_with: fn(_) -> _ = #expr_path; from_py_with } }; quote! { Ok(#self_ty { #ident: #pyo3_path::impl_::frompyobject::extract_struct_field_with(#extractor, obj, #struct_name, #field_name)? }) } } else { quote! { Ok(#self_ty { #ident: #pyo3_path::impl_::frompyobject::extract_struct_field(obj, #struct_name, #field_name)? }) } } } else if let Some(FromPyWithAttribute { kw, value: expr_path, }) = from_py_with { let extractor = quote_spanned! { kw.span => { let from_py_with: fn(_) -> _ = #expr_path; from_py_with } }; quote! { #pyo3_path::impl_::frompyobject::extract_tuple_struct_field_with(#extractor, obj, #struct_name, 0).map(#self_ty) } } else { quote! { #pyo3_path::impl_::frompyobject::extract_tuple_struct_field(obj, #struct_name, 0).map(#self_ty) } } } fn build_tuple_struct(&self, struct_fields: &[TupleStructField], ctx: &Ctx) -> 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))| { if let Some(FromPyWithAttribute { kw, value: expr_path, .. }) = &field.from_py_with { let extractor = quote_spanned! { kw.span => { let from_py_with: fn(_) -> _ = #expr_path; from_py_with } }; quote! { #pyo3_path::impl_::frompyobject::extract_tuple_struct_field_with(#extractor, &#ident, #struct_name, #index)? } } else { quote!{ #pyo3_path::impl_::frompyobject::extract_tuple_struct_field(&#ident, #struct_name, #index)? }} }); 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), } ) } fn build_struct(&self, struct_fields: &[NamedStructField<'_>], ctx: &Ctx) -> 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.unraw().to_string(); let getter = match field .getter .as_ref() .unwrap_or(&FieldGetter::GetAttr(parse_quote!(attribute), None)) { FieldGetter::GetAttr(_, Some(name)) => { quote!(#pyo3_path::types::PyAnyMethods::getattr(obj, #pyo3_path::intern!(obj.py(), #name))) } FieldGetter::GetAttr(_, None) => { let name = self .rename_rule .map(|rule| utils::apply_renaming_rule(rule, &field_name)); let name = name.as_deref().unwrap_or(&field_name); quote!(#pyo3_path::types::PyAnyMethods::getattr(obj, #pyo3_path::intern!(obj.py(), #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) => { let name = self .rename_rule .map(|rule| utils::apply_renaming_rule(rule, &field_name)); let name = name.as_deref().unwrap_or(&field_name); quote!(#pyo3_path::types::PyAnyMethods::get_item(obj, #pyo3_path::intern!(obj.py(), #name))) } }; let extractor = if let Some(FromPyWithAttribute { kw, value: expr_path, }) = &field.from_py_with { let extractor = quote_spanned! { kw.span => { let from_py_with: fn(_) -> _ = #expr_path; from_py_with } }; quote! (#pyo3_path::impl_::frompyobject::extract_struct_field_with(#extractor, &#getter?, #struct_name, #field_name)?) } else { quote!(#pyo3_path::impl_::frompyobject::extract_struct_field(&value, #struct_name, #field_name)?) }; let extracted = if let Some(default) = &field.default { let default_expr = if let Some(default_expr) = &default.value { default_expr.to_token_stream() } else { quote!(::std::default::Default::default()) }; quote!(if let ::std::result::Result::Ok(value) = #getter { #extractor } else { #default_expr }) } else { quote!({ let value = #getter?; #extractor }) }; fields.push(quote!(#ident: #extracted)); } quote!(::std::result::Result::Ok(#self_ty{#fields})) } #[cfg(feature = "experimental-inspect")] fn write_input_type(&self, builder: &mut ConcatenationBuilder, ctx: &Ctx) { match &self.ty { ContainerType::StructNewtype(_, from_py_with, ty) => { Self::write_field_input_type(from_py_with, ty, builder, ctx); } ContainerType::TupleNewtype(from_py_with, ty) => { Self::write_field_input_type(from_py_with, ty, builder, ctx); } ContainerType::Tuple(tups) => { builder.push_str("tuple["); for (i, TupleStructField { from_py_with, ty }) in tups.iter().enumerate() { if i > 0 { builder.push_str(", "); } Self::write_field_input_type(from_py_with, ty, builder, ctx); } builder.push_str("]"); } ContainerType::Struct(_) => { // TODO: implement using a Protocol? builder.push_str("_typeshed.Incomplete") } } } #[cfg(feature = "experimental-inspect")] fn write_field_input_type( from_py_with: &Option, ty: &syn::Type, builder: &mut ConcatenationBuilder, ctx: &Ctx, ) { if from_py_with.is_some() { // We don't know what from_py_with is doing builder.push_str("_typeshed.Incomplete") } else { let mut ty = ty.clone(); elide_lifetimes(&mut ty); let pyo3_crate_path = &ctx.pyo3_path; builder.push_tokens( quote! { <#ty as #pyo3_crate_path::FromPyObject<'_, '_>>::INPUT_TYPE.as_bytes() }, ) } } } 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 options = ContainerAttributes::from_attrs(&tokens.attrs)?; let ctx = &Ctx::new(&options.krate, None); let Ctx { pyo3_path, .. } = &ctx; let (_, ty_generics, _) = tokens.generics.split_for_impl(); let mut trait_generics = tokens.generics.clone(); let lt_param = if let Some(lt) = verify_and_get_lifetime(&trait_generics)? { lt.clone() } else { trait_generics.params.push(parse_quote!('py)); parse_quote!('py) }; let (impl_generics, _, where_clause) = trait_generics.split_for_impl(); let mut where_clause = where_clause.cloned().unwrap_or_else(|| parse_quote!(where)); for param in trait_generics.type_params() { let gen_ident = ¶m.ident; where_clause .predicates .push(parse_quote!(#gen_ident: #pyo3_path::conversion::FromPyObjectOwned<#lt_param>)) } let derives = match &tokens.data { syn::Data::Enum(en) => { if options.transparent.is_some() || 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, options.clone())?; 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.clone())?; st.build(ctx) } syn::Data::Union(_) => bail_spanned!( tokens.span() => "#[derive(FromPyObject)] is not supported for unions" ), }; #[cfg(feature = "experimental-inspect")] let input_type = { let mut builder = ConcatenationBuilder::default(); if tokens .generics .params .iter() .all(|p| matches!(p, syn::GenericParam::Lifetime(_))) { match &tokens.data { syn::Data::Enum(en) => { Enum::new(en, &tokens.ident, options)?.write_input_type(&mut builder, ctx) } syn::Data::Struct(st) => { let ident = &tokens.ident; Container::new(&st.fields, parse_quote!(#ident), options.clone())? .write_input_type(&mut builder, ctx) } syn::Data::Union(_) => { // Not supported at this point builder.push_str("_typeshed.Incomplete") } } } else { // We don't know how to deal with generic parameters // Blocked by https://github.com/rust-lang/rust/issues/76560 builder.push_str("_typeshed.Incomplete") }; let input_type = builder.into_token_stream(&ctx.pyo3_path); quote! { const INPUT_TYPE: &'static str = unsafe { ::std::str::from_utf8_unchecked(#input_type) }; } }; #[cfg(not(feature = "experimental-inspect"))] let input_type = quote! {}; let ident = &tokens.ident; Ok(quote!( #[automatically_derived] impl #impl_generics #pyo3_path::FromPyObject<'_, #lt_param> for #ident #ty_generics #where_clause { type Error = #pyo3_path::PyErr; fn extract(obj: #pyo3_path::Borrowed<'_, #lt_param, #pyo3_path::PyAny>) -> ::std::result::Result { let obj: &#pyo3_path::Bound<'_, _> = &*obj; #derives } #input_type } )) } pyo3-macros-backend-0.27.2/src/intopyobject.rs000064400000000000000000000571571046102023000173420ustar 00000000000000use crate::attributes::{IntoPyWithAttribute, RenamingRule}; use crate::derive_attributes::{ContainerAttributes, FieldAttributes}; #[cfg(feature = "experimental-inspect")] use crate::introspection::{elide_lifetimes, ConcatenationBuilder}; use crate::utils::{self, Ctx}; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::ext::IdentExt; use syn::spanned::Spanned as _; use syn::{parse_quote, DataEnum, DeriveInput, Fields, Ident, Index, Result}; struct ItemOption(Option); enum IntoPyObjectTypes { Transparent(syn::Type), Opaque { target: TokenStream, output: TokenStream, error: TokenStream, }, } struct IntoPyObjectImpl { types: IntoPyObjectTypes, body: TokenStream, } struct NamedStructField<'a> { ident: &'a syn::Ident, field: &'a syn::Field, item: Option, into_py_with: Option, } struct TupleStructField<'a> { field: &'a syn::Field, into_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::Field), /// 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(&'a syn::Field), } /// Data container /// /// Either describes a struct or an enum variant. struct Container<'a, const REF: bool> { path: syn::Path, receiver: Option, ty: ContainerType<'a>, rename_rule: Option, } /// Construct a container based on fields, identifier and attributes. impl<'a, const REF: bool> Container<'a, REF> { /// /// Fails if the variant has no fields or incompatible attributes. fn new( receiver: Option, fields: &'a Fields, path: syn::Path, options: ContainerAttributes, ) -> Result { let style = match fields { Fields::Unnamed(unnamed) if !unnamed.unnamed.is_empty() => { ensure_spanned!( options.rename_all.is_none(), options.rename_all.span() => "`rename_all` is useless on tuple structs and variants." ); let mut tuple_fields = unnamed .unnamed .iter() .map(|field| { let attrs = FieldAttributes::from_attrs(&field.attrs)?; ensure_spanned!( attrs.getter.is_none(), attrs.getter.unwrap().span() => "`item` and `attribute` are not permitted on tuple struct elements." ); Ok(TupleStructField { field, into_py_with: attrs.into_py_with, }) }) .collect::>>()?; if tuple_fields.len() == 1 { // Always treat a 1-length tuple struct as "transparent", even without the // explicit annotation. let TupleStructField { field, into_py_with, } = tuple_fields.pop().unwrap(); ensure_spanned!( into_py_with.is_none(), into_py_with.span() => "`into_py_with` is not permitted on `transparent` structs" ); ContainerType::TupleNewtype(field) } else if options.transparent.is_some() { 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() => { if options.transparent.is_some() { ensure_spanned!( named.named.iter().count() == 1, fields.span() => "transparent structs and variants can only have 1 field" ); let field = named.named.iter().next().unwrap(); let attrs = FieldAttributes::from_attrs(&field.attrs)?; ensure_spanned!( attrs.getter.is_none(), attrs.getter.unwrap().span() => "`transparent` structs may not have `item` nor `attribute` for the inner field" ); ensure_spanned!( options.rename_all.is_none(), options.rename_all.span() => "`rename_all` is not permitted on `transparent` structs and variants" ); ensure_spanned!( attrs.into_py_with.is_none(), attrs.into_py_with.span() => "`into_py_with` is not permitted on `transparent` structs or variants" ); ContainerType::StructNewtype(field) } else { let struct_fields = named .named .iter() .map(|field| { let ident = field .ident .as_ref() .expect("Named fields should have identifiers"); let attrs = FieldAttributes::from_attrs(&field.attrs)?; Ok(NamedStructField { ident, field, item: attrs.getter.and_then(|getter| match getter { crate::derive_attributes::FieldGetter::GetItem(_, lit) => { Some(ItemOption(lit)) } crate::derive_attributes::FieldGetter::GetAttr(_, _) => None, }), into_py_with: attrs.into_py_with, }) }) .collect::>>()?; ContainerType::Struct(struct_fields) } } _ => bail_spanned!( fields.span() => "cannot derive `IntoPyObject` for empty structs" ), }; let v = Container { path, receiver, ty: style, rename_rule: options.rename_all.map(|v| v.value.rule), }; Ok(v) } fn match_pattern(&self) -> TokenStream { let path = &self.path; let pattern = match &self.ty { ContainerType::Struct(fields) => fields .iter() .enumerate() .map(|(i, f)| { let ident = f.ident; let new_ident = format_ident!("arg{i}"); quote! {#ident: #new_ident,} }) .collect::(), ContainerType::StructNewtype(field) => { let ident = field.ident.as_ref().unwrap(); quote!(#ident: arg0) } ContainerType::Tuple(fields) => { let i = (0..fields.len()).map(Index::from); let idents = (0..fields.len()).map(|i| format_ident!("arg{i}")); quote! { #(#i: #idents,)* } } ContainerType::TupleNewtype(_) => quote!(0: arg0), }; quote! { #path{ #pattern } } } /// Build derivation body for a struct. fn build(&self, ctx: &Ctx) -> IntoPyObjectImpl { match &self.ty { ContainerType::StructNewtype(field) | ContainerType::TupleNewtype(field) => { self.build_newtype_struct(field, ctx) } ContainerType::Tuple(fields) => self.build_tuple_struct(fields, ctx), ContainerType::Struct(fields) => self.build_struct(fields, ctx), } } fn build_newtype_struct(&self, field: &syn::Field, ctx: &Ctx) -> IntoPyObjectImpl { let Ctx { pyo3_path, .. } = ctx; let ty = &field.ty; let unpack = self .receiver .as_ref() .map(|i| { let pattern = self.match_pattern(); quote! { let #pattern = #i;} }) .unwrap_or_default(); IntoPyObjectImpl { types: IntoPyObjectTypes::Transparent(ty.clone()), body: quote_spanned! { ty.span() => #unpack #pyo3_path::conversion::IntoPyObject::into_pyobject(arg0, py) }, } } fn build_struct(&self, fields: &[NamedStructField<'_>], ctx: &Ctx) -> IntoPyObjectImpl { let Ctx { pyo3_path, .. } = ctx; let unpack = self .receiver .as_ref() .map(|i| { let pattern = self.match_pattern(); quote! { let #pattern = #i;} }) .unwrap_or_default(); let setter = fields .iter() .enumerate() .map(|(i, f)| { let key = f .item .as_ref() .and_then(|item| item.0.as_ref()) .map(|item| item.into_token_stream()) .unwrap_or_else(|| { let name = f.ident.unraw().to_string(); self.rename_rule.map(|rule| utils::apply_renaming_rule(rule, &name)).unwrap_or(name).into_token_stream() }); let value = Ident::new(&format!("arg{i}"), f.field.ty.span()); if let Some(expr_path) = f.into_py_with.as_ref().map(|i|&i.value) { let cow = if REF { quote!(::std::borrow::Cow::Borrowed(#value)) } else { quote!(::std::borrow::Cow::Owned(#value)) }; quote! { let into_py_with: fn(::std::borrow::Cow<'_, _>, #pyo3_path::Python<'py>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'py, #pyo3_path::PyAny>> = #expr_path; #pyo3_path::types::PyDictMethods::set_item(&dict, #key, into_py_with(#cow, py)?)?; } } else { quote! { #pyo3_path::types::PyDictMethods::set_item(&dict, #key, #value)?; } } }) .collect::(); IntoPyObjectImpl { types: IntoPyObjectTypes::Opaque { target: quote!(#pyo3_path::types::PyDict), output: quote!(#pyo3_path::Bound<'py, Self::Target>), error: quote!(#pyo3_path::PyErr), }, body: quote! { #unpack let dict = #pyo3_path::types::PyDict::new(py); #setter ::std::result::Result::Ok::<_, Self::Error>(dict) }, } } fn build_tuple_struct(&self, fields: &[TupleStructField<'_>], ctx: &Ctx) -> IntoPyObjectImpl { let Ctx { pyo3_path, .. } = ctx; let unpack = self .receiver .as_ref() .map(|i| { let pattern = self.match_pattern(); quote! { let #pattern = #i;} }) .unwrap_or_default(); let setter = fields .iter() .enumerate() .map(|(i, f)| { let ty = &f.field.ty; let value = Ident::new(&format!("arg{i}"), f.field.ty.span()); if let Some(expr_path) = f.into_py_with.as_ref().map(|i|&i.value) { let cow = if REF { quote!(::std::borrow::Cow::Borrowed(#value)) } else { quote!(::std::borrow::Cow::Owned(#value)) }; quote_spanned! { ty.span() => { let into_py_with: fn(::std::borrow::Cow<'_, _>, #pyo3_path::Python<'py>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'py, #pyo3_path::PyAny>> = #expr_path; into_py_with(#cow, py)? }, } } else { quote_spanned! { ty.span() => #pyo3_path::conversion::IntoPyObject::into_pyobject(#value, py) .map(#pyo3_path::BoundObject::into_any) .map(#pyo3_path::BoundObject::into_bound)?, } } }) .collect::(); IntoPyObjectImpl { types: IntoPyObjectTypes::Opaque { target: quote!(#pyo3_path::types::PyTuple), output: quote!(#pyo3_path::Bound<'py, Self::Target>), error: quote!(#pyo3_path::PyErr), }, body: quote! { #unpack #pyo3_path::types::PyTuple::new(py, [#setter]) }, } } #[cfg(feature = "experimental-inspect")] fn write_output_type(&self, builder: &mut ConcatenationBuilder, ctx: &Ctx) { match &self.ty { ContainerType::StructNewtype(field) | ContainerType::TupleNewtype(field) => { Self::write_field_output_type(&None, &field.ty, builder, ctx); } ContainerType::Tuple(tups) => { builder.push_str("tuple["); for ( i, TupleStructField { into_py_with, field, }, ) in tups.iter().enumerate() { if i > 0 { builder.push_str(", "); } Self::write_field_output_type(into_py_with, &field.ty, builder, ctx); } builder.push_str("]"); } ContainerType::Struct(_) => { // TODO: implement using a Protocol? builder.push_str("_typeshed.Incomplete") } } } #[cfg(feature = "experimental-inspect")] fn write_field_output_type( into_py_with: &Option, ty: &syn::Type, builder: &mut ConcatenationBuilder, ctx: &Ctx, ) { if into_py_with.is_some() { // We don't know what into_py_with is doing builder.push_str("_typeshed.Incomplete") } else { let mut ty = ty.clone(); elide_lifetimes(&mut ty); let pyo3_crate_path = &ctx.pyo3_path; builder.push_tokens( quote! { <#ty as #pyo3_crate_path::IntoPyObject<'_>>::OUTPUT_TYPE.as_bytes() }, ) } } } /// Describes derivation input of an enum. struct Enum<'a, const REF: bool> { variants: Vec>, } impl<'a, const REF: bool> Enum<'a, REF> { /// 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 `IntoPyObject` for empty enum" ); let variants = data_enum .variants .iter() .map(|variant| { let attrs = ContainerAttributes::from_attrs(&variant.attrs)?; let var_ident = &variant.ident; ensure_spanned!( !variant.fields.is_empty(), variant.ident.span() => "cannot derive `IntoPyObject` for empty variants" ); Container::new( None, &variant.fields, parse_quote!(#ident::#var_ident), attrs, ) }) .collect::>>()?; Ok(Enum { variants }) } /// Build derivation body for enums. fn build(&self, ctx: &Ctx) -> IntoPyObjectImpl { let Ctx { pyo3_path, .. } = ctx; let variants = self .variants .iter() .map(|v| { let IntoPyObjectImpl { body, .. } = v.build(ctx); let pattern = v.match_pattern(); quote! { #pattern => { {#body} .map(#pyo3_path::BoundObject::into_any) .map(#pyo3_path::BoundObject::into_bound) .map_err(::std::convert::Into::<#pyo3_path::PyErr>::into) } } }) .collect::(); IntoPyObjectImpl { types: IntoPyObjectTypes::Opaque { target: quote!(#pyo3_path::types::PyAny), output: quote!(#pyo3_path::Bound<'py, >::Target>), error: quote!(#pyo3_path::PyErr), }, body: quote! { match self { #variants } }, } } #[cfg(feature = "experimental-inspect")] fn write_output_type(&self, builder: &mut ConcatenationBuilder, ctx: &Ctx) { for (i, var) in self.variants.iter().enumerate() { if i > 0 { builder.push_str(" | "); } var.write_output_type(builder, ctx); } } } // if there is a `'py` lifetime, we treat it as the `Python<'py>` lifetime fn verify_and_get_lifetime(generics: &syn::Generics) -> Option<&syn::LifetimeParam> { let mut lifetimes = generics.lifetimes(); lifetimes.find(|l| l.lifetime.ident == "py") } pub fn build_derive_into_pyobject(tokens: &DeriveInput) -> Result { let options = ContainerAttributes::from_attrs(&tokens.attrs)?; let ctx = &Ctx::new(&options.krate, None); let Ctx { pyo3_path, .. } = &ctx; let (_, ty_generics, _) = tokens.generics.split_for_impl(); let mut trait_generics = tokens.generics.clone(); if REF { trait_generics.params.push(parse_quote!('_a)); } let lt_param = if let Some(lt) = verify_and_get_lifetime(&trait_generics) { lt.clone() } else { trait_generics.params.push(parse_quote!('py)); parse_quote!('py) }; let (impl_generics, _, where_clause) = trait_generics.split_for_impl(); let mut where_clause = where_clause.cloned().unwrap_or_else(|| parse_quote!(where)); for param in trait_generics.type_params() { let gen_ident = ¶m.ident; where_clause.predicates.push(if REF { parse_quote!(&'_a #gen_ident: #pyo3_path::conversion::IntoPyObject<'py>) } else { parse_quote!(#gen_ident: #pyo3_path::conversion::IntoPyObject<'py>) }) } let IntoPyObjectImpl { types, body } = match &tokens.data { syn::Data::Enum(en) => { if options.transparent.is_some() { bail_spanned!(tokens.span() => "`transparent` is not supported at top level for enums"); } if let Some(rename_all) = options.rename_all { bail_spanned!(rename_all.span() => "`rename_all` is not supported at top level for enums"); } let en = Enum::::new(en, &tokens.ident)?; en.build(ctx) } syn::Data::Struct(st) => { let ident = &tokens.ident; let st = Container::::new( Some(Ident::new("self", Span::call_site())), &st.fields, parse_quote!(#ident), options.clone(), )?; st.build(ctx) } syn::Data::Union(_) => bail_spanned!( tokens.span() => "#[derive(`IntoPyObject`)] is not supported for unions" ), }; let (target, output, error) = match types { IntoPyObjectTypes::Transparent(ty) => { if REF { ( quote! { <&'_a #ty as #pyo3_path::IntoPyObject<'py>>::Target }, quote! { <&'_a #ty as #pyo3_path::IntoPyObject<'py>>::Output }, quote! { <&'_a #ty as #pyo3_path::IntoPyObject<'py>>::Error }, ) } else { ( quote! { <#ty as #pyo3_path::IntoPyObject<'py>>::Target }, quote! { <#ty as #pyo3_path::IntoPyObject<'py>>::Output }, quote! { <#ty as #pyo3_path::IntoPyObject<'py>>::Error }, ) } } IntoPyObjectTypes::Opaque { target, output, error, } => (target, output, error), }; let ident = &tokens.ident; let ident = if REF { quote! { &'_a #ident} } else { quote! { #ident } }; #[cfg(feature = "experimental-inspect")] let output_type = { let mut builder = ConcatenationBuilder::default(); if tokens .generics .params .iter() .all(|p| matches!(p, syn::GenericParam::Lifetime(_))) { match &tokens.data { syn::Data::Enum(en) => { Enum::::new(en, &tokens.ident)?.write_output_type(&mut builder, ctx) } syn::Data::Struct(st) => { let ident = &tokens.ident; Container::::new( Some(Ident::new("self", Span::call_site())), &st.fields, parse_quote!(#ident), options, )? .write_output_type(&mut builder, ctx) } syn::Data::Union(_) => { // Not supported at this point builder.push_str("_typeshed.Incomplete") } } } else { // We don't know how to deal with generic parameters // Blocked by https://github.com/rust-lang/rust/issues/76560 builder.push_str("_typeshed.Incomplete") }; let output_type = builder.into_token_stream(&ctx.pyo3_path); quote! { const OUTPUT_TYPE: &'static str = unsafe { ::std::str::from_utf8_unchecked(#output_type) }; } }; #[cfg(not(feature = "experimental-inspect"))] let output_type = quote! {}; Ok(quote!( #[automatically_derived] impl #impl_generics #pyo3_path::conversion::IntoPyObject<#lt_param> for #ident #ty_generics #where_clause { type Target = #target; type Output = #output; type Error = #error; #output_type fn into_pyobject(self, py: #pyo3_path::Python<#lt_param>) -> ::std::result::Result< >::Output, >::Error, > { #body } } )) } pyo3-macros-backend-0.27.2/src/introspection.rs000064400000000000000000000530221046102023000175140ustar 00000000000000//! Generates introspection data i.e. JSON strings in the .pyo3i0 section. //! //! There is a JSON per PyO3 proc macro (pyclass, pymodule, pyfunction...). //! //! These JSON blobs can refer to each others via the _PYO3_INTROSPECTION_ID constants //! providing unique ids for each element. //! //! The JSON blobs format must be synchronized with the `pyo3_introspection::introspection.rs::Chunk` //! type that is used to parse them. use crate::method::{FnArg, RegularArg}; use crate::pyfunction::FunctionSignature; use crate::utils::PyO3CratePath; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; use std::borrow::Cow; use std::collections::hash_map::DefaultHasher; use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::mem::take; use std::sync::atomic::{AtomicUsize, Ordering}; use syn::visit_mut::{visit_type_mut, VisitMut}; use syn::{Attribute, Ident, Lifetime, ReturnType, Type, TypePath}; static GLOBAL_COUNTER_FOR_UNIQUE_NAMES: AtomicUsize = AtomicUsize::new(0); #[allow(clippy::too_many_arguments)] pub fn module_introspection_code<'a>( pyo3_crate_path: &PyO3CratePath, name: &str, members: impl IntoIterator, members_cfg_attrs: impl IntoIterator>, incomplete: bool, ) -> TokenStream { IntrospectionNode::Map( [ ("type", IntrospectionNode::String("module".into())), ("id", IntrospectionNode::IntrospectionId(None)), ("name", IntrospectionNode::String(name.into())), ( "members", IntrospectionNode::List( members .into_iter() .zip(members_cfg_attrs) .map(|(member, attributes)| AttributedIntrospectionNode { node: IntrospectionNode::IntrospectionId(Some(ident_to_type(member))), attributes, }) .collect(), ), ), ("incomplete", IntrospectionNode::Bool(incomplete)), ] .into(), ) .emit(pyo3_crate_path) } pub fn class_introspection_code( pyo3_crate_path: &PyO3CratePath, ident: &Ident, name: &str, ) -> TokenStream { IntrospectionNode::Map( [ ("type", IntrospectionNode::String("class".into())), ( "id", IntrospectionNode::IntrospectionId(Some(ident_to_type(ident))), ), ("name", IntrospectionNode::String(name.into())), ] .into(), ) .emit(pyo3_crate_path) } #[allow(clippy::too_many_arguments)] pub fn function_introspection_code( pyo3_crate_path: &PyO3CratePath, ident: Option<&Ident>, name: &str, signature: &FunctionSignature<'_>, first_argument: Option<&'static str>, returns: ReturnType, decorators: impl IntoIterator, parent: Option<&Type>, ) -> TokenStream { let mut desc = HashMap::from([ ("type", IntrospectionNode::String("function".into())), ("name", IntrospectionNode::String(name.into())), ( "arguments", arguments_introspection_data(signature, first_argument, parent), ), ( "returns", if let Some((_, returns)) = signature .attribute .as_ref() .and_then(|attribute| attribute.value.returns.as_ref()) { IntrospectionNode::String(returns.to_python().into()) } else { match returns { ReturnType::Default => IntrospectionNode::String("None".into()), ReturnType::Type(_, ty) => match *ty { Type::Tuple(t) if t.elems.is_empty() => { // () is converted to None in return types IntrospectionNode::String("None".into()) } mut ty => { if let Some(class_type) = parent { replace_self(&mut ty, class_type); } elide_lifetimes(&mut ty); IntrospectionNode::OutputType { rust_type: ty, is_final: false, } } }, } }, ), ]); if let Some(ident) = ident { desc.insert( "id", IntrospectionNode::IntrospectionId(Some(ident_to_type(ident))), ); } let decorators = decorators .into_iter() .map(|d| IntrospectionNode::String(d.into()).into()) .collect::>(); if !decorators.is_empty() { desc.insert("decorators", IntrospectionNode::List(decorators)); } if let Some(parent) = parent { desc.insert( "parent", IntrospectionNode::IntrospectionId(Some(Cow::Borrowed(parent))), ); } IntrospectionNode::Map(desc).emit(pyo3_crate_path) } pub fn attribute_introspection_code( pyo3_crate_path: &PyO3CratePath, parent: Option<&Type>, name: String, value: String, mut rust_type: Type, is_final: bool, ) -> TokenStream { let mut desc = HashMap::from([ ("type", IntrospectionNode::String("attribute".into())), ("name", IntrospectionNode::String(name.into())), ( "parent", IntrospectionNode::IntrospectionId(parent.map(Cow::Borrowed)), ), ]); if value == "..." { // We need to set a type, but not need to set the value to ..., all attributes have a value if let Some(parent) = parent { replace_self(&mut rust_type, parent); } elide_lifetimes(&mut rust_type); desc.insert( "annotation", IntrospectionNode::OutputType { rust_type, is_final, }, ); } else { desc.insert( "annotation", if is_final { // Type checkers can infer the type from the value because it's typing.Literal[value] // So, following stubs best practices, we only write typing.Final and not // typing.Final[typing.literal[value]] IntrospectionNode::String("typing.Final".into()) } else { IntrospectionNode::OutputType { rust_type, is_final, } }, ); desc.insert("value", IntrospectionNode::String(value.into())); } IntrospectionNode::Map(desc).emit(pyo3_crate_path) } fn arguments_introspection_data<'a>( signature: &'a FunctionSignature<'a>, first_argument: Option<&'a str>, class_type: Option<&Type>, ) -> IntrospectionNode<'a> { let mut argument_desc = signature.arguments.iter().filter(|arg| { matches!( arg, FnArg::Regular(_) | FnArg::VarArgs(_) | FnArg::KwArgs(_) ) }); let mut posonlyargs = Vec::new(); let mut args = Vec::new(); let mut vararg = None; let mut kwonlyargs = Vec::new(); let mut kwarg = None; if let Some(first_argument) = first_argument { posonlyargs.push( IntrospectionNode::Map( [("name", IntrospectionNode::String(first_argument.into()))].into(), ) .into(), ); } for (i, param) in signature .python_signature .positional_parameters .iter() .enumerate() { let arg_desc = if let Some(FnArg::Regular(arg_desc)) = argument_desc.next() { arg_desc } else { panic!("Less arguments than in python signature"); }; let arg = argument_introspection_data(param, arg_desc, class_type); if i < signature.python_signature.positional_only_parameters { posonlyargs.push(arg); } else { args.push(arg) } } if let Some(param) = &signature.python_signature.varargs { let Some(FnArg::VarArgs(arg_desc)) = argument_desc.next() else { panic!("Fewer arguments than in python signature"); }; let mut params = HashMap::from([("name", IntrospectionNode::String(param.into()))]); if let Some(annotation) = &arg_desc.annotation { params.insert("annotation", IntrospectionNode::String(annotation.into())); } vararg = Some(IntrospectionNode::Map(params)); } for (param, _) in &signature.python_signature.keyword_only_parameters { let Some(FnArg::Regular(arg_desc)) = argument_desc.next() else { panic!("Less arguments than in python signature"); }; kwonlyargs.push(argument_introspection_data(param, arg_desc, class_type)); } if let Some(param) = &signature.python_signature.kwargs { let Some(FnArg::KwArgs(arg_desc)) = argument_desc.next() else { panic!("Less arguments than in python signature"); }; let mut params = HashMap::from([("name", IntrospectionNode::String(param.into()))]); if let Some(annotation) = &arg_desc.annotation { params.insert("annotation", IntrospectionNode::String(annotation.into())); } kwarg = Some(IntrospectionNode::Map(params)); } let mut map = HashMap::new(); if !posonlyargs.is_empty() { map.insert("posonlyargs", IntrospectionNode::List(posonlyargs)); } if !args.is_empty() { map.insert("args", IntrospectionNode::List(args)); } if let Some(vararg) = vararg { map.insert("vararg", vararg); } if !kwonlyargs.is_empty() { map.insert("kwonlyargs", IntrospectionNode::List(kwonlyargs)); } if let Some(kwarg) = kwarg { map.insert("kwarg", kwarg); } IntrospectionNode::Map(map) } fn argument_introspection_data<'a>( name: &'a str, desc: &'a RegularArg<'_>, class_type: Option<&Type>, ) -> AttributedIntrospectionNode<'a> { let mut params: HashMap<_, _> = [("name", IntrospectionNode::String(name.into()))].into(); if desc.default_value.is_some() { params.insert( "default", IntrospectionNode::String(desc.default_value().into()), ); } if let Some(annotation) = &desc.annotation { params.insert("annotation", IntrospectionNode::String(annotation.into())); } else if desc.from_py_with.is_none() { // If from_py_with is set we don't know anything on the input type if let Some(ty) = desc.option_wrapped_type { // Special case to properly generate a `T | None` annotation let mut ty = ty.clone(); if let Some(class_type) = class_type { replace_self(&mut ty, class_type); } elide_lifetimes(&mut ty); params.insert( "annotation", IntrospectionNode::InputType { rust_type: ty, nullable: true, }, ); } else { let mut ty = desc.ty.clone(); if let Some(class_type) = class_type { replace_self(&mut ty, class_type); } elide_lifetimes(&mut ty); params.insert( "annotation", IntrospectionNode::InputType { rust_type: ty, nullable: false, }, ); } } IntrospectionNode::Map(params).into() } enum IntrospectionNode<'a> { String(Cow<'a, str>), Bool(bool), IntrospectionId(Option>), InputType { rust_type: Type, nullable: bool }, OutputType { rust_type: Type, is_final: bool }, Map(HashMap<&'static str, IntrospectionNode<'a>>), List(Vec>), } impl IntrospectionNode<'_> { fn emit(self, pyo3_crate_path: &PyO3CratePath) -> TokenStream { let mut content = ConcatenationBuilder::default(); self.add_to_serialization(&mut content, pyo3_crate_path); content.into_static( pyo3_crate_path, format_ident!("PYO3_INTROSPECTION_1_{}", unique_element_id()), ) } fn add_to_serialization( self, content: &mut ConcatenationBuilder, pyo3_crate_path: &PyO3CratePath, ) { match self { Self::String(string) => { content.push_str_to_escape(&string); } Self::Bool(value) => content.push_str(if value { "true" } else { "false" }), Self::IntrospectionId(ident) => { content.push_str("\""); content.push_tokens(if let Some(ident) = ident { quote! { #ident::_PYO3_INTROSPECTION_ID.as_bytes() } } else { quote! { _PYO3_INTROSPECTION_ID.as_bytes() } }); content.push_str("\""); } Self::InputType { rust_type, nullable, } => { content.push_str("\""); content.push_tokens(quote! { <#rust_type as #pyo3_crate_path::impl_::extract_argument::PyFunctionArgument< { #[allow(unused_imports)] use #pyo3_crate_path::impl_::pyclass::Probe as _; #pyo3_crate_path::impl_::pyclass::IsFromPyObject::<#rust_type>::VALUE } >>::INPUT_TYPE.as_bytes() }); if nullable { content.push_str(" | None"); } content.push_str("\""); } Self::OutputType { rust_type, is_final, } => { content.push_str("\""); if is_final { content.push_str("typing.Final["); } content.push_tokens(quote! { <#rust_type as #pyo3_crate_path::impl_::introspection::PyReturnType>::OUTPUT_TYPE.as_bytes() }); if is_final { content.push_str("]"); } content.push_str("\""); } Self::Map(map) => { content.push_str("{"); for (i, (key, value)) in map.into_iter().enumerate() { if i > 0 { content.push_str(","); } content.push_str_to_escape(key); content.push_str(":"); value.add_to_serialization(content, pyo3_crate_path); } content.push_str("}"); } Self::List(list) => { content.push_str("["); for (i, AttributedIntrospectionNode { node, attributes }) in list.into_iter().enumerate() { if attributes.is_empty() { if i > 0 { content.push_str(","); } node.add_to_serialization(content, pyo3_crate_path); } else { // We serialize the element to easily gate it behind the attributes let mut nested_builder = ConcatenationBuilder::default(); if i > 0 { nested_builder.push_str(","); } node.add_to_serialization(&mut nested_builder, pyo3_crate_path); let nested_content = nested_builder.into_token_stream(pyo3_crate_path); content.push_tokens(quote! { #(#attributes)* #nested_content }); } } content.push_str("]"); } } } } struct AttributedIntrospectionNode<'a> { node: IntrospectionNode<'a>, attributes: &'a [Attribute], } impl<'a> From> for AttributedIntrospectionNode<'a> { fn from(node: IntrospectionNode<'a>) -> Self { Self { node, attributes: &[], } } } #[derive(Default)] pub struct ConcatenationBuilder { elements: Vec, current_string: String, } impl ConcatenationBuilder { pub fn push_tokens(&mut self, token_stream: TokenStream) { if !self.current_string.is_empty() { self.elements.push(ConcatenationBuilderElement::String(take( &mut self.current_string, ))); } self.elements .push(ConcatenationBuilderElement::TokenStream(token_stream)); } pub fn push_str(&mut self, value: &str) { self.current_string.push_str(value); } fn push_str_to_escape(&mut self, value: &str) { self.current_string.push('"'); for c in value.chars() { match c { '\\' => self.current_string.push_str("\\\\"), '"' => self.current_string.push_str("\\\""), c => { if c < char::from(32) { panic!("ASCII chars below 32 are not allowed") } else { self.current_string.push(c); } } } } self.current_string.push('"'); } pub fn into_token_stream(self, pyo3_crate_path: &PyO3CratePath) -> TokenStream { let mut elements = self.elements; if !self.current_string.is_empty() { elements.push(ConcatenationBuilderElement::String(self.current_string)); } if let [ConcatenationBuilderElement::String(string)] = elements.as_slice() { // We avoid the const_concat! macro if there is only a single string return quote! { #string.as_bytes() }; } quote! { { const PIECES: &[&[u8]] = &[#(#elements , )*]; &#pyo3_crate_path::impl_::concat::combine_to_array::<{ #pyo3_crate_path::impl_::concat::combined_len(PIECES) }>(PIECES) } } } fn into_static(self, pyo3_crate_path: &PyO3CratePath, ident: Ident) -> TokenStream { let mut elements = self.elements; if !self.current_string.is_empty() { elements.push(ConcatenationBuilderElement::String(self.current_string)); } // #[no_mangle] is required to make sure some linkers like Linux ones do not mangle the section name too. quote! { const _: () = { const PIECES: &[&[u8]] = &[#(#elements , )*]; const PIECES_LEN: usize = #pyo3_crate_path::impl_::concat::combined_len(PIECES); #[used] #[no_mangle] static #ident: #pyo3_crate_path::impl_::introspection::SerializedIntrospectionFragment = #pyo3_crate_path::impl_::introspection::SerializedIntrospectionFragment { length: PIECES_LEN as u32, fragment: #pyo3_crate_path::impl_::concat::combine_to_array::(PIECES) }; }; } } } enum ConcatenationBuilderElement { String(String), TokenStream(TokenStream), } impl ToTokens for ConcatenationBuilderElement { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::String(s) => quote! { #s.as_bytes() }.to_tokens(tokens), Self::TokenStream(ts) => ts.to_tokens(tokens), } } } /// Generates a new unique identifier for linking introspection objects together pub fn introspection_id_const() -> TokenStream { let id = unique_element_id().to_string(); quote! { #[doc(hidden)] pub const _PYO3_INTROSPECTION_ID: &'static str = #id; } } pub fn unique_element_id() -> u64 { let mut hasher = DefaultHasher::new(); format!("{:?}", Span::call_site()).hash(&mut hasher); // Distinguishes between call sites GLOBAL_COUNTER_FOR_UNIQUE_NAMES .fetch_add(1, Ordering::Relaxed) .hash(&mut hasher); // If there are multiple elements in the same call site hasher.finish() } fn ident_to_type(ident: &Ident) -> Cow<'static, Type> { Cow::Owned( TypePath { path: ident.clone().into(), qself: None, } .into(), ) } /// Replaces all explicit lifetimes in `self` with elided (`'_`) lifetimes /// /// This is useful if `Self` is used in `const` context, where explicit /// lifetimes are not allowed (yet). pub fn elide_lifetimes(ty: &mut Type) { struct ElideLifetimesVisitor; impl VisitMut for ElideLifetimesVisitor { fn visit_lifetime_mut(&mut self, l: &mut syn::Lifetime) { *l = Lifetime::new("'_", l.span()); } } ElideLifetimesVisitor.visit_type_mut(ty); } // Replace Self in types with the given type fn replace_self(ty: &mut Type, self_target: &Type) { struct SelfReplacementVisitor<'a> { self_target: &'a Type, } impl VisitMut for SelfReplacementVisitor<'_> { fn visit_type_mut(&mut self, ty: &mut Type) { if let Type::Path(type_path) = ty { if type_path.qself.is_none() && type_path.path.segments.len() == 1 && type_path.path.segments[0].ident == "Self" && type_path.path.segments[0].arguments.is_empty() { // It is Self *ty = self.self_target.clone(); return; } } visit_type_mut(self, ty); } } SelfReplacementVisitor { self_target }.visit_type_mut(ty); } pyo3-macros-backend-0.27.2/src/konst.rs000064400000000000000000000050341046102023000157520ustar 00000000000000use std::borrow::Cow; use std::ffi::CString; use crate::attributes::{self, get_pyo3_options, take_attributes, NameAttribute}; use crate::utils::{Ctx, LitCStr}; use proc_macro2::{Ident, Span}; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, spanned::Spanned, Result, }; pub struct ConstSpec { pub rust_ident: syn::Ident, pub attributes: ConstAttributes, } impl ConstSpec { pub fn python_name(&self) -> Cow<'_, Ident> { if let Some(name) = &self.attributes.name { Cow::Borrowed(&name.value.0) } else { Cow::Owned(self.rust_ident.unraw()) } } /// Null-terminated Python name pub fn null_terminated_python_name(&self, ctx: &Ctx) -> LitCStr { let name = self.python_name().to_string(); LitCStr::new(CString::new(name).unwrap(), Span::call_site(), ctx) } } pub struct ConstAttributes { pub is_class_attr: bool, pub name: Option, } pub enum PyO3ConstAttribute { Name(NameAttribute), } impl Parse for PyO3ConstAttribute { fn parse(input: ParseStream<'_>) -> Result { let lookahead = input.lookahead1(); if lookahead.peek(attributes::kw::name) { input.parse().map(PyO3ConstAttribute::Name) } else { Err(lookahead.error()) } } } impl ConstAttributes { pub fn from_attrs(attrs: &mut Vec) -> syn::Result { let mut attributes = ConstAttributes { is_class_attr: false, name: None, }; 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.27.2/src/lib.rs000064400000000000000000000017211046102023000153610ustar 00000000000000//! This crate contains the implementation of the proc macro attributes #![warn(elided_lifetimes_in_paths, unused_lifetimes)] #![cfg_attr(docsrs, feature(doc_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 combine_errors; mod derive_attributes; mod frompyobject; mod intopyobject; #[cfg(feature = "experimental-inspect")] mod introspection; 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 intopyobject::build_derive_into_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.27.2/src/method.rs000064400000000000000000001312741046102023000161020ustar 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::pyfunction::{PyFunctionWarning, WarningFactory}; use crate::pyversions::is_abi3_before; use crate::utils::{expr_to_python, Ctx, LitCStr}; use crate::{ attributes::{FromPyWithAttribute, TextSignatureAttribute, TextSignatureAttributeValue}, params::{impl_arg_params, Holders}, pyfunction::{ FunctionSignature, PyFunctionArgPyO3Attributes, PyFunctionOptions, SignatureAttribute, }, quotes, utils::{self, 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>, #[cfg(feature = "experimental-inspect")] pub annotation: Option, } impl RegularArg<'_> { pub fn default_value(&self) -> String { if let Self { default_value: Some(arg_default), .. } = self { expr_to_python(arg_default) } else if let RegularArg { option_wrapped_type: Some(..), .. } = self { // functions without a `#[pyo3(signature = (...))]` option // will treat trailing `Option` arguments as having a default of `None` "None".to_string() } else { "...".to_string() } } } /// Pythons *args argument #[derive(Clone, Debug)] pub struct VarargsArg<'a> { pub name: Cow<'a, syn::Ident>, pub ty: &'a syn::Type, #[cfg(feature = "experimental-inspect")] pub annotation: Option, } /// Pythons **kwarg argument #[derive(Clone, Debug)] pub struct KwargsArg<'a> { pub name: Cow<'a, syn::Ident>, pub ty: &'a syn::Type, #[cfg(feature = "experimental-inspect")] pub annotation: Option, } #[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, } #[allow(clippy::large_enum_variant)] // See #5039 #[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, #[cfg(feature = "experimental-inspect")] annotation, .. }) = self { *self = Self::VarArgs(VarargsArg { name: name.clone(), ty, #[cfg(feature = "experimental-inspect")] annotation: annotation.clone(), }); 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(..), #[cfg(feature = "experimental-inspect")] annotation, .. }) = self { *self = Self::KwArgs(KwargsArg { name: name.clone(), ty, #[cfg(feature = "experimental-inspect")] annotation: annotation.clone(), }); 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`, duplicates 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), #[cfg(feature = "experimental-inspect")] annotation: None, })) } } } pub fn default_value(&self) -> String { if let Self::Regular(args) = self { args.default_value() } else { "...".to_string() } } } 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", 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 _)) .cast_unchecked::<#pyo3_path::types::PyType>() ) }; Some(quote! { unsafe { #ret }, }) } FnType::FnModule(span) => { let py = syn::Ident::new("py", Span::call_site()); let slf: Ident = syn::Ident::new("_slf", 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 _)) .cast_unchecked::<#pyo3_path::types::PyModule>() ) }; Some(quote! { unsafe { #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::impl_::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; let bound_ref = quote! { unsafe { #pyo3_path::impl_::pymethods::BoundRef::ref_from_ptr(#py, &#slf) } }; 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>( #bound_ref.0, &mut #holder, ) }, ctx, ) } SelfType::TryFromBoundRef(span) => { let pyo3_path = pyo3_path.to_tokens_spanned(*span); error_mode.handle_error( quote_spanned! { *span => #bound_ref.cast::<#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 before 3.10) 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_none() && !is_abi3_before(3, 10) { // For functions that accept **kwargs, always prefer varargs for now based on // historical performance testing. // // FASTCALL not compatible with `abi3` before 3.10 Self::Fastcall } else { Self::Varargs } } } #[derive(Clone)] 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 warnings: Vec, #[cfg(feature = "experimental-inspect")] pub output: syn::ReturnType, } 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, ) -> Result> { let PyFunctionOptions { text_signature, name, signature, warnings, .. } = options; let mut python_name = name.map(|name| name.value.0); let fn_type = Self::parse_fn_type(sig, meth_attrs, &mut python_name)?; 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, warnings, #[cfg(feature = "experimental-inspect")] output: sig.output.clone(), }) } 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, ) -> Result { let mut method_attributes = parse_method_attributes(meth_attrs)?; 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!("`{first}` may not be combined with"); 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"); } } 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 = unsafe { #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 = unsafe { #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() { quote! { function( // NB #self_arg includes a comma, so none inserted here #self_arg #(#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 { let fut = future.await; #pyo3_path::impl_::wrap::converter(&fut).wrap(fut) }, ) }}; 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() { quote! { function( // NB #self_arg includes a comma, so none inserted here #self_arg #(#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_ident = Ident::new("ret", *output_span); let ret_expr = quote! { let #ret_ident = #call; }; let return_conversion = quotes::map_result_into_ptr(quotes::ok_wrap(ret_ident.to_token_stream(), 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 warnings = self.warnings.build_py_warning(ctx); 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 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> { let function = #rust_name; // Shadow the function name to avoid #3017 #init_holders #warnings let result = #call; 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); 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> { let function = #rust_name; // Shadow the function name to avoid #3017 #arg_convert #init_holders #warnings let result = #call; 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); 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> { let function = #rust_name; // Shadow the function name to avoid #3017 #arg_convert #init_holders #warnings let result = #call; 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); 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::impl_::callback::IntoPyCallbackOutput; let function = #rust_name; // Shadow the function name to avoid #3017 #arg_convert #init_holders #warnings let result = #call; let initializer: #pyo3_path::PyClassInitializer::<#cls> = result.convert(py)?; #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 { unsafe { #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) -> syn::Result { 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) -> 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("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) -> 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)? { 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: PyClassGuard<'_, Self>` or `slf: PyClassGuardMut<'_, 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.27.2/src/module.rs000064400000000000000000000636261046102023000161140ustar 00000000000000//! Code generation for the function that initializes a python module and adds classes and function. #[cfg(feature = "experimental-inspect")] use crate::introspection::{ attribute_introspection_code, introspection_id_const, module_introspection_code, }; #[cfg(feature = "experimental-inspect")] use crate::utils::expr_to_python; use crate::{ attributes::{ self, kw, take_attributes, take_pyo3_options, CrateAttribute, GILUsedAttribute, ModuleAttribute, NameAttribute, SubmoduleAttribute, }, combine_errors::CombineErrors, get_doc, pyclass::PyClassPyO3Option, pyfunction::{impl_wrap_pyfunction, PyFunctionOptions}, utils::{has_attribute, has_attribute_with_namespace, Ctx, IdentOrStr, LitCStr}, }; 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, gil_used: 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 $(, $extra:literal)?) => { { ensure_spanned!( self.$key.is_none(), $key.span() => concat!("`", stringify!($key), "` may only be specified once" $(, $extra)?) ); self.$key = Some($key); } }; } attrs .into_iter() .map(|attr| { 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, " (it is implicitly always specified for nested modules)" ), PyModulePyO3Option::GILUsed(gil_used) => { set_option!(gil_used) } } Ok(()) }) .try_combine_syn_errors()?; 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(); #[cfg(feature = "experimental-inspect")] let mut introspection_chunks = Vec::new(); #[cfg(not(feature = "experimental-inspect"))] let introspection_chunks = 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; let mut module_consts = Vec::new(); let mut module_consts_cfg_attrs = Vec::new(); let _: Vec<()> = (*items).iter_mut().map(|item|{ 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` or `const` 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` or `const` 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` or `const` 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` or `const` 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_mod .attrs .push(parse_quote_spanned!(item_mod.mod_token.span()=> #[pyo3(submodule)])); } } Item::ForeignMod(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::Trait(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::Const(item) => { if !find_and_remove_attribute(&mut item.attrs, "pymodule_export") { return Ok(()); } module_consts.push(item.ident.clone()); module_consts_cfg_attrs.push(get_cfg_attributes(&item.attrs)); #[cfg(feature = "experimental-inspect")] { let cfg_attrs = get_cfg_attributes(&item.attrs); let chunk = attribute_introspection_code( pyo3_path, None, item.ident.unraw().to_string(), expr_to_python(&item.expr), (*item.ty).clone(), true, ); introspection_chunks.push(quote! { #(#cfg_attrs)* #chunk }); } } Item::Static(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::Macro(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::ExternCrate(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::Impl(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::TraitAlias(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::Type(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } Item::Union(item) => { ensure_spanned!( !has_attribute(&item.attrs, "pymodule_export"), item.span() => "`#[pymodule_export]` may only be used on `use` or `const` statements" ); } _ => (), } Ok(()) }).try_combine_syn_errors()?; #[cfg(feature = "experimental-inspect")] let introspection = module_introspection_code( pyo3_path, &name.to_string(), &module_items, &module_items_cfg_attrs, pymodule_init.is_some(), ); #[cfg(not(feature = "experimental-inspect"))] let introspection = quote! {}; #[cfg(feature = "experimental-inspect")] let introspection_id = introspection_id_const(); #[cfg(not(feature = "experimental-inspect"))] let introspection_id = quote! {}; 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(), options.gil_used.map_or(true, |op| op.value.value), ); let module_consts_names = module_consts.iter().map(|i| i.unraw().to_string()); Ok(quote!( #(#attrs)* #vis #mod_token #ident { #(#items)* #initialization #introspection #introspection_id #(#introspection_chunks)* 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)?; )* #( #(#module_consts_cfg_attrs)* #pyo3_path::types::PyModuleMethods::add(module, #module_consts_names, #module_consts)?; )* #pymodule_init ::std::result::Result::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 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, options.gil_used.map_or(true, |op| op.value.value), ); #[cfg(feature = "experimental-inspect")] let introspection = module_introspection_code(pyo3_path, &name.unraw().to_string(), &[], &[], true); #[cfg(not(feature = "experimental-inspect"))] let introspection = quote! {}; #[cfg(feature = "experimental-inspect")] let introspection_id = introspection_id_const(); #[cfg(not(feature = "experimental-inspect"))] let introspection_id = quote! {}; // 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)))); Ok(quote! { #[doc(hidden)] #vis mod #ident { #initialization #introspection #introspection_id } // 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, gil_used: 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; #[doc(hidden)] // so wrapped submodules can see what gil_used is pub static __PYO3_GIL_USED: bool = #gil_used; }; 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 { unsafe { #pyo3_path::impl_::trampoline::module_init(|py| _PYO3_DEF.make_module(py, #gil_used)) } } }); } 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(); for mut stmt in func.block.stmts.drain(..) { if let syn::Stmt::Item(Item::Fn(func)) = &mut stmt { if let Some((pyfn_span, 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_spanned! { pyfn_span => #wrapped_function { use #pyo3_path::types::PyModuleMethods; #module_name.add_function(#pyo3_path::wrap_pyfunction!(#name, #module_name.as_borrowed())?)?; #[deprecated(note = "`pyfn` will be removed in a future PyO3 version, use declarative `#[pymodule]` with `mod` instead")] #[allow(dead_code)] const PYFN_ATTRIBUTE: () = (); const _: () = PYFN_ATTRIBUTE; } }; 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<(Span, PyFnArgs)> = 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.path().span(), 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 } impl PartialEq for IdentOrStr<'_> { fn eq(&self, other: &syn::Ident) -> bool { match self { IdentOrStr::Str(s) => other == s, IdentOrStr::Ident(i) => other == i, } } } 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), GILUsed(GILUsedAttribute), } 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 if lookahead.peek(attributes::kw::gil_used) { input.parse().map(PyModulePyO3Option::GILUsed) } else { Err(lookahead.error()) } } } pyo3-macros-backend-0.27.2/src/params.rs000064400000000000000000000244541046102023000161060ustar 00000000000000use crate::utils::Ctx; use crate::{ attributes::FromPyWithAttribute, 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, } impl Holders { pub fn new() -> Self { Holders { holders: 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 init_holders(&self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let holders = &self.holders; quote! { #[allow(clippy::let_unit_value)] #(let mut #holders = #pyo3_path::impl_::extract_argument::FunctionArgumentHolder::INIT;)* } } } /// 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 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 #from_py_with_holder = #from_py_with; }) }) .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 = unsafe { #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; impl_regular_arg_param(arg, from_py_with, arg_value, holders, 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_argument_with_default( _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 let use_probe = quote! { #[allow(unused_imports)] use #pyo3_path::impl_::pyclass::Probe as _; }; macro_rules! quote_arg_span { ($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => { #use_probe $($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 = default.map(|tokens| some_wrap(tokens, ctx)); } if let Some(FromPyWithAttribute { kw, .. }) = arg.from_py_with { let extractor = quote_spanned! { kw.span => { let from_py_with: fn(_) -> _ = #from_py_with; from_py_with } }; if let Some(default) = default { quote_arg_span! { #pyo3_path::impl_::extract_argument::from_py_with_with_default( #arg_value, #name_str, #extractor, #[allow(clippy::redundant_closure)] { || #default } )? } } else { let unwrap = quote! {unsafe { #pyo3_path::impl_::extract_argument::unwrap_required_argument(#arg_value) }}; quote_arg_span! { #pyo3_path::impl_::extract_argument::from_py_with( #unwrap, #name_str, #extractor, )? } } } 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()); let unwrap = quote! {unsafe { #pyo3_path::impl_::extract_argument::unwrap_required_argument(#arg_value) }}; quote_arg_span! { #pyo3_path::impl_::extract_argument::extract_argument( #unwrap, &mut #holder, #name_str )? } } } pyo3-macros-backend-0.27.2/src/pyclass.rs000064400000000000000000002755551046102023000163130ustar 00000000000000use std::borrow::Cow; use std::fmt::Debug; 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, ImplItemFn, Result, Token}; use crate::attributes::kw::frozen; use crate::attributes::{ self, kw, take_pyo3_options, CrateAttribute, ExtendsAttribute, FreelistAttribute, ModuleAttribute, NameAttribute, NameLitStr, RenameAllAttribute, StrFormatterAttribute, }; use crate::combine_errors::CombineErrors; #[cfg(feature = "experimental-inspect")] use crate::introspection::{ class_introspection_code, function_introspection_code, introspection_id_const, }; use crate::konst::{ConstAttributes, ConstSpec}; use crate::method::{FnArg, FnSpec, PyArg, RegularArg}; use crate::pyfunction::ConstructorAttribute; #[cfg(feature = "experimental-inspect")] use crate::pyfunction::FunctionSignature; use crate::pyimpl::{gen_py_const, get_cfg_attributes, PyClassMethodsType}; #[cfg(feature = "experimental-inspect")] use crate::pymethod::field_python_name; use crate::pymethod::{ impl_py_class_attribute, impl_py_getter_def, impl_py_setter_def, MethodAndMethodDef, MethodAndSlotDef, PropertyType, SlotDef, __GETITEM__, __HASH__, __INT__, __LEN__, __REPR__, __RICHCMP__, __STR__, }; use crate::pyversions::{is_abi3_before, is_py_before}; use crate::utils::{self, apply_renaming_rule, Ctx, LitCStr, PythonDoc}; 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_struct_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 immutable_type: 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 str: Option, pub subclass: Option, pub unsendable: Option, pub weakref: Option, pub generic: Option, pub from_py_object: Option, pub skip_from_py_object: 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), ImmutableType(kw::immutable_type), Mapping(kw::mapping), Module(ModuleAttribute), Name(NameAttribute), Ord(kw::ord), RenameAll(RenameAllAttribute), Sequence(kw::sequence), SetAll(kw::set_all), Str(StrFormatterAttribute), Subclass(kw::subclass), Unsendable(kw::unsendable), Weakref(kw::weakref), Generic(kw::generic), FromPyObject(kw::from_py_object), SkipFromPyObject(kw::skip_from_py_object), } 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::immutable_type) { input.parse().map(PyClassPyO3Option::ImmutableType) } 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::str) { input.parse().map(PyClassPyO3Option::Str) } 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 if lookahead.peek(attributes::kw::generic) { input.parse().map(PyClassPyO3Option::Generic) } else if lookahead.peek(attributes::kw::from_py_object) { input.parse().map(PyClassPyO3Option::FromPyObject) } else if lookahead.peek(attributes::kw::skip_from_py_object) { input.parse().map(PyClassPyO3Option::SkipFromPyObject) } 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); } }; } match option { PyClassPyO3Option::Crate(krate) => set_option!(krate), PyClassPyO3Option::Dict(dict) => { ensure_spanned!( !is_abi3_before(3, 9), 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::ImmutableType(immutable_type) => { ensure_spanned!( !(is_py_before(3, 10) || is_abi3_before(3, 14)), immutable_type.span() => "`immutable_type` requires Python >= 3.10 or >= 3.14 (ABI3)" ); set_option!(immutable_type) } 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::Str(str) => set_option!(str), PyClassPyO3Option::Subclass(subclass) => set_option!(subclass), PyClassPyO3Option::Unsendable(unsendable) => set_option!(unsendable), PyClassPyO3Option::Weakref(weakref) => { ensure_spanned!( !is_abi3_before(3, 9), weakref.span() => "`weakref` requires Python >= 3.9 when using the `abi3` feature" ); set_option!(weakref); } PyClassPyO3Option::Generic(generic) => set_option!(generic), PyClassPyO3Option::SkipFromPyObject(skip_from_py_object) => { ensure_spanned!( self.from_py_object.is_none(), skip_from_py_object.span() => "`skip_from_py_object` and `from_py_object` are mutually exclusive" ); set_option!(skip_from_py_object) } PyClassPyO3Option::FromPyObject(from_py_object) => { ensure_spanned!( self.skip_from_py_object.is_none(), from_py_object.span() => "`skip_from_py_object` and `from_py_object` are mutually exclusive" ); set_option!(from_py_object) } } 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| match FieldPyO3Options::take_pyo3_options(&mut field.attrs) { Ok(options) => Ok((&*field, options)), Err(e) => Err(e), }, ) .collect::>(), syn::Fields::Unnamed(fields) => fields .unnamed .iter_mut() .map( |field| match FieldPyO3Options::take_pyo3_options(&mut field.attrs) { Ok(options) => Ok((&*field, options)), Err(e) => Err(e), }, ) .collect::>(), syn::Fields::Unit => { let mut results = Vec::new(); if let Some(attr) = args.options.set_all { results.push(Err(syn::Error::new_spanned(attr, UNIT_SET))); }; if let Some(attr) = args.options.get_all { results.push(Err(syn::Error::new_spanned(attr, UNIT_GET))); }; results } } .into_iter() .try_combine_syn_errors()?; 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 get_class_python_module_and_name<'a>(cls: &'a Ident, args: &'a PyClassArgs) -> String { let name = get_class_python_name(cls, args); if let Some(module) = &args.options.module { let value = module.value.value(); format!("{value}.{name}") } else { name.to_string() } } 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, ctx); if let Some(str) = &args.options.str { if str.value.is_some() { // check if any renaming is present let no_naming_conflict = field_options.iter().all(|x| x.1.name.is_none()) & args.options.name.is_none() & args.options.rename_all.is_none(); ensure_spanned!(no_naming_conflict, str.value.span() => "The format string syntax is incompatible with any renaming via `name` or `rename_all`"); } } let mut default_methods = descriptors_to_items( cls, args.options.rename_all.as_ref(), args.options.frozen, field_options, ctx, )?; let (default_class_geitem, default_class_geitem_method) = pyclass_class_geitem(&args.options, &syn::parse_quote!(#cls), ctx)?; if let Some(default_class_geitem_method) = default_class_geitem_method { default_methods.push(default_class_geitem_method); } let (default_str, default_str_slot) = implement_pyclass_str(&args.options, &syn::parse_quote!(#cls), 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); slots.extend(default_str_slot); let py_class_impl = PyClassImplsBuilder::new(cls, args, methods_type, default_methods, 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 #default_str #default_class_geitem } }) } 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"); } if let Some(generic) = &args.options.generic { bail_spanned!(generic.span() => "enums do not support #[pyclass(generic)]"); } 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)?; let cfg_attrs = get_cfg_attributes(&variant.attrs); Ok(PyClassEnumUnitVariant { ident, options, cfg_attrs, }) } 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 EnumVariant for PyClassEnumVariant<'_> { 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, cfg_attrs: Vec<&'a syn::Attribute>, } impl EnumVariant for PyClassEnumUnitVariant<'_> { 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(()) } } // todo(remove this dead code allowance once __repr__ is implemented #[allow(dead_code)] pub enum PyFmtName { Str, Repr, } fn implement_py_formatting( ty: &syn::Type, ctx: &Ctx, option: &StrFormatterAttribute, ) -> (ImplItemFn, MethodAndSlotDef) { let mut fmt_impl = match &option.value { Some(opt) => { let fmt = &opt.fmt; let args = &opt .args .iter() .map(|member| quote! {self.#member}) .collect::>(); let fmt_impl: ImplItemFn = syn::parse_quote! { fn __pyo3__generated____str__(&self) -> ::std::string::String { ::std::format!(#fmt, #(#args, )*) } }; fmt_impl } None => { let fmt_impl: syn::ImplItemFn = syn::parse_quote! { fn __pyo3__generated____str__(&self) -> ::std::string::String { ::std::format!("{}", &self) } }; fmt_impl } }; let fmt_slot = generate_protocol_slot( ty, &mut fmt_impl, &__STR__, "__str__", #[cfg(feature = "experimental-inspect")] FunctionIntrospectionData { names: &["__str__"], arguments: Vec::new(), returns: parse_quote! { ::std::string::String }, }, ctx, ) .unwrap(); (fmt_impl, fmt_slot) } fn implement_pyclass_str( options: &PyClassPyO3Options, ty: &syn::Type, ctx: &Ctx, ) -> (Option, Option) { match &options.str { Some(option) => { let (default_str, default_str_slot) = implement_py_formatting(ty, ctx, option); (Some(default_str), Some(default_str_slot)) } _ => (None, None), } } fn impl_enum( enum_: PyClassEnum<'_>, args: &PyClassArgs, doc: PythonDoc, methods_type: PyClassMethodsType, ctx: &Ctx, ) -> Result { if let Some(str_fmt) = &args.options.str { ensure_spanned!(str_fmt.value.is_none(), str_fmt.value.span() => "The format string syntax cannot be used with enums") } 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, 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 variant_cfg_check = generate_cfg_check(&variants, cls); let (default_repr, default_repr_slot) = { let variants_repr = variants.iter().map(|variant| { let variant_name = variant.ident; let cfg_attrs = &variant.cfg_attrs; // 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! { #(#cfg_attrs)* #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)?; (repr_impl, repr_slot) }; let (default_str, default_str_slot) = implement_pyclass_str(&args.options, &ty, ctx); 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; let cfg_attrs = &variant.cfg_attrs; quote! { #(#cfg_attrs)* #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)?; (int_impl, int_slot) }; let (default_richcmp, default_richcmp_slot) = pyclass_richcmp_simple_enum( &args.options, &ty, repr_type, #[cfg(feature = "experimental-inspect")] &get_class_python_name(cls, args).to_string(), 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); default_slots.extend(default_str_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), &v.cfg_attrs)), ctx, ), default_slots, ) .doc(doc) .impl_all(ctx)?; Ok(quote! { #variant_cfg_check #pytypeinfo #pyclass_impls #[doc(hidden)] #[allow(non_snake_case)] impl #cls { #default_repr #default_int #default_richcmp #default_hash #default_str } }) } 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, 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 (default_str, default_str_slot) = implement_pyclass_str(&args.options, &ty, ctx); let mut default_slots = vec![]; default_slots.extend(default_richcmp_slot); default_slots.extend(default_hash_slot); default_slots.extend(default_str_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); let enum_into_pyobject_impl = { let match_arms = 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 as ::std::convert::From>::from(self).add_subclass(#variant_cls); unsafe { #pyo3_path::Bound::new(py, pyclass_init).map(|b| b.cast_into_unchecked()) } } } }); let output_type = if cfg!(feature = "experimental-inspect") { let full_name = get_class_python_module_and_name(cls, &args); quote! { const OUTPUT_TYPE: &'static str = #full_name; } } else { quote! {} }; quote! { impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls { type Target = Self; type Output = #pyo3_path::Bound<'py, >::Target>; type Error = #pyo3_path::PyErr; #output_type fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result< ::Output, ::Error, > { match self { #(#match_arms)* } } } } }; let pyclass_impls: TokenStream = [ impl_builder.impl_pyclass(ctx), enum_into_pyobject_impl, impl_builder.impl_pyclassimpl(ctx)?, impl_builder.impl_add_to_module(ctx), impl_builder.impl_freelist(ctx), impl_builder.impl_introspection(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, 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 #default_str } #(#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 { pyo3_path, .. }: &Ctx, variant_cls_type: &syn::Type, field_names: &[Ident], ) -> syn::Result<(MethodAndMethodDef, syn::ImplItemFn)> { let ident = format_ident!("__match_args__"); let field_names_unraw = field_names.iter().map(|name| name.unraw()); let mut match_args_impl: syn::ImplItemFn = { parse_quote! { #[classattr] fn #ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'_, #pyo3_path::types::PyTuple>> { #pyo3_path::types::PyTuple::new::<&str, _>(py, [ #(stringify!(#field_names_unraw),)* ]) } } }; let spec = FnSpec::parse( &mut match_args_impl.sig, &mut match_args_impl.attrs, Default::default(), )?; let variant_match_args = impl_py_class_attribute(variant_cls_type, &spec, ctx)?; Ok((variant_match_args, match_args_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::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { #[allow(unused_imports)] use #pyo3_path::impl_::pyclass::Probe as _; match &*slf.into_super() { #enum_name::#variant_ident { #field_name, .. } => #pyo3_path::impl_::pyclass::ConvertField::< { #pyo3_path::impl_::pyclass::IsIntoPyObjectRef::<#field_type>::VALUE }, { #pyo3_path::impl_::pyclass::IsIntoPyObject::<#field_type>::VALUE }, >::convert_field::<#field_type>(#field_name, py), _ => ::core::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, &field_names)?; field_getters.push(variant_match_args); let cls_impl = quote! { #[doc(hidden)] #[allow(non_snake_case)] impl #variant_cls { #[allow(clippy::too_many_arguments)] 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<#enum_name> as ::std::convert::From<#enum_name>>::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::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { #[allow(unused_imports)] use #pyo3_path::impl_::pyclass::Probe as _; match &*slf.into_super() { #enum_name::#variant_ident ( #(#field_access_tokens), *) => #pyo3_path::impl_::pyclass::ConvertField::< { #pyo3_path::impl_::pyclass::IsIntoPyObjectRef::<#field_type>::VALUE }, { #pyo3_path::impl_::pyclass::IsIntoPyObject::<#field_type>::VALUE }, >::convert_field::<#field_type>(val, py), _ => ::core::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::PyClassGuard<'_, Self>) -> #pyo3_path::PyResult { ::std::result::Result::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 => #pyo3_path::IntoPyObjectExt::into_py_any(#variant_cls::#field_access(slf, py)?, py) } }) .collect(); let mut get_item_method_impl: syn::ImplItemFn = parse_quote! { fn __getitem__(slf: #pyo3_path::PyClassGuard<'_, Self>, py: #pyo3_path::Python<'_>, idx: usize) -> #pyo3_path::PyResult< #pyo3_path::Py<#pyo3_path::PyAny>> { match idx { #( #match_arms, )* _ => ::std::result::Result::Err(#pyo3_path::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, &field_names)?; field_getters.push(variant_match_args); let cls_impl = quote! { #[doc(hidden)] #[allow(non_snake_case)] impl #variant_cls { #[allow(clippy::too_many_arguments)] 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<#enum_name> as ::std::convert::From<#enum_name>>::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) } #[cfg(feature = "experimental-inspect")] struct FunctionIntrospectionData<'a> { names: &'a [&'a str], arguments: Vec>, returns: syn::Type, } fn generate_protocol_slot( cls: &syn::Type, method: &mut syn::ImplItemFn, slot: &SlotDef, name: &str, #[cfg(feature = "experimental-inspect")] introspection_data: FunctionIntrospectionData<'_>, ctx: &Ctx, ) -> syn::Result { let spec = FnSpec::parse( &mut method.sig, &mut Vec::new(), PyFunctionOptions::default(), )?; #[cfg_attr(not(feature = "experimental-inspect"), allow(unused_mut))] let mut def = slot.generate_type_slot(&syn::parse_quote!(#cls), &spec, name, ctx)?; #[cfg(feature = "experimental-inspect")] { // We generate introspection data let signature = FunctionSignature::from_arguments(introspection_data.arguments); let returns = introspection_data.returns; def.add_introspection( introspection_data .names .iter() .flat_map(|name| { function_introspection_code( &ctx.pyo3_path, None, name, &signature, Some("self"), parse_quote!(-> #returns), [], Some(cls), ) }) .collect(), ); } Ok(def) } 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(), )?; 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< Item = ( &'a syn::Ident, Cow<'a, syn::Ident>, &'a Vec<&'a syn::Attribute>, ), >, 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()), }), }, }; unit_variant_names .into_iter() .map(|(var, py_name, attrs)| { let method = gen_py_const(&cls_type, &variant_to_attribute(var, &py_name), ctx); let associated_method_tokens = method.associated_method; let method_def_tokens = method.method_def; let associated_method = quote! { #(#attrs)* #associated_method_tokens }; let method_def = quote! { #(#attrs)* #method_def_tokens }; MethodAndMethodDef { associated_method, method_def, } }) .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()), }), }, }; 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 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::Py<#pyo3_path::PyAny>> { ::std::result::Result::Ok(py.get_type::<#variant_cls>().into_any().unbind()) } }; let method_def = quote! { #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static( #pyo3_path::impl_::pymethods::PyMethodDefType::ClassAttribute({ #pyo3_path::impl_::pymethods::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, #[cfg(feature = "experimental-inspect")] annotation: 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, warnings: vec![], #[cfg(feature = "experimental-inspect")] output: syn::ReturnType::Default, }; 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, #[cfg(feature = "experimental-inspect")] annotation: 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, warnings: vec![], #[cfg(feature = "experimental-inspect")] output: syn::ReturnType::Default, }; 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 mut arg = parse_quote!(py: Python<'_>); let py = FnArg::parse(&mut arg)?; let signature = crate::pyfunction::FunctionSignature::from_arguments(vec![py]); 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.unraw(), signature, convention: crate::method::CallingConvention::Noargs, text_signature: None, asyncness: None, unsafety: None, warnings: vec![], #[cfg(feature = "experimental-inspect")] output: parse_quote!(-> #variant_cls_type), }; 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 renaming_rule = rename_all.map(|rename_all| rename_all.value.rule); #[cfg_attr(not(feature = "experimental-inspect"), allow(unused_mut))] let mut getter = impl_py_getter_def( &ty, PropertyType::Descriptor { field_index, field, python_name: options.name.as_ref(), renaming_rule, }, ctx, )?; #[cfg(feature = "experimental-inspect")] { // We generate introspection data let return_type = &field.ty; getter.add_introspection(function_introspection_code( &ctx.pyo3_path, None, &field_python_name(field, options.name.as_ref(), renaming_rule)?, &FunctionSignature::from_arguments(vec![]), Some("self"), parse_quote!(-> #return_type), vec!["property".into()], Some(&parse_quote!(#cls)), )); } 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 renaming_rule = rename_all.map(|rename_all| rename_all.value.rule); #[cfg_attr(not(feature = "experimental-inspect"), allow(unused_mut))] let mut setter = impl_py_setter_def( &ty, PropertyType::Descriptor { field_index, field, python_name: options.name.as_ref(), renaming_rule, }, ctx, )?; #[cfg(feature = "experimental-inspect")] { // We generate introspection data let name = field_python_name(field, options.name.as_ref(), renaming_rule)?; setter.add_introspection(function_introspection_code( &ctx.pyo3_path, None, &name, &FunctionSignature::from_arguments(vec![FnArg::Regular(RegularArg { name: Cow::Owned(format_ident!("value")), ty: &field.ty, from_py_with: None, default_value: None, option_wrapped_type: None, annotation: None, })]), Some("self"), syn::ReturnType::Default, vec![format!("{name}.setter")], Some(&parse_quote!(#cls)), )); } items.push(setter); }; } Ok(items) } fn impl_pytypeinfo(cls: &syn::Ident, attr: &PyClassArgs, 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 } }; let python_type = if cfg!(feature = "experimental-inspect") { let full_name = get_class_python_module_and_name(cls, attr); quote! { const PYTHON_TYPE: &'static str = #full_name; } } else { quote! {} }; quote! { unsafe impl #pyo3_path::type_object::PyTypeInfo for #cls { const NAME: &'static str = #cls_name; const MODULE: ::std::option::Option<&'static str> = #module; #python_type #[inline] fn type_object_raw(py: #pyo3_path::Python<'_>) -> *mut #pyo3_path::ffi::PyTypeObject { use #pyo3_path::prelude::PyTypeMethods; <#cls as #pyo3_path::impl_::pyclass::PyClassImpl>::lazy_type_object() .get_or_try_init(py) .unwrap_or_else(|e| #pyo3_path::impl_::pyclass::type_object_init_failed( py, e, ::NAME )) .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 => { #pyo3_path::IntoPyObjectExt::into_py_any(self_val == other, py) }, #pyo3_path::pyclass::CompareOp::Ne => { #pyo3_path::IntoPyObjectExt::into_py_any(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 => { #pyo3_path::IntoPyObjectExt::into_py_any(self_val > other, py) }, #pyo3_path::pyclass::CompareOp::Lt => { #pyo3_path::IntoPyObjectExt::into_py_any(self_val < other, py) }, #pyo3_path::pyclass::CompareOp::Le => { #pyo3_path::IntoPyObjectExt::into_py_any(self_val <= other, py) }, #pyo3_path::pyclass::CompareOp::Ge => { #pyo3_path::IntoPyObjectExt::into_py_any(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, #[cfg(feature = "experimental-inspect")] class_name: &str, 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."); } 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) = other.cast::() { 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(|_| { other.cast::().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::Py<#pyo3_path::PyAny>> { #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__", #[cfg(feature = "experimental-inspect")] FunctionIntrospectionData { names: &["__eq__", "__ne__"], arguments: vec![FnArg::Regular(RegularArg { name: Cow::Owned(format_ident!("other")), // we need to set a type, let's pick something small, it is overridden by annotation anyway ty: &parse_quote!(!), from_py_with: None, default_value: None, option_wrapped_type: None, annotation: Some(match (options.eq.is_some(), options.eq_int.is_some()) { (true, true) => { format!("{class_name} | int") } (true, false) => class_name.into(), (false, true) => "int".into(), (false, false) => unreachable!(), }), })], returns: parse_quote! { ::std::primitive::bool }, }, ctx, )? } else { generate_default_protocol_slot(cls, &mut richcmp_impl, &__RICHCMP__, ctx)? }; 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::Py<#pyo3_path::PyAny>> { let self_val = self; if let ::std::result::Result::Ok(other) = other.cast::() { 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__", #[cfg(feature = "experimental-inspect")] FunctionIntrospectionData { names: if options.ord.is_some() { &["__eq__", "__ne__", "__lt__", "__le__", "__gt__", "__ge__"] } else { &["__eq__", "__ne__"] }, arguments: vec![FnArg::Regular(RegularArg { name: Cow::Owned(format_ident!("other")), ty: &parse_quote!(&#cls), from_py_with: None, default_value: None, option_wrapped_type: None, annotation: None, })], returns: parse_quote! { ::std::primitive::bool }, }, ctx, )?; 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."; ); } 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__", #[cfg(feature = "experimental-inspect")] FunctionIntrospectionData { names: &["__hash__"], arguments: Vec::new(), returns: parse_quote! { ::std::primitive::u64 }, }, ctx, )?; Ok((Some(hash_impl), Some(hash_slot))) } None => Ok((None, None)), } } fn pyclass_class_geitem( options: &PyClassPyO3Options, cls: &syn::Type, ctx: &Ctx, ) -> Result<(Option, Option)> { let Ctx { pyo3_path, .. } = ctx; match options.generic { Some(_) => { let ident = format_ident!("__class_getitem__"); let mut class_geitem_impl: syn::ImplItemFn = { parse_quote! { #[classmethod] fn #ident<'py>( cls: &#pyo3_path::Bound<'py, #pyo3_path::types::PyType>, key: &#pyo3_path::Bound<'py, #pyo3_path::types::PyAny> ) -> #pyo3_path::PyResult<#pyo3_path::Bound<'py, #pyo3_path::types::PyGenericAlias>> { #pyo3_path::types::PyGenericAlias::new(cls.py(), cls.as_any(), key) } } }; let spec = FnSpec::parse( &mut class_geitem_impl.sig, &mut class_geitem_impl.attrs, Default::default(), )?; let class_geitem_method = crate::pymethod::impl_py_method_def( cls, &spec, &spec.get_doc(&class_geitem_impl.attrs, ctx)?, Some(quote!(#pyo3_path::ffi::METH_CLASS)), ctx, )?; Ok((Some(class_geitem_impl), Some(class_geitem_method))) } 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 { Ok([ self.impl_pyclass(ctx), self.impl_into_py(ctx), self.impl_pyclassimpl(ctx)?, self.impl_add_to_module(ctx), self.impl_freelist(ctx), self.impl_introspection(ctx), ] .into_iter() .collect()) } 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_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() { let output_type = if cfg!(feature = "experimental-inspect") { let full_name = get_class_python_module_and_name(cls, self.attr); quote! { const OUTPUT_TYPE: &'static str = #full_name; } } else { quote! {} }; quote! { impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls { type Target = Self; type Output = #pyo3_path::Bound<'py, >::Target>; type Error = #pyo3_path::PyErr; #output_type fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result< ::Output, ::Error, > { #pyo3_path::Bound::new(py, self) } } } } 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(); let is_immutable_type = self.attr.options.immutable_type.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 } }; let pyclass_base_type_impl = attr.options.subclass.map(|subclass| { quote_spanned! { subclass.span() => impl #pyo3_path::impl_::pyclass::PyClassBaseType for #cls { type LayoutAsBase = #pyo3_path::impl_::pycell::PyClassObject; type BaseNativeType = ::BaseNativeType; type Initializer = #pyo3_path::pyclass_init::PyClassInitializer; type PyClassMutability = ::PyClassMutability; } } }); let assertions = if attr.options.unsendable.is_some() { TokenStream::new() } else { let assert = quote_spanned! { cls.span() => #pyo3_path::impl_::pyclass::assert_pyclass_sync::<#cls>(); }; quote! { const _: () = { #assert }; } }; let type_name = if cfg!(feature = "experimental-inspect") { let full_name = get_class_python_module_and_name(cls, self.attr); quote! { const TYPE_NAME: &'static str = #full_name; } } else { quote! {} }; let extract_pyclass_with_clone = if let Some(from_py_object) = self.attr.options.from_py_object { let input_ty = if cfg!(feature = "experimental-inspect") { quote!(const INPUT_TYPE: &'static str = <#cls as #pyo3_path::impl_::pyclass::PyClassImpl>::TYPE_NAME;) } else { TokenStream::new() }; quote_spanned! { from_py_object.span() => impl<'a, 'py> #pyo3_path::FromPyObject<'a, 'py> for #cls where Self: ::std::clone::Clone, { type Error = #pyo3_path::pyclass::PyClassGuardError<'a, 'py>; #input_ty fn extract(obj: #pyo3_path::Borrowed<'a, 'py, #pyo3_path::PyAny>) -> ::std::result::Result { ::std::result::Result::Ok(::std::clone::Clone::clone(&*obj.extract::<#pyo3_path::PyClassGuard<'_, #cls>>()?)) } } } } else if self.attr.options.skip_from_py_object.is_none() { quote!( impl #pyo3_path::impl_::pyclass::ExtractPyClassWithClone for #cls {} ) } else { TokenStream::new() }; Ok(quote! { #extract_pyclass_with_clone #assertions #pyclass_base_type_impl 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; const IS_IMMUTABLE_TYPE: bool = #is_immutable_type; 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; #type_name 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) } const RAW_DOC: &'static ::std::ffi::CStr = #doc; const DOC: &'static ::std::ffi::CStr = { use #pyo3_path::impl_ as impl_; use impl_::pyclass::Probe as _; const DOC_PIECES: &'static [&'static [u8]] = impl_::pyclass::doc::PyClassDocGenerator::< #cls, { impl_::pyclass::HasNewTextSignature::<#cls>::VALUE } >::DOC_PIECES; const LEN: usize = impl_::concat::combined_len(DOC_PIECES); const DOC: &'static [u8] = &impl_::concat::combine_to_array::(DOC_PIECES); impl_::pyclass::doc::doc_bytes_as_cstr(DOC) }; #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<'_>) -> &'static ::std::sync::Mutex<#pyo3_path::impl_::freelist::PyObjectFreeList> { static FREELIST: #pyo3_path::sync::PyOnceLock<::std::sync::Mutex<#pyo3_path::impl_::freelist::PyObjectFreeList>> = #pyo3_path::sync::PyOnceLock::new(); &FREELIST.get_or_init(py, || ::std::sync::Mutex::new(#pyo3_path::impl_::freelist::PyObjectFreeList::with_capacity(#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() } } #[cfg(feature = "experimental-inspect")] fn impl_introspection(&self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let name = get_class_python_name(self.cls, self.attr).to_string(); let ident = self.cls; let static_introspection = class_introspection_code(pyo3_path, ident, &name); let introspection_id = introspection_id_const(); quote! { #static_introspection impl #ident { #introspection_id } } } #[cfg(not(feature = "experimental-inspect"))] fn impl_introspection(&self, _ctx: &Ctx) -> TokenStream { quote! {} } } 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); } } fn generate_cfg_check(variants: &[PyClassEnumUnitVariant<'_>], cls: &syn::Ident) -> TokenStream { if variants.is_empty() { return quote! {}; } let mut conditions = Vec::new(); for variant in variants { let cfg_attrs = &variant.cfg_attrs; if cfg_attrs.is_empty() { // There's at least one variant of the enum without cfg attributes, // so the check is not necessary return quote! {}; } for attr in cfg_attrs { if let syn::Meta::List(meta) = &attr.meta { let cfg_tokens = &meta.tokens; conditions.push(quote! { not(#cfg_tokens) }); } } } quote_spanned! { cls.span() => #[cfg(all(#(#conditions),*))] ::core::compile_error!(concat!("#[pyclass] can't be used on enums without any variants - all variants of enum `", stringify!(#cls), "` have been configured out by cfg attributes")); } } 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.27.2/src/pyfunction/signature.rs000064400000000000000000000565731046102023000210310ustar 00000000000000use crate::{ attributes::{kw, KeywordAttribute}, method::{FnArg, RegularArg}, }; use proc_macro2::{Span, TokenStream}; use quote::ToTokens; use syn::{ ext::IdentExt, parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, Token, }; #[derive(Clone)] pub struct Signature { paren_token: syn::token::Paren, pub items: Punctuated, pub returns: Option<(Token![->], PyTypeAnnotation)>, } 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![,])?; let returns = if input.peek(Token![->]) { Some((input.parse()?, input.parse()?)) } else { None }; Ok(Signature { paren_token, items, returns, }) } } impl ToTokens for Signature { fn to_tokens(&self, tokens: &mut TokenStream) { self.paren_token .surround(tokens, |tokens| self.items.to_tokens(tokens)); if let Some((arrow, returns)) = &self.returns { arrow.to_tokens(tokens); returns.to_tokens(tokens); } } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignatureItemArgument { pub ident: syn::Ident, pub colon_and_annotation: Option<(Token![:], PyTypeAnnotation)>, 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, pub colon_and_annotation: Option<(Token![:], PyTypeAnnotation)>, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignatureItemKwargs { pub asterisks: (Token![*], Token![*]), pub ident: syn::Ident, pub colon_and_annotation: Option<(Token![:], PyTypeAnnotation)>, } #[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()?, colon_and_annotation: if input.peek(Token![:]) { Some((input.parse()?, input.parse()?)) } else { None }, })) } } } 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()?, colon_and_annotation: if input.peek(Token![:]) { Some((input.parse()?, input.parse()?)) } else { None }, 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((colon, annotation)) = &self.colon_and_annotation { colon.to_tokens(tokens); annotation.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()?, colon_and_annotation: if input.peek(Token![:]) { Some((input.parse()?, input.parse()?)) } else { None }, }) } } 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()?, colon_and_annotation: if input.peek(Token![:]) { Some((input.parse()?, input.parse()?)) } else { None }, }) } } 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); } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct PyTypeAnnotation(syn::LitStr); impl Parse for PyTypeAnnotation { fn parse(input: ParseStream<'_>) -> syn::Result { Ok(Self(input.parse()?)) } } impl ToTokens for PyTypeAnnotation { fn to_tokens(&self, tokens: &mut TokenStream) { self.0.to_tokens(tokens); } } impl PyTypeAnnotation { pub fn to_python(&self) -> String { self.0.value() } } 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, Clone)] 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() } } #[derive(Clone)] 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 { 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; } 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" ) }; if let Some(returns) = &attribute.value.returns { ensure_spanned!( cfg!(feature = "experimental-inspect"), returns.1.span() => "Return type annotation in the signature is only supported with the `experimental-inspect` feature" ); } 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(), )?; let FnArg::Regular(fn_arg) = fn_arg 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." ); }; if let Some((_, default)) = &arg.eq_and_default { fn_arg.default_value = Some(default.clone()); } if let Some((_, annotation)) = &arg.colon_and_annotation { ensure_spanned!( cfg!(feature = "experimental-inspect"), annotation.span() => "Type annotations in the signature is only supported with the `experimental-inspect` feature" ); #[cfg(feature = "experimental-inspect")] { fn_arg.annotation = Some(annotation.to_python()); } } } 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)?; if let Some((_, annotation)) = &varargs.colon_and_annotation { ensure_spanned!( cfg!(feature = "experimental-inspect"), annotation.span() => "Type annotations in the signature is only supported with the `experimental-inspect` feature" ); #[cfg(feature = "experimental-inspect")] { let FnArg::VarArgs(fn_arg) = fn_arg 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." ); }; fn_arg.annotation = Some(annotation.to_python()); } } } 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)?; if let Some((_, annotation)) = &kwargs.colon_and_annotation { ensure_spanned!( cfg!(feature = "experimental-inspect"), annotation.span() => "Type annotations in the signature is only supported with the `experimental-inspect` feature" ); #[cfg(feature = "experimental-inspect")] { let FnArg::KwArgs(fn_arg) = fn_arg 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." ); }; fn_arg.annotation = Some(annotation.to_python()); } } } 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>) -> Self { 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 { .. }) = arg { // This argument is required, all previous arguments must also have been required assert_eq!( python_signature.required_positional_parameters, python_signature.positional_parameters.len(), ); python_signature.required_positional_parameters = python_signature.positional_parameters.len() + 1; } python_signature .positional_parameters .push(arg.name().unraw().to_string()); } Self { arguments, python_signature, attribute: None, } } fn default_value_for_parameter(&self, parameter: &str) -> String { if let Some(fn_arg) = self.arguments.iter().find(|arg| arg.name() == parameter) { fn_arg.default_value() } else { "...".to_string() } } 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.27.2/src/pyfunction.rs000064400000000000000000000364061046102023000170210ustar 00000000000000use crate::attributes::KeywordAttribute; use crate::combine_errors::CombineErrors; #[cfg(feature = "experimental-inspect")] use crate::introspection::{function_introspection_code, introspection_id_const}; use crate::utils::{Ctx, LitCStr}; use crate::{ attributes::{ self, get_pyo3_options, take_attributes, take_pyo3_options, CrateAttribute, FromPyWithAttribute, NameAttribute, TextSignatureAttribute, }, method::{self, CallingConvention, FnArg}, pymethod::check_generic, }; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, ToTokens}; use std::cmp::PartialEq; use std::ffi::CString; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::{ext::IdentExt, spanned::Spanned, LitStr, Path, Result, Token}; 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) } } type PyFunctionWarningMessageAttribute = KeywordAttribute; type PyFunctionWarningCategoryAttribute = KeywordAttribute; pub struct PyFunctionWarningAttribute { pub message: PyFunctionWarningMessageAttribute, pub category: Option, pub span: Span, } #[derive(PartialEq, Clone)] pub enum PyFunctionWarningCategory { Path(Path), UserWarning, DeprecationWarning, // TODO: unused for now, intended for pyo3(deprecated) special-case } #[derive(Clone)] pub struct PyFunctionWarning { pub message: LitStr, pub category: PyFunctionWarningCategory, pub span: Span, } impl From for PyFunctionWarning { fn from(value: PyFunctionWarningAttribute) -> Self { Self { message: value.message.value, category: value .category .map_or(PyFunctionWarningCategory::UserWarning, |cat| { PyFunctionWarningCategory::Path(cat.value) }), span: value.span, } } } pub trait WarningFactory { fn build_py_warning(&self, ctx: &Ctx) -> TokenStream; fn span(&self) -> Span; } impl WarningFactory for PyFunctionWarning { fn build_py_warning(&self, ctx: &Ctx) -> TokenStream { let message = &self.message.value(); let c_message = LitCStr::new( CString::new(message.clone()).unwrap(), Spanned::span(&message), ctx, ); let pyo3_path = &ctx.pyo3_path; let category = match &self.category { PyFunctionWarningCategory::Path(path) => quote! {#path}, PyFunctionWarningCategory::UserWarning => { quote! {#pyo3_path::exceptions::PyUserWarning} } PyFunctionWarningCategory::DeprecationWarning => { quote! {#pyo3_path::exceptions::PyDeprecationWarning} } }; quote! { #pyo3_path::PyErr::warn(py, &<#category as #pyo3_path::PyTypeInfo>::type_object(py), #c_message, 1)?; } } fn span(&self) -> Span { self.span } } impl WarningFactory for Vec { fn build_py_warning(&self, ctx: &Ctx) -> TokenStream { let warnings = self.iter().map(|warning| warning.build_py_warning(ctx)); quote! { #(#warnings)* } } fn span(&self) -> Span { self.iter() .map(|val| val.span()) .reduce(|acc, span| acc.join(span).unwrap_or(acc)) .unwrap() } } impl Parse for PyFunctionWarningAttribute { fn parse(input: ParseStream<'_>) -> Result { let mut message: Option = None; let mut category: Option = None; let span = input.parse::()?.span(); let content; syn::parenthesized!(content in input); while !content.is_empty() { let lookahead = content.lookahead1(); if lookahead.peek(attributes::kw::message) { message = content .parse::() .map(Some)?; } else if lookahead.peek(attributes::kw::category) { category = content .parse::() .map(Some)?; } else { return Err(lookahead.error()); } if content.peek(Token![,]) { content.parse::()?; } } Ok(PyFunctionWarningAttribute { message: message.ok_or(syn::Error::new( content.span(), "missing `message` in `warn` attribute", ))?, category, span, }) } } impl ToTokens for PyFunctionWarningAttribute { fn to_tokens(&self, tokens: &mut TokenStream) { let message_tokens = self.message.to_token_stream(); let category_tokens = self .category .as_ref() .map_or(quote! {}, |cat| cat.to_token_stream()); let token_stream = quote! { warn(#message_tokens, #category_tokens) }; tokens.extend(token_stream); } } #[derive(Default)] pub struct PyFunctionOptions { pub pass_module: Option, pub name: Option, pub signature: Option, pub text_signature: Option, pub krate: Option, pub warnings: Vec, } impl Parse for PyFunctionOptions { fn parse(input: ParseStream<'_>) -> Result { let mut options = PyFunctionOptions::default(); let attrs = Punctuated::::parse_terminated(input)?; options.add_attributes(attrs)?; Ok(options) } } pub enum PyFunctionOption { Name(NameAttribute), PassModule(attributes::kw::pass_module), Signature(SignatureAttribute), TextSignature(TextSignatureAttribute), Crate(CrateAttribute), Warning(PyFunctionWarningAttribute), } 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 if lookahead.peek(attributes::kw::warn) { input.parse().map(PyFunctionOption::Warning) } 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), PyFunctionOption::Warning(warning) => { self.warnings.push(warning.into()); } } } 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, warnings, } = 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) .try_combine_syn_errors()?; let signature = if let Some(signature) = signature { FunctionSignature::from_arguments_and_attribute(arguments, signature)? } else { FunctionSignature::from_arguments(arguments) }; let vis = &func.vis; let name = &func.sig.ident; #[cfg(feature = "experimental-inspect")] let introspection = function_introspection_code( pyo3_path, Some(name), &name.to_string(), &signature, None, func.sig.output.clone(), [] as [String; 0], None, ); #[cfg(not(feature = "experimental-inspect"))] let introspection = quote! {}; #[cfg(feature = "experimental-inspect")] let introspection_id = introspection_id_const(); #[cfg(not(feature = "experimental-inspect"))] let introspection_id = quote! {}; 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, warnings, #[cfg(feature = "experimental-inspect")] output: func.sig.output.clone(), }; let wrapper_ident = format_ident!("__pyfunction_{}", spec.name); if spec.asyncness.is_some() { ensure_spanned!( cfg!(feature = "experimental-async"), spec.asyncness.span() => "async functions are only supported with the `experimental-async` feature" ); } 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 static _PYO3_DEF: #pyo3_path::impl_::pyfunction::PyFunctionDef = MakeDef::_PYO3_DEF; #introspection_id } // Generate the definition 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 { // We're using this to initialize a static, so it's fine. #[allow(clippy::declare_interior_mutable_const)] const _PYO3_DEF: #pyo3_path::impl_::pyfunction::PyFunctionDef = #pyo3_path::impl_::pyfunction::PyFunctionDef::from_method_def(#methoddef); } #[allow(non_snake_case)] #wrapper #introspection }; Ok(wrapped_pyfunction) } pyo3-macros-backend-0.27.2/src/pyimpl.rs000064400000000000000000000402771046102023000161360ustar 00000000000000use std::collections::HashSet; use crate::combine_errors::CombineErrors; #[cfg(feature = "experimental-inspect")] use crate::introspection::{attribute_introspection_code, function_introspection_code}; #[cfg(feature = "experimental-inspect")] use crate::method::{FnSpec, FnType}; #[cfg(feature = "experimental-inspect")] use crate::utils::expr_to_python; use crate::utils::{has_attribute, has_attribute_with_namespace, Ctx, PyO3CratePath}; use crate::{ attributes::{take_pyo3_options, CrateAttribute}, konst::{ConstAttributes, ConstSpec}, pyfunction::PyFunctionOptions, pymethod::{ self, is_proto_method, GeneratedPyMethod, MethodAndMethodDef, MethodAndSlotDef, PyMethod, }, }; use proc_macro2::TokenStream; use quote::{format_ident, quote}; #[cfg(feature = "experimental-inspect")] use syn::Ident; use syn::{ parse::{Parse, ParseStream}, spanned::Spanned, ImplItemFn, 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) } } fn check_pyfunction(pyo3_path: &PyO3CratePath, meth: &mut ImplItemFn) -> syn::Result<()> { let mut error = None; meth.attrs.retain(|attr| { let attrs = [attr.clone()]; if has_attribute(&attrs, "pyfunction") || has_attribute_with_namespace(&attrs, Some(pyo3_path), &["pyfunction"]) || has_attribute_with_namespace(&attrs, Some(pyo3_path), &["prelude", "pyfunction"]) { error = Some(err_spanned!(meth.sig.span() => "functions inside #[pymethods] do not need to be annotated with #[pyfunction]")); false } else { true } }); error.map_or(Ok(()), Err) } pub fn impl_methods( ty: &syn::Type, impls: &mut [syn::ImplItem], methods_type: PyClassMethodsType, options: PyImplOptions, ) -> syn::Result { let mut extra_fragments = 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(); let _: Vec<()> = impls .iter_mut() .map(|iimpl| { 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()); check_pyfunction(&ctx.pyo3_path, meth)?; let method = PyMethod::parse(&mut meth.sig, &mut meth.attrs, fun_options)?; #[cfg(feature = "experimental-inspect")] extra_fragments.push(method_introspection_code(&method.spec, ty, ctx)); match pymethod::gen_py_method(ty, method, &meth.attrs, 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); extra_fragments.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)?; 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)])); } #[cfg(feature = "experimental-inspect")] extra_fragments.push(attribute_introspection_code( &ctx.pyo3_path, Some(ty), spec.python_name().to_string(), expr_to_python(&konst.expr), konst.ty.clone(), true, )); } } 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" ), _ => {} } Ok(()) }) .try_combine_syn_errors()?; 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! { #(#extra_fragments)* #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 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::Py<#pyo3_path::PyAny>> { #pyo3_path::IntoPyObjectExt::into_py_any(#cls::#member, py) } }; let method_def = quote! { #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static( #pyo3_path::impl_::pymethods::PyMethodDefType::ClassAttribute({ #pyo3_path::impl_::pymethods::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),*] }) } } } pub(crate) fn get_cfg_attributes(attrs: &[syn::Attribute]) -> Vec<&syn::Attribute> { attrs .iter() .filter(|attr| attr.path().is_ident("cfg")) .collect() } #[cfg(feature = "experimental-inspect")] fn method_introspection_code(spec: &FnSpec<'_>, parent: &syn::Type, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let name = spec.python_name.to_string(); // __richcmp__ special case if name == "__richcmp__" { // We expend into each individual method return ["__eq__", "__ne__", "__lt__", "__le__", "__gt__", "__ge__"] .into_iter() .map(|method_name| { let mut spec = (*spec).clone(); spec.python_name = Ident::new(method_name, spec.python_name.span()); // We remove the CompareOp arg, this is safe because the signature is always the same // First the other value to compare with then the CompareOp // We cant to keep the first argument type, hence this hack spec.signature.arguments.pop(); spec.signature.python_signature.positional_parameters.pop(); method_introspection_code(&spec, parent, ctx) }) .collect(); } // We map or ignore some magic methods // TODO: this might create a naming conflict let name = match name.as_str() { "__concat__" => "__add__".into(), "__repeat__" => "__mul__".into(), "__inplace_concat__" => "__iadd__".into(), "__inplace_repeat__" => "__imul__".into(), "__getbuffer__" | "__releasebuffer__" | "__traverse__" | "__clear__" => return quote! {}, _ => name, }; // We introduce self/cls argument and setup decorators let mut first_argument = None; let mut output = spec.output.clone(); let mut decorators = Vec::new(); match &spec.tp { FnType::Getter(_) => { first_argument = Some("self"); decorators.push("property".into()); } FnType::Setter(_) => { first_argument = Some("self"); decorators.push(format!("{name}.setter")); } FnType::Fn(_) => { first_argument = Some("self"); } FnType::FnNew | FnType::FnNewClass(_) => { first_argument = Some("cls"); output = syn::ReturnType::Default; // The __new__ Python function return type is None } FnType::FnClass(_) => { first_argument = Some("cls"); decorators.push("classmethod".into()); } FnType::FnStatic => { decorators.push("staticmethod".into()); } FnType::FnModule(_) => (), // TODO: not sure this can happen FnType::ClassAttribute => { first_argument = Some("cls"); // TODO: this combination only works with Python 3.9-3.11 https://docs.python.org/3.11/library/functions.html#classmethod decorators.push("classmethod".into()); decorators.push("property".into()); } } function_introspection_code( pyo3_path, None, &name, &spec.signature, first_argument, output, decorators, Some(parent), ) } pyo3-macros-backend-0.27.2/src/pymethod.rs000064400000000000000000001761551046102023000164620ustar 00000000000000use std::borrow::Cow; use std::ffi::CString; use crate::attributes::{FromPyWithAttribute, NameAttribute, RenamingRule}; #[cfg(feature = "experimental-inspect")] use crate::introspection::unique_element_id; use crate::method::{CallingConvention, ExtractErrorMode, PyArg}; use crate::params::{impl_regular_arg_param, Holders}; use crate::pyfunction::WarningFactory; 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, Field, Ident, 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, } #[cfg(feature = "experimental-inspect")] impl MethodAndMethodDef { pub fn add_introspection(&mut self, data: TokenStream) { let const_name = format_ident!("_{}", unique_element_id()); // We need an explicit name here self.associated_method.extend(quote! { const #const_name: () = { #data }; }); } } /// 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, } #[cfg(feature = "experimental-inspect")] impl MethodAndSlotDef { pub fn add_introspection(&mut self, data: TokenStream) { let const_name = format_ident!("_{}", unique_element_id()); // We need an explicit name here self.associated_method.extend(quote! { const #const_name: () = { #data }; }); } } pub enum GeneratedPyMethod { Method(MethodAndMethodDef), Proto(MethodAndSlotDef), SlotTraitImpl(String, TokenStream), } pub struct PyMethod<'a> { kind: PyMethodKind, method_name: String, pub 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__)), // 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), "__clear__" => PyMethodKind::Proto(PyMethodProtoKind::Clear), // Not a proto _ => PyMethodKind::Fn, } } } enum PyMethodProtoKind { Slot(&'static SlotDef), Call, Traverse, Clear, SlotFragment(&'static SlotFragmentDef), } impl<'a> PyMethod<'a> { pub fn parse( sig: &'a mut syn::Signature, meth_attrs: &mut Vec, options: PyFunctionOptions, ) -> Result { check_generic(sig)?; ensure_function_options_valid(&options)?; let spec = FnSpec::parse(sig, meth_attrs, options)?; let method_name = spec.python_name.to_string(); let kind = PyMethodKind::from_name(&method_name); Ok(Self { kind, method_name, spec, }) } } pub fn is_proto_method(name: &str) -> bool { match PyMethodKind::from_name(name) { PyMethodKind::Fn => false, PyMethodKind::Proto(_) => true, } } pub fn gen_py_method( cls: &syn::Type, method: PyMethod<'_>, meth_attrs: &[syn::Attribute], ctx: &Ctx, ) -> Result { let spec = &method.spec; let Ctx { pyo3_path, .. } = ctx; if spec.asyncness.is_some() { ensure_spanned!( cfg!(feature = "experimental-async"), spec.asyncness.span() => "async functions are only supported with the `experimental-async` feature" ); } 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::Clear => { GeneratedPyMethod::Proto(impl_clear_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 {typ} parameters"); 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::impl_::pymethods::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_impl = spec.text_signature_call_signature().map(|text_signature| { quote! { #[allow(unknown_lints, non_local_definitions)] impl #pyo3_path::impl_::pyclass::doc::PyClassNewTextSignature for #cls { const TEXT_SIGNATURE: &'static str = #text_signature; } } }); 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 { #text_signature_impl #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 Python interpreter is \ prohibited inside implementations of `__traverse__`, i.e. `Python::attach` 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 Python interpreter is \ prohibited inside implementations of `__traverse__`, i.e. `Python::attach` will panic." } } ensure_spanned!( spec.warnings.is_empty(), spec.warnings.span() => "__traverse__ cannot be used with #[pyo3(warn)]" ); 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::ffi::c_void, ) -> ::std::ffi::c_int { #pyo3_path::impl_::pymethods::_call_traverse::<#cls>(slf, #cls::#rust_fn_ident, visit, arg, #cls::__pymethod_traverse__) } }; 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_clear_slot(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); let self_type = match &spec.tp { FnType::Fn(self_type) => self_type, _ => bail_spanned!(spec.name.span() => "expected instance method for `__clear__` function"), }; let mut holders = Holders::new(); let slf = self_type.receiver(cls, ExtractErrorMode::Raise, &mut holders, ctx); if let [arg, ..] = args { bail_spanned!(arg.ty().span() => "`__clear__` function expected to have no arguments"); } let name = &spec.name; let holders = holders.init_holders(ctx); let fncall = if py_arg.is_some() { quote!(#cls::#name(#slf, py)) } else { quote!(#cls::#name(#slf)) }; let associated_method = quote! { pub unsafe extern "C" fn __pymethod___clear____( _slf: *mut #pyo3_path::ffi::PyObject, ) -> ::std::ffi::c_int { #pyo3_path::impl_::pymethods::_call_clear(_slf, |py, _slf| { #holders let result = #fncall; let result = #pyo3_path::impl_::wrap::converter(&result).wrap(result)?; ::std::result::Result::Ok(result) }, #cls::__pymethod___clear____) } }; let slot_def = quote! { #pyo3_path::ffi::PyType_Slot { slot: #pyo3_path::ffi::Py_tp_clear, pfunc: #cls::__pymethod___clear____ as #pyo3_path::ffi::inquiry as _ } }; Ok(MethodAndSlotDef { associated_method, slot_def, }) } pub(crate) 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)" ); ensure_spanned!( spec.warnings.is_empty(), spec.warnings.span() => "#[classattr] cannot be used with #[pyo3(warn)]" ); 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::Py<#pyo3_path::PyAny>> { let function = #cls::#name; // Shadow the method name to avoid #3017 let result = #body; #pyo3_path::impl_::wrap::converter(&result).map_into_pyobject(py, result) } }; let method_def = quote! { #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static( #pyo3_path::impl_::pymethods::PyMethodDefType::ClassAttribute({ #pyo3_path::impl_::pymethods::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 #ident = #from_py_with; }, 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 extract = impl_regular_arg_param( arg, ident, quote!(::std::option::Option::Some(_value.into())), &mut holders, ctx, ); quote! { #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); quote! { #[allow(unused_imports)] use #pyo3_path::impl_::pyclass::Probe as _; let _val = #pyo3_path::impl_::extract_argument::extract_argument(_value.into(), &mut #holder, #name)?; } } }; let mut cfg_attrs = TokenStream::new(); if let PropertyType::Descriptor { field, .. } = &property_type { for attr in field .attrs .iter() .filter(|attr| attr.path().is_ident("cfg")) { attr.to_tokens(&mut cfg_attrs); } } let warnings = if let PropertyType::Function { spec, .. } = &property_type { spec.warnings.build_py_warning(ctx) } else { quote!() }; let init_holders = holders.init_holders(ctx); 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::ffi::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 #warnings let result = #setter_impl; #pyo3_path::impl_::callback::convert(py, result) } }; let method_def = quote! { #cfg_attrs #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static( #pyo3_path::impl_::pymethods::PyMethodDefType::Setter( #pyo3_path::impl_::pymethods::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 generator = quote_spanned! { ty.span() => #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Runtime( || GENERATOR.generate(#python_name, #doc) ) }; // This is separate so that the unsafe below does not inherit the span and thus does not // trigger the `unsafe_code` lint let method_def = quote! { #cfg_attrs { #[allow(unused_imports)] // might not be used if all probes are positive use #pyo3_path::impl_::pyclass::Probe as _; 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::IsIntoPyObjectRef::<#ty>::VALUE }, { #pyo3_path::impl_::pyclass::IsIntoPyObject::<#ty>::VALUE }, > = unsafe { #pyo3_path::impl_::pyclass::PyClassGetterGenerator::new() }; #generator } }; 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::impl_::callback::convert(py, #call) }; let init_holders = holders.init_holders(ctx); let warnings = spec.warnings.build_py_warning(ctx); 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 #warnings let result = #body; result } }; let method_def = quote! { #cfg_attrs #pyo3_path::impl_::pyclass::MaybeRuntimePyMethodDef::Static( #pyo3_path::impl_::pymethods::PyMethodDefType::Getter( #pyo3_path::impl_::pymethods::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, 'b>(args: &'a [FnArg<'b>]) -> (Option<&'a PyArg<'b>>, &'a [FnArg<'b>]) { 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 = field_python_name(field, *python_name, *renaming_rule)?; 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) -> Result> { match self { PropertyType::Descriptor { field, .. } => { utils::get_doc(&field.attrs, None, ctx).map(Cow::Owned) } PropertyType::Function { doc, .. } => Ok(Cow::Borrowed(doc)), } } } pub 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::impl_::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::ffi::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, format_ident!("ref_from_ptr"), quote! { #ident }, ctx ), Ty::MaybeNullObject => extract_object( extract_error_mode, holders, arg, format_ident!("ref_from_ptr"), quote! { if #ident.is_null() { #pyo3_path::ffi::Py_None() } else { #ident } }, ctx ), Ty::NonNullObject => extract_object( extract_error_mode, holders, arg, format_ident!("ref_from_non_null"), quote! { #ident }, ctx ), Ty::IPowModulo => extract_object( extract_error_mode, holders, arg, format_ident!("ref_from_ptr"), 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<'_>, ref_from_method: Ident, source_ptr: TokenStream, ctx: &Ctx, ) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let name = arg.name().unraw().to_string(); let extract = if let Some(FromPyWithAttribute { kw, value: extractor, }) = arg.from_py_with() { let extractor = quote_spanned! { kw.span => { let from_py_with: fn(_) -> _ = #extractor; from_py_with } }; quote! { #pyo3_path::impl_::extract_argument::from_py_with( unsafe { #pyo3_path::impl_::pymethods::BoundRef::#ref_from_method(py, &#source_ptr).0 }, #name, #extractor, ) } } else { let holder = holders.push_holder(Span::call_site()); quote! {{ #[allow(unused_imports)] use #pyo3_path::impl_::pyclass::Probe as _; #pyo3_path::impl_::extract_argument::extract_argument( unsafe { #pyo3_path::impl_::pymethods::BoundRef::#ref_from_method(py, &#source_ptr).0 }, &mut #holder, #name ) }} }; let extracted = extract_error_mode.handle_error(extract, ctx); quote!(#extracted) } enum ReturnMode { ReturnSelf, Conversion(TokenGenerator), SpecializedConversion(TokenGenerator, TokenGenerator), } impl ReturnMode { fn return_call_output(&self, call: TokenStream, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; match self { ReturnMode::Conversion(conversion) => { let conversion = TokenGeneratorCtx(*conversion, ctx); quote! { let _result: #pyo3_path::PyResult<#conversion> = #pyo3_path::impl_::callback::convert(py, #call); #pyo3_path::impl_::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}; (&_result).#tag().convert(py, _result) } } ReturnMode::ReturnSelf => quote! { let _result: #pyo3_path::PyResult<()> = #pyo3_path::impl_::callback::convert(py, #call); _result?; #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! { #[allow(non_snake_case)] 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),*) }; let body = if let Some(return_mode) = return_mode { return_mode.return_call_output(call, ctx) } else { quote! { let result = #call; #pyo3_path::impl_::callback::convert(py, result) } }; let warnings = spec.warnings.build_py_warning(ctx); Ok(quote! { #warnings #body }) } 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 { #[allow(non_snake_case)] 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) } } pub fn field_python_name( field: &Field, name_attr: Option<&NameAttribute>, renaming_rule: Option, ) -> Result { if let Some(name_attr) = name_attr { return Ok(name_attr.value.0.to_string()); } let Some(ident) = &field.ident else { bail_spanned!(field.span() => "`get` and `set` with tuple struct fields require `name`"); }; let mut name = ident.unraw().to_string(); if let Some(rule) = renaming_rule { name = utils::apply_renaming_rule(rule, &name); } Ok(name) } pyo3-macros-backend-0.27.2/src/pyversions.rs000064400000000000000000000006011046102023000170300ustar 00000000000000use pyo3_build_config::PythonVersion; pub fn is_abi3_before(major: u8, minor: u8) -> bool { let config = pyo3_build_config::get(); config.abi3 && !config.is_free_threaded() && config.version < PythonVersion { major, minor } } pub fn is_py_before(major: u8, minor: u8) -> bool { let config = pyo3_build_config::get(); config.version < PythonVersion { major, minor } } pyo3-macros-backend-0.27.2/src/quotes.rs000064400000000000000000000021661046102023000161370ustar 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 => { let obj = #obj; #[allow(clippy::useless_conversion)] #pyo3_path::impl_::wrap::converter(&obj).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); let py = syn::Ident::new("py", proc_macro2::Span::call_site()); quote_spanned! { *output_span => { let result = #result; #pyo3_path::impl_::wrap::converter(&result).map_into_ptr(#py, result) }} } pyo3-macros-backend-0.27.2/src/utils.rs000064400000000000000000000267371046102023000157710ustar 00000000000000use crate::attributes::{CrateAttribute, RenamingRule}; use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned, 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, ) -> syn::Result { 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(); let mut current_part_span = None; for attr in attrs { if attr.path().is_ident("doc") { if let Ok(nv) = attr.meta.require_name_value() { current_part_span = match current_part_span { None => Some(nv.value.span()), Some(span) => span.join(nv.value.span()), }; 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(quote_spanned!(current_part_span.unwrap_or(Span::call_site()) => #current_part)); 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( quote_spanned!(current_part_span.unwrap_or(Span::call_site()) => #current_part), ); } 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); }); Ok(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).map_err(|e| { syn::Error::new( current_part_span.unwrap_or(Span::call_site()), format!( "Python doc may not contain nul byte, found nul at position {}", e.nul_position() ), ) })?; Ok(PythonDoc(PythonDocKind::LitCStr(LitCStr::new( docs, current_part_span.unwrap_or(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) enum IdentOrStr<'a> { Str(&'a str), Ident(syn::Ident), } pub(crate) fn has_attribute(attrs: &[syn::Attribute], ident: &str) -> bool { has_attribute_with_namespace(attrs, None, &[ident]) } pub(crate) 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)) }) } pub fn expr_to_python(expr: &syn::Expr) -> String { match expr { // literal values syn::Expr::Lit(syn::ExprLit { lit, .. }) => match lit { syn::Lit::Str(s) => s.token().to_string(), syn::Lit::Char(c) => c.token().to_string(), syn::Lit::Int(i) => i.base10_digits().to_string(), syn::Lit::Float(f) => f.base10_digits().to_string(), syn::Lit::Bool(b) => { if b.value() { "True".to_string() } else { "False".to_string() } } _ => "...".to_string(), }, // None syn::Expr::Path(syn::ExprPath { qself, path, .. }) if qself.is_none() && path.is_ident("None") => { "None".to_string() } // others, unsupported yet so defaults to `...` _ => "...".to_string(), } }