bon-macros-3.7.1/.cargo_vcs_info.json0000644000000001500000000000100131000ustar { "git": { "sha1": "bc940a1209420d3a613f8ea4ecf1992d984b9951" }, "path_in_vcs": "bon-macros" }bon-macros-3.7.1/Cargo.lock0000644000000070350000000000100110640ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "bon-macros" version = "3.7.1" dependencies = [ "darling", "expect-test", "ident_case", "prettyplease", "proc-macro2", "quote", "rustversion", "syn", ] [[package]] name = "darling" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a79c4acb1fd5fa3d9304be4c76e031c54d2e92d172a393e24b19a14fe8532fe9" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74875de90daf30eb59609910b84d4d368103aaec4c924824c6799b28f77d6a1d" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn", ] [[package]] name = "darling_macro" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e79f8e61677d5df9167cd85265f8e5f64b215cdea3fb55eebc3e622e44c7a146" dependencies = [ "darling_core", "quote", "syn", ] [[package]] name = "dissimilar" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" [[package]] name = "expect-test" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63af43ff4431e848fb47472a920f14fa71c24de13255a5692e93d4e90302acb0" dependencies = [ "dissimilar", "once_cell", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "prettyplease" version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", "syn", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "rustversion" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" bon-macros-3.7.1/Cargo.toml0000644000000133010000000000100111000ustar # 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.59.0" name = "bon-macros" version = "3.7.1" build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = """ This is a proc-macro crate that is supposed to be a private implementation detail of the `bon` crate """ homepage = "https://bon-rs.com/" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/elastio/bon" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--generate-link-to-definition"] targets = ["x86_64-unknown-linux-gnu"] [features] alloc = [] default = [] experimental-overwritable = [] implied-bounds = [] std = [] [lib] name = "bon_macros" path = "src/lib.rs" proc-macro = true [dependencies.darling] version = "0.21.0" [dependencies.ident_case] version = "1" [dependencies.prettyplease] version = "0.2" [dependencies.proc-macro2] version = "1.0.88" [dependencies.quote] version = "1" [dependencies.rustversion] version = "1.0" [dependencies.syn] version = "2.0.56" features = [ "full", "visit-mut", "visit", ] [dev-dependencies.expect-test] version = "1.4.1" [lints.clippy] assertions_on_result_states = "warn" await_holding_refcell_ref = "warn" bool_to_int_with_if = "warn" borrow_as_ptr = "warn" branches_sharing_code = "warn" cast_lossless = "warn" cast_ptr_alignment = "warn" checked_conversions = "warn" clear_with_drain = "warn" cloned_instead_of_copied = "warn" collection_is_never_read = "warn" copy_iterator = "warn" dbg_macro = "warn" debug_assert_with_mut_call = "warn" default_union_representation = "warn" doc_link_with_quotes = "warn" doc_markdown = "warn" empty_drop = "warn" empty_line_after_doc_comments = "warn" empty_line_after_outer_attr = "warn" expl_impl_clone_on_copy = "warn" explicit_into_iter_loop = "warn" explicit_iter_loop = "warn" filter_map_next = "warn" flat_map_option = "warn" float_cmp = "warn" fn_params_excessive_bools = "warn" fn_to_numeric_cast_any = "warn" format_push_string = "warn" if_then_some_else_none = "warn" implicit_clone = "warn" implicit_saturating_sub = "warn" imprecise_flops = "warn" index_refutable_slice = "warn" indexing_slicing = "warn" invalid_upcast_comparisons = "warn" iter_not_returning_iterator = "warn" iter_on_empty_collections = "warn" iter_with_drain = "warn" large_include_file = "warn" linkedlist = "warn" lossy_float_literal = "warn" manual_clamp = "warn" manual_instant_elapsed = "warn" manual_let_else = "allow" manual_ok_or = "warn" many_single_char_names = "warn" map_flatten = "warn" map_unwrap_or = "allow" match_wild_err_arm = "warn" maybe_infinite_iter = "warn" mixed_read_write_in_expression = "warn" mut_mut = "warn" naive_bytecount = "warn" needless_bitwise_bool = "warn" needless_collect = "warn" needless_continue = "warn" needless_for_each = "warn" negative_feature_names = "warn" no_effect_underscore_binding = "warn" no_mangle_with_rust_abi = "warn" non_send_fields_in_send_ty = "warn" nonstandard_macro_braces = "warn" or_fun_call = "warn" path_buf_push_overwrite = "warn" ptr_as_ptr = "warn" range_minus_one = "warn" range_plus_one = "warn" rc_buffer = "warn" rc_mutex = "warn" read_zero_byte_vec = "warn" redundant_clone = "warn" redundant_feature_names = "warn" ref_binding_to_reference = "warn" ref_option_ref = "warn" rest_pat_in_fully_bound_structs = "warn" same_functions_in_if_condition = "warn" string_lit_as_bytes = "warn" suboptimal_flops = "warn" suspicious_operation_groupings = "warn" suspicious_xor_used_as_pow = "warn" trailing_empty_array = "warn" trait_duplication_in_bounds = "warn" transmute_ptr_to_ptr = "warn" transmute_undefined_repr = "warn" try_err = "warn" type_repetition_in_bounds = "warn" unchecked_duration_subtraction = "warn" unicode_not_nfc = "warn" unnecessary_join = "warn" unnecessary_self_imports = "warn" unnecessary_struct_initialization = "warn" unnecessary_wraps = "warn" unnested_or_patterns = "warn" unused_async = "warn" unused_peekable = "warn" unused_rounding = "warn" useless_let_if_seq = "warn" useless_transmute = "warn" verbose_bit_mask = "warn" wildcard_dependencies = "warn" zero_sized_map_values = "warn" [lints.clippy.nursery] level = "warn" priority = -2 [lints.clippy.pedantic] level = "warn" priority = -1 [lints.rust] deprecated_safe = "warn" elided_lifetimes_in_paths = "warn" explicit_outlives_requirements = "warn" ffi_unwind_calls = "warn" let_underscore_drop = "warn" macro_use_extern_crate = "warn" meta_variable_misuse = "warn" missing_abi = "warn" missing_debug_implementations = "warn" missing_docs = "warn" missing_unsafe_on_extern = "warn" non_ascii_idents = "warn" non_local_definitions = "warn" redundant_lifetimes = "warn" single_use_lifetimes = "warn" trivial_casts = "warn" trivial_numeric_casts = "warn" unit_bindings = "warn" unnameable_types = "warn" unreachable_pub = "warn" unsafe_code = "warn" unstable_features = "warn" unused_extern_crates = "warn" unused_import_braces = "warn" unused_lifetimes = "warn" unused_macro_rules = "warn" unused_qualifications = "warn" variant_size_differences = "warn" [lints.rust.rust_2024_compatibility] level = "warn" priority = 1 [lints.rust.tail_expr_drop_order] level = "allow" priority = 2 [lints.rust.unexpected_cfgs] level = "warn" priority = 0 check-cfg = ["cfg(nightly)"] [lints.rustdoc] missing_crate_level_docs = "warn" unescaped_backticks = "warn" bon-macros-3.7.1/Cargo.toml.orig000064400000000000000000000042021046102023000145610ustar 00000000000000[package] name = "bon-macros" version = "3.7.1" description = """ This is a proc-macro crate that is supposed to be a private implementation detail of the `bon` crate """ edition = "2021" homepage = "https://bon-rs.com/" license = "MIT OR Apache-2.0" repository = "https://github.com/elastio/bon" # This MSRV was chosen because Rust supports mixing generic type and const # parameters only starting with this version. We require this feature for the # cases when the builder is generated for a function or struct that uses const # generics. The generated builder **always** appends a generic type parameter # for the type state at the end of the Builder generics list. So for functions # or structs that use const generics the generated builder will have a generic # type parameter after the const generic parameter, which is only supported # starting from Rust 1.59.0. rust-version = "1.59.0" [package.metadata.docs.rs] all-features = true # Generate clickable links in the source code view in the docs rustdoc-args = ["--generate-link-to-definition"] # We don't need the docs to be built for every first-tier target. # This crate is not platform-specific. targets = ["x86_64-unknown-linux-gnu"] [lints] workspace = true [lib] proc-macro = true [dependencies] # Patch version 0.20.7 of darling added `flatten` feature. We use it, so we # need to specify an explicit patch version requirement darling = "0.21.0" # This dependency is used by `darling` itself, so we use it as well for case # conversions to share the same dependency. ident_case = "1" # This version still supports our MSRV and it is where this bug was fixed: # https://github.com/dtolnay/proc-macro2/issues/470 proc-macro2 = "1.0.88" quote = "1" # This is the highest version that supports our MSRV syn = { version = "2.0.56", features = ["full", "visit-mut", "visit"] } prettyplease = "0.2" rustversion = "1.0" [features] default = [] alloc = [] std = [] # See the docs on this feature in the `bon`'s crate `Cargo.toml` experimental-overwritable = [] # See the docs on this feature in the `bon`'s crate `Cargo.toml` implied-bounds = [] [dev-dependencies] expect-test = "1.4.1" bon-macros-3.7.1/README.md000064400000000000000000000006361046102023000131600ustar 00000000000000# `bon-macros` This is a proc-macro crate that is supposed to be a private implementation detail of the [`bon`] crate. Don't add it to your dependencies directly! The API surface of this crate is unstable, and your code may break if you use items from `bon-macros` bypassing the `bon` crate. Instead, use the proc macros from here via the reexports in the [`bon`] crate. [`bon`]: https://docs.rs/bon/latest/bon/ bon-macros-3.7.1/src/bon.rs000064400000000000000000000022311046102023000136050ustar 00000000000000use crate::builder; use crate::normalization::{ExpandCfg, Expansion}; use crate::util::prelude::*; use darling::ast::NestedMeta; use darling::FromMeta; pub(crate) fn generate(params: TokenStream, item: TokenStream) -> TokenStream { crate::error::handle_errors(item.clone(), || try_generate(params, item)) .unwrap_or_else(std::convert::identity) } pub(crate) fn try_generate(params: TokenStream, item: TokenStream) -> Result { let item = syn::parse2(item)?; let ctx = ExpandCfg { current_macro: format_ident!("bon"), config: params, item, }; let input = match ctx.expand_cfg()? { Expansion::Expanded(input) => input, Expansion::Recurse(output) => return Ok(output), }; let params = NestedMeta::parse_meta_list(input.config)?; let params = FromMeta::from_list(¶ms)?; match *input.item { syn::Item::Impl(item_impl) => builder::item_impl::generate(params, item_impl), _ => bail!( &input.item, "`#[bon]` attribute is expected to be placed on an `impl` block \ but it was placed on other syntax instead" ), } } bon-macros-3.7.1/src/builder/builder_gen/builder_decl.rs000064400000000000000000000116341046102023000213600ustar 00000000000000use crate::builder::builder_gen::NamedMember; use crate::util::prelude::*; impl super::BuilderGenCtx { pub(super) fn builder_decl(&self) -> TokenStream { let builder_vis = &self.builder_type.vis; let builder_ident = &self.builder_type.ident; let generics_decl = &self.generics.decl_with_defaults; let where_clause = &self.generics.where_clause; let phantom_data = self.phantom_data(); let state_mod = &self.state_mod.ident; // The fields can't be hidden using Rust's privacy syntax. // The details about this are described in the blog post: // https://bon-rs.com/blog/the-weird-of-function-local-types-in-rust. // // We could use `#[cfg(not(rust_analyzer))]` to hide the private fields in IDE. // However, RA would then not be able to type-check the generated code, which // may or may not be a problem, because the main thing is that the type signatures // would still work in RA. let private_field_attrs = { // The message is defined separately to make it single-line in the // generated code. This simplifies the task of removing unnecessary // attributes from the generated code when preparing for demo purposes. let deprecated_msg = "\ this field should not be used directly; it's an implementation detail, and \ if you access it directly, you may break some internal unsafe invariants; \ if you found yourself needing it, then you are probably doing something wrong; \ feel free to open an issue/discussion in our GitHub repository \ (https://github.com/elastio/bon) or ask for help in our Discord server \ (https://bon-rs.com/discord)"; quote! { #[doc(hidden)] #[deprecated = #deprecated_msg] } }; let receiver_field = self.receiver().map(|receiver| { let ident = &receiver.field_ident; let ty = &receiver.without_self_keyword; quote! { #ident: #ty, } }); let must_use_message = format!( "the builder does nothing until you call `{}()` on it to finish building", self.finish_fn.ident ); let allows = super::allow_warnings_on_member_types(); let start_fn_args_fields_idents = self.start_fn_args().map(|member| &member.ident); let start_fn_args_fields_types = self.start_fn_args().map(|member| &member.ty.norm); let named_members_types = self.named_members().map(NamedMember::underlying_norm_ty); let docs = &self.builder_type.docs; let state_var = &self.state_var; let custom_fields_idents = self.custom_fields().map(|field| &field.ident); let custom_fields_types = self.custom_fields().map(|field| &field.norm_ty); quote! { #[must_use = #must_use_message] #(#docs)* #allows #[allow( // We use `__private` prefix for all fields intentionally to hide them clippy::struct_field_names, // This lint doesn't emerge until you manually expand the macro. Just // because `bon` developers need to expand the macros a lot it makes // sense to just silence it to avoid some noise. This lint is triggered // by the big PhantomData type generated by the macro clippy::type_complexity )] #builder_vis struct #builder_ident< #(#generics_decl,)* // Having the `State` trait bound on the struct declaration is important // for future proofing. It will allow us to use this bound in the `Drop` // implementation of the builder if we ever add one. @Veetaha already did // some experiments with `MaybeUninit` that requires a custom drop impl, // so this could be useful in the future. // // On the flip side, if we have a custom `Drop` impl, then partially moving // the builder will be impossible. So.. it's a trade-off, and it's probably // not a big deal to remove this bound from here if we feel like it. #state_var: #state_mod::State = #state_mod::Empty > #where_clause { #private_field_attrs __unsafe_private_phantom: #phantom_data, #receiver_field #( #start_fn_args_fields_idents: #start_fn_args_fields_types, )* #( #custom_fields_idents: #custom_fields_types, )* #private_field_attrs __unsafe_private_named: ( #( ::core::option::Option<#named_members_types>, )* ), } } } } bon-macros-3.7.1/src/builder/builder_gen/builder_derives/clone.rs000064400000000000000000000072521046102023000232130ustar 00000000000000use super::BuilderGenCtx; use crate::builder::builder_gen::top_level_config::DeriveConfig; use crate::util::prelude::*; impl BuilderGenCtx { pub(super) fn derive_clone(&self, derive: &DeriveConfig) -> TokenStream { let bon = &self.bon; let generics_decl = &self.generics.decl_without_defaults; let generic_args = &self.generics.args; let builder_ident = &self.builder_type.ident; let clone = quote!(::core::clone::Clone); let clone_receiver = self.receiver().map(|receiver| { let ident = &receiver.field_ident; let ty = &receiver.without_self_keyword; quote! { #ident: <#ty as #clone>::clone(&self.#ident), } }); let clone_start_fn_args = self.start_fn_args().map(|member| { let member_ident = &member.ident; let member_ty = &member.ty.norm; quote! { // The type hint here is necessary to get better error messages // that point directly to the type that doesn't implement `Clone` // in the input code using the span info from the type hint. #member_ident: <#member_ty as #clone>::clone(&self.#member_ident) } }); let where_clause = self.where_clause_for_derive(&clone, derive); let state_mod = &self.state_mod.ident; let clone_named_members = self.named_members().map(|member| { let member_index = &member.index; // The type hint here is necessary to get better error messages // that point directly to the type that doesn't implement `Clone` // in the input code using the span info from the type hint. let ty = member.underlying_norm_ty(); quote! { #bon::__::better_errors::clone_member::<#ty>( &self.__unsafe_private_named.#member_index ) } }); let clone_fields = self.custom_fields().map(|member| { let member_ident = &member.ident; let member_ty = &member.norm_ty; quote! { // The type hint here is necessary to get better error messages // that point directly to the type that doesn't implement `Clone` // in the input code using the span info from the type hint. #member_ident: <#member_ty as #clone>::clone(&self.#member_ident) } }); let state_var = &self.state_var; quote! { #[automatically_derived] impl< #(#generics_decl,)* #state_var: #state_mod::State > #clone for #builder_ident< #(#generic_args,)* #state_var > #where_clause { fn clone(&self) -> Self { Self { __unsafe_private_phantom: ::core::marker::PhantomData, #clone_receiver #( #clone_start_fn_args, )* #( #clone_fields, )* // We clone named members individually instead of cloning // the entire tuple to improve error messages in case if // one of the members doesn't implement `Clone`. This avoids // a sentence that say smth like // ``` // required for `(...big type...)` to implement `Clone` // ``` __unsafe_private_named: ( #( #clone_named_members, )* ), } } } } } } bon-macros-3.7.1/src/builder/builder_gen/builder_derives/debug.rs000064400000000000000000000077311046102023000232030ustar 00000000000000use crate::builder::builder_gen::member::Member; use crate::builder::builder_gen::models::BuilderGenCtx; use crate::builder::builder_gen::top_level_config::DeriveConfig; use crate::util::prelude::*; impl BuilderGenCtx { pub(super) fn derive_debug(&self, derive: &DeriveConfig) -> TokenStream { let bon = &self.bon; let format_members = self.members.iter().filter_map(|member| { match member { Member::StartFn(member) => { let member_ident = &member.ident; let member_ident_str = member_ident.to_string(); let member_ty = &member.ty.norm; Some(quote! { output.field( #member_ident_str, #bon::__::better_errors::as_dyn_debug::<#member_ty>( &self.#member_ident ) ); }) } Member::Field(member) => { let member_ident = &member.ident; let member_ident_str = member_ident.to_string(); let member_ty = &member.norm_ty; Some(quote! { output.field( #member_ident_str, #bon::__::better_errors::as_dyn_debug::<#member_ty>( &self.#member_ident ) ); }) } Member::Named(member) => { let member_index = &member.index; let member_ident_str = &member.name.snake_raw_str; let member_ty = member.underlying_norm_ty(); Some(quote! { if let Some(value) = &self.__unsafe_private_named.#member_index { output.field( #member_ident_str, #bon::__::better_errors::as_dyn_debug::<#member_ty>(value) ); } }) } // The values for these members are computed only in the finishing // function where the builder is consumed, and they aren't stored // in the builder itself. Member::FinishFn(_) | Member::Skip(_) => None, } }); let format_receiver = self.receiver().map(|receiver| { let ident = &receiver.field_ident; let ident_str = ident.to_string(); let ty = &receiver.without_self_keyword; quote! { output.field( #ident_str, #bon::__::better_errors::as_dyn_debug::<#ty>( &self.#ident ) ); } }); let debug = quote!(::core::fmt::Debug); let where_clause = self.where_clause_for_derive(&debug, derive); let state_mod = &self.state_mod.ident; let generics_decl = &self.generics.decl_without_defaults; let generic_args = &self.generics.args; let builder_ident = &self.builder_type.ident; let state_var = &self.state_var; let builder_ident_str = builder_ident.to_string(); quote! { #[automatically_derived] impl< #(#generics_decl,)* #state_var: #state_mod::State > #debug for #builder_ident< #(#generic_args,)* #state_var > #where_clause { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { let mut output = f.debug_struct(#builder_ident_str); #format_receiver #(#format_members)* output.finish() } } } } } bon-macros-3.7.1/src/builder/builder_gen/builder_derives/into.rs000064400000000000000000000050221046102023000230550ustar 00000000000000use crate::builder::builder_gen::models::BuilderGenCtx; use crate::util::prelude::*; impl BuilderGenCtx { pub(super) fn derive_into(&self) -> Result { if let Some(asyncness) = &self.finish_fn.asyncness { bail!( asyncness, "`#[builder(derive(Into))` is not supported for async functions \ because `From::from()` method is a synchronous method" ) } if let Some(unsafety) = &self.finish_fn.unsafety { bail!( unsafety, "`#[builder(derive(Into))` is not supported for unsafe functions \ because `From::from()` method is a safe method" ) } if let Some(arg) = self.finish_fn_args().next() { bail!( &arg.config.finish_fn.span(), "`#[builder(derive(Into))` is incompatible with `#[builder(finish_fn)]` members \ because `From::from()` method accepts zero parameters" ) } let output_ty = match &self.finish_fn.output { syn::ReturnType::Default => bail!( &self.start_fn.ident, "`#[builder(derive(Into))` is not supported for functions with the implicit unit return type; \ if you have a use case where it makes sense to implement `From for ()`, \ please open an issue, and in the meantime annotate the function return type explicitly \ with `-> ()`" ), syn::ReturnType::Type(_, output_ty) => output_ty.as_ref(), }; let state_mod = &self.state_mod.ident; let generics_decl = &self.generics.decl_without_defaults; let generic_args = &self.generics.args; let where_clause = &self.generics.where_clause; let builder_ident = &self.builder_type.ident; let state_var = &self.state_var; let finish_fn_ident = &self.finish_fn.ident; let builder_ty = quote! { #builder_ident<#(#generic_args,)* #state_var> }; let tokens = quote! { #[automatically_derived] impl< #(#generics_decl,)* #state_var: #state_mod::IsComplete > ::core::convert::From<#builder_ty> for #output_ty #where_clause { fn from(builder: #builder_ty) -> Self { #builder_ident::#finish_fn_ident(builder) } } }; Ok(tokens) } } bon-macros-3.7.1/src/builder/builder_gen/builder_derives/into_future.rs000064400000000000000000000170201046102023000244500ustar 00000000000000use crate::builder::builder_gen::models::BuilderGenCtx; use crate::builder::builder_gen::top_level_config::IntoFutureConfig; use crate::util::prelude::*; use std::borrow::Cow; use std::collections::BTreeSet; use syn::visit_mut::VisitMut; impl BuilderGenCtx { pub(super) fn derive_into_future(&self, config: &IntoFutureConfig) -> Result { if self.finish_fn.asyncness.is_none() { // While it is technically possible to call a synchronous function // inside of the `IntoFuture::into_future()`, it's better force the // user to mark the function as `async` explicitly. Otherwise it may // indicate of some logic bug where the developer mistakenly marks // a function that could be sync with `derive(IntoFuture)`. bail!( &self.finish_fn.ident, "`#[builder(derive(IntoFuture(...)))` can only be used with async functions; \ using it with a synchronous function is likely a mistake" ); } if let Some(unsafety) = &self.finish_fn.unsafety { bail!( unsafety, "`#[builder(derive(IntoFuture(...)))` is not supported for unsafe functions \ because `IntoFuture::into_future()` method is a safe method" ); } if let Some(arg) = self.finish_fn_args().next() { bail!( &arg.config.finish_fn.span(), "`#[builder(derive(IntoFuture(...)))` is incompatible with `#[builder(finish_fn)]` members \ because `IntoFuture::into_future()` method accepts zero parameters" ); } let state_mod = &self.state_mod.ident; let builder_ident = &self.builder_type.ident; let state_var = &self.state_var; let finish_fn_ident = &self.finish_fn.ident; let box_ = &config.box_ident; let SignatureForIntoFuture { generics_decl, generic_args, where_clause, builder_lifetime, output_ty, } = self.signature_for_into_future(); let state_lifetime = builder_lifetime .clone() .unwrap_or_else(|| syn::Lifetime::new("'static", Span::call_site())); let builder_lifetime = Option::into_iter(builder_lifetime); let send_bound = if config.is_send { quote! { + ::core::marker::Send } } else { quote! {} }; let bon = &self.bon; let alloc = if cfg!(feature = "std") { quote!(::std) } else if cfg!(feature = "alloc") { quote!(#bon::__::alloc) } else { bail!( &config.box_ident, "`#[builder(derive(IntoFuture(Box)))]` requires either `std` or \ `alloc` feature to be enabled" ) }; let tokens = quote! { #[automatically_derived] impl< #(#generics_decl,)* #state_var: #state_mod::IsComplete + #state_lifetime > ::core::future::IntoFuture for #builder_ident<#(#generic_args,)* #state_var> #where_clause { type Output = #output_ty; type IntoFuture = ::core::pin::Pin< #alloc::boxed::#box_< dyn ::core::future::Future #send_bound #(+ #builder_lifetime)* > >; fn into_future(self) -> Self::IntoFuture { #alloc::boxed::#box_::pin(#builder_ident::#finish_fn_ident(self)) } } }; Ok(tokens) } /// Handle the special case for a builder that captures lifetimes. /// /// Collapse all lifetimes into a single `'builder` lifetime. This is /// because `dyn Trait` supports only a single `+ 'lifetime` bound. fn signature_for_into_future(&self) -> SignatureForIntoFuture<'_> { let generics_decl = &self.generics.decl_without_defaults; let generic_args = &self.generics.args; let where_clause = &self.generics.where_clause; let output_ty = match &self.finish_fn.output { syn::ReturnType::Default => Cow::Owned(syn::parse_quote!(())), syn::ReturnType::Type(_, output_ty) => Cow::Borrowed(output_ty.as_ref()), }; let contains_lifetimes = matches!( self.generics.args.first(), Some(syn::GenericArgument::Lifetime(_)) ); if !contains_lifetimes { return SignatureForIntoFuture { generics_decl: Cow::Borrowed(generics_decl), generic_args: Cow::Borrowed(generic_args), where_clause: where_clause.as_ref().map(Cow::Borrowed), builder_lifetime: None, output_ty, }; } let builder_lifetime = syn::Lifetime::new("'builder", Span::call_site()); let new_generic_args = generic_args .iter() .map(|arg| match arg { syn::GenericArgument::Lifetime(_) => { syn::GenericArgument::Lifetime(builder_lifetime.clone()) } _ => arg.clone(), }) .collect::>(); let mut original_lifetimes = BTreeSet::new(); let mut new_generics_decl = vec![syn::parse_quote!(#builder_lifetime)]; for param in generics_decl { match param { syn::GenericParam::Lifetime(lifetime) => { original_lifetimes.insert(&lifetime.lifetime.ident); } _ => { new_generics_decl.push(param.clone()); } } } let mut replace_lifetimes = ReplaceLifetimes { replacement: &builder_lifetime, original_lifetimes: &original_lifetimes, }; let mut new_where_clause = where_clause.clone(); if let Some(where_clause) = &mut new_where_clause { replace_lifetimes.visit_where_clause_mut(where_clause); } let mut output_ty = output_ty.into_owned(); replace_lifetimes.visit_type_mut(&mut output_ty); SignatureForIntoFuture { generics_decl: Cow::Owned(new_generics_decl), generic_args: Cow::Owned(new_generic_args), where_clause: new_where_clause.map(Cow::Owned), builder_lifetime: Some(builder_lifetime), output_ty: Cow::Owned(output_ty), } } } struct SignatureForIntoFuture<'a> { generics_decl: Cow<'a, [syn::GenericParam]>, generic_args: Cow<'a, [syn::GenericArgument]>, where_clause: Option>, builder_lifetime: Option, output_ty: Cow<'a, syn::Type>, } struct ReplaceLifetimes<'a> { replacement: &'a syn::Lifetime, original_lifetimes: &'a BTreeSet<&'a syn::Ident>, } impl VisitMut for ReplaceLifetimes<'_> { fn visit_lifetime_mut(&mut self, lifetime: &mut syn::Lifetime) { if self.original_lifetimes.contains(&lifetime.ident) { *lifetime = self.replacement.clone(); } } fn visit_item_mut(&mut self, _: &mut syn::Item) { // Don't recurse into child items. They don't inherit the parent item's // lifetimes. } fn visit_bound_lifetimes_mut(&mut self, _: &mut syn::BoundLifetimes) { // Don't recurse into bound lifetime declarations. They introduce // local lifetimes that we should keep as is } } bon-macros-3.7.1/src/builder/builder_gen/builder_derives/mod.rs000064400000000000000000000046121046102023000226670ustar 00000000000000mod clone; mod debug; mod into; mod into_future; use super::top_level_config::{DeriveConfig, DerivesConfig}; use super::BuilderGenCtx; use crate::util::prelude::*; use darling::ast::GenericParamExt; impl BuilderGenCtx { pub(crate) fn builder_derives(&self) -> Result { let DerivesConfig { clone, debug, into, into_future, } = &self.builder_type.derives; let mut tokens = TokenStream::new(); if let Some(derive) = clone { tokens.extend(self.derive_clone(derive)); } if let Some(derive) = debug { tokens.extend(self.derive_debug(derive)); } if into.is_present() { tokens.extend(self.derive_into()?); } if let Some(derive) = into_future { tokens.extend(self.derive_into_future(derive)?); } Ok(tokens) } /// We follow the logic of the standard `#[derive(...)]` macros such as `Clone` and `Debug`. /// They add bounds of their respective traits to every generic type parameter on the struct /// without trying to analyze if that bound is actually required for the derive to work, so /// it's a conservative approach. /// /// However, the user can also override these bounds using the `bounds(...)` attribute for /// the specific derive. fn where_clause_for_derive( &self, target_trait_bounds: &TokenStream, derive: &DeriveConfig, ) -> TokenStream { let derive_specific_predicates = derive .bounds .as_ref() .map(ToTokens::to_token_stream) .unwrap_or_else(|| { let bounds = self .generics .decl_without_defaults .iter() .filter_map(syn::GenericParam::as_type_param) .map(|param| { let ident = ¶m.ident; quote! { #ident: #target_trait_bounds } }); quote! { #( #bounds, )* } }); let inherent_item_predicates = self.generics.where_clause_predicates(); quote! { where #( #inherent_item_predicates, )* #derive_specific_predicates } } } bon-macros-3.7.1/src/builder/builder_gen/finish_fn.rs000064400000000000000000000164271046102023000207130ustar 00000000000000use super::member::{Member, PosFnMember}; use crate::util::prelude::*; impl super::BuilderGenCtx { fn finish_fn_member_expr(&self, member: &Member) -> TokenStream { let member = match member { Member::Named(member) => member, Member::Skip(member) => { return member .value .as_ref() .map(|value| self.sanitize_expr(value)) .unwrap_or_else(|| quote! { ::core::default::Default::default() }); } Member::StartFn(member) => { let ident = &member.ident; return quote! { self.#ident }; } Member::FinishFn(member) => { return member .conversion() .unwrap_or_else(|| member.ident.to_token_stream()); } Member::Field(member) => { let ident = &member.ident; return quote! { self.#ident }; } }; let index = &member.index; let member_field = quote! { self.__unsafe_private_named.#index }; let default = member .config .default .as_ref() .map(|default| default.value.as_ref()); match default { Some(Some(default)) => { let default = if member.config.into.is_present() { quote! { Into::into((|| #default)()) } } else { quote! { #default } }; // Special case for `const` because `unwrap_or_else` is not `const` // and closure calls aren't supported in `const` contexts at the time // of this writing (Rust 1.86.0). if self.const_.is_some() { return quote! { match #member_field { Some(value) => value, None => #default, } }; } quote! { ::core::option::Option::unwrap_or_else(#member_field, || #default) } } Some(None) => { quote! { ::core::option::Option::unwrap_or_default(#member_field) } } None => { // For `Option` the default value is always `None`. So we can just return // the value of the member field itself (which is already an `Option`). if member.is_special_option_ty() { return member_field; } // SAFETY: we know that the member is set because we are in // the `finish` function where this method uses the trait // bound of `IsSet` for every required member. It's also // not possible to intervene with the builder's state from // the outside because all members of the builder are considered // private (we even generate random names for them to make it // impossible to access them from the outside in the same module). // // We also make sure to use fully qualified paths to methods // involved in setting the value for the required member to make // sure no trait/function in scope can override the behavior. // Special case for `const` mode where `unwrap_unchecked` is // unstable in Rust <1.83.0. if self.const_.is_some() { return quote! { match #member_field { Some(value) => value, // SAFETY: see the big safety comment above None => unsafe { ::core::hint::unreachable_unchecked() }, } }; } quote! { // SAFETY: see the big safety comment above unsafe { ::core::option::Option::unwrap_unchecked(#member_field) } } } } } pub(super) fn finish_fn(&self) -> TokenStream { let members_vars_decls = self.members.iter().map(|member| { let expr = self.finish_fn_member_expr(member); let var_ident = member.orig_ident(); // The type hint is necessary in some cases to assist the compiler // in type inference. // // For example, if the expression is passed to a function that accepts // an impl Trait such as `impl Default`, and the expression itself looks // like `Default::default()`. In this case nothing hints to the compiler // the resulting type of the expression, so we add a type hint via an // intermediate variable here. // // This variable can also be accessed by other member's `default` // or `skip` expressions. let ty = member.norm_ty(); quote! { let #var_ident: #ty = #expr; } }); let state_mod = &self.state_mod.ident; let finish_fn_params = self.finish_fn_args().map(PosFnMember::fn_input_param); let body = &self.finish_fn.body.generate(self); let asyncness = &self.finish_fn.asyncness; let unsafety = &self.finish_fn.unsafety; let special_attrs = &self.finish_fn.special_attrs; let attrs = &self.finish_fn.attrs; let finish_fn_vis = &self.finish_fn.vis; let finish_fn_ident = &self.finish_fn.ident; let output = &self.finish_fn.output; let state_var = &self.state_var; let const_ = &self.const_; // `#[target_feature]` is not compatible with `#[inline(always)]`, // so we need to downgrade it to `#[inline] let inline_attr = self .finish_fn .special_attrs .iter() .find_map(|attr| { attr.meta .path() .is_ident("target_feature") .then(|| quote! { #[inline] }) }) .unwrap_or_else(|| quote! { #[inline(always)] }); quote! { #(#attrs)* #inline_attr #[allow( // This is intentional. We want the builder syntax to compile away clippy::inline_always, // This lint flags any function that returns a possibly `!Send` future. // However, it doesn't apply in the generic context where the future is // `Send` if the generic parameters are `Send` as well, so we just suppress // this lint. See the issue: https://github.com/rust-lang/rust-clippy/issues/6947 clippy::future_not_send, clippy::missing_const_for_fn, )] #(#special_attrs)* #finish_fn_vis #const_ #asyncness #unsafety fn #finish_fn_ident( self, #(#finish_fn_params,)* ) #output where #state_var: #state_mod::IsComplete { #(#members_vars_decls)* #body } } } } bon-macros-3.7.1/src/builder/builder_gen/getters.rs000064400000000000000000000244131046102023000204170ustar 00000000000000use super::member::{GetterConfig, GetterKind}; use super::{BuilderGenCtx, NamedMember}; use crate::parsing::SpannedKey; use crate::util::prelude::*; use syn::punctuated::Punctuated; use syn::spanned::Spanned; pub(crate) struct GettersCtx<'a> { base: &'a BuilderGenCtx, member: &'a NamedMember, config: &'a GetterConfig, } impl<'a> GettersCtx<'a> { pub(crate) fn new(base: &'a BuilderGenCtx, member: &'a NamedMember) -> Option { Some(Self { base, member, config: member.config.getter.as_ref()?, }) } pub(crate) fn getter_methods(self) -> Result { let name = self.config.name.as_deref().cloned().unwrap_or_else(|| { syn::Ident::new( &format!("get_{}", self.member.name.snake.raw_name()), self.member.name.snake.span(), ) }); let vis = self .config .vis .as_deref() .unwrap_or(&self.base.builder_type.vis) .clone(); let docs = self.config.docs.as_deref().cloned().unwrap_or_else(|| { let header = format!( "_**Getter.**_ Returns `{}`, which must be set before calling this method.\n\n", self.member.name.snake, ); std::iter::once(syn::parse_quote!(#[doc = #header])) .chain(self.member.docs.iter().cloned()) .collect() }); let return_ty = self.return_ty()?; let body = self.body(); let state_var = &self.base.state_var; let member_pascal = &self.member.name.pascal; let state_mod = &self.base.state_mod.ident; let const_ = &self.base.const_; Ok(quote! { #( #docs )* #[allow( // This is intentional. We want the builder syntax to compile away clippy::inline_always, clippy::missing_const_for_fn, )] #[inline(always)] #[must_use = "this method has no side effects; it only returns a value"] #vis #const_ fn #name(&self) -> #return_ty where #state_var::#member_pascal: #state_mod::IsSet, { #body } }) } fn body(&self) -> TokenStream { let index = &self.member.index; let member = quote! { self.__unsafe_private_named.#index }; let bon = &self.base.bon; match self.config.kind.as_deref() { Some(GetterKind::Copy) => { // Use a `_` type hint with the span of the original type // to make the compiler point to the original type in case // if the type doesn't implement `Copy`. let span = self.member.underlying_orig_ty().span(); let ty = quote_spanned!(span=> _); let copy = quote! { #bon::__::better_errors::copy_member::<#ty>(&#member) }; if !self.member.is_required() { return copy; } quote! { // SAFETY: the method requires S::{Member}: IsSet, so it's Some unsafe { ::core::option::Option::unwrap_unchecked(#copy) } } } Some(GetterKind::Clone) => { // Use a `_` type hint with the span of the original type // to make the compiler point to the original type in case // if the type doesn't implement `Clone`. let span = self.member.underlying_orig_ty().span(); let ty = quote_spanned!(span=> _); let clone = quote! { <#ty as ::core::clone::Clone>::clone }; if !self.member.is_required() { return quote! { #clone(&#member) }; } quote! { match &#member { Some(value) => #clone(value), // SAFETY: the method requires S::{Member}: IsSet, so it's Some None => unsafe { ::core::hint::unreachable_unchecked() }, } } } Some(GetterKind::Deref(ty)) => { // Assign the span of the deref target type to the `value` variable // so that compiler points to that type if there is a type mismatch. let span = ty.span(); let value = quote_spanned!(span=> value); if !self.member.is_required() { return quote! { // Explicit match is important to trigger an implicit deref coercion // that can potentially do multiple derefs to the reach the target type. match &#member { Some(#value) => Some(#value), None => None, } }; } quote! { // Explicit match is important to trigger an implicit deref coercion // that can potentially do multiple derefs to the reach the target type. match &#member { Some(#value) => #value, // SAFETY: the method requires S::{Member}: IsSet, so it's Some None => unsafe { ::core::hint::unreachable_unchecked() }, } } } None => { if !self.member.is_required() { return quote! { ::core::option::Option::as_ref(&#member) }; } quote! { match &#member { Some(value) => value, // SAFETY: the method requires S::{Member}: IsSet, so it's Some None => unsafe { ::core::hint::unreachable_unchecked() }, } } } } } fn return_ty(&self) -> Result { let underlying_return_ty = self.underlying_return_ty()?; Ok(if self.member.is_required() { quote! { #underlying_return_ty } } else { // We are not using the fully qualified path to `Option` here // to make function signature in IDE popus shorter and more // readable. quote! { Option<#underlying_return_ty> } }) } fn underlying_return_ty(&self) -> Result { let ty = self.member.underlying_norm_ty(); let kind = match &self.config.kind { Some(kind) => kind, None => return Ok(quote! { &#ty }), }; match &kind.value { GetterKind::Copy | GetterKind::Clone => Ok(quote! { #ty }), GetterKind::Deref(Some(deref_target)) => Ok(quote! { &#deref_target }), GetterKind::Deref(None) => Self::infer_deref_target(ty, kind), } } fn infer_deref_target( underlying_member_ty: &syn::Type, kind: &SpannedKey, ) -> Result { use quote_spanned as qs; let span = underlying_member_ty.span(); #[allow(clippy::type_complexity)] let deref_target_inference_table: &[(_, &dyn Fn(&Punctuated<_, _>) -> _)] = &[ ("Vec", &|args| args.first().map(|arg| qs!(span=> [#arg]))), ("Box", &|args| args.first().map(ToTokens::to_token_stream)), ("Rc", &|args| args.first().map(ToTokens::to_token_stream)), ("Arc", &|args| args.first().map(ToTokens::to_token_stream)), ("String", &|args| args.is_empty().then(|| qs!(span=> str))), ("CString", &|args| { // CStr is available via `core` since 1.64.0: // https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html#c-compatible-ffi-types-in-core-and-alloc let module = if rustversion::cfg!(since(1.64.0)) { format_ident!("core") } else { format_ident!("std") }; args.is_empty().then(|| qs!(span=> ::#module::ffi::CStr)) }), ("OsString", &|args| { args.is_empty().then(|| qs!(span=> ::std::ffi::OsStr)) }), ("PathBuf", &|args| { args.is_empty().then(|| qs!(span=> ::std::path::Path)) }), ("Cow", &|args| { args.iter() .find(|arg| matches!(arg, syn::GenericArgument::Type(_))) .map(ToTokens::to_token_stream) }), ]; let err = || { let inferable_types = deref_target_inference_table .iter() .map(|(name, _)| format!("- {name}")) .join("\n"); err!( &kind.key, "can't infer the `Deref::Target` for the getter from the member's type; \ please specify the return type (target of the deref coercion) explicitly \ in parentheses without the leading `&`;\n\ example: `#[builder(getter(deref(TargetTypeHere))]`\n\ \n\ automatic deref target detection is supported only for the following types:\n\ {inferable_types}", ) }; let path = underlying_member_ty.as_path_no_qself().ok_or_else(err)?; let last_segment = path.segments.last().ok_or_else(err)?; let empty_punctuated = Punctuated::new(); let args = match &last_segment.arguments { syn::PathArguments::AngleBracketed(args) => &args.args, _ => &empty_punctuated, }; let last_segment_ident_str = last_segment.ident.to_string(); let inferred = deref_target_inference_table .iter() .find(|(name, _)| last_segment_ident_str == *name) .and_then(|(_, infer)| infer(args)) .ok_or_else(err)?; Ok(quote!(&#inferred)) } } bon-macros-3.7.1/src/builder/builder_gen/input_fn/mod.rs000064400000000000000000000442041046102023000213430ustar 00000000000000mod validation; use super::models::{AssocMethodReceiverCtxParams, FinishFnParams}; use super::top_level_config::TopLevelConfig; use super::{ AssocMethodCtxParams, BuilderGenCtx, FinishFnBody, Generics, Member, MemberOrigin, RawMember, }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; use crate::normalization::{GenericsNamespace, NormalizeSelfTy, SyntaxVariant}; use crate::parsing::{ItemSigConfig, SpannedKey}; use crate::util::prelude::*; use std::borrow::Cow; use std::rc::Rc; use syn::punctuated::Punctuated; use syn::visit_mut::VisitMut; pub(crate) struct FnInputCtx<'a> { namespace: &'a GenericsNamespace, fn_item: SyntaxVariant, impl_ctx: Option>, config: TopLevelConfig, start_fn: StartFnParams, self_ty_prefix: Option, } pub(crate) struct FnInputCtxParams<'a> { pub(crate) namespace: &'a GenericsNamespace, pub(crate) fn_item: SyntaxVariant, pub(crate) impl_ctx: Option>, pub(crate) config: TopLevelConfig, } pub(crate) struct ImplCtx { pub(crate) self_ty: Box, pub(crate) generics: syn::Generics, /// Lint suppressions from the original item that will be inherited by all items /// generated by the macro. If the original syntax used `#[expect(...)]`, /// then it must be represented as `#[allow(...)]` here. pub(crate) allow_attrs: Vec, } impl<'a> FnInputCtx<'a> { pub(crate) fn new(params: FnInputCtxParams<'a>) -> Result { let start_fn = params.config.start_fn.clone(); let start_fn_ident = start_fn .name .map(SpannedKey::into_value) .unwrap_or_else(|| { let fn_ident = ¶ms.fn_item.norm.sig.ident; // Special case for the method named `new`. We rename it to `builder` // since this is the name that is conventionally used by starting // function in the builder pattern. We also want to make // the `#[builder]` attribute on the method `new` fully compatible // with deriving a builder from a struct. if params.impl_ctx.is_some() && fn_ident == "new" { syn::Ident::new("builder", fn_ident.span()) } else { fn_ident.clone() } }); let start_fn = StartFnParams { ident: start_fn_ident, vis: start_fn.vis.map(SpannedKey::into_value), docs: start_fn .docs .map(SpannedKey::into_value) .unwrap_or_else(|| { params .fn_item .norm .attrs .iter() .filter(|attr| attr.is_doc_expr()) .cloned() .collect() }), // Override on the start fn to use the generics from the // target function itself. We must not duplicate the generics // from the impl block here generics: Some(Generics::new( params .fn_item .norm .sig .generics .params .iter() .cloned() .collect(), params.fn_item.norm.sig.generics.where_clause.clone(), )), }; let self_ty_prefix = params.impl_ctx.as_deref().and_then(|impl_ctx| { let prefix = impl_ctx .self_ty .as_path()? .path .segments .last()? .ident .to_string(); Some(prefix) }); let ctx = Self { namespace: params.namespace, fn_item: params.fn_item, impl_ctx: params.impl_ctx, config: params.config, self_ty_prefix, start_fn, }; ctx.validate()?; Ok(ctx) } fn assoc_method_ctx(&self) -> Result> { let self_ty = match self.impl_ctx.as_deref() { Some(impl_ctx) => impl_ctx.self_ty.clone(), None => return Ok(None), }; Ok(Some(AssocMethodCtxParams { self_ty, receiver: self.assoc_method_receiver_ctx_params()?, })) } fn assoc_method_receiver_ctx_params(&self) -> Result> { let receiver = match self.fn_item.norm.sig.receiver() { Some(receiver) => receiver, None => return Ok(None), }; let builder_attr_on_receiver = receiver .attrs .iter() .find(|attr| attr.path().is_ident("builder")); if let Some(attr) = builder_attr_on_receiver { bail!( attr, "#[builder] attributes on the receiver are not supported" ); } let self_ty = match self.impl_ctx.as_deref() { Some(impl_ctx) => &impl_ctx.self_ty, None => return Ok(None), }; let mut without_self_keyword = receiver.ty.clone(); NormalizeSelfTy { self_ty }.visit_type_mut(&mut without_self_keyword); Ok(Some(AssocMethodReceiverCtxParams { with_self_keyword: receiver.clone(), without_self_keyword, })) } fn generics(&self) -> Generics { let impl_ctx = self.impl_ctx.as_ref(); let norm_fn_params = &self.fn_item.norm.sig.generics.params; let params = impl_ctx .map(|impl_ctx| merge_generic_params(&impl_ctx.generics.params, norm_fn_params)) .unwrap_or_else(|| norm_fn_params.iter().cloned().collect()); let where_clauses = [ self.fn_item.norm.sig.generics.where_clause.clone(), impl_ctx.and_then(|impl_ctx| impl_ctx.generics.where_clause.clone()), ]; let where_clause = where_clauses .into_iter() .flatten() .reduce(|mut combined, clause| { combined.predicates.extend(clause.predicates); combined }); Generics::new(params, where_clause) } pub(crate) fn adapted_fn(&self) -> Result { let mut orig = self.fn_item.orig.clone(); if let Some(name) = self.config.start_fn.name.as_deref() { if *name == orig.sig.ident { bail!( &name, "the starting function name must be different from the name \ of the positional function under the #[builder] attribute" ) } } else { // By default the original positional function becomes hidden. orig.vis = syn::Visibility::Inherited; // Remove all doc comments from the function itself to avoid docs duplication // which may lead to duplicating doc tests, which in turn implies repeated doc // tests execution, which means worse tests performance. // // We don't do this for the case when the positional function is exposed // alongside the builder which implies that the docs should be visible // as the function itself is visible. orig.attrs.retain(|attr| !attr.is_doc_expr()); let bon = &self.config.bon; orig.attrs.extend([ syn::parse_quote!(#[doc(hidden)]), // We don't rename the function immediately, but instead defer the renaming // to a later stage. This is because the original name of the function can // be used by other macros that may need a stable identifier. // // For example, if `#[tracing::instrument]` is placed on the function, // the function name will be used as a span name. syn::parse_quote!(#[#bon::__::__privatize]), ]); } // Remove any `#[builder]` attributes that were meant for this proc macro. orig.attrs.retain(|attr| !attr.path().is_ident("builder")); // Remove all doc comments attributes from function arguments, because they are // not valid in that position in regular Rust code. The cool trick is that they // are still valid syntactically when a proc macro like this one pre-processes // them and removes them from the expanded code. We use the doc comments to put // them on the generated setter methods. // // We also strip all `builder(...)` attributes because this macro processes them // and they aren't needed in the output. for arg in &mut orig.sig.inputs { arg.attrs_mut() .retain(|attr| !attr.is_doc_expr() && !attr.path().is_ident("builder")); } orig.attrs.push(syn::parse_quote!(#[allow( // It's fine if there are too many positional arguments in the function // because the whole purpose of this macro is to fight with this problem // at the call site by generating a builder, while keeping the fn definition // site the same with tons of positional arguments which don't harm readability // there because their names are explicitly specified at the definition site. clippy::too_many_arguments, // It's fine to use many bool arguments in the function signature because // all of them will be named at the call site when the builder is used. clippy::fn_params_excessive_bools, )])); Ok(orig) } pub(crate) fn into_builder_gen_ctx(self) -> Result { let assoc_method_ctx = self.assoc_method_ctx()?; let members = self .fn_item .apply_ref(|fn_item| fn_item.sig.inputs.iter().filter_map(syn::FnArg::as_typed)) .into_iter() .map(|arg| { let pat = match arg.norm.pat.as_ref() { syn::Pat::Ident(pat) => pat, _ => bail!( &arg.orig.pat, "use a simple `identifier: type` syntax for the function argument; \ destructuring patterns in arguments aren't supported by the `#[builder]`", ), }; let ty = SyntaxVariant { norm: arg.norm.ty.clone(), orig: arg.orig.ty.clone(), }; Ok(RawMember { attrs: &arg.norm.attrs, ident: pat.ident.clone(), ty, }) }) .collect::>>()?; let members = Member::from_raw(&self.config, MemberOrigin::FnArg, members)?; let generics = self.generics(); let mut adapted_fn_sig = self.adapted_fn()?.sig; if self.config.start_fn.name.is_none() { crate::privatize::privatize_fn_name(&mut adapted_fn_sig); } let finish_fn_body = FnCallBody { sig: adapted_fn_sig, impl_ctx: self.impl_ctx.clone(), }; let ItemSigConfig { name: finish_fn_ident, vis: finish_fn_vis, docs: finish_fn_docs, } = self.config.finish_fn; let is_special_builder_method = self.impl_ctx.is_some() && (self.fn_item.norm.sig.ident == "new" || self.fn_item.norm.sig.ident == "builder"); let finish_fn_ident = finish_fn_ident .map(SpannedKey::into_value) .unwrap_or_else(|| { // For `builder` methods the `build` finisher is more conventional if is_special_builder_method { format_ident!("build") } else { format_ident!("call") } }); let finish_fn_docs = finish_fn_docs .map(SpannedKey::into_value) .unwrap_or_else(|| { vec![syn::parse_quote! { /// Finishes building and performs the requested action. }] }); let finish_fn = FinishFnParams { ident: finish_fn_ident, vis: finish_fn_vis.map(SpannedKey::into_value), unsafety: self.fn_item.norm.sig.unsafety, asyncness: self.fn_item.norm.sig.asyncness, special_attrs: get_propagated_attrs(&self.fn_item.norm.attrs)?, body: Box::new(finish_fn_body), output: self.fn_item.norm.sig.output, attrs: finish_fn_docs, }; let fn_allows = self .fn_item .norm .attrs .iter() .filter_map(syn::Attribute::to_allow); let allow_attrs = self .impl_ctx .as_ref() .into_iter() .flat_map(|impl_ctx| impl_ctx.allow_attrs.iter().cloned()) .chain(fn_allows) .collect(); let builder_ident = || { let user_override = self.config.builder_type.name.map(SpannedKey::into_value); if let Some(user_override) = user_override { return user_override; } let ty_prefix = self.self_ty_prefix.unwrap_or_default(); // A special case for the `new` or `builder` method. // We don't insert the `Builder` suffix in this case because // this special case should be compatible with deriving // a builder from a struct. // // We can arrive inside of this branch only if the function under // the macro is called `new` or `builder`. if is_special_builder_method { return format_ident!("{ty_prefix}Builder"); } let pascal_case_fn = self.fn_item.norm.sig.ident.snake_to_pascal_case(); format_ident!("{ty_prefix}{pascal_case_fn}Builder") }; let builder_type = BuilderTypeParams { ident: builder_ident(), derives: self.config.derive, docs: self.config.builder_type.docs.map(SpannedKey::into_value), vis: self.config.builder_type.vis.map(SpannedKey::into_value), }; BuilderGenCtx::new(BuilderGenCtxParams { bon: self.config.bon, namespace: Cow::Borrowed(self.namespace), members, allow_attrs, const_: self.config.const_, on: self.config.on, assoc_method_ctx, generics, orig_item_vis: self.fn_item.norm.vis, builder_type, state_mod: self.config.state_mod, start_fn: self.start_fn, finish_fn, }) } } struct FnCallBody { sig: syn::Signature, impl_ctx: Option>, } impl FinishFnBody for FnCallBody { fn generate(&self, ctx: &BuilderGenCtx) -> TokenStream { let asyncness = &self.sig.asyncness; let maybe_await = asyncness.is_some().then(|| quote!(.await)); // Filter out lifetime generic arguments, because they are not needed // to be specified explicitly when calling the function. This also avoids // the problem that it's not always possible to specify lifetimes in // the turbofish syntax. See the problem of late-bound lifetimes specification // in the issue https://github.com/rust-lang/rust/issues/42868 let generic_args = self .sig .generics .params .iter() .filter(|arg| !matches!(arg, syn::GenericParam::Lifetime(_))) .map(syn::GenericParam::to_generic_argument); let prefix = ctx .assoc_method_ctx .as_ref() .and_then(|ctx| { let ident = &ctx.receiver.as_ref()?.field_ident; Some(quote!(self.#ident.)) }) .or_else(|| { let self_ty = &self.impl_ctx.as_deref()?.self_ty; Some(quote!(<#self_ty>::)) }); let fn_ident = &self.sig.ident; // The variables with values of members are in scope for this expression. let member_vars = ctx.members.iter().map(Member::orig_ident); quote! { #prefix #fn_ident::<#(#generic_args,)*>( #( #member_vars ),* ) #maybe_await } } } /// To merge generic params we need to make sure lifetimes are always the first /// in the resulting list according to Rust syntax restrictions. fn merge_generic_params( left: &Punctuated, right: &Punctuated, ) -> Vec { let is_lifetime = |param: &&_| matches!(param, &&syn::GenericParam::Lifetime(_)); let (left_lifetimes, left_rest): (Vec<_>, Vec<_>) = left.iter().partition(is_lifetime); let (right_lifetimes, right_rest): (Vec<_>, Vec<_>) = right.iter().partition(is_lifetime); left_lifetimes .into_iter() .chain(right_lifetimes) .chain(left_rest) .chain(right_rest) .cloned() .collect() } const PROPAGATED_ATTRIBUTES: &[&str] = &["must_use", "track_caller", "target_feature"]; fn get_propagated_attrs(attrs: &[syn::Attribute]) -> Result> { PROPAGATED_ATTRIBUTES .iter() .copied() .filter_map(|needle| find_propagated_attr(attrs, needle).transpose()) .collect() } fn find_propagated_attr(attrs: &[syn::Attribute], needle: &str) -> Result> { let mut iter = attrs .iter() .filter(|attr| attr.meta.path().is_ident(needle)); let result = iter.next(); if let Some(second) = iter.next() { bail!( second, "found multiple #[{}], but bon only works with exactly one or zero.", needle ); } if let Some(attr) = result { if let syn::AttrStyle::Inner(_) = attr.style { bail!( attr, "#[{}] attribute must be placed on the function itself, \ not inside it.", needle ); } } Ok(result.cloned()) } bon-macros-3.7.1/src/builder/builder_gen/input_fn/validation.rs000064400000000000000000000054421046102023000227170ustar 00000000000000use crate::util::prelude::*; use syn::visit::Visit; impl super::FnInputCtx<'_> { pub(super) fn validate(&self) -> Result { if self.impl_ctx.is_none() { let explanation = "\ which likely means the builder attribute was used inside of an \ impl block; the impl block needs to be annotated with the #[bon] \ attribute and the builder attribute must be spelled as #[builder] \ without any additional path prefix, since it's used as a simple \ inert config attribute for #[bon] in impl blocks; more info on \ inert vs active attributes: \ https://doc.rust-lang.org/reference/attributes.html#active-and-inert-attributes"; if let Some(receiver) = &self.fn_item.orig.sig.receiver() { bail!( &receiver.self_token, "this function contains a `self` parameter {explanation}" ); } let mut ctx = FindSelfReference::default(); ctx.visit_item_fn(&self.fn_item.orig); if let Some(self_span) = ctx.self_span { bail!( &self_span, "this function contains a `Self` type reference {explanation}" ); } } if let Some(const_) = &self.config.const_ { if self.fn_item.orig.sig.constness.is_none() { bail!( &const_, "#[builder(const)] requires the underlying function to be \ marked as `const fn`" ); } } Ok(()) } pub(crate) fn warnings(&self) -> TokenStream { // We used to emit some warnings here previously, but then that logic // was removed. The code for the `warnings()` method was preserved just // in case if we need to issue some new warnings again. However, it's not // critical. Feel free to eliminate this method if you feel like it. let _ = self; TokenStream::new() } } #[derive(Default)] struct FindSelfReference { self_span: Option, } impl Visit<'_> for FindSelfReference { fn visit_item(&mut self, _: &syn::Item) { // Don't recurse into nested items. We are interested in the reference // to `Self` on the current item level } fn visit_path(&mut self, path: &syn::Path) { if self.self_span.is_some() { return; } syn::visit::visit_path(self, path); let first_segment = match path.segments.first() { Some(first_segment) => first_segment, _ => return, }; if first_segment.ident == "Self" { self.self_span = Some(first_segment.ident.span()); } } } bon-macros-3.7.1/src/builder/builder_gen/input_struct.rs000064400000000000000000000203501046102023000215010ustar 00000000000000use super::models::FinishFnParams; use super::top_level_config::TopLevelConfig; use super::{ AssocMethodCtxParams, BuilderGenCtx, FinishFnBody, Generics, Member, MemberOrigin, RawMember, }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; use crate::normalization::{GenericsNamespace, SyntaxVariant}; use crate::parsing::{ItemSigConfig, SpannedKey}; use crate::util::prelude::*; use std::borrow::Cow; use syn::visit::Visit; use syn::visit_mut::VisitMut; fn parse_top_level_config(item_struct: &syn::ItemStruct) -> Result { let configs = item_struct .attrs .iter() .filter(|attr| attr.path().is_ident("builder")) .map(|attr| { let meta = match &attr.meta { syn::Meta::List(meta) => meta, syn::Meta::Path(_) => bail!( &attr.meta, "this empty `#[builder]` attribute is redundant; remove it" ), syn::Meta::NameValue(_) => bail!( &attr.meta, "`#[builder = ...]` syntax is unsupported; use `#[builder(...)]` instead" ), }; crate::parsing::require_non_empty_paren_meta_list_or_name_value(&attr.meta)?; Ok(meta.tokens.clone()) }) .collect::>>()?; TopLevelConfig::parse_for_struct(configs) } pub(crate) struct StructInputCtx { struct_item: SyntaxVariant, config: TopLevelConfig, struct_ty: syn::Type, } impl StructInputCtx { pub(crate) fn new(orig_struct: syn::ItemStruct) -> Result { let params = parse_top_level_config(&orig_struct)?; let generic_args = orig_struct .generics .params .iter() .map(syn::GenericParam::to_generic_argument); let struct_ident = &orig_struct.ident; let struct_ty = syn::parse_quote!(#struct_ident<#(#generic_args),*>); let mut norm_struct = orig_struct.clone(); // Structs are free to use `Self` inside of their trait bounds and any // internal type contexts. However, when copying these bounds to the // builder struct and its impl blocks we need to get rid of `Self` // references and replace them with the actual struct type. crate::normalization::NormalizeSelfTy { self_ty: &struct_ty, } .visit_item_struct_mut(&mut norm_struct); let struct_item = SyntaxVariant { orig: orig_struct, norm: norm_struct, }; Ok(Self { struct_item, config: params, struct_ty, }) } pub(crate) fn into_builder_gen_ctx(self) -> Result { let fields = self .struct_item .apply_ref(|struct_item| match &struct_item.fields { syn::Fields::Named(fields) => Ok(fields), _ => { bail!(&struct_item, "Only structs with named fields are supported") } }); let norm_fields = fields.norm?; let orig_fields = fields.orig?; let members = norm_fields .named .iter() .zip(&orig_fields.named) .map(|(norm_field, orig_field)| { let ident = norm_field.ident.clone().ok_or_else(|| { err!(norm_field, "only structs with named fields are supported") })?; let ty = SyntaxVariant { norm: Box::new(norm_field.ty.clone()), orig: Box::new(orig_field.ty.clone()), }; Ok(RawMember { attrs: &norm_field.attrs, ident, ty, }) }) .collect::>>()?; let members = Member::from_raw(&self.config, MemberOrigin::StructField, members)?; let generics = Generics::new( self.struct_item .norm .generics .params .iter() .cloned() .collect(), self.struct_item.norm.generics.where_clause.clone(), ); let finish_fn_body = StructLiteralBody { struct_ident: self.struct_item.norm.ident.clone(), }; let ItemSigConfig { name: start_fn_ident, vis: start_fn_vis, docs: start_fn_docs, } = self.config.start_fn; let start_fn_ident = start_fn_ident .map(SpannedKey::into_value) .unwrap_or_else(|| syn::Ident::new("builder", self.struct_item.norm.ident.span())); let ItemSigConfig { name: finish_fn_ident, vis: finish_fn_vis, docs: finish_fn_docs, } = self.config.finish_fn; let finish_fn_ident = finish_fn_ident .map(SpannedKey::into_value) .unwrap_or_else(|| syn::Ident::new("build", start_fn_ident.span())); let struct_ty = &self.struct_ty; let finish_fn = FinishFnParams { ident: finish_fn_ident, vis: finish_fn_vis.map(SpannedKey::into_value), unsafety: None, asyncness: None, special_attrs: vec![syn::parse_quote! { #[must_use = "building a struct without using it is likely a bug"] }], body: Box::new(finish_fn_body), output: syn::parse_quote!(-> #struct_ty), attrs: finish_fn_docs .map(SpannedKey::into_value) .unwrap_or_else(|| { vec![syn::parse_quote! { /// Finish building and return the requested object }] }), }; let start_fn_docs = start_fn_docs .map(SpannedKey::into_value) .unwrap_or_else(|| { let docs = format!( "Create an instance of [`{}`] using the builder syntax", self.struct_item.norm.ident ); vec![syn::parse_quote!(#[doc = #docs])] }); let start_fn = StartFnParams { ident: start_fn_ident, vis: start_fn_vis.map(SpannedKey::into_value), docs: start_fn_docs, generics: None, }; let assoc_method_ctx = Some(AssocMethodCtxParams { self_ty: self.struct_ty.into(), receiver: None, }); let allow_attrs = self .struct_item .norm .attrs .iter() .filter_map(syn::Attribute::to_allow) .collect(); let builder_type = { let ItemSigConfig { name, vis, docs } = self.config.builder_type; let builder_ident = name.map(SpannedKey::into_value).unwrap_or_else(|| { format_ident!("{}Builder", self.struct_item.norm.ident.raw_name()) }); BuilderTypeParams { derives: self.config.derive, ident: builder_ident, docs: docs.map(SpannedKey::into_value), vis: vis.map(SpannedKey::into_value), } }; let mut namespace = GenericsNamespace::default(); namespace.visit_item_struct(&self.struct_item.orig); BuilderGenCtx::new(BuilderGenCtxParams { bon: self.config.bon, namespace: Cow::Owned(namespace), members, allow_attrs, const_: self.config.const_, on: self.config.on, assoc_method_ctx, generics, orig_item_vis: self.struct_item.norm.vis, builder_type, state_mod: self.config.state_mod, start_fn, finish_fn, }) } } struct StructLiteralBody { struct_ident: syn::Ident, } impl FinishFnBody for StructLiteralBody { fn generate(&self, ctx: &BuilderGenCtx) -> TokenStream { let Self { struct_ident } = self; // The variables with values of members are in scope for this expression. let member_vars = ctx.members.iter().map(Member::orig_ident); quote! { #struct_ident { #(#member_vars,)* } } } } bon-macros-3.7.1/src/builder/builder_gen/member/config/blanket.rs000064400000000000000000000056431046102023000231020ustar 00000000000000use super::MemberConfig; use crate::builder::builder_gen::member::MemberOrigin; use crate::builder::builder_gen::top_level_config::OnConfig; use crate::util::prelude::*; use std::fmt; pub(crate) enum BlanketParamName { Into, Overwritable, SettersDocDefaultSkip, } impl fmt::Display for BlanketParamName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Into => fmt::Display::fmt(&super::ParamName::Into, f), Self::Overwritable => fmt::Display::fmt(&super::ParamName::Overwritable, f), Self::SettersDocDefaultSkip => f.write_str("setters(doc(default(skip)))"), } } } impl BlanketParamName { fn value_in_on_config(&self, cfg: &OnConfig) -> darling::util::Flag { match self { Self::Into => cfg.into, Self::Overwritable => cfg.overwritable, Self::SettersDocDefaultSkip => cfg.setters.doc.default.skip, } } fn value_in_member_config(&self, cfg: &MemberConfig) -> darling::util::Flag { match self { Self::Into => cfg.into, Self::Overwritable => cfg.overwritable, Self::SettersDocDefaultSkip => cfg .setters .as_ref() .and_then(|setters| setters.doc.default.as_ref()) .map(|default| default.skip) .unwrap_or_default(), } } } pub(crate) struct EvalBlanketFlagParam<'a> { pub(crate) on: &'a [OnConfig], pub(crate) param_name: BlanketParamName, pub(crate) member_config: &'a MemberConfig, pub(crate) scrutinee: &'a syn::Type, pub(crate) origin: MemberOrigin, } impl EvalBlanketFlagParam<'_> { pub(crate) fn eval(self) -> Result { let Self { on, param_name, member_config, scrutinee, origin, } = self; let verdict_from_on = on .iter() .map(|params| Ok((params, scrutinee.matches(¶ms.type_pattern)?))) .collect::>>()? .into_iter() .filter(|(_, matched)| *matched) .map(|(params, _)| param_name.value_in_on_config(params)) .find(darling::util::Flag::is_present); let value_in_member = param_name.value_in_member_config(member_config); let flag = match (verdict_from_on, value_in_member.is_present()) { (Some(_), true) => { bail!( &value_in_member.span(), "this `#[builder({param_name})]` attribute is redundant, because \ `{param_name}` is already implied for this member via the \ `#[builder(on(...))]` at the top of the {}", origin.parent_construct(), ); } (Some(flag), false) => flag, (None, _) => value_in_member, }; Ok(flag) } } bon-macros-3.7.1/src/builder/builder_gen/member/config/getter.rs000064400000000000000000000057621046102023000227560ustar 00000000000000use crate::parsing::SpannedKey; use crate::util::prelude::*; use darling::FromMeta; #[derive(Debug, Default)] pub(crate) struct GetterConfig { pub(crate) name: Option>, pub(crate) vis: Option>, pub(crate) docs: Option>>, /// Returns `&T` if [`None`] pub(crate) kind: Option>, } #[derive(Debug)] pub(crate) enum GetterKind { /// Returns `T` via [`Copy`] Copy, /// Returns `T` via [`Clone`] Clone, /// Returns `&::Target`. /// If the type is `None`, it will be inferred from the member's type. Deref(Option>), } impl FromMeta for GetterConfig { fn from_meta(meta: &syn::Meta) -> Result { if let syn::Meta::Path(_) = meta { return Ok(Self::default()); } // Reject empty parens such as `#[builder(getter())]` crate::parsing::require_non_empty_paren_meta_list_or_name_value(meta)?; // Nested `Parsed` struct used as a helper for parsing the verbose form #[derive(FromMeta)] struct Parsed { name: Option>, vis: Option>, #[darling(rename = "doc", default, with = parse_docs, map = Some)] docs: Option>>, copy: Option>, clone: Option>, #[darling(default, map = Some, with = parse_deref)] deref: Option>>>, } let Parsed { name, vis, docs, copy, clone, deref, } = Parsed::from_meta(meta)?; let kinds = [ copy.map(|cfg| cfg.with_value(GetterKind::Copy)), clone.map(|cfg| cfg.with_value(GetterKind::Clone)), deref.map(|ty| ty.map_value(GetterKind::Deref)), ]; let kinds = kinds.into_iter().flatten().collect::>(); if let [kind1, kind2, ..] = kinds.as_slice() { bail!( &kind1.key, "`{}` can't be specified together with `{}`", kind1.key, kind2.key ); } let kind = kinds.into_iter().next(); Ok(Self { name, vis, docs, kind, }) } } fn parse_deref(meta: &syn::Meta) -> Result>>> { let value = match meta { syn::Meta::NameValue(_) => bail!( meta, "expected `getter(deref)` or `getter(deref(...))` syntax" ), syn::Meta::Path(_) => None, syn::Meta::List(meta) => Some(syn::parse2(meta.tokens.clone())?), }; SpannedKey::new(meta.path(), value) } fn parse_docs(meta: &syn::Meta) -> Result>> { crate::parsing::parse_docs_without_self_mentions("builder struct's impl block", meta) } bon-macros-3.7.1/src/builder/builder_gen/member/config/mod.rs000064400000000000000000000326271046102023000222430ustar 00000000000000mod blanket; mod getter; mod setters; mod with; pub(crate) use blanket::*; pub(crate) use getter::*; pub(crate) use setters::*; pub(crate) use with::*; use super::MemberOrigin; use crate::builder::builder_gen::TopLevelConfig; use crate::parsing::SpannedKey; use crate::util::prelude::*; use std::fmt; #[derive(Debug, darling::FromAttributes)] #[darling(attributes(builder))] pub(crate) struct MemberConfig { /// Assign a default value to the member it it's not specified. /// /// An optional expression can be provided to set the value for the member, /// otherwise its [`Default`] trait impl will be used. #[darling(with = parse_optional_expr, map = Some)] pub(crate) default: Option>>, /// Make the member a private field in the builder struct. /// This is useful when the user needs to add custom fields to the builder, /// that they would use in the custom methods they add to the builder. /// /// This is similar to `skip`. The difference is that `field` is evaluated /// inside of the starting function, and stored in the builder. Its initialization /// expression thus has access to all `start_fn` parameters. It must be declared /// strictly after `#[builder(start_fn)]` members (if any) or right at the top of /// the members list. #[darling(with = parse_optional_expr, map = Some)] pub(crate) field: Option>>, /// Make the member gettable. [`GetterConfig`] specifies the signature for /// the getter. /// /// This takes the same attributes as the setter fns; `name`, `vis`, and `doc` /// and produces a getter method that returns the value of the member. /// By default, the value is returned by a shared reference (&T). pub(crate) getter: Option>, /// Accept the value for the member in the finishing function parameters. pub(crate) finish_fn: darling::util::Flag, /// Enables an `Into` conversion for the setter method. pub(crate) into: darling::util::Flag, /// Rename the name exposed in the builder API. pub(crate) name: Option, /// Allows setting the value for the member repeatedly. This reduces the /// number of type states and thus increases the compilation performance. /// /// However, this also means that unintended overwrites won't be caught /// at compile time. Measure the compilation time before and after enabling /// this option to see if it's worth it. pub(crate) overwritable: darling::util::Flag, /// Disables the special handling for a member of type `Option`. The /// member no longer has the default of `None`. It also becomes a required /// member unless a separate `#[builder(default = ...)]` attribute is /// also specified. pub(crate) required: darling::util::Flag, /// Configurations for the setter methods. #[darling(with = crate::parsing::parse_non_empty_paren_meta_list)] pub(crate) setters: Option, /// Skip generating a setter method for this member. /// /// An optional expression can be provided to set the value for the member, /// otherwise its [`Default`] trait impl will be used. #[darling(with = parse_optional_expr, map = Some)] pub(crate) skip: Option>>, /// Accept the value for the member in the starting function parameters. pub(crate) start_fn: darling::util::Flag, /// Customize the setter signature and body with a custom closure or a well-known /// function. The closure/function must return the value of the type of the member, /// or optionally a `Result<_>` type where `_` is used to mark the type of /// the member. In this case the generated setters will be fallible /// (they'll propagate the `Result`). pub(crate) with: Option>, } #[derive(PartialEq, Eq, Clone, Copy)] enum ParamName { Default, Field, Getter, FinishFn, Into, Name, Overwritable, Required, Setters, Skip, StartFn, With, } impl fmt::Display for ParamName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let str = match self { Self::Default => "default", Self::Field => "field", Self::Getter => "getter", Self::FinishFn => "finish_fn", Self::Into => "into", Self::Name => "name", Self::Overwritable => "overwritable", Self::Required => "required", Self::Setters => "setters", Self::Skip => "skip", Self::StartFn => "start_fn", Self::With => "with", }; f.write_str(str) } } impl MemberConfig { fn validate_mutually_exclusive( &self, attr_name: ParamName, attr_span: Span, mutually_exclusive: &[ParamName], ) -> Result<()> { self.validate_compat(attr_name, attr_span, mutually_exclusive, true) } fn validate_mutually_allowed( &self, attr_name: ParamName, attr_span: Span, mutually_allowed: &[ParamName], ) -> Result<()> { self.validate_compat(attr_name, attr_span, mutually_allowed, false) } fn validate_compat( &self, attr_name: ParamName, attr_span: Span, patterns: &[ParamName], mutually_exclusive: bool, ) -> Result<()> { let conflicting: Vec<_> = self .specified_param_names() .filter(|name| *name != attr_name && patterns.contains(name) == mutually_exclusive) .collect(); if conflicting.is_empty() { return Ok(()); } let conflicting = conflicting .iter() .map(|name| format!("`{name}`")) .join(", "); bail!( &attr_span, "`{attr_name}` attribute can't be specified together with {conflicting}", ); } fn specified_param_names(&self) -> impl Iterator { let Self { default, field, getter, finish_fn, into, name, overwritable, required, setters, skip, start_fn, with, } = self; let attrs = [ (default.is_some(), ParamName::Default), (field.is_some(), ParamName::Field), (getter.is_some(), ParamName::Getter), (finish_fn.is_present(), ParamName::FinishFn), (into.is_present(), ParamName::Into), (name.is_some(), ParamName::Name), (overwritable.is_present(), ParamName::Overwritable), (required.is_present(), ParamName::Required), (setters.is_some(), ParamName::Setters), (skip.is_some(), ParamName::Skip), (start_fn.is_present(), ParamName::StartFn), (with.is_some(), ParamName::With), ]; attrs .into_iter() .filter(|(is_present, _)| *is_present) .map(|(_, name)| name) } pub(crate) fn validate(&self, top_config: &TopLevelConfig, origin: MemberOrigin) -> Result { if top_config.const_.is_some() { self.require_const_compat()?; } if !cfg!(feature = "experimental-overwritable") && self.overwritable.is_present() { bail!( &self.overwritable.span(), "🔬 `overwritable` attribute is experimental and requires \ \"experimental-overwritable\" cargo feature to be enabled; \ we would be glad to make this attribute stable if you find it useful; \ please leave a 👍 reaction under the issue https://github.com/elastio/bon/issues/149 \ to help us measure the demand for this feature; it would be \ double-awesome if you could also describe your use case in \ a comment under the issue for us to understand how it's used \ in practice", ); } if let Some(getter) = &self.getter { self.validate_mutually_exclusive( ParamName::Getter, getter.key.span(), &[ParamName::Overwritable], )?; } if self.start_fn.is_present() { self.validate_mutually_allowed( ParamName::StartFn, self.start_fn.span(), // TODO: add support for `#[builder(getter)]` with `start_fn` &[ParamName::Into], )?; } if self.finish_fn.is_present() { self.validate_mutually_allowed( ParamName::FinishFn, self.finish_fn.span(), &[ParamName::Into], )?; } if let Some(field) = &self.field { self.validate_mutually_allowed(ParamName::Field, field.key.span(), &[])?; } if let Some(skip) = &self.skip { match origin { MemberOrigin::FnArg => { bail!( &skip.key.span(), "`skip` attribute is not supported on function arguments; \ use a local variable instead.", ); } MemberOrigin::StructField => {} } if let Some(Some(_expr)) = self.default.as_deref() { bail!( &skip.key.span(), "`skip` attribute can't be specified with the `default` attribute; \ if you wanted to specify a value for the member, then use \ the following syntax instead `#[builder(skip = value)]`", ); } self.validate_mutually_allowed(ParamName::Skip, skip.key.span(), &[])?; } if let Some(with) = &self.with { self.validate_mutually_exclusive(ParamName::With, with.key.span(), &[ParamName::Into])?; } if let Some(setters) = &self.setters { if let Some(default) = &setters.doc.default { if self.default.is_none() { bail!( &default.key, "`#[builder(setters(doc(default(...)))]` may only be specified \ when #[builder(default)] is also specified", ); } } } Ok(()) } fn require_const_compat(&self) -> Result { fn validate_default_trait_or_expr(attr: &Option>>) -> Result { let attr = match attr { Some(attr) => attr, None => return Ok(()), }; let name = attr.key.to_string(); if let Some(expr) = &attr.value { return crate::parsing::require_embeddable_const_expr(expr); } bail!( &attr.key, "bare #[builder({name})] is incompatible with #[builder(const)] \ because Default::default() can not be called in const context; \ provide an explicit value via #[builder({name} = ...)] instead", ) } validate_default_trait_or_expr(&self.default)?; validate_default_trait_or_expr(&self.skip)?; validate_default_trait_or_expr(&self.field)?; if self.into.is_present() { bail!( &self.into.span(), "#[builder(into)] is incompatible with #[builder(const)] \ because Into::into() can not be called in const context", ); } if let Some(getter) = &self.getter { if let Some(getter_kind) = &getter.kind { match &getter_kind.value { GetterKind::Copy => {} GetterKind::Clone => { bail!( &getter_kind.key, "#[builder(getter(clone))] is incompatible with #[builder(const)] \ because Clone::clone() can not be called in const context", ) } GetterKind::Deref(_) => { bail!( &getter_kind.key, "#[builder(getter(deref))] is incompatible with #[builder(const)] \ because Deref::deref() can not be called in const context", ) } } } } if let Some(with) = &self.with { match &with.value { WithConfig::Closure(closure) => { crate::parsing::require_embeddable_const_expr(&closure.body)?; } WithConfig::Some(_) => {} WithConfig::FromIter(from_iter) => { bail!( &from_iter, "from_iter is incompatible with #[builder(const)] because \ FromIterator::from_iter() can not be called in const context", ) } } } Ok(()) } } fn parse_optional_expr(meta: &syn::Meta) -> Result>> { match meta { syn::Meta::Path(path) => SpannedKey::new(path, None), syn::Meta::List(_) => Err(Error::unsupported_format("list").with_span(meta)), syn::Meta::NameValue(meta) => SpannedKey::new(&meta.path, Some(meta.value.clone())), } } bon-macros-3.7.1/src/builder/builder_gen/member/config/setters.rs000064400000000000000000000113341046102023000231450ustar 00000000000000use crate::parsing::{ItemSigConfig, ItemSigConfigParsing, SpannedKey}; use crate::util::prelude::*; use darling::ast::NestedMeta; use darling::FromMeta; use syn::punctuated::Punctuated; const DOCS_CONTEXT: &str = "builder struct's impl block"; fn parse_setter_fn(meta: &syn::Meta) -> Result> { let params = ItemSigConfigParsing { meta, reject_self_mentions: Some(DOCS_CONTEXT), } .parse()?; SpannedKey::new(meta.path(), params) } fn parse_docs(meta: &syn::Meta) -> Result>> { crate::parsing::parse_docs_without_self_mentions(DOCS_CONTEXT, meta) } #[derive(Debug, Default)] pub(crate) struct SettersConfig { pub(crate) name: Option>, pub(crate) vis: Option>, pub(crate) doc: SettersDocConfig, pub(crate) fns: SettersFnsConfig, } impl FromMeta for SettersConfig { fn from_meta(meta: &syn::Meta) -> Result { let meta: Punctuated = crate::parsing::parse_paren_meta_list_with_terminated(meta)?; let (docs, remaining_meta): (Vec<_>, Vec<_>) = meta .into_iter() .partition(|meta| meta.path().is_ident("doc")); let doc = SettersDocConfig::from_docs_entries(docs)?; #[derive(FromMeta)] struct Parsed { name: Option>, vis: Option>, #[darling(flatten)] fns: SettersFnsConfig, } let remaining_meta = remaining_meta .into_iter() .map(NestedMeta::Meta) .collect::>(); let parsed: Parsed = Parsed::from_list(&remaining_meta)?; Ok(Self { name: parsed.name, vis: parsed.vis, fns: parsed.fns, doc, }) } } #[derive(Debug, Default, FromMeta)] pub(crate) struct SettersFnsConfig { /// Config for the setter that accepts the value of type T for a member of /// type `Option` or with `#[builder(default)]`. /// /// By default, it's named `{member}` without any prefix or suffix. #[darling(default, with = parse_setter_fn, map = Some)] pub(crate) some_fn: Option>, /// The setter that accepts the value of type `Option` for a member of /// type `Option` or with `#[builder(default)]`. /// /// By default, it's named `maybe_{member}`. #[darling(default, with = parse_setter_fn, map = Some)] pub(crate) option_fn: Option>, } #[derive(Debug, Default)] pub(crate) struct SettersDocConfig { /// Overrides the content of the doc comments. pub(crate) content: Option>>, /// Overrides the look of the default value showcase in the docs header. pub(crate) default: Option>, } impl SettersDocConfig { fn from_docs_entries(docs: Vec) -> Result { let mut doc_config = None; let mut doc_content = None; for doc in docs { match doc.require_list()?.delimiter { syn::MacroDelimiter::Paren(_) => { if doc_config.is_some() { bail!(&doc, "repeated `doc(...)` attribute is not allowed"); } doc_config = Some(doc); } syn::MacroDelimiter::Brace(_) => { if doc_content.is_some() { bail!(&doc, "repeated `doc {{...}}` attribute is not allowed"); } doc_content = Some(doc); } syn::MacroDelimiter::Bracket(_) => { bail!(&doc, "wrong delimiter, expected doc(...) or doc {{...}}",); } } } #[derive(FromMeta)] struct Parsed { #[darling(with = crate::parsing::parse_non_empty_paren_meta_list)] default: Option>, } let config = doc_config .as_ref() .map(crate::parsing::parse_non_empty_paren_meta_list::) .transpose()?; let content = doc_content.as_ref().map(parse_docs).transpose()?; let mut me = Self { content, default: None, }; if let Some(Parsed { default }) = config { me.default = default; // More keys may be added here in the future } Ok(me) } } #[derive(Debug, Default, FromMeta)] pub(crate) struct SettersDocDefaultConfig { /// If `true`, the default value showcase in the docs header will be skipped. pub(crate) skip: darling::util::Flag, } bon-macros-3.7.1/src/builder/builder_gen/member/config/with/closure.rs000064400000000000000000000101441046102023000241010ustar 00000000000000use crate::parsing::SimpleClosure; use crate::util::prelude::*; use darling::FromMeta; const INVALID_RETURN_TYPE_ERROR: &str = "\ expected one of the following: (1) no return type annotation; this means the closure is expected to return a value of the same type as the member's underlying type(*); (2) `-> *Result<_, {{ErrorType}}>` or `-> *Result<_>` return type annotation; this means the closure is expected to return a `Result` where the `Ok` variant is of the same type as the member's underlying type(*); this syntax allows you to define a fallbile setter (one that returns a `Result`); the `_` placeholder must be spelled literally to mark the underlying type(*) of the member; an optional second generic parameter for the error type is allowed; the return type doesn't have to be named `Result` exactly, the only requirement is that it must have the `Result` suffix; for example if you have a type alias `ApiResult<_>`, then it'll work fine; (*) underlying type is the type of the member stripped from the `Option` wrapper if this member is of `Option` type and no `#[builder(required)]` annotation is present"; #[derive(Debug)] pub(crate) struct SetterClosure { pub(crate) inputs: Vec, pub(crate) body: Box, pub(crate) output: Option, } #[derive(Debug)] pub(crate) struct SetterClosureOutput { pub(crate) result_path: syn::Path, pub(crate) err_ty: Option, } #[derive(Debug)] pub(crate) struct SetterClosureInput { pub(crate) pat: syn::PatIdent, pub(crate) ty: Box, } impl FromMeta for SetterClosure { fn from_meta(item: &syn::Meta) -> Result { let closure = SimpleClosure::from_meta(item)?; let inputs = closure .inputs .into_iter() .map(|input| { Ok(SetterClosureInput { ty: input.ty.ok_or_else(|| { err!(&input.pat, "expected a type for the setter input parameter") })?, pat: input.pat, }) }) .collect::>()?; let return_type = match closure.output { syn::ReturnType::Default => None, syn::ReturnType::Type(_, ty) => { let err = || err!(&ty, "{INVALID_RETURN_TYPE_ERROR}"); let ty = ty .as_generic_angle_bracketed_path(|last_segment| { // We allow for arbitrary `Result` type variations // including custom type aliases like `ApiResult<_>` last_segment.to_string().ends_with("Result") }) .ok_or_else(err)?; if !(1..=2).contains(&ty.args.len()) { return Err(err()); } let mut args = ty.args.iter(); let ok_ty = args.next().ok_or_else(err)?; if !matches!(ok_ty, syn::GenericArgument::Type(syn::Type::Infer(_))) { return Err(err()); } let err_ty = args .next() .map(|arg| match arg { syn::GenericArgument::Type(ty) => Ok(ty.clone()), _ => Err(err()), }) .transpose()?; let mut result_path = ty.path.clone(); // We store the error type of the result separately. // Strip the generic arguments, because we only care // about the path of the `Result` in `result_path` field. result_path .segments .last_mut() .expect("BUG: segments can't be empty") .arguments = syn::PathArguments::None; Some(SetterClosureOutput { result_path, err_ty, }) } }; Ok(Self { inputs, body: closure.body, output: return_type, }) } } bon-macros-3.7.1/src/builder/builder_gen/member/config/with/mod.rs000064400000000000000000000036041046102023000232070ustar 00000000000000mod closure; pub(crate) use closure::*; use crate::util::prelude::*; use darling::FromMeta; #[derive(Debug)] pub(crate) enum WithConfig { /// Closure syntax e.g. `#[builder(with = |param: Type| body)]` Closure(SetterClosure), /// Well-known path [`Option::Some`] Some(syn::Path), /// Well-known path [`FromIterator::from_iter`] or `<_>::from_iter` FromIter(syn::ExprPath), } impl WithConfig { pub(crate) fn as_closure(&self) -> Option<&SetterClosure> { match self { Self::Closure(closure) => Some(closure), _ => None, } } } impl FromMeta for WithConfig { fn from_meta(meta: &syn::Meta) -> darling::Result { let err = || { err!( meta, "expected a closure e.g. `#[builder(with = |param: T| expression)]` or \ a well-known function path which could be one of:\n\ - #[builder(with = Some)]\n\ - #[builder(with = FromIterator::from_iter)]\n\ - #[builder(with = <_>::from_iter)] (same as above, but shorter)", ) }; let name_val = match meta { syn::Meta::NameValue(meta) => meta, _ => return Err(err()), }; if let syn::Expr::Closure(_) = name_val.value { return SetterClosure::from_meta(meta).map(Self::Closure); } let path = match &name_val.value { syn::Expr::Path(path) => path, _ => return Err(err()), }; crate::parsing::reject_attrs(&path.attrs)?; if *path == syn::parse_quote!(Some) { return Ok(Self::Some(path.path.clone())); } if *path == syn::parse_quote!(FromIterator::from_iter) || *path == syn::parse_quote!(<_>::from_iter) { return Ok(Self::FromIter(path.clone())); } Err(err()) } } bon-macros-3.7.1/src/builder/builder_gen/member/into_conversion.rs000064400000000000000000000044401046102023000234250ustar 00000000000000use super::config::{BlanketParamName, EvalBlanketFlagParam}; use super::{NamedMember, PosFnMember}; use crate::builder::builder_gen::top_level_config::OnConfig; use crate::util::prelude::*; impl NamedMember { pub(super) fn merge_config_into(&mut self, on: &[OnConfig]) -> Result { // `with` is mutually exclusive with `into`. So there is nothing to merge here // if `with` is present. if self.config.with.is_some() { return Ok(()); } // For optional named members the target of the `Into` conversion is the type // inside of the `Option`, not the `Option` itself because we generate // a setter that accepts `T` itself. It also makes this logic stable regardless // if `Option` is used or the member of type `T` has `#[builder(default)]` on it. let scrutinee = self.underlying_orig_ty(); self.config.into = EvalBlanketFlagParam { on, param_name: BlanketParamName::Into, member_config: &self.config, scrutinee, origin: self.origin, } .eval()?; Ok(()) } } impl PosFnMember { pub(crate) fn merge_config_into(&mut self, on: &[OnConfig]) -> Result { // Positional members are never optional. Users must always specify them, so there // is no need for us to look into the `Option` generic parameter, because the // `Option` itself is the target of the into conversion, not the `T` inside it. let scrutinee = self.ty.orig.as_ref(); self.config.into = EvalBlanketFlagParam { on, param_name: BlanketParamName::Into, member_config: &self.config, scrutinee, origin: self.origin, } .eval()?; Ok(()) } pub(crate) fn fn_input_param(&self) -> TokenStream { let ty = &self.ty.norm; let ident = &self.ident; if self.config.into.is_present() { quote! { #ident: impl Into<#ty> } } else { quote! { #ident: #ty } } } pub(crate) fn conversion(&self) -> Option { if !self.config.into.is_present() { return None; } let ident = &self.ident; Some(quote! { Into::into(#ident) }) } } bon-macros-3.7.1/src/builder/builder_gen/member/mod.rs000064400000000000000000000215151046102023000207700ustar 00000000000000mod config; mod into_conversion; mod named; pub(crate) use config::*; pub(crate) use named::*; use super::top_level_config::OnConfig; use super::TopLevelConfig; use crate::normalization::SyntaxVariant; use crate::util::prelude::*; use config::MemberConfig; use darling::FromAttributes; use std::fmt; #[derive(Debug, Clone, Copy)] pub(crate) enum MemberOrigin { FnArg, StructField, } impl fmt::Display for MemberOrigin { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::FnArg => write!(f, "function argument"), Self::StructField => write!(f, "struct field"), } } } impl MemberOrigin { fn parent_construct(self) -> &'static str { match self { Self::FnArg => "function", Self::StructField => "struct", } } } #[derive(Debug)] pub(crate) enum Member { /// Member that was marked with `#[builder(start_fn)]` StartFn(PosFnMember), /// Member that was marked with `#[builder(field)]` Field(CustomField), /// Member that was marked with `#[builder(finish_fn)]` FinishFn(PosFnMember), /// Regular named member included in the typestate. Named(NamedMember), /// Member that was marked with `#[builder(skip)]` Skip(SkipMember), } #[derive(Debug)] pub(crate) struct CustomField { pub(crate) ident: syn::Ident, pub(crate) norm_ty: Box, /// Initial value of the field pub(crate) init: Option, } #[derive(Debug)] pub(crate) struct PosFnMember { /// Specifies what syntax the member comes from. pub(crate) origin: MemberOrigin, /// Original identifier of the member pub(crate) ident: syn::Ident, /// Type of the member pub(crate) ty: SyntaxVariant>, /// Parameters configured by the user explicitly via attributes pub(crate) config: MemberConfig, } /// Member that was skipped by the user with `#[builder(skip)]` #[derive(Debug)] pub(crate) struct SkipMember { pub(crate) ident: syn::Ident, /// Normalized type of the member pub(crate) norm_ty: Box, /// Value to assign to the member pub(crate) value: Option, } pub(crate) struct RawMember<'a> { pub(crate) attrs: &'a [syn::Attribute], pub(crate) ident: syn::Ident, pub(crate) ty: SyntaxVariant>, } impl Member { // False-positive lint. We can't elide the lifetime in `RawMember` because // anonymous lifetimes in impl traits are unstable, and we shouldn't omit // the lifetime parameter because we want to be explicit about its existence // (there is an other lint that checks for this). #[allow(single_use_lifetimes)] pub(crate) fn from_raw<'a>( top_config: &TopLevelConfig, origin: MemberOrigin, members: impl IntoIterator>, ) -> Result> { let on = &top_config.on; let mut members = members .into_iter() .map(|member| { for attr in member.attrs { if attr.meta.path().is_ident("builder") { crate::parsing::require_non_empty_paren_meta_list_or_name_value( &attr.meta, )?; } } let config = MemberConfig::from_attributes(member.attrs)?; config.validate(top_config, origin)?; Ok((member, config)) }) .collect::>>()? .into_iter() .peekable(); let mut output = vec![]; // Collect `start_fn` members while let Some((member, config)) = members.next_if(|(_, cfg)| cfg.start_fn.is_present()) { let member = PosFnMember::new(origin, member, on, config)?; output.push(Self::StartFn(member)); } // Collect `field` members while let Some((member, config)) = members.next_if(|(_, cfg)| cfg.field.is_some()) { let init = config .field .expect("validated `field.is_some()` in `next_if`") .value; let member = CustomField::new(member, init)?; output.push(Self::Field(member)); } // Collect `finish_fn` members while let Some((member, cfg)) = members.next_if(|(_, cfg)| cfg.finish_fn.is_present()) { let member = PosFnMember::new(origin, member, on, cfg)?; output.push(Self::FinishFn(member)); } let mut named_count = 0; for (member, config) in members { let RawMember { attrs, ident, ty } = member; if let Some(value) = config.skip { output.push(Self::Skip(SkipMember { ident, norm_ty: ty.norm, value: value.value, })); continue; } let active_flag = |flag: darling::util::Flag| flag.is_present().then(|| flag.span()); let incorrect_order = None .or_else(|| active_flag(config.start_fn)) .or_else(|| Some(config.field.as_ref()?.key.span())) .or_else(|| active_flag(config.finish_fn)); if let Some(span) = incorrect_order { bail!( &span, "incorrect members ordering; expected ordering:\n\ (1) members annotated with #[builder(start_fn)]\n\ (2) members annotated with #[builder(field)]\n\ (3) members annotated with #[builder(finish_fn)]\n\ (4) all other members in any order", ); } // XXX: docs are collected only for named members. There is no obvious // place where to put the docs for positional and skipped members. // // Even if there are some docs on them and the function syntax is used // then these docs will just be removed from the output function. // It's probably fine since the doc comments are there in the code // itself which is also useful for people reading the source code. let docs = attrs .iter() .filter(|attr| attr.is_doc_expr()) .cloned() .collect(); let mut member = NamedMember { index: named_count.into(), origin, name: MemberName::new(ident, &config), ty, config, docs, }; member.merge_on_config(on)?; member.validate()?; output.push(Self::Named(member)); named_count += 1; } Ok(output) } } impl Member { pub(crate) fn norm_ty(&self) -> &syn::Type { match self { Self::Field(me) => &me.norm_ty, Self::FinishFn(me) | Self::StartFn(me) => &me.ty.norm, Self::Named(me) => &me.ty.norm, Self::Skip(me) => &me.norm_ty, } } pub(crate) fn orig_ident(&self) -> &syn::Ident { match self { Self::Field(me) => &me.ident, Self::FinishFn(me) | Self::StartFn(me) => &me.ident, Self::Named(me) => &me.name.orig, Self::Skip(me) => &me.ident, } } pub(crate) fn as_named(&self) -> Option<&NamedMember> { match self { Self::Named(me) => Some(me), _ => None, } } pub(crate) fn as_field(&self) -> Option<&CustomField> { match self { Self::Field(me) => Some(me), _ => None, } } pub(crate) fn as_start_fn(&self) -> Option<&PosFnMember> { match self { Self::StartFn(me) => Some(me), _ => None, } } pub(crate) fn as_finish_fn(&self) -> Option<&PosFnMember> { match self { Self::FinishFn(me) => Some(me), _ => None, } } } impl PosFnMember { fn new( origin: MemberOrigin, member: RawMember<'_>, on: &[OnConfig], config: MemberConfig, ) -> Result { let RawMember { attrs: _, ident, ty, } = member; let mut me = Self { origin, ident, ty, config, }; me.merge_config_into(on)?; Ok(me) } } impl CustomField { fn new(member: RawMember<'_>, init: Option) -> Result { if member.ident.to_string().starts_with("__") { bail!( &member.ident, "field names starting with `__` are reserved for `bon`'s internal use; \ please, select a different name", ); } Ok(Self { ident: member.ident, norm_ty: member.ty.norm, init, }) } } bon-macros-3.7.1/src/builder/builder_gen/member/named.rs000064400000000000000000000260101046102023000212700ustar 00000000000000use super::config::MemberConfig; use super::{config, MemberOrigin}; use crate::builder::builder_gen::member::config::SettersFnsConfig; use crate::builder::builder_gen::top_level_config::OnConfig; use crate::normalization::SyntaxVariant; use crate::parsing::{ItemSigConfig, SpannedKey}; use crate::util::prelude::*; #[derive(Debug)] pub(crate) struct MemberName { /// Original name of the member (unchanged). It's used in the finishing /// function of the builder to create a variable for each member. pub(crate) orig: syn::Ident, /// `snake_case` version of the member name. By default it's the `orig` name /// itself with the `_` prefix stripped. Otherwise the user can override it /// via `#[builder(name = custom_name)]` pub(crate) snake: syn::Ident, /// `snake_case` version of the member name as a string without the `r#` prefix /// (if there is any in the `snake` representation). It's computed and /// stored separately to avoid recomputing it multiple times. It's used /// to derive names for other identifiers that are based on the `snake_case` name. pub(crate) snake_raw_str: String, /// `PascalCase` version of the member name. It's always computed as the /// `snake` variant converted to `PascalCase`. The user doesn't have the /// granular control over this name. Users can only specify the snake case /// version of the name, and the pascal case is derived from it. pub(crate) pascal: syn::Ident, /// `PascalCase` version of the member name as a string. It's computed and /// stored separately to avoid recomputing it multiple times. It's guaranteed /// to not have the `r#` prefix because: /// /// There are no pascal case keywords in Rust except for `Self`, which /// is anyway not allowed even as a raw identifier: /// pub(crate) pascal_str: String, } impl MemberName { pub(crate) fn new(orig: syn::Ident, config: &MemberConfig) -> Self { let snake = config.name.clone().unwrap_or_else(|| { let orig_str = orig.to_string(); let norm = orig_str // Remove the leading underscore from the member name since it's used // to denote unused symbols in Rust. That doesn't mean the builder // API should expose that knowledge to the caller. .strip_prefix('_') .unwrap_or(&orig_str); // Preserve the original identifier span to make IDE's "go to definition" work correctly // and make error messages point to the correct place. syn::Ident::new_maybe_raw(norm, orig.span()) }); let pascal = snake.snake_to_pascal_case(); Self { orig, snake_raw_str: snake.raw_name(), snake, pascal_str: pascal.to_string(), pascal, } } } /// Regular member for which the builder should have setter methods #[derive(Debug)] pub(crate) struct NamedMember { /// Specifies what syntax the member comes from. pub(crate) origin: MemberOrigin, /// Index of the member relative to other named members. The index is 0-based. pub(crate) index: syn::Index, /// Name of the member is used to generate names for the setters, names for /// the associated types and type aliases in the builder state, etc. pub(crate) name: MemberName, /// Doc comments on top of the original syntax. These are copied to the setters /// unless there are overrides for them. pub(crate) docs: Vec, /// Type of the member has to be known to generate the types for fields in /// the builder, signatures of the setter methods, etc. pub(crate) ty: SyntaxVariant>, /// Parameters configured by the user explicitly via attributes pub(crate) config: MemberConfig, } impl NamedMember { pub(super) fn validate(&self) -> Result { if let Some(default) = &self.config.default { if self.is_special_option_ty() { bail!( &default.key, "`Option<_>` already implies a default of `None`, \ so explicit #[builder(default)] is redundant", ); } } let member_docs_not_copied = self .config .setters .as_ref() .map(|setters| { if setters.doc.content.is_some() { return true; } let SettersFnsConfig { some_fn, option_fn } = &setters.fns; matches!( (some_fn.as_deref(), option_fn.as_deref()), ( Some(ItemSigConfig { docs: Some(_), .. }), Some(ItemSigConfig { docs: Some(_), .. }) ) ) }) .unwrap_or(false); if !member_docs_not_copied { crate::parsing::reject_self_mentions_in_docs( "builder struct's impl block", &self.docs, )?; } self.validate_setters_config()?; if self.config.required.is_present() && !self.ty.norm.is_option() { bail!( &self.config.required.span(), "`#[builder(required)]` can only be applied to members of \ type `Option` to disable their special handling", ); } Ok(()) } fn validate_setters_config(&self) -> Result { let setters = match &self.config.setters { Some(setters) => setters, None => return Ok(()), }; if self.is_required() { let SettersFnsConfig { some_fn, option_fn } = &setters.fns; let unexpected_setter = option_fn.as_ref().or(some_fn.as_ref()); if let Some(setter) = unexpected_setter { bail!( &setter.key, "`{}` setter function applies only to members with `#[builder(default)]` \ or members of `Option` type (if #[builder(required)] is not set)", setter.key ); } } if let SettersFnsConfig { some_fn: Some(some_fn), option_fn: Some(option_fn), } = &setters.fns { let setter_fns = &[some_fn, option_fn]; Self::validate_unused_setters_cfg(setter_fns, &setters.name, |config| &config.name)?; Self::validate_unused_setters_cfg(setter_fns, &setters.vis, |config| &config.vis)?; Self::validate_unused_setters_cfg(setter_fns, &setters.doc.content, |config| { &config.docs })?; } Ok(()) } fn validate_unused_setters_cfg( overrides: &[&SpannedKey], config: &Option>, get_val: impl Fn(&ItemSigConfig) -> &Option>, ) -> Result { let config = match config { Some(config) => config, None => return Ok(()), }; let overrides_values = overrides .iter() .copied() .map(|over| get_val(&over.value).as_ref()); if !overrides_values.clone().all(|over| over.is_some()) { return Ok(()); } let setters = overrides .iter() .map(|over| format!("`{}`", over.key)) .join(", "); bail!( &config.key, "this `{name}` configuration is unused because all of the \ {setters} setters contain a `{name}` override", name = config.key, ); } /// Returns `true` if this member is of `Option<_>` type, but returns `false` /// if `#[builder(required)]` is set. pub(crate) fn is_special_option_ty(&self) -> bool { !self.config.required.is_present() && self.ty.norm.is_option() } /// Returns `false` if the member has a default value. It means this member /// is required to be set before building can be finished. pub(crate) fn is_required(&self) -> bool { self.config.default.is_none() && !self.is_special_option_ty() } /// A stateful member is the one that has a corresponding associated type in /// the builder's type state trait. This is used to track the fact that the /// member was set or not. This is necessary to make sure all members without /// default values are set before building can be finished. pub(crate) fn is_stateful(&self) -> bool { self.is_required() || !self.config.overwritable.is_present() } /// Returns the normalized type of the member stripping the `Option<_>` /// wrapper if it's present unless `#[builder(required)]` is set. pub(crate) fn underlying_norm_ty(&self) -> &syn::Type { self.underlying_ty(&self.ty.norm) } /// Returns the original type of the member stripping the `Option<_>` /// wrapper if it's present unless `#[builder(required)]` is set. pub(crate) fn underlying_orig_ty(&self) -> &syn::Type { self.underlying_ty(&self.ty.orig) } fn underlying_ty<'m>(&'m self, ty: &'m syn::Type) -> &'m syn::Type { if self.config.required.is_present() || self.config.default.is_some() { ty } else { ty.option_type_param().unwrap_or(ty) } } pub(crate) fn is(&self, other: &Self) -> bool { self.index == other.index } pub(crate) fn merge_on_config(&mut self, on: &[OnConfig]) -> Result { // This is a temporary hack. We only allow `on(_, required)` as the // first `on(...)` clause. Instead we should implement the extended design: // https://github.com/elastio/bon/issues/152 if let Some(on) = on.first().filter(|on| on.required.is_present()) { if self.is_special_option_ty() { self.config.required = on.required; } } self.merge_config_into(on)?; self.merge_setters_doc_skip(on)?; // FIXME: refactor this to make it more consistent with `into` // and allow for non-boolean flags in `OnConfig`. E.g. add support // for `with = closure` to `on` as well. self.config.overwritable = config::EvalBlanketFlagParam { on, param_name: config::BlanketParamName::Overwritable, member_config: &self.config, scrutinee: self.underlying_norm_ty(), origin: self.origin, } .eval()?; Ok(()) } fn merge_setters_doc_skip(&mut self, on: &[OnConfig]) -> Result { let skip = config::EvalBlanketFlagParam { on, param_name: config::BlanketParamName::SettersDocDefaultSkip, member_config: &self.config, scrutinee: self.underlying_norm_ty(), origin: self.origin, } .eval()?; let setters = self.config.setters.get_or_insert_with(Default::default); let default = setters.doc.default.get_or_insert_with(Default::default); default.skip = skip; Ok(()) } } bon-macros-3.7.1/src/builder/builder_gen/mod.rs000064400000000000000000000275341046102023000175300ustar 00000000000000mod builder_decl; mod builder_derives; mod finish_fn; mod getters; mod member; mod models; mod setters; mod start_fn; mod state_mod; mod top_level_config; pub(crate) mod input_fn; pub(crate) mod input_struct; pub(crate) use top_level_config::TopLevelConfig; use crate::util::prelude::*; use getters::GettersCtx; use member::{CustomField, Member, MemberOrigin, NamedMember, PosFnMember, RawMember}; use models::{AssocMethodCtxParams, AssocMethodReceiverCtx, BuilderGenCtx, FinishFnBody, Generics}; use setters::SettersCtx; pub(crate) struct MacroOutput { pub(crate) start_fn: syn::ItemFn, pub(crate) other_items: TokenStream, } impl BuilderGenCtx { fn receiver(&self) -> Option<&AssocMethodReceiverCtx> { self.assoc_method_ctx.as_ref()?.receiver.as_ref() } fn named_members(&self) -> impl Iterator { self.members.iter().filter_map(Member::as_named) } fn custom_fields(&self) -> impl Iterator { self.members.iter().filter_map(Member::as_field) } fn start_fn_args(&self) -> impl Iterator { self.members.iter().filter_map(Member::as_start_fn) } fn finish_fn_args(&self) -> impl Iterator { self.members.iter().filter_map(Member::as_finish_fn) } fn stateful_members(&self) -> impl Iterator { self.named_members().filter(|member| member.is_stateful()) } pub(crate) fn output(self) -> Result { let mut start_fn = self.start_fn(); let state_mod = state_mod::StateModGenCtx::new(&self).state_mod(); let builder_decl = self.builder_decl(); let builder_impl = self.builder_impl()?; let builder_derives = self.builder_derives()?; let default_allows = syn::parse_quote!(#[allow( // We have a `deprecated` lint on all `bon::__` items which we // use in the generated code extensively deprecated )]); let allows = self.allow_attrs.iter().cloned().chain([default_allows]); // -- Postprocessing -- // Here we parse all items back and add the `allow` attributes to them. let other_items = quote! { #builder_decl #builder_impl #builder_derives #state_mod }; let other_items_str = other_items.to_string(); let other_items: syn::File = syn::parse2(other_items).map_err(|err| { err!( &Span::call_site(), "bug in the `bon` crate: the macro generated code that contains syntax errors; \ please report this issue at our Github repository: \ https://github.com/elastio/bon;\n\ syntax error in generated code: {err:#?};\n\ generated code:\n\ ```rust {other_items_str}\n\ ```", ) })?; let mut other_items = other_items.items; for item in &mut other_items { if let Some(attrs) = item.attrs_mut() { attrs.extend(allows.clone()); } } start_fn.attrs.extend(allows); Ok(MacroOutput { start_fn, other_items: quote!(#(#other_items)*), }) } fn builder_impl(&self) -> Result { let finish_fn = self.finish_fn(); let accessor_methods = self .named_members() .map(|member| { let setters = SettersCtx::new(self, member).setter_methods()?; let getters = GettersCtx::new(self, member) .map(GettersCtx::getter_methods) .transpose()? .unwrap_or_default(); // Output all accessor methods for the same member adjecently. // This is important in the generated rustdoc output, because // rustdoc lists methods in the order they appear in the source. Ok([setters, getters]) }) .collect::>>()? .into_iter() .flatten(); let generics_decl = &self.generics.decl_without_defaults; let generic_args = &self.generics.args; let where_clause = &self.generics.where_clause; let builder_ident = &self.builder_type.ident; let state_mod = &self.state_mod.ident; let state_var = &self.state_var; let allows = allow_warnings_on_member_types(); Ok(quote! { #allows #[automatically_derived] impl< #(#generics_decl,)* #state_var: #state_mod::State > #builder_ident<#(#generic_args,)* #state_var> #where_clause { #finish_fn #(#accessor_methods)* } }) } /// Generates code that has no meaning to the compiler, but it helps /// IDEs to provide better code highlighting, completions and other /// hints. fn ide_hints(&self) -> TokenStream { let type_patterns = self .on .iter() .map(|params| ¶ms.type_pattern) .collect::>(); if type_patterns.is_empty() { return quote! {}; } quote! { // This is wrapped in a special cfg set by `rust-analyzer` to enable this // code for rust-analyzer's analysis only, but prevent the code from being // compiled by `rustc`. Rust Analyzer should be able to use the syntax // provided inside of the block to figure out the semantic meaning of // the tokens passed to the attribute. #[allow(unexpected_cfgs)] { #[cfg(rust_analyzer)] { // Let IDEs know that these are type patterns like the ones that // could be written in a type annotation for a variable. Note that // we don't initialize the variable with any value because we don't // have any meaningful value to assign to this variable, especially // because its type may contain wildcard patterns like `_`. This is // used only to signal the IDEs that these tokens are meant to be // type patterns by placing them in the context where type patterns // are expected. let _: (#(#type_patterns,)*); } } } } fn phantom_data(&self) -> TokenStream { let member_types = self.members.iter().filter_map(|member| { match member { // The types of these members already appear in the struct as regular fields. Member::StartFn(_) | Member::Field(_) | Member::Named(_) => None, Member::FinishFn(member) => Some(member.ty.norm.as_ref()), Member::Skip(member) => Some(member.norm_ty.as_ref()), } }); let receiver_ty = self .assoc_method_ctx .as_ref() .map(|ctx| ctx.self_ty.as_ref()); let generic_types = self.generics.args.iter().filter_map(|arg| match arg { syn::GenericArgument::Type(ty) => Some(ty), _ => None, }); let types = std::iter::empty() .chain(receiver_ty) .chain(member_types) .chain(generic_types) .map(|ty| { // Wrap `ty` in another phantom data because it can be `?Sized`, // and simply using it as a type of the tuple member would // be wrong, because tuple's members must be sized. // // We also wrap this in an `fn() -> ...` to make the compiler think // that the builder doesn't "own" an instance of the given type. // This removes unnecessary requirements when evaluating the // applicability of the auto traits. quote!(fn() -> ::core::marker::PhantomData<#ty>) }); let lifetimes = self.generics.args.iter().filter_map(|arg| match arg { syn::GenericArgument::Lifetime(lifetime) => Some(lifetime), _ => None, }); let state_var = &self.state_var; quote! { ::core::marker::PhantomData<( // We have to store the builder state in phantom data otherwise it // would be reported as an unused type parameter. // // We also wrap this in an `fn() -> ...` to make the compiler think // that the builder doesn't "own" an instance of the given type. // This removes unnecessary requirements when evaluating the // applicability of the auto traits. fn() -> #state_var, // Even though lifetimes will most likely be used somewhere in // member types, it is not guaranteed in case of functions/methods, // so we mention them all separately. This covers a special case // for function builders where the lifetime can be entirely unused // (the language permis that). // // This edge case was discovered thanks to @tonywu6 ❤️: // https://github.com/elastio/bon/issues/206 #( &#lifetimes (), )* // There is an interesting quirk with lifetimes in Rust, which is the // reason why we thoughtlessly store all the function parameter types // in phantom data here. // // Suppose a function was defined with an argument of type `&'a T` // and then we generate an impl block (simplified): // // ``` // impl<'a, T, U> for Foo // where // U: Into<&'a T>, // {} // ``` // Then compiler will complain with the message "the parameter type `T` // may not live long enough". So we would need to manually add the bound // `T: 'a` to fix this. However, it's hard to infer such a bound in macro // context. A workaround for that would be to store the `&'a T` inside of // the struct itself, which auto-implies this bound for us implicitly. // // That's a weird implicit behavior in Rust, I suppose there is a reasonable // explanation for it, I just didn't care to research it yet ¯\_(ツ)_/¯. #(#types,)* )> } } /// Wrap the user-supplied expression in a closure to prevent it from /// breaking out of the surrounding scope. /// /// Closures aren't supported in `const` contexts at the time of this writing /// (Rust 1.86.0), so we don't wrap the expression in a closure in `const` case /// but we require the expression to be simple so that it doesn't break out of /// the surrounding scope. /// /// This function assumes the expression underwent a `require_embeddable_const_expr` /// check if `const` is enabled. Search for it in code to see where it happens. fn sanitize_expr(&self, expr: &syn::Expr) -> TokenStream { if self.const_.is_some() { return quote!(#expr); } quote! { (|| #expr)() } } } fn allow_warnings_on_member_types() -> TokenStream { quote! { // This warning may occur when the original unnormalized syntax was // using parens around an `impl Trait` like that: // ``` // &(impl Clone + Default) // ``` // in which case the normalized version will be: // ``` // &(T) // ``` // // And it triggers the warning. We just suppress it here. #[allow(unused_parens)] } } bon-macros-3.7.1/src/builder/builder_gen/models.rs000064400000000000000000000344671046102023000202370ustar 00000000000000use super::member::Member; use super::top_level_config::{DerivesConfig, OnConfig}; use crate::normalization::GenericsNamespace; use crate::parsing::{BonCratePath, ItemSigConfig, SpannedKey}; use crate::util::prelude::*; use std::borrow::Cow; pub(super) trait FinishFnBody { /// Generate the `finish` function body from the ready-made variables. /// The generated function body may assume that there are variables /// named the same as the members in scope. fn generate(&self, ctx: &BuilderGenCtx) -> TokenStream; } pub(super) struct AssocMethodReceiverCtx { pub(super) with_self_keyword: syn::Receiver, pub(super) without_self_keyword: Box, /// Name of the receiver field in the builder struct. pub(super) field_ident: syn::Ident, } pub(super) struct AssocMethodReceiverCtxParams { pub(super) with_self_keyword: syn::Receiver, pub(super) without_self_keyword: Box, } pub(super) struct AssocMethodCtx { /// The `Self` type of the impl block. It doesn't contain any nested /// `Self` keywords in it. This is prohibited by Rust's syntax itself. pub(super) self_ty: Box, /// Present only if the method has a receiver, i.e. `self` or `&self` or /// `&mut self` or `self: ExplicitType`. pub(super) receiver: Option, } pub(super) struct AssocMethodCtxParams { /// The `Self` type of the impl block. It doesn't contain any nested /// `Self` keywords in it. This is prohibited by Rust's syntax itself. pub(super) self_ty: Box, /// Present only if the method has a receiver, i.e. `self` or `&self` or /// `&mut self` or `self: ExplicitType`. pub(super) receiver: Option, } pub(super) struct FinishFn { pub(super) ident: syn::Ident, /// Visibility override specified by the user pub(super) vis: syn::Visibility, /// Additional attributes to apply to the item pub(super) attrs: Vec, pub(super) unsafety: Option, pub(super) asyncness: Option, /// Special attributes to propagate, such as [`must_use`]() /// and [`track_caller`]() pub(super) special_attrs: Vec, pub(super) body: Box, pub(super) output: syn::ReturnType, } pub(super) struct FinishFnParams { pub(super) ident: syn::Ident, /// Visibility override specified by the user pub(super) vis: Option, pub(super) attrs: Vec, pub(super) unsafety: Option, pub(super) asyncness: Option, pub(super) special_attrs: Vec, pub(super) body: Box, pub(super) output: syn::ReturnType, } pub(super) struct StartFn { pub(super) ident: syn::Ident, pub(super) vis: syn::Visibility, pub(super) docs: Vec, /// Overrides the default generics pub(super) generics: Option, } pub(super) struct StartFnParams { pub(super) ident: syn::Ident, /// If present overrides the default visibility derived from the builder's type. pub(super) vis: Option, pub(super) docs: Vec, /// Overrides the default generics pub(super) generics: Option, } pub(super) struct BuilderType { pub(super) ident: syn::Ident, /// Visibility of the builder module itself. pub(super) vis: syn::Visibility, pub(super) derives: DerivesConfig, pub(super) docs: Vec, } pub(super) struct BuilderTypeParams { pub(super) ident: syn::Ident, pub(super) vis: Option, pub(super) derives: DerivesConfig, pub(super) docs: Option>, } pub(super) struct StateMod { pub(super) ident: syn::Ident, /// Visibility of the builder module itself. pub(super) vis: syn::Visibility, /// Visibility equivalent to the [`Self::vis`], but for items /// generated inside the builder child module. pub(super) vis_child: syn::Visibility, /// Visibility equivalent to the [`Self::vis_child`], but for items /// generated inside one more level of nesting in the builder child module. pub(super) vis_child_child: syn::Visibility, pub(super) docs: Vec, } pub(super) struct Generics { pub(super) where_clause: Option, /// Original generics that may contain default values in them. This is only /// suitable for use in places where default values for generic parameters /// are allowed. pub(super) decl_with_defaults: Vec, /// Generic parameters without default values in them. This is suitable for /// use as generics in function signatures or impl blocks. pub(super) decl_without_defaults: Vec, /// Mirrors the `decl` representing how generic params should be represented /// when these parameters are passed through as arguments in a turbofish. pub(super) args: Vec, } pub(crate) struct BuilderGenCtx { pub(super) bon: BonCratePath, /// Name of the generic variable that holds the builder's state. pub(super) state_var: syn::Ident, pub(super) members: Vec, /// Lint suppressions from the original item that will be inherited by all items /// generated by the macro. If the original syntax used `#[expect(...)]`, /// then it must be represented as `#[allow(...)]` here. pub(super) allow_attrs: Vec, pub(super) const_: Option, pub(super) on: Vec, pub(super) generics: Generics, pub(super) assoc_method_ctx: Option, pub(super) builder_type: BuilderType, pub(super) state_mod: StateMod, pub(super) start_fn: StartFn, pub(super) finish_fn: FinishFn, } pub(super) struct BuilderGenCtxParams<'a> { pub(crate) bon: BonCratePath, pub(super) namespace: Cow<'a, GenericsNamespace>, pub(super) members: Vec, pub(super) allow_attrs: Vec, pub(super) const_: Option, pub(super) on: Vec, /// This is the visibility of the original item that the builder is generated for. /// For example, the `struct` or `fn` item visibility that the `#[builder]` or /// `#[derive(Builder)]` attribute is applied to. /// /// It is used as the default visibility for all the generated items unless /// explicitly overridden at a more specific level. pub(super) orig_item_vis: syn::Visibility, /// Generics to apply to the builder type. pub(super) generics: Generics, pub(super) assoc_method_ctx: Option, pub(super) builder_type: BuilderTypeParams, pub(super) state_mod: ItemSigConfig, pub(super) start_fn: StartFnParams, pub(super) finish_fn: FinishFnParams, } impl BuilderGenCtx { pub(super) fn new(params: BuilderGenCtxParams<'_>) -> Result { let BuilderGenCtxParams { bon, namespace, members, allow_attrs, const_, on, generics, orig_item_vis, assoc_method_ctx, builder_type, state_mod, start_fn, finish_fn, } = params; let builder_type = BuilderType { ident: builder_type.ident, vis: builder_type.vis.unwrap_or(orig_item_vis), derives: builder_type.derives, docs: builder_type.docs.unwrap_or_else(|| { let doc = format!( "Use builder syntax to set the inputs and finish with [`{0}()`](Self::{0}()).", finish_fn.ident ); vec![syn::parse_quote! { #[doc = #doc] }] }), }; let state_mod = { let is_ident_overridden = state_mod.name.is_some(); let ident = state_mod .name .map(SpannedKey::into_value) .unwrap_or_else(|| builder_type.ident.pascal_to_snake_case()); if builder_type.ident == ident { if is_ident_overridden { bail!( &ident, "the builder module name must be different from the builder type name" ) } bail!( &builder_type.ident, "couldn't infer the builder module name that doesn't conflict with \ the builder type name; by default, the builder module name is set \ to a snake_case equivalent of the builder type name; the snake_case \ conversion doesn't produce a different name for this builder type \ name; consider using PascalCase for the builder type name or specify \ a separate name for the builder module explicitly via \ `#[builder(state_mod = {{new_name}})]`" ); } // The builder module is private by default, meaning all symbols under // that module can't be accessed from outside the module where the builder // is defined. This makes the builder type signature unnamable from outside // the module where we output the builder. The users need to explicitly // opt-in to make the builder module public. let vis = state_mod .vis .map(SpannedKey::into_value) .unwrap_or_else(|| syn::Visibility::Inherited); // The visibility for child items is based on the visibility of the // builder type itself, because the types and traits from this module // are part of the builder's generic type state parameter signature. let vis_child = builder_type.vis.clone().into_equivalent_in_child_module()?; let vis_child_child = vis_child.clone().into_equivalent_in_child_module()?; StateMod { vis, vis_child, vis_child_child, ident, docs: state_mod .docs .map(SpannedKey::into_value) .unwrap_or_else(|| { let docs = format!( "Tools for manipulating the type state of [`{}`].\n\ \n\ See the [detailed guide](https://bon-rs.com/guide/typestate-api) \ that describes how all the pieces here fit together.", builder_type.ident ); vec![syn::parse_quote!(#[doc = #docs])] }), } }; let start_fn = StartFn { ident: start_fn.ident, vis: start_fn.vis.unwrap_or_else(|| builder_type.vis.clone()), docs: start_fn.docs, generics: start_fn.generics, }; let finish_fn = FinishFn { ident: finish_fn.ident, vis: finish_fn.vis.unwrap_or_else(|| builder_type.vis.clone()), attrs: finish_fn.attrs, unsafety: finish_fn.unsafety, asyncness: finish_fn.asyncness, special_attrs: finish_fn.special_attrs, body: finish_fn.body, output: finish_fn.output, }; let state_var = { let possible_names = ["S", "State", "BuilderState"]; possible_names .iter() .find(|&&candidate| !namespace.idents.contains(candidate)) .map(|&name| syn::Ident::new(name, Span::call_site())) .unwrap_or_else(|| namespace.unique_ident(format!("{}_", possible_names[0]))) }; let assoc_method_ctx = assoc_method_ctx.map(|ctx| { let receiver = ctx.receiver.map(|receiver| { let start_fn_arg_names = members .iter() .filter_map(Member::as_start_fn) .map(|member| member.ident.to_string()) .collect(); let field_ident = crate::normalization::unique_name( &start_fn_arg_names, "self_receiver".to_owned(), ); AssocMethodReceiverCtx { with_self_keyword: receiver.with_self_keyword, without_self_keyword: receiver.without_self_keyword, field_ident: syn::Ident::new(&field_ident, Span::call_site()), } }); AssocMethodCtx { self_ty: ctx.self_ty, receiver, } }); Ok(Self { bon, state_var, members, allow_attrs, const_, on, generics, assoc_method_ctx, builder_type, state_mod, start_fn, finish_fn, }) } } impl Generics { pub(super) fn new( decl_with_defaults: Vec, where_clause: Option, ) -> Self { let decl_without_defaults = decl_with_defaults .iter() .cloned() .map(|mut param| { match &mut param { syn::GenericParam::Type(param) => { param.default = None; } syn::GenericParam::Const(param) => { param.default = None; } syn::GenericParam::Lifetime(_) => {} } param }) .collect(); let args = decl_with_defaults .iter() .map(syn::GenericParam::to_generic_argument) .collect(); Self { where_clause, decl_with_defaults, decl_without_defaults, args, } } pub(super) fn where_clause_predicates(&self) -> impl Iterator { self.where_clause .as_ref() .into_iter() .flat_map(|clause| &clause.predicates) } } bon-macros-3.7.1/src/builder/builder_gen/setters/mod.rs000064400000000000000000000573721046102023000212240ustar 00000000000000use super::member::{SetterClosure, WithConfig}; use super::{BuilderGenCtx, NamedMember}; use crate::parsing::ItemSigConfig; use crate::util::prelude::*; use std::iter; pub(crate) struct SettersCtx<'a> { base: &'a BuilderGenCtx, member: &'a NamedMember, } impl<'a> SettersCtx<'a> { pub(crate) fn new(base: &'a BuilderGenCtx, member: &'a NamedMember) -> Self { Self { base, member } } pub(crate) fn setter_methods(&self) -> Result { match SettersItems::new(self) { SettersItems::Required(item) => self.setter_for_required_member(item), SettersItems::Optional(setters) => self.setters_for_optional_member(setters), } } fn setter_for_required_member(&self, item: SetterItem) -> Result { let inputs; let expr; let member_type = self.member.ty.norm.as_ref(); if let Some(with) = &self.member.config.with { inputs = self.underlying_inputs_from_with(with)?; expr = self.member_expr_from_with(with); } else if self.member.config.into.is_present() { inputs = vec![( pat_ident("value"), syn::parse_quote!(impl Into<#member_type>), )]; expr = quote!(Into::into(value)); } else { inputs = vec![(pat_ident("value"), member_type.clone())]; expr = quote!(value); } let body = SetterBody::SetMember { expr: quote!(::core::option::Option::Some(#expr)), }; Ok(self.setter_method(Setter { item, imp: SetterImpl { inputs, body }, })) } fn setters_for_optional_member(&self, items: OptionalSettersItems) -> Result { if let Some(with) = &self.member.config.with { return self.setters_for_optional_member_having_with(with, items); } let underlying_ty = self.member.underlying_norm_ty(); let underlying_ty: syn::Type = if self.member.config.into.is_present() { syn::parse_quote!(impl Into<#underlying_ty>) } else { underlying_ty.clone() }; let some_fn = Setter { item: items.some_fn, imp: SetterImpl { inputs: vec![(pat_ident("value"), underlying_ty.clone())], body: SetterBody::Forward { body: { let option_fn_name = &items.option_fn.name; quote! { self.#option_fn_name(Some(value)) } }, }, }, }; let option_fn = Setter { item: items.option_fn, imp: SetterImpl { inputs: vec![( pat_ident("value"), syn::parse_quote!(Option<#underlying_ty>), )], body: SetterBody::SetMember { expr: if self.member.config.into.is_present() { quote! { Option::map(value, Into::into) } } else { quote!(value) }, }, }, }; Ok([self.setter_method(some_fn), self.setter_method(option_fn)].concat()) } fn setters_for_optional_member_having_with( &self, with: &WithConfig, items: OptionalSettersItems, ) -> Result { let inputs = self.underlying_inputs_from_with(with)?; let idents = inputs.iter().map(|(pat, _)| &pat.ident); // If the closure accepts just a single input avoid wrapping it // in a tuple in the `option_fn` setter. let tuple_if_many = |val: TokenStream| -> TokenStream { if inputs.len() == 1 { val } else { quote!((#val)) } }; let ident_maybe_tuple = tuple_if_many(quote!( #( #idents ),* )); let some_fn = Setter { item: items.some_fn, imp: SetterImpl { inputs: inputs.clone(), body: SetterBody::Forward { body: { let option_fn_name = &items.option_fn.name; quote! { self.#option_fn_name(Some(#ident_maybe_tuple)) } }, }, }, }; let option_fn_impl = SetterImpl { inputs: { let input_types = inputs.iter().map(|(_, ty)| ty); let input_types = tuple_if_many(quote!(#( #input_types, )*)); vec![(pat_ident("value"), syn::parse_quote!(Option<#input_types>))] }, body: SetterBody::SetMember { expr: { let expr = self.member_expr_from_with(with); quote! { // Not using `Option::map` here because the `#expr` // can contain a `?` operator for a fallible operation. match value { Some(#ident_maybe_tuple) => Some(#expr), None => None, } } }, }, }; let option_fn = Setter { item: items.option_fn, imp: option_fn_impl, }; Ok([self.setter_method(some_fn), self.setter_method(option_fn)].concat()) } /// This method is reused between the setter for the required member and /// the `some_fn` setter for the optional member. /// /// We intentionally keep the name and signature of the setter method /// for an optional member that accepts the value under the option the /// same as the setter method for the required member to keep the API /// of the builder compatible when a required member becomes optional. /// To be able to explicitly pass an `Option` value to the setter method /// users need to use the `maybe_{member_ident}` method. fn underlying_inputs_from_with( &self, with: &WithConfig, ) -> Result> { let inputs = match with { WithConfig::Closure(closure) => closure .inputs .iter() .map(|input| (input.pat.clone(), (*input.ty).clone())) .collect(), WithConfig::Some(some) => { let input_ty = self .member .underlying_norm_ty() .option_type_param() .ok_or_else(|| { if self.member.ty.norm.is_option() { err!( some, "the underlying type of this member is not `Option`; \ by default, members of type `Option` are optional and their \ 'underlying type' is the type under the `Option`; \ you might be missing #[builder(required)]` annotation \ for this member" ) } else { err!( &self.member.underlying_norm_ty(), "`with = Some` only works for members with the underlying \ type of `Option`;" ) } })?; vec![(pat_ident("value"), input_ty.clone())] } WithConfig::FromIter(from_iter) => { let collection_ty = self.member.underlying_norm_ty(); let well_known_single_arg_suffixes = ["Vec", "Set", "Deque", "Heap", "List"]; let err = || { let mut from_iter_path = quote!(#from_iter).to_string(); from_iter_path.retain(|c| !c.is_whitespace()); err!( collection_ty, "the underlying type of this member is not a known collection type; \ only a collection type that matches the following patterns will be \ accepted by `#[builder(with = {from_iter_path})], where * at \ the beginning means the collection type may start with any prefix:\n\ - *Map\n\ {}", well_known_single_arg_suffixes .iter() .map(|suffix| { format!("- *{suffix}") }) .join("\n") ) }; let path = collection_ty.as_path_no_qself().ok_or_else(err)?; let last_segment = path.segments.last().ok_or_else(err)?; let args = match &last_segment.arguments { syn::PathArguments::AngleBracketed(args) => &args.args, _ => return Err(err()), }; let last_segment_ident_str = last_segment.ident.to_string(); let item_ty = if well_known_single_arg_suffixes .iter() .any(|suffix| last_segment_ident_str.ends_with(suffix)) { // We don't compare for `len == 1` because there may be an optional last // type argument for the allocator if args.is_empty() { return Err(err()); } let arg = args.first().ok_or_else(err)?; quote!(#arg) } else if last_segment_ident_str.ends_with("Map") { // We don't compare for `len == 2` because there may be an optional last // type argument for the allocator if args.len() < 2 { return Err(err()); } let mut args = args.iter(); let key = args.next().ok_or_else(err)?; let value = args.next().ok_or_else(err)?; quote!((#key, #value)) } else { return Err(err()); }; vec![( pat_ident("iter"), syn::parse_quote!(impl IntoIterator), )] } }; Ok(inputs) } fn member_expr_from_with(&self, with: &WithConfig) -> TokenStream { match with { WithConfig::Closure(closure) => self.member_expr_from_with_closure(with, closure), WithConfig::Some(some) => quote!(#some(value)), WithConfig::FromIter(from_iter) => quote!(#from_iter(iter)), } } fn member_expr_from_with_closure( &self, with: &WithConfig, closure: &SetterClosure, ) -> TokenStream { let body = &closure.body; let ty = self.member.underlying_norm_ty().to_token_stream(); let output = Self::maybe_wrap_in_result(with, ty); // Closures aren't supported in `const` contexts at the time of this writing // (Rust 1.86.0), so we don't wrap it in a closure but we require the expression // to be simple so that it doesn't break out of the surrounding scope. // Search for `require_embeddable_const_expr` for more. if self.base.const_.is_some() { let body = quote! {{ let value: #output = #body; value }}; if closure.output.is_none() { return body; } return quote! { match #body { Ok(value) => value, Err(err) => return Err(err), } }; } // Avoid wrapping the body in a block if it's already a block. let body = if matches!(body.as_ref(), syn::Expr::Block(_)) { body.to_token_stream() } else { quote!({ #body }) }; let question_mark = closure .output .is_some() .then(|| syn::Token![?](Span::call_site())); quote! { (move || -> #output #body)() #question_mark } } fn maybe_wrap_in_result(with: &WithConfig, ty: TokenStream) -> TokenStream { let closure = match with { WithConfig::Closure(closure) => closure, _ => return ty, }; let output = match closure.output.as_ref() { Some(output) => output, None => return ty, }; let result_path = &output.result_path; let err_ty = output.err_ty.iter(); quote! { #result_path< #ty #(, #err_ty )* > } } fn setter_method(&self, setter: Setter) -> TokenStream { let Setter { item, imp } = setter; let maybe_mut = match imp.body { SetterBody::Forward { .. } => None, SetterBody::SetMember { .. } => Some(syn::Token![mut](Span::call_site())), }; let body = match imp.body { SetterBody::Forward { body } => body, SetterBody::SetMember { expr } => { let mut output = if !self.member.is_stateful() { quote! { self } } else { let builder_ident = &self.base.builder_type.ident; let maybe_receiver_field = self.base.receiver().map(|receiver| { let ident = &receiver.field_ident; quote!(#ident: self.#ident,) }); let start_fn_args_fields_idents = self.base.start_fn_args().map(|member| &member.ident); let custom_fields_idents = self.base.custom_fields().map(|field| &field.ident); quote! { #builder_ident { __unsafe_private_phantom: ::core::marker::PhantomData, #( #custom_fields_idents: self.#custom_fields_idents, )* #maybe_receiver_field #( #start_fn_args_fields_idents: self.#start_fn_args_fields_idents, )* __unsafe_private_named: self.__unsafe_private_named, } } }; let result_output = self .member .config .with .as_ref() .and_then(|with| with.as_closure()?.output.as_ref()); if let Some(result_output) = result_output { let result_path = &result_output.result_path; output = quote!(#result_path::Ok(#output)); } let index = &self.member.index; quote! { self.__unsafe_private_named.#index = #expr; #output } } }; let state_mod = &self.base.state_mod.ident; let mut return_type = if !self.member.is_stateful() { quote! { Self } } else { let state_transition = format_ident!("Set{}", self.member.name.pascal_str); let builder_ident = &self.base.builder_type.ident; let generic_args = &self.base.generics.args; let state_var = &self.base.state_var; quote! { #builder_ident<#(#generic_args,)* #state_mod::#state_transition<#state_var>> } }; if let Some(with) = &self.member.config.with { return_type = Self::maybe_wrap_in_result(with, return_type); } let where_clause = (!self.member.config.overwritable.is_present()).then(|| { let state_var = &self.base.state_var; let member_pascal = &self.member.name.pascal; quote! { where #state_var::#member_pascal: #state_mod::IsUnset, } }); let SetterItem { name, vis, docs } = item; let pats = imp.inputs.iter().map(|(pat, _)| pat); let types = imp.inputs.iter().map(|(_, ty)| ty); let const_ = &self.base.const_; quote! { #( #docs )* #[allow( // This is intentional. We want the builder syntax to compile away clippy::inline_always, // We don't want to avoid using `impl Trait` in the setter. This way // the setter signature is easier to read, and anyway if you want to // specify a type hint for the method that accepts an `impl Into`, then // your design of this setter already went wrong. clippy::impl_trait_in_params, clippy::missing_const_for_fn, )] #[inline(always)] #vis #const_ fn #name(#maybe_mut self, #( #pats: #types ),*) -> #return_type #where_clause { #body } } } } struct Setter { item: SetterItem, imp: SetterImpl, } struct SetterImpl { inputs: Vec<(syn::PatIdent, syn::Type)>, body: SetterBody, } enum SetterBody { /// The setter forwards the call to another method. Forward { body: TokenStream }, /// The setter sets the member as usual and transitions the builder state. SetMember { expr: TokenStream }, } enum SettersItems { Required(SetterItem), Optional(OptionalSettersItems), } struct OptionalSettersItems { some_fn: SetterItem, option_fn: SetterItem, } struct SetterItem { name: syn::Ident, vis: syn::Visibility, docs: Vec, } impl SettersItems { fn new(ctx: &SettersCtx<'_>) -> Self { let SettersCtx { member, base } = ctx; let builder_type = &base.builder_type; let config = member.config.setters.as_ref(); let common_name = config.and_then(|config| config.name.as_deref()); let common_vis = config.and_then(|config| config.vis.as_deref()); let common_docs = config.and_then(|config| config.doc.content.as_deref().map(Vec::as_slice)); let doc = |docs: &str| iter::once(syn::parse_quote!(#[doc = #docs])); if member.is_required() { let docs = common_docs.unwrap_or(&member.docs); let header = "_**Required.**_\n\n"; let docs = doc(header).chain(docs.iter().cloned()).collect(); return Self::Required(SetterItem { name: common_name.unwrap_or(&member.name.snake).clone(), vis: common_vis.unwrap_or(&builder_type.vis).clone(), docs, }); } let some_fn = config.and_then(|config| config.fns.some_fn.as_deref()); let some_fn_name = some_fn .and_then(ItemSigConfig::name) .or(common_name) .unwrap_or(&member.name.snake) .clone(); let option_fn = config.and_then(|config| config.fns.option_fn.as_deref()); let option_fn_name = option_fn .and_then(ItemSigConfig::name) .cloned() .unwrap_or_else(|| { let base_name = common_name.unwrap_or(&member.name.snake); // It's important to preserve the original identifier span // to make IDE's "go to definition" work correctly. It's so // important that this doesn't use `format_ident!`, but rather // `syn::Ident::new` to set the span of the `Ident` explicitly. syn::Ident::new(&format!("maybe_{}", base_name.raw_name()), base_name.span()) }); let default = member.config.default.as_deref().and_then(|default| { if let Some(setters) = &member.config.setters { if let Some(default) = &setters.doc.default { if default.skip.is_present() { return None; } } } let default = default .clone() .or_else(|| well_known_default(&member.ty.norm)) .unwrap_or_else(|| { let ty = &member.ty.norm; syn::parse_quote!(<#ty as Default>::default()) }); let file = syn::parse_quote!(const _: () = #default;); let file = prettyplease::unparse(&file); let begin = file.find('=')?; let default = file.get(begin + 1..)?.trim(); let default = default.strip_suffix(';')?; Some(default.to_owned()) }); let default = default.as_deref(); // FIXME: the docs shouldn't reference the companion setter if that // setter has a lower visibility. let some_fn_docs = some_fn .and_then(ItemSigConfig::docs) .or(common_docs) .unwrap_or(&member.docs); let setter_names = (&some_fn_name, &option_fn_name); let some_fn_docs = { let header = optional_setter_docs(default, setter_names); doc(&header).chain(some_fn_docs.iter().cloned()).collect() }; let option_fn_docs = option_fn .and_then(ItemSigConfig::docs) .or(common_docs) .unwrap_or(&member.docs); let option_fn_docs = { let header = optional_setter_docs(default, setter_names); doc(&header).chain(option_fn_docs.iter().cloned()).collect() }; let some_fn = SetterItem { name: some_fn_name, vis: some_fn .and_then(ItemSigConfig::vis) .or(common_vis) .unwrap_or(&builder_type.vis) .clone(), docs: some_fn_docs, }; let option_fn = config.and_then(|config| config.fns.option_fn.as_deref()); let option_fn = SetterItem { name: option_fn_name, vis: option_fn .and_then(ItemSigConfig::vis) .or(common_vis) .unwrap_or(&builder_type.vis) .clone(), docs: option_fn_docs, }; Self::Optional(OptionalSettersItems { some_fn, option_fn }) } } fn optional_setter_docs( default: Option<&str>, (some_fn, option_fn): (&syn::Ident, &syn::Ident), ) -> String { let default = default .map(|default| { if default.contains('\n') || default.len() > 80 { format!(" _**Default:**_\n````rust,ignore\n{default}\n````\n\n") } else { format!(" _**Default:**_ ```{default}```.\n\n") } }) .unwrap_or_default(); format!( "_**Optional** \ ([Some](Self::{some_fn}()) / [Option](Self::{option_fn}()) setters).\ _{default}\ \n\n" ) } fn well_known_default(ty: &syn::Type) -> Option { let path = match ty { syn::Type::Path(syn::TypePath { path, qself: None }) => path, _ => return None, }; use syn::parse_quote as pq; let ident = path.get_ident()?.to_string(); let value = match ident.as_str() { "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32" | "i64" | "i128" | "isize" => pq!(0), "f32" | "f64" => pq!(0.0), "bool" => pq!(false), "char" => pq!('\0'), "String" => pq!(""), _ => return None, }; Some(value) } /// Unfortunately there is no `syn::Parse` impl for `PatIdent` directly, /// so we use this workaround instead. fn pat_ident(ident_name: &'static str) -> syn::PatIdent { let ident = syn::Ident::new(ident_name, Span::call_site()); let pat: syn::Pat = syn::parse_quote!(#ident); match pat { syn::Pat::Ident(pat_ident) => pat_ident, _ => unreachable!("can't parse something else than PatIdent here: {pat:?}"), } } bon-macros-3.7.1/src/builder/builder_gen/start_fn.rs000064400000000000000000000117721046102023000205660ustar 00000000000000use super::member::PosFnMember; use crate::util::prelude::*; impl super::BuilderGenCtx { pub(super) fn start_fn(&self) -> syn::ItemFn { let builder_ident = &self.builder_type.ident; let docs = &self.start_fn.docs; let vis = &self.start_fn.vis; let start_fn_ident = &self.start_fn.ident; // TODO: we can use a shorter syntax with anonymous lifetimes to make // the generated code and function signature displayed by rust-analyzer // a bit shorter and easier to read. However, the caveat is that we can // do this only for lifetimes that have no bounds and if they don't appear // in the where clause. Research `darling`'s lifetime tracking API and // maybe implement this in the future let generics = self.start_fn.generics.as_ref().unwrap_or(&self.generics); let generics_decl = &generics.decl_without_defaults; let where_clause = &generics.where_clause; let generic_args = &self.generics.args; let receiver = self.receiver(); let receiver_field_init = receiver.map(|receiver| { let ident = &receiver.field_ident; let self_token = &receiver.with_self_keyword.self_token; quote! { #ident: #self_token, } }); let receiver = receiver.map(|receiver| { let mut receiver = receiver.with_self_keyword.clone(); if receiver.reference.is_none() { receiver.mutability = None; } quote! { #receiver, } }); let start_fn_params = self.start_fn_args().map(PosFnMember::fn_input_param); // Assign `start_fn_args` to intermediate variables, which may be used // by custom fields init expressions. This is needed only if there is // a conversion configured for the `start_fn` members, otherwise these // are already available in scope as function arguments directly. let start_fn_vars = self.start_fn_args().filter_map(|member| { let ident = &member.ident; let ty = &member.ty.orig; let conversion = member.conversion()?; Some(quote! { let #ident: #ty = #conversion; }) }); let start_fn_args_fields_idents = self.start_fn_args().map(|member| &member.ident); // Create custom fields in separate variables. This way custom fields // declared lower in the struct definition can reference custom fields // declared higher in their init expressions. let custom_fields_vars = self.custom_fields().map(|field| { let ident = &field.ident; let ty = &field.norm_ty; let init = field .init .as_ref() .map(|init| self.sanitize_expr(init)) .unwrap_or_else(|| quote! { ::core::default::Default::default() }); quote! { let #ident: #ty = #init; } }); let custom_fields_idents = self.custom_fields().map(|field| &field.ident); let ide_hints = self.ide_hints(); // `Default` trait implementation is provided only for tuples up to 12 // elements in the standard library 😳: // https://github.com/rust-lang/rust/blob/67bb749c2e1cf503fee64842963dd3e72a417a3f/library/core/src/tuple.rs#L213 let named_members_field_init = if self.named_members().take(13).count() <= 12 && self.const_.is_none() { quote!(::core::default::Default::default()) } else { let none = format_ident!("None"); let nones = self.named_members().map(|_| &none); quote! { (#(#nones,)*) } }; let const_ = &self.const_; syn::parse_quote! { #(#docs)* #[inline(always)] #[allow( // This is intentional. We want the builder syntax to compile away clippy::inline_always, // We normalize `Self` references intentionally to simplify code generation clippy::use_self, // Let's keep it as non-const for now to avoid restricting ourselfves to only // const operations. clippy::missing_const_for_fn, )] #vis #const_ fn #start_fn_ident< #(#generics_decl),* >( #receiver #(#start_fn_params,)* ) -> #builder_ident< #(#generic_args,)* > #where_clause { #ide_hints #( #start_fn_vars )* #( #custom_fields_vars )* #builder_ident { __unsafe_private_phantom: ::core::marker::PhantomData, #( #custom_fields_idents, )* #receiver_field_init #( #start_fn_args_fields_idents, )* __unsafe_private_named: #named_members_field_init, } } } } } bon-macros-3.7.1/src/builder/builder_gen/state_mod.rs000064400000000000000000000240621046102023000207210ustar 00000000000000use super::BuilderGenCtx; use crate::util::prelude::*; pub(super) struct StateModGenCtx<'a> { base: &'a BuilderGenCtx, stateful_members_snake: Vec<&'a syn::Ident>, stateful_members_pascal: Vec<&'a syn::Ident>, sealed_item_decl: TokenStream, sealed_item_impl: TokenStream, } impl<'a> StateModGenCtx<'a> { pub(super) fn new(builder_gen: &'a BuilderGenCtx) -> Self { Self { base: builder_gen, stateful_members_snake: builder_gen .stateful_members() .map(|member| &member.name.snake) .collect(), stateful_members_pascal: builder_gen .stateful_members() .map(|member| &member.name.pascal) .collect(), // A const item in a trait makes it non-object safe, which is convenient, // because we want that restriction in this case. sealed_item_decl: quote! { #[doc(hidden)] const SEALED: sealed::Sealed; }, sealed_item_impl: quote! { const SEALED: sealed::Sealed = sealed::Sealed; }, } } pub(super) fn state_mod(&self) -> TokenStream { let bon = &self.base.bon; let vis = &self.base.state_mod.vis; let vis_child = &self.base.state_mod.vis_child; let vis_child_child = &self.base.state_mod.vis_child_child; let state_mod_docs = &self.base.state_mod.docs; let state_mod_ident = &self.base.state_mod.ident; let state_trait = self.state_trait(); let is_complete_trait = self.is_complete_trait(); let members_names_mod = self.members_names_mod(); let state_transitions = self.state_transitions(); quote! { #[allow( // These are intentional. By default, the builder module is private // and can't be accessed outside of the module where the builder // type is defined. This makes the builder type "anonymous" to // the outside modules, which is a good thing if users don't want // to expose this API surface. // // Also, there are some genuinely private items like the `Sealed` // enum and members "name" enums that we don't want to expose even // to the module that defines the builder. These APIs are not // public, and users instead should only reference the traits // and state transition type aliases from here. unnameable_types, unreachable_pub, clippy::redundant_pub_crate )] #( #state_mod_docs )* #vis mod #state_mod_ident { #[doc(inline)] #vis_child use #bon::__::{IsSet, IsUnset}; use #bon::__::{Set, Unset}; mod sealed { #vis_child_child struct Sealed; } #state_trait #is_complete_trait #members_names_mod #state_transitions } } } fn state_transitions(&self) -> TokenStream { // Not using `Iterator::zip` here to make it possible to scale this in // case if we add more vecs here. We are not using `Itertools`, so // its `multiunzip` is not available. let mut set_members_structs = Vec::with_capacity(self.stateful_members_snake.len()); let mut state_impls = Vec::with_capacity(self.stateful_members_snake.len()); let vis_child = &self.base.state_mod.vis_child; let sealed_item_impl = &self.sealed_item_impl; for member in self.base.stateful_members() { let member_pascal = &member.name.pascal; let docs = format!( "Represents a [`State`] that has [`IsSet`] implemented for [`State::{member_pascal}`].\n\n\ The state for all other members is left the same as in the input state.", ); let struct_ident = format_ident!("Set{}", member.name.pascal_str); set_members_structs.push(quote! { #[doc = #docs] #vis_child struct #struct_ident( // We `S` in an `fn() -> ...` to make the compiler think // that the builder doesn't "own" an instance of `S`. // This removes unnecessary requirements when evaluating the // applicability of the auto traits. ::core::marker::PhantomData S> ); }); let states = self.base.stateful_members().map(|other_member| { if other_member.is(member) { let member_snake = &member.name.snake; quote! { Set } } else { let member_pascal = &other_member.name.pascal; quote! { S::#member_pascal } } }); let stateful_members_pascal = &self.stateful_members_pascal; state_impls.push(quote! { #[doc(hidden)] impl State for #struct_ident { #( type #stateful_members_pascal = #states; )* #sealed_item_impl } }); } let stateful_members_snake = &self.stateful_members_snake; let stateful_members_pascal = &self.stateful_members_pascal; quote! { /// Represents a [`State`] that has [`IsUnset`] implemented for all members. /// /// This is the initial state of the builder before any setters are called. #vis_child struct Empty(()); #( #set_members_structs )* #[doc(hidden)] impl State for Empty { #( type #stateful_members_pascal = Unset; )* #sealed_item_impl } #( #state_impls )* } } fn state_trait(&self) -> TokenStream { let assoc_types_docs = self.stateful_members_snake.iter().map(|member_snake| { format!( "Type state of the member `{member_snake}`.\n\ \n\ It can implement either [`IsSet`] or [`IsUnset`]", ) }); let vis_child = &self.base.state_mod.vis_child; let sealed_item_decl = &self.sealed_item_decl; let stateful_members_pascal = &self.stateful_members_pascal; let docs_suffix = if stateful_members_pascal.is_empty() { "" } else { "\n\n\ You can use the associated types of this trait to control the state of individual members \ with the [`IsSet`] and [`IsUnset`] traits. You can change the state of the members with \ the `Set*` structs available in this module." }; let docs = format!( "Builder's type state specifies if members are set or not (unset).{docs_suffix}" ); quote! { #[doc = #docs] #vis_child trait State: ::core::marker::Sized { #( #[doc = #assoc_types_docs] type #stateful_members_pascal; )* #sealed_item_decl } } } fn is_complete_trait(&self) -> TokenStream { let required_members_pascal = self .base .named_members() .filter(|member| member.is_required()) .map(|member| &member.name.pascal) .collect::>(); // Associated types bounds syntax that provides implied bounds for them // is available only since Rust 1.79.0. So this is an opt-in feature that // bumps the MSRV of the crate. See more details in the comment on this // cargo feature's declaration in `bon/Cargo.toml`. let maybe_assoc_type_bounds = cfg!(feature = "implied-bounds").then(|| { quote! { < #( #required_members_pascal: IsSet, )* > } }); let vis_child = &self.base.state_mod.vis_child; let sealed_item_decl = &self.sealed_item_decl; let sealed_item_impl = &self.sealed_item_impl; let builder_ident = &self.base.builder_type.ident; let finish_fn = &self.base.finish_fn.ident; let docs = format!( "Marker trait that indicates that all required members are set.\n\n\ In this state, you can finish building by calling the method \ [`{builder_ident}::{finish_fn}()`](super::{builder_ident}::{finish_fn}())", ); quote! { #[doc = #docs] #vis_child trait IsComplete: State #maybe_assoc_type_bounds { #sealed_item_decl } #[doc(hidden)] impl IsComplete for S where #( S::#required_members_pascal: IsSet, )* { #sealed_item_impl } } } fn members_names_mod(&self) -> TokenStream { let vis_child_child = &self.base.state_mod.vis_child_child; let stateful_members_snake = &self.stateful_members_snake; // The message is defined separately to make it single-line in the // generated code. This simplifies the task of removing unnecessary // attributes from the generated code when preparing for demo purposes. let deprecated_msg = "\ this should not be used directly; it is an implementation detail; \ use the Set* type aliases to control the \ state of members instead"; quote! { #[deprecated = #deprecated_msg] #[doc(hidden)] #[allow(non_camel_case_types)] mod members { #( #vis_child_child struct #stateful_members_snake(()); )* } } } } bon-macros-3.7.1/src/builder/builder_gen/top_level_config/mod.rs000064400000000000000000000247021046102023000230400ustar 00000000000000mod on; pub(crate) use on::OnConfig; use crate::parsing::{BonCratePath, ItemSigConfig, ItemSigConfigParsing, SpannedKey}; use crate::util::prelude::*; use darling::ast::NestedMeta; use darling::FromMeta; use syn::parse::Parser; use syn::punctuated::Punctuated; use syn::ItemFn; fn parse_finish_fn(meta: &syn::Meta) -> Result { ItemSigConfigParsing { meta, reject_self_mentions: Some("builder struct's impl block"), } .parse() } fn parse_builder_type(meta: &syn::Meta) -> Result { ItemSigConfigParsing { meta, reject_self_mentions: Some("builder struct"), } .parse() } fn parse_state_mod(meta: &syn::Meta) -> Result { ItemSigConfigParsing { meta, reject_self_mentions: Some("builder's state module"), } .parse() } fn parse_start_fn(meta: &syn::Meta) -> Result { ItemSigConfigParsing { meta, reject_self_mentions: None, } .parse() } #[derive(Debug, FromMeta)] pub(crate) struct TopLevelConfig { /// Specifies whether the generated functions should be `const`. /// /// It is marked as `#[darling(skip)]` because `const` is a keyword, that /// can't be parsed as a `syn::Ident` and therefore as a `syn::Meta` item. /// We manually parse it from the beginning `builder(...)`. #[darling(skip)] pub(crate) const_: Option, /// Overrides the path to the `bon` crate. This is useful when the macro is /// wrapped in another macro that also reexports `bon`. #[darling(rename = "crate", default)] pub(crate) bon: BonCratePath, #[darling(default, with = parse_start_fn)] pub(crate) start_fn: ItemSigConfig, #[darling(default, with = parse_finish_fn)] pub(crate) finish_fn: ItemSigConfig, #[darling(default, with = parse_builder_type)] pub(crate) builder_type: ItemSigConfig, #[darling(default, with = parse_state_mod)] pub(crate) state_mod: ItemSigConfig, #[darling(multiple, with = crate::parsing::parse_non_empty_paren_meta_list)] pub(crate) on: Vec, /// Specifies the derives to apply to the builder. #[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list)] pub(crate) derive: DerivesConfig, } impl TopLevelConfig { pub(crate) fn parse_for_fn(fn_item: &ItemFn, config: Option) -> Result { let other_configs = fn_item .attrs .iter() .filter(|attr| attr.path().is_ident("builder")) .map(|attr| { if let syn::Meta::List(_) = attr.meta { crate::parsing::require_non_empty_paren_meta_list_or_name_value(&attr.meta)?; } let meta_list = darling::util::parse_attribute_to_meta_list(attr)?; Ok(meta_list.tokens) }); let configs = config .map(Ok) .into_iter() .chain(other_configs) .collect::>>()?; let me = Self::parse_for_any(configs)?; if me.start_fn.name.is_none() { let ItemSigConfig { name: _, vis, docs } = &me.start_fn; let unexpected_param = None .or_else(|| vis.as_ref().map(SpannedKey::key)) .or_else(|| docs.as_ref().map(SpannedKey::key)); if let Some(unexpected_param) = unexpected_param { bail!( unexpected_param, "#[builder(start_fn({unexpected_param}))] requires that you \ also specify #[builder(start_fn(name))] which makes the starting \ function not to replace the positional function under the #[builder] \ attribute; by default (without the explicit #[builder(start_fn(name))]) \ the name, visibility and documentation of the positional \ function are all copied to the starting function, and the positional \ function under the #[builder] attribute becomes private with \ #[doc(hidden)] and it's renamed (the name is not guaranteed \ to be stable) to make it inaccessible even within the current module", ); } } Ok(me) } pub(crate) fn parse_for_struct(configs: Vec) -> Result { Self::parse_for_any(configs) } fn parse_for_any(mut configs: Vec) -> Result { fn parse_const_prefix( parse: syn::parse::ParseStream<'_>, ) -> syn::Result<(Option, TokenStream)> { let const_ = parse.parse().ok(); if const_.is_some() && !parse.is_empty() { parse.parse::()?; } let rest = parse.parse()?; Ok((const_, rest)) } // Try to parse the first token of the first config as `const` token. // We have to do this manually because `syn` doesn't support parsing // keywords in the `syn::Meta` keys. Yeah, unfortunately it means that // the users must ensure they place `const` right at the beginning of // their `#[builder(...)]` attributes. let mut const_ = None; if let Some(config) = configs.first_mut() { (const_, *config) = parse_const_prefix.parse2(std::mem::take(config))?; } let configs = configs .into_iter() .map(NestedMeta::parse_meta_list) .collect::, _>>()? .into_iter() .flatten() .collect::>(); // This is a temporary hack. We only allow `on(_, required)` as the // first `on(...)` clause. Instead we should implement an extended design: // https://github.com/elastio/bon/issues/152 let mut on_configs = configs .iter() .enumerate() .filter_map(|(i, meta)| match meta { NestedMeta::Meta(syn::Meta::List(meta)) if meta.path.is_ident("on") => { Some((i, meta)) } _ => None, }) .peekable(); while let Some((i, _)) = on_configs.next() { if let Some((j, next_on)) = on_configs.peek() { if *j != i + 1 { bail!( next_on, "this `on(...)` clause is out of order; all `on(...)` \ clauses must be consecutive; there shouldn't be any \ other parameters between them", ) } } } let me = Self { const_, ..Self::from_list(&configs)? }; if let Some(on) = me.on.iter().skip(1).find(|on| on.required.is_present()) { bail!( &on.required.span(), "`required` can only be specified in the first `on(...)` clause; \ this restriction may be lifted in the future", ); } if let Some(first_on) = me.on.first().filter(|on| on.required.is_present()) { if !matches!(first_on.type_pattern, syn::Type::Infer(_)) { bail!( &first_on.type_pattern, "`required` can only be used with the wildcard type pattern \ i.e. `on(_, required)`; this restriction may be lifted in the future", ); } } Ok(me) } } #[derive(Debug, Clone, Default, FromMeta)] pub(crate) struct DerivesConfig { #[darling(rename = "Clone")] pub(crate) clone: Option, #[darling(rename = "Debug")] pub(crate) debug: Option, #[darling(rename = "Into")] pub(crate) into: darling::util::Flag, #[darling(rename = "IntoFuture")] pub(crate) into_future: Option, } #[derive(Debug, Clone, Default)] pub(crate) struct DeriveConfig { pub(crate) bounds: Option>, } #[derive(Debug, Clone)] pub(crate) struct IntoFutureConfig { pub(crate) box_ident: syn::Ident, pub(crate) is_send: bool, } impl syn::parse::Parse for IntoFutureConfig { fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { // Parse "Box" as the required first argument. let box_ident: syn::Ident = input.parse()?; if box_ident != "Box" { return Err(syn::Error::new( box_ident.span(), "expected `Box` as the first argument, only boxed futures are supported", )); } // Check for optional ", ?Send" part. let is_send = if input.peek(syn::Token![,]) { input.parse::()?; // Parse "?Send" as a single unit. if input.peek(syn::Token![?]) { input.parse::()?; let send_ident: syn::Ident = input.parse()?; if send_ident != "Send" { return Err(syn::Error::new( send_ident.span(), "expected `Send` after ?", )); } false } else { return Err(input.error("expected `?Send` as the second argument")); } } else { true }; // Ensure no trailing tokens. if !input.is_empty() { return Err(input.error("unexpected tokens after arguments")); } Ok(Self { box_ident, is_send }) } } impl FromMeta for IntoFutureConfig { fn from_meta(meta: &syn::Meta) -> Result { let meta = match meta { syn::Meta::List(meta) => meta, _ => bail!(meta, "expected an attribute of form `IntoFuture(Box, ...)`"), }; meta.require_parens_delim()?; let me = syn::parse2(meta.tokens.clone())?; Ok(me) } } impl FromMeta for DeriveConfig { fn from_meta(meta: &syn::Meta) -> Result { if let syn::Meta::Path(_) = meta { return Ok(Self { bounds: None }); } meta.require_list()?.require_parens_delim()?; #[derive(FromMeta)] struct Parsed { #[darling(with = crate::parsing::parse_paren_meta_list_with_terminated)] bounds: Punctuated, } let Parsed { bounds } = Parsed::from_meta(meta)?; Ok(Self { bounds: Some(bounds), }) } } bon-macros-3.7.1/src/builder/builder_gen/top_level_config/on.rs000064400000000000000000000101731046102023000226720ustar 00000000000000use crate::util::prelude::*; use darling::util::Flag; use darling::FromMeta; use syn::parse::Parse; use syn::spanned::Spanned; use syn::visit::Visit; #[derive(Debug)] pub(crate) struct OnConfig { pub(crate) type_pattern: syn::Type, pub(crate) into: Flag, pub(crate) overwritable: Flag, pub(crate) required: Flag, pub(crate) setters: OnSettersConfig, } #[derive(Debug, Default, FromMeta)] pub(crate) struct OnSettersConfig { #[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list)] pub(crate) doc: OnSettersDocConfig, } #[derive(Debug, Default, FromMeta)] pub(crate) struct OnSettersDocConfig { #[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list)] pub(crate) default: OnSettersDocDefaultConfig, } #[derive(Debug, Default, FromMeta)] pub(crate) struct OnSettersDocDefaultConfig { pub(crate) skip: Flag, } impl Parse for OnConfig { fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { let type_pattern = input.parse()?; let comma = input.parse::()?; let rest: TokenStream = input.parse()?; #[derive(FromMeta)] struct Parsed { into: Flag, overwritable: Flag, required: Flag, #[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list)] setters: OnSettersConfig, } if rest.is_empty() { return Err(syn::Error::new( comma.span(), "expected at least one parameter after the comma in `on(type_pattern, ...)`", )); } let parsed: Parsed = crate::parsing::parse_non_empty_paren_meta_list( &syn::parse_quote_spanned!(comma.span=> on(#rest)), )?; if !cfg!(feature = "experimental-overwritable") && parsed.overwritable.is_present() { return Err(syn::Error::new( parsed.overwritable.span(), "🔬 `overwritable` attribute is experimental and requires \ \"experimental-overwritable\" cargo feature to be enabled; \ we would be glad to make this attribute stable if you find it useful; \ please leave a 👍 reaction under the issue https://github.com/elastio/bon/issues/149 \ to help us measure the demand for this feature; it would be \ double-awesome if you could also describe your use case in \ a comment under the issue for us to understand how it's used \ in practice", )); } struct FindAttr { attr: Option, } impl Visit<'_> for FindAttr { fn visit_attribute(&mut self, attr: &'_ syn::Attribute) { self.attr.get_or_insert_with(|| attr.span()); } } let mut find_attr = FindAttr { attr: None }; find_attr.visit_type(&type_pattern); if let Some(attr) = find_attr.attr { return Err(syn::Error::new( attr, "nested attributes are not allowed in the type pattern of \ #[builder(on(type_pattern, ...))]", )); } // The validation is done in the process of matching the types. To make // sure that matching traverses the full pattern we match it with itself. let type_pattern_matches_itself = type_pattern.matches(&type_pattern)?; assert!( type_pattern_matches_itself, "BUG: the type pattern does not match itself: {type_pattern:#?}" ); Ok(Self { type_pattern, into: parsed.into, overwritable: parsed.overwritable, required: parsed.required, setters: parsed.setters, }) } } impl FromMeta for OnConfig { fn from_meta(meta: &syn::Meta) -> Result { let meta = match meta { syn::Meta::List(meta) => meta, _ => bail!( meta, "expected an attribute of form `on(type_pattern, ...)`" ), }; let me = syn::parse2(meta.tokens.clone())?; Ok(me) } } bon-macros-3.7.1/src/builder/item_fn.rs000064400000000000000000000027251046102023000161060ustar 00000000000000use super::builder_gen::input_fn::{FnInputCtx, FnInputCtxParams}; use super::builder_gen::{MacroOutput, TopLevelConfig}; use crate::normalization::SyntaxVariant; use crate::util::prelude::*; use syn::visit_mut::VisitMut; pub(crate) fn generate( config: TopLevelConfig, orig_fn: syn::ItemFn, namespace: &crate::normalization::GenericsNamespace, ) -> Result { let mut norm_fn = orig_fn.clone(); crate::normalization::NormalizeLifetimes::new(namespace).visit_item_fn_mut(&mut norm_fn); crate::normalization::NormalizeImplTraits::new(namespace).visit_item_fn_mut(&mut norm_fn); let fn_item = SyntaxVariant { orig: orig_fn, norm: norm_fn, }; let ctx = FnInputCtx::new(FnInputCtxParams { namespace, fn_item, impl_ctx: None, config, })?; let adapted_fn = ctx.adapted_fn()?; let warnings = ctx.warnings(); let MacroOutput { start_fn, other_items, } = ctx.into_builder_gen_ctx()?.output()?; Ok(quote! { // Keep original function at the top. It seems like rust-analyzer // does better job of highlighting syntax when it is here. Assuming // this is because rust-analyzer prefers the first occurrence of the // span when highlighting. // // See this issue for details: https://github.com/rust-lang/rust-analyzer/issues/18438 #adapted_fn #warnings #start_fn #other_items }) } bon-macros-3.7.1/src/builder/item_impl.rs000064400000000000000000000175011046102023000164420ustar 00000000000000use super::builder_gen::input_fn::{FnInputCtx, FnInputCtxParams, ImplCtx}; use super::builder_gen::TopLevelConfig; use crate::normalization::{GenericsNamespace, SyntaxVariant}; use crate::parsing::BonCratePath; use crate::util::prelude::*; use darling::FromMeta; use std::rc::Rc; use syn::visit::Visit; use syn::visit_mut::VisitMut; #[derive(FromMeta)] pub(crate) struct ImplInputParams { /// Overrides the path to the `bon` crate. This is useful when the macro is /// wrapped in another macro that also reexports `bon`. #[darling(rename = "crate", default)] bon: BonCratePath, } // ImplInputParams will evolve in the future where we'll probably want to move from it #[allow(clippy::needless_pass_by_value)] pub(crate) fn generate( impl_params: ImplInputParams, mut orig_impl_block: syn::ItemImpl, ) -> Result { let mut namespace = GenericsNamespace::default(); namespace.visit_item_impl(&orig_impl_block); if let Some((_, trait_path, _)) = &orig_impl_block.trait_ { bail!(trait_path, "Impls of traits are not supported yet"); } let (builder_fns, other_items): (Vec<_>, Vec<_>) = orig_impl_block.items.into_iter().partition(|item| { let fn_item = match item { syn::ImplItem::Fn(fn_item) => fn_item, _ => return false, }; fn_item .attrs .iter() .any(|attr| attr.path().is_ident("builder")) }); if builder_fns.is_empty() { return Err(no_builder_attrs_error(&other_items)); } orig_impl_block.items = builder_fns; // We do this back-and-forth with normalizing various syntax and saving original // to provide cleaner code generation that is easier to consume for IDEs and for // rust-analyzer specifically. // // For codegen logic we would like to have everything normalized. For example, we // want to assume `Self` is replaced with the original type and all lifetimes are // named, and `impl Traits` are desugared into type parameters. // // However, in output code we want to preserve existing `Self` references to make // sure rust-analyzer highlights them properly. If we just strip `Self` from output // code, then rust-analyzer won't be able to associate what `Self` token maps to in // the input. It would highlight `Self` as an "unresolved symbol" let mut norm_impl_block = orig_impl_block.clone(); crate::normalization::NormalizeLifetimes::new(&namespace) .visit_item_impl_mut(&mut norm_impl_block); crate::normalization::NormalizeImplTraits::new(&namespace) .visit_item_impl_mut(&mut norm_impl_block); // Retain a variant of the impl block without the normalized `Self` mentions. // This way we preserve the original code that the user wrote with `Self` mentions // as much as possible, therefore IDE's are able to provide correct syntax highlighting // for `Self` mentions, because they aren't removed from the generated code output let mut norm_selfful_impl_block = norm_impl_block.clone(); crate::normalization::NormalizeSelfTy { self_ty: &norm_impl_block.self_ty.clone(), } .visit_item_impl_mut(&mut norm_impl_block); let impl_ctx = Rc::new(ImplCtx { self_ty: norm_impl_block.self_ty, generics: norm_impl_block.generics, allow_attrs: norm_impl_block .attrs .iter() .filter_map(syn::Attribute::to_allow) .collect(), }); let outputs = orig_impl_block .items .into_iter() .zip(norm_impl_block.items) .map(|(orig_item, norm_item)| { let norm_fn = match norm_item { syn::ImplItem::Fn(norm_fn) => norm_fn, _ => unreachable!(), }; let orig_fn = match orig_item { syn::ImplItem::Fn(orig_fn) => orig_fn, _ => unreachable!(), }; let norm_fn = conv_impl_item_fn_into_fn_item(norm_fn)?; let orig_fn = conv_impl_item_fn_into_fn_item(orig_fn)?; let mut config = TopLevelConfig::parse_for_fn(&orig_fn, None)?; if let BonCratePath::Explicit(path) = config.bon { bail!( &path, "`crate` parameter should be specified via `#[bon(crate = path::to::bon)]` \ when impl block syntax is used; no need to specify it in the method's \ `#[builder]` attribute" ); } config.bon.clone_from(&impl_params.bon); let fn_item = SyntaxVariant { orig: orig_fn, norm: norm_fn, }; let ctx = FnInputCtx::new(FnInputCtxParams { namespace: &namespace, fn_item, impl_ctx: Some(impl_ctx.clone()), config, })?; let adapted_fn = ctx.adapted_fn()?; let warnings = ctx.warnings(); let mut output = ctx.into_builder_gen_ctx()?.output()?; output.other_items.extend(warnings); Result::<_>::Ok((adapted_fn, output)) }) .collect::>>()?; let new_impl_items = outputs.iter().flat_map(|(adapted_fn, output)| { let start_fn = &output.start_fn; [syn::parse_quote!(#adapted_fn), syn::parse_quote!(#start_fn)] }); norm_selfful_impl_block.items = other_items; norm_selfful_impl_block.items.extend(new_impl_items); let other_items = outputs.iter().map(|(_, output)| &output.other_items); Ok(quote! { // Keep the original impl block at the top. It seems like rust-analyzer // does better job of highlighting syntax when it is here. Assuming // this is because rust-analyzer prefers the first occurrence of the // span when highlighting. // // See this issue for details: https://github.com/rust-lang/rust-analyzer/issues/18438 #norm_selfful_impl_block #(#other_items)* }) } fn conv_impl_item_fn_into_fn_item(func: syn::ImplItemFn) -> Result { let syn::ImplItemFn { attrs, vis, defaultness, sig, block, } = func; if let Some(defaultness) = &defaultness { bail!(defaultness, "Default functions are not supported yet"); } Ok(syn::ItemFn { attrs, vis, sig, block: Box::new(block), }) } fn no_builder_attrs_error(other_items: &[syn::ImplItem]) -> Error { let builder_like_err = other_items.iter().find_map(|item| { let item = match item { syn::ImplItem::Fn(fn_item) => fn_item, _ => return None, }; let builder_like = item .attrs .iter() .find(|attr| attr.path().ends_with_segment("builder"))?; let builder_like_str = darling::util::path_to_string(builder_like.path()); let builder_like_prefix = builder_like_str .strip_suffix("builder") .unwrap_or(&builder_like_str); Some(err!( &builder_like.path(), "#[bon] macro found no #[builder] attributes in the impl block, but \ it looks like this attribute was meant for #[bon]; note that #[bon] \ expects a bare #[builder] attribute without the `{builder_like_prefix}` \ prefix; #[builder] acts as a simple config attribute for the active \ #[bon] attribute in impl blocks; more info on inert vs active attributes: \ https://doc.rust-lang.org/reference/attributes.html#active-and-inert-attributes" )) }); if let Some(err) = builder_like_err { return err; } err!( &Span::call_site(), "there are no #[builder] functions in the impl block, so there is no \ need for a #[bon] attribute here" ) } bon-macros-3.7.1/src/builder/item_struct.rs000064400000000000000000000014041046102023000170200ustar 00000000000000use super::builder_gen::input_struct::StructInputCtx; use super::builder_gen::MacroOutput; use crate::util::prelude::*; pub(crate) fn generate(orig_struct: syn::ItemStruct) -> Result { let struct_ident = orig_struct.ident.clone(); let ctx = StructInputCtx::new(orig_struct)?; let MacroOutput { mut start_fn, other_items, } = ctx.into_builder_gen_ctx()?.output()?; let impl_generics = std::mem::take(&mut start_fn.sig.generics); let (generics_decl, generic_args, where_clause) = impl_generics.split_for_impl(); Ok(quote! { #[automatically_derived] impl #generics_decl #struct_ident #generic_args #where_clause { #start_fn } #other_items }) } bon-macros-3.7.1/src/builder/mod.rs000064400000000000000000000051651046102023000152450ustar 00000000000000mod builder_gen; pub(crate) mod item_impl; mod item_fn; mod item_struct; use crate::normalization::{ExpandCfg, Expansion, GenericsNamespace}; use crate::util; use crate::util::prelude::*; use builder_gen::TopLevelConfig; use syn::parse::Parser; use syn::visit::Visit; pub(crate) fn generate_from_derive(item: TokenStream) -> TokenStream { try_generate_from_derive(item).unwrap_or_else(Error::write_errors) } fn try_generate_from_derive(item: TokenStream) -> Result { match syn::parse2(item)? { syn::Item::Struct(item_struct) => item_struct::generate(item_struct), _ => bail!( &Span::call_site(), "only `struct` items are supported by the `#[derive(bon::Builder)]` attribute" ), } } pub(crate) fn generate_from_attr(params: TokenStream, item: TokenStream) -> TokenStream { crate::error::handle_errors(item.clone(), || { try_generate_from_attr(params.clone(), item) }) .unwrap_or_else(|fallback| [generate_completion_triggers(params), fallback].concat()) } fn try_generate_from_attr(params: TokenStream, item: TokenStream) -> Result { let item = syn::parse2(item)?; let ctx = ExpandCfg { current_macro: format_ident!("builder"), config: params, item, }; let input = match ctx.expand_cfg()? { Expansion::Expanded(input) => input, Expansion::Recurse(output) => return Ok(output), }; let main_output = match *input.item { syn::Item::Fn(item_fn) => { let mut namespace = GenericsNamespace::default(); namespace.visit_token_stream(input.config.clone()); namespace.visit_item_fn(&item_fn); let config = TopLevelConfig::parse_for_fn(&item_fn, Some(input.config.clone()))?; item_fn::generate(config, item_fn, &namespace)? } syn::Item::Struct(struct_item) => { bail!( &struct_item.struct_token, "to generate a builder for a struct, use `#[derive(bon::Builder)]` instead; \ `#[bon::builder]` syntax is supported only for functions starting with bon v3" ) } _ => bail!( &Span::call_site(), "only `fn` items are supported by the `#[bon::builder]` attribute" ), }; let output = [generate_completion_triggers(input.config), main_output].concat(); Ok(output) } fn generate_completion_triggers(params: TokenStream) -> TokenStream { let meta = util::ide::parse_comma_separated_meta .parse2(params) .unwrap_or_default(); util::ide::generate_completion_triggers(meta) } bon-macros-3.7.1/src/collections/map.rs000064400000000000000000000027171046102023000161330ustar 00000000000000use crate::util::prelude::*; use syn::parse::ParseStream; use syn::punctuated::Punctuated; use syn::{Expr, Token}; pub(crate) fn parse_macro_input( input: ParseStream<'_>, ) -> Result, syn::Error> { Punctuated::parse_terminated_with(input, parse_map_pair) } fn parse_map_pair(pair: ParseStream<'_>) -> Result<(Expr, Expr), syn::Error> { let key = pair.parse().map_err(|_| pair.error(pair))?; let _: Token![:] = pair.parse()?; let value = pair.parse()?; Ok((key, value)) } pub(crate) fn generate(entries: Punctuated<(Expr, Expr), Token![,]>) -> TokenStream { let error = super::validate_expressions_are_unique("key in the map", entries.iter().map(|(k, _)| k)); let items = entries.into_iter().map(|(key, value)| { quote!(( ::core::convert::Into::into(#key), ::core::convert::Into::into(#value), )) }); let output = quote! { ::core::iter::FromIterator::from_iter([ #(#items),* ]) }; // We unconditionally return `output` as part of the result to make sure IDEs // see this output and see what input tokens map to what output tokens. This // way IDEs can provide better help to the developer even when there are errors. error .map(|err| { let err = err.write_errors(); quote! {{ #err #output }} }) .unwrap_or(output) } bon-macros-3.7.1/src/collections/mod.rs000064400000000000000000000022021046102023000161220ustar 00000000000000pub(crate) mod map; pub(crate) mod set; use crate::util::prelude::*; use std::collections::HashSet; pub(crate) fn validate_expressions_are_unique<'k, I>(err_label: &str, items: I) -> Option where I: IntoIterator, { let mut errors = Error::accumulator(); let mut exprs = HashSet::new(); items .into_iter() .filter(|item| is_pure(item)) .for_each(|new_item| { let existing = match exprs.replace(new_item) { Some(existing) => existing, _ => return, }; errors.extend([ err!(existing, "duplicate {err_label}"), err!(new_item, "duplicate {err_label}"), ]); }); errors.finish().err() } fn is_pure(expr: &syn::Expr) -> bool { match expr { syn::Expr::Binary(binary) => is_pure(&binary.left) && is_pure(&binary.right), syn::Expr::Group(group) => is_pure(&group.expr), syn::Expr::Lit(_) => true, syn::Expr::Paren(paren) => is_pure(&paren.expr), syn::Expr::Unary(unary) => is_pure(&unary.expr), _ => false, } } bon-macros-3.7.1/src/collections/set.rs000064400000000000000000000015611046102023000161450ustar 00000000000000use crate::util::prelude::*; use syn::punctuated::Punctuated; use syn::{Expr, Token}; pub(crate) fn generate(entries: Punctuated) -> TokenStream { let error = super::validate_expressions_are_unique("value in the set", &entries); let entries = entries.into_iter(); let output = quote! { ::core::iter::FromIterator::from_iter([ #(::core::convert::Into::into(#entries)),* ]) }; // We unconditionally return `output` as part of the result to make sure IDEs // see this output and see what input tokens map to what output tokens. This // way IDEs can provide better help to the developer even when there are errors. error .map(|err| { let err = err.write_errors(); quote! {{ #err #output }} }) .unwrap_or(output) } bon-macros-3.7.1/src/error/mod.rs000064400000000000000000000141051046102023000147420ustar 00000000000000mod panic_context; use crate::util::prelude::*; use proc_macro2::{Group, TokenTree}; use std::panic::AssertUnwindSafe; use syn::parse::Parse; /// Handle the error or panic returned from the macro logic. /// /// The error may be either a syntax error or a logic error. In either case, we /// want to return a [`TokenStream`] that still provides good IDE experience. /// See [`Fallback`] for details. /// /// This function also catches panics. Importantly, we don't use panics for error /// handling! A panic is always a bug! However, we still handle it to provide /// better IDE experience even if there are some bugs in the macro implementation. /// /// One known bug that may cause panics when using Rust Analyzer is the following one: /// pub(crate) fn handle_errors( item: TokenStream, imp: impl FnOnce() -> Result, ) -> Result { let panic_listener = panic_context::PanicListener::register(); std::panic::catch_unwind(AssertUnwindSafe(imp)) .unwrap_or_else(|err| { let msg = panic_context::message_from_panic_payload(err.as_ref()) .unwrap_or_else(|| "".to_owned()); let msg = if msg.contains("unsupported proc macro punctuation character") { format!( "known bug in rust-analyzer: {msg};\n\ Github issue: https://github.com/rust-lang/rust-analyzer/issues/18244" ) } else { let context = panic_listener .get_last_panic() .map(|ctx| format!("\n\n{ctx}")) .unwrap_or_default(); format!( "proc-macro panicked (may be a bug in the crate `bon`): {msg};\n\ please report this issue at our Github repository: \ https://github.com/elastio/bon{context}" ) }; Err(err!(&Span::call_site(), "{msg}")) }) .map_err(|err| { let compile_error = err.write_errors(); let item = strip_invalid_tt(item); syn::parse2::(item) .map(|fallback| quote!(#compile_error #fallback)) .unwrap_or_else(|_| compile_error) }) } /// This is used in error handling for better IDE experience. For example, while /// the developer is writing the function code they'll have a bunch of syntax /// errors in the process. While that happens the proc macro should output at /// least some representation of the input code that the developer wrote with /// a separate compile error entry. This keeps the syntax highlighting and IDE /// type analysis, completions and other hints features working even if macro /// fails to parse some syntax or finds some other logic errors. /// /// This utility does very low-level parsing to strip doc comments from the /// input. This is to prevent the IDE from showing errors that "doc comments /// aren't allowed on function arguments". It also removes `#[builder(...)]` /// attributes that need to be processed by this macro to avoid the IDE from /// reporting those as well. struct Fallback { output: TokenStream, } impl Parse for Fallback { fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { let mut output = TokenStream::new(); loop { let found_attr = input.step(|cursor| { let mut cursor = *cursor; while let Some((tt, next)) = cursor.token_tree() { match &tt { TokenTree::Group(group) => { let fallback: Self = syn::parse2(group.stream())?; let new_group = Group::new(group.delimiter(), fallback.output); output.extend([TokenTree::Group(new_group)]); } TokenTree::Punct(punct) if punct.as_char() == '#' => { return Ok((true, cursor)); } TokenTree::Punct(_) | TokenTree::Ident(_) | TokenTree::Literal(_) => { output.extend([tt]); } } cursor = next; } Ok((false, cursor)) })?; if !found_attr { return Ok(Self { output }); } input .call(syn::Attribute::parse_outer)? .into_iter() .filter(|attr| !attr.is_doc_expr() && !attr.path().is_ident("builder")) .for_each(|attr| attr.to_tokens(&mut output)); } } } impl ToTokens for Fallback { fn to_tokens(&self, tokens: &mut TokenStream) { self.output.to_tokens(tokens); } } /// Workaround for the RA bug where it generates an invalid Punct token tree with /// the character `{`. /// /// ## Issues /// /// - [Bug in RA](https://github.com/rust-lang/rust-analyzer/issues/18244) /// - [Bug in proc-macro2](https://github.com/dtolnay/proc-macro2/issues/470) (already fixed) fn strip_invalid_tt(tokens: TokenStream) -> TokenStream { fn recurse(tt: TokenTree) -> TokenTree { match &tt { TokenTree::Group(group) => { let mut group = Group::new(group.delimiter(), strip_invalid_tt(group.stream())); group.set_span(group.span()); TokenTree::Group(group) } _ => tt, } } let mut tokens = tokens.into_iter(); std::iter::from_fn(|| { // In newer versions of `proc-macro2` this code panics here (earlier) loop { // If this panics it means the next token tree is invalid. // We can't do anything about it, and we just ignore it. // Luckily, `proc-macro2` consumes the invalid token tree // so this doesn't cause an infinite loop. if let Ok(tt) = std::panic::catch_unwind(AssertUnwindSafe(|| tokens.next())) { return tt.map(recurse); } } }) .collect() } bon-macros-3.7.1/src/error/panic_context.rs000064400000000000000000000153511046102023000170250ustar 00000000000000// The new name is used on newer rust versions #[rustversion::since(1.81.0)] use std::panic::PanicHookInfo as StdPanicHookInfo; // The deprecated name for is used on older rust versions #[rustversion::before(1.81.0)] use std::panic::PanicInfo as StdPanicHookInfo; use std::any::Any; use std::cell::RefCell; use std::fmt; use std::rc::Rc; fn with_global_panic_context(f: impl FnOnce(&mut GlobalPanicContext) -> T) -> T { thread_local! { /// A lazily initialized global panic context. It aggregates the panics from the /// current thread. This is used to capture info about the panic after the /// `catch_unwind` call and observe the context of the panic that happened. /// /// Unfortunately, we can't use a global static variable that would be /// accessible by all threads because `std::sync::Mutex::new` became /// `const` only in Rust 1.63.0, which is above our MSRV 1.59.0. However, /// a thread-local works perfectly fine for our use case because we don't /// spawn threads in proc macros. static GLOBAL: RefCell = const { RefCell::new(GlobalPanicContext { last_panic: None, initialized: false, }) }; } GLOBAL.with(|global| f(&mut global.borrow_mut())) } struct GlobalPanicContext { last_panic: Option, initialized: bool, } /// This struct without any fields exists to make sure that [`PanicListener::register()`] /// is called first before the code even attempts to get the last panic information. #[derive(Default)] pub(super) struct PanicListener { /// Required to make sure struct is not constructable via a struct literal /// in the code outside of this module. _private: (), } impl PanicListener { pub(super) fn register() -> Self { with_global_panic_context(Self::register_with_global) } fn register_with_global(global: &mut GlobalPanicContext) -> Self { if global.initialized { return Self { _private: () }; } let prev_panic_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |panic_info| { with_global_panic_context(|global| { let panics_count = global.last_panic.as_ref().map(|p| p.0.panics_count); let panics_count = panics_count.unwrap_or(0) + 1; global.last_panic = Some(PanicContext::from_std(panic_info, panics_count)); }); prev_panic_hook(panic_info); })); global.initialized = true; Self { _private: () } } /// Returns the last panic that happened since the [`PanicListener::register()`] call. // `self` is required to make sure this code runs only after we initialized // the global panic listener in the `register` method. #[allow(clippy::unused_self)] pub(super) fn get_last_panic(&self) -> Option { with_global_panic_context(|global| global.last_panic.clone()) } } /// Contains all the necessary bits of information about the occurred panic. #[derive(Clone)] pub(super) struct PanicContext(Rc); struct PanicContextShared { #[allow(clippy::incompatible_msrv)] backtrace: backtrace::Backtrace, location: Option, thread: String, /// Defines the number of panics that happened before this one. Each panic /// increments this counter. This is useful to know how many panics happened /// before the current one. panics_count: usize, } impl PanicContext { #[allow(clippy::incompatible_msrv)] fn from_std(std_panic_info: &StdPanicHookInfo<'_>, panics_count: usize) -> Self { let location = std_panic_info.location(); let current_thread = std::thread::current(); let thread_ = current_thread .name() .map(String::from) .unwrap_or_else(|| format!("{:?}", current_thread.id())); Self(Rc::new(PanicContextShared { // This is expected, and we handle the compatibility with // conditional compilation via the `rustversion` crate. #[allow(clippy::incompatible_msrv)] backtrace: backtrace::Backtrace::capture(), location: location.map(PanicLocation::from_std), thread: thread_, panics_count, })) } } impl fmt::Debug for PanicContext { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) } } impl fmt::Display for PanicContext { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let PanicContextShared { location, backtrace, thread, panics_count, } = &*self.0; write!(f, "panic occurred")?; if let Some(location) = location { write!(f, " at {location}")?; } write!(f, " in thread '{thread}'")?; if *panics_count > 1 { write!(f, " (total panics observed: {panics_count})")?; } #[allow(clippy::incompatible_msrv)] if backtrace.status() == backtrace::BacktraceStatus::Captured { write!(f, "\nbacktrace:\n{backtrace}")?; } Ok(()) } } /// Extract the message of a panic. pub(super) fn message_from_panic_payload(payload: &dyn Any) -> Option { if let Some(str_slice) = payload.downcast_ref::<&str>() { return Some((*str_slice).to_owned()); } if let Some(owned_string) = payload.downcast_ref::() { return Some(owned_string.clone()); } None } /// Location of the panic call site. #[derive(Clone)] struct PanicLocation { file: String, line: u32, col: u32, } impl PanicLocation { fn from_std(loc: &std::panic::Location<'_>) -> Self { Self { file: loc.file().to_owned(), line: loc.line(), col: loc.column(), } } } impl fmt::Display for PanicLocation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}:{}", self.file, self.line, self.col) } } #[rustversion::since(1.65.0)] mod backtrace { pub(super) use std::backtrace::{Backtrace, BacktraceStatus}; } #[rustversion::before(1.65.0)] mod backtrace { #[derive(PartialEq)] pub(super) enum BacktraceStatus { Captured, } pub(super) struct Backtrace; impl Backtrace { pub(super) fn capture() -> Self { Self } pub(super) fn status(&self) -> BacktraceStatus { BacktraceStatus::Captured } } impl std::fmt::Display for Backtrace { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("{update your Rust compiler to >=1.65.0 to see the backtrace}") } } } bon-macros-3.7.1/src/lib.rs000064400000000000000000000243611046102023000136050ustar 00000000000000#![doc = include_str!("../README.md")] #![allow( clippy::redundant_pub_crate, clippy::wildcard_imports, clippy::map_unwrap_or, clippy::items_after_statements, clippy::missing_const_for_fn, clippy::option_option, clippy::option_if_let_else, clippy::enum_glob_use, clippy::too_many_lines, clippy::if_not_else, // This crate doesn't expose complex public function signatures, any occurrences // of `&Option` are internal and they are likely just small private functions // that take a reference to an `Option` to make the callsite cleaner avoiding // the unnecessary `Option::as_ref()` calls. clippy::ref_option, // We can't use the explicit captures syntax due to the MSRV impl_trait_overcaptures, // There are too many false-positives for syn::Ident if_let_rescope, )] mod bon; mod builder; mod collections; mod error; mod normalization; mod parsing; mod privatize; mod util; #[cfg(test)] mod tests; /// Generates a builder for the function or method it's placed on. /// /// ## Quick examples /// /// You can turn a function with positional parameters into a function with /// named parameters just by placing the `#[builder]` attribute on top of it. /// /// ```rust ignore /// use bon::builder; /// /// #[builder] /// fn greet(name: &str, level: Option) -> String { /// let level = level.unwrap_or(0); /// /// format!("Hello {name}! Your level is {level}") /// } /// /// let greeting = greet() /// .name("Bon") /// .level(24) // <- setting `level` is optional, we could omit it /// .call(); /// /// assert_eq!(greeting, "Hello Bon! Your level is 24"); /// ``` /// /// You can also use the `#[builder]` attribute with associated methods: /// /// ```rust ignore /// use bon::bon; /// /// struct User { /// id: u32, /// name: String, /// } /// /// #[bon] // <- this attribute is required on impl blocks that contain `#[builder]` /// impl User { /// #[builder] /// fn new(id: u32, name: String) -> Self { /// Self { id, name } /// } /// /// #[builder] /// fn greet(&self, target: &str, level: Option<&str>) -> String { /// let level = level.unwrap_or("INFO"); /// let name = &self.name; /// /// format!("[{level}] {name} says hello to {target}") /// } /// } /// /// // The method named `new` generates `builder()/build()` methods /// let user = User::builder() /// .id(1) /// .name("Bon".to_owned()) /// .build(); /// /// // All other methods generate `method_name()/call()` methods /// let greeting = user /// .greet() /// .target("the world") /// // `level` is optional, we can omit it here /// .call(); /// /// assert_eq!(user.id, 1); /// assert_eq!(user.name, "Bon"); /// assert_eq!(greeting, "[INFO] Bon says hello to the world"); /// ``` /// /// The builder never panics. Any mistakes such as missing required fields /// or setting the same field twice will be reported as compile-time errors. /// /// See the full documentation for more details: /// - [Guide](https://bon-rs.com/guide/overview) /// - [Attributes reference](https://bon-rs.com/reference/builder) #[proc_macro_attribute] pub fn builder( params: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { builder::generate_from_attr(params.into(), item.into()).into() } /// Derives a builder for the struct it's placed on. /// /// ## Quick example /// /// Add a `#[derive(Builder)]` attribute to your struct to generate a `builder()` method for it. /// /// ```rust ignore /// use bon::{bon, builder, Builder}; /// /// #[derive(Builder)] /// struct User { /// name: String, /// is_admin: bool, /// level: Option, /// } /// /// let user = User::builder() /// .name("Bon".to_owned()) /// // `level` is optional, we could omit it here /// .level(24) /// // call setters in any order /// .is_admin(true) /// .build(); /// /// assert_eq!(user.name, "Bon"); /// assert_eq!(user.level, Some(24)); /// assert!(user.is_admin); /// ``` /// /// The builder never panics. Any mistakes such as missing required fields /// or setting the same field twice will be reported as compile-time errors. /// /// See the full documentation for more details: /// - [Guide](https://bon-rs.com/guide/overview) /// - [Attributes reference](https://bon-rs.com/reference/builder) #[proc_macro_derive(Builder, attributes(builder))] pub fn derive_builder(item: proc_macro::TokenStream) -> proc_macro::TokenStream { builder::generate_from_derive(item.into()).into() } /// Companion macro for [`builder`]. You should place it on top of the `impl` block /// where you want to define methods with the [`builder`] macro. /// /// It provides the necessary context to the [`builder`] macros on top of the functions /// inside of the `impl` block. You'll get compile errors without that context. /// /// # Quick example /// /// ```rust ignore /// use bon::bon; /// /// struct User { /// id: u32, /// name: String, /// } /// /// #[bon] // <- this attribute is required on impl blocks that contain `#[builder]` /// impl User { /// #[builder] /// fn new(id: u32, name: String) -> Self { /// Self { id, name } /// } /// /// #[builder] /// fn greet(&self, target: &str, level: Option<&str>) -> String { /// let level = level.unwrap_or("INFO"); /// let name = &self.name; /// /// format!("[{level}] {name} says hello to {target}") /// } /// } /// /// // The method named `new` generates `builder()/build()` methods /// let user = User::builder() /// .id(1) /// .name("Bon".to_owned()) /// .build(); /// /// // All other methods generate `method_name()/call()` methods /// let greeting = user /// .greet() /// .target("the world") /// // `level` is optional, we can omit it here /// .call(); /// /// assert_eq!(user.id, 1); /// assert_eq!(user.name, "Bon"); /// assert_eq!(greeting, "[INFO] Bon says hello to the world"); /// ``` /// /// The builder never panics. Any mistakes such as missing required fields /// or setting the same field twice will be reported as compile-time errors. /// /// For details on this macro [see the overview](https://bon-rs.com/guide/overview). /// /// [`builder`]: macro@builder #[proc_macro_attribute] pub fn bon( params: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { bon::generate(params.into(), item.into()).into() } /// Creates any map-like collection that implements [`FromIterator<(K, V)>`]. /// /// It automatically converts each key and value to the target type using [`Into`]. /// This way you can write a map of `String`s without the need to call `.to_owned()` /// or `.to_string()` on every string literal: /// /// ```rust /// # use bon_macros as bon; /// # use std::collections::HashMap; /// let map: HashMap = bon::map! { /// "key1": "value1", /// format!("key{}", 2): "value2", /// "key3": format!("value{}", 3), /// }; /// ``` /// /// There is no separate variant for [`BTreeMap`] and [`HashMap`]. Instead, you /// should annotate the return type of this macro with the desired type or make /// sure the compiler can infer the collection type from other context. /// /// # Compile errors /// /// The macro conservatively rejects duplicate keys in the map with a compile error. /// This check works for very simple expressions that involve only literal values. /// /// ```rust compile_fail /// # use bon_macros as bon; /// # use std::collections::HashMap; /// let map: HashMap = bon::map! { /// "key1": "value1", /// "key2": "value2" /// "key1": "value3", // compile error: `duplicate key in the map` /// }; /// ``` /// /// [`FromIterator<(K, V)>`]: https://doc.rust-lang.org/stable/std/iter/trait.FromIterator.html /// [`Into`]: https://doc.rust-lang.org/stable/std/convert/trait.Into.html /// [`BTreeMap`]: https://doc.rust-lang.org/stable/std/collections/struct.BTreeMap.html /// [`HashMap`]: https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html #[proc_macro] pub fn map(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let entries = syn::parse_macro_input!(input with collections::map::parse_macro_input); collections::map::generate(entries).into() } /// Creates any set-like collection that implements [`FromIterator`]. /// /// It automatically converts each value to the target type using [`Into`]. /// This way you can write a set of `String`s without the need to call `.to_owned()` /// or `.to_string()` on every string literal: /// /// ```rust /// # use bon_macros as bon; /// # use std::collections::HashSet; /// let set: HashSet = bon::set![ /// "value1", /// format!("value{}", 2), /// "value3", /// ]; /// ``` /// /// There is no separate variant for [`BTreeSet`] and [`HashSet`]. Instead, you /// should annotate the return type of this macro with the desired type or make /// sure the compiler can infer the collection type from other context. /// /// # Compile errors /// /// The macro conservatively rejects duplicate values in the set with a compile error. /// This check works for very simple expressions that involve only literal values. /// /// ```rust compile_fail /// # use bon_macros as bon; /// # use std::collections::HashSet; /// let set: HashSet = bon::set![ /// "value1", /// "value2" /// "value1", // compile error: `duplicate value in the set` /// ]; /// ``` /// /// [`FromIterator`]: https://doc.rust-lang.org/stable/std/iter/trait.FromIterator.html /// [`Into`]: https://doc.rust-lang.org/stable/std/convert/trait.Into.html /// [`BTreeSet`]: https://doc.rust-lang.org/stable/std/collections/struct.BTreeSet.html /// [`HashSet`]: https://doc.rust-lang.org/stable/std/collections/struct.HashSet.html #[proc_macro] pub fn set(input: proc_macro::TokenStream) -> proc_macro::TokenStream { use syn::punctuated::Punctuated; let entries = syn::parse_macro_input!(input with Punctuated::parse_terminated); collections::set::generate(entries).into() } // Private implementation detail. Don't use it directly! // This attribute renames the function provided to it to `__orig_{fn_name}` #[doc(hidden)] #[proc_macro_attribute] pub fn __privatize( _params: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { privatize::privatize_fn(input.into()).into() } bon-macros-3.7.1/src/normalization/cfg/mod.rs000064400000000000000000000221651046102023000172430ustar 00000000000000mod parse; mod visit; use crate::util::prelude::*; use darling::ast::NestedMeta; use parse::CfgSyntax; use std::collections::BTreeSet; use syn::parse::Parser; pub(crate) enum Expansion { Expanded(Expanded), Recurse(TokenStream), } pub(crate) struct Expanded { pub(crate) config: TokenStream, pub(crate) item: Box, } pub(crate) struct ExpandCfg { pub(crate) current_macro: syn::Ident, pub(crate) config: TokenStream, pub(crate) item: Box, } impl ExpandCfg { pub(crate) fn expand_cfg(mut self) -> Result { let predicates = self.collect_predicates()?; if predicates.is_empty() { return Ok(Expansion::Expanded(Expanded { config: self.config, item: self.item, })); } let predicate_results = match parse::parse_predicate_results(self.config.clone())? { Some(predicate_results) => predicate_results, None => return self.into_recursion(0, &predicates), }; // Update the config to remove the `@cfgs(...)` prefix from them self.config = predicate_results.rest; let true_predicates: BTreeSet<_> = predicates .iter() .map(ToString::to_string) .zip(predicate_results.results) .filter(|(_, result)| *result) .map(|(predicate, _)| predicate) .collect(); visit::visit_attrs(&mut self.item, |attrs| eval_cfgs(&true_predicates, attrs))?; // Collect predicates again after the cfgs were evaluated. This is needed // because cfgs may create new cfgs e.g.: `#[cfg_attr(foo, cfg_attr(bar, ...))]`. let predicates = self.collect_predicates()?; if predicates.is_empty() { return Ok(Expansion::Expanded(Expanded { config: self.config, item: self.item, })); } self.into_recursion(predicate_results.recursion_counter + 1, &predicates) } /// There is no mutation happening here, but we just reuse the same /// visitor implementation that works with mutable references. fn collect_predicates(&mut self) -> Result> { let mut predicates = vec![]; let mut visited = BTreeSet::new(); visit::visit_attrs(&mut self.item, |attrs| { for attr in attrs { let cfg_syntax = match CfgSyntax::from_meta(&attr.meta)? { Some(cfg_syntax) => cfg_syntax, None => continue, }; let predicate = match cfg_syntax { CfgSyntax::Cfg(predicate) => predicate, CfgSyntax::CfgAttr(cfg_attr) => cfg_attr.predicate.to_token_stream(), }; if visited.insert(predicate.to_string()) { predicates.push(predicate); } } Ok(true) })?; Ok(predicates) } fn into_recursion( self, recursion_counter: usize, predicates: &[TokenStream], ) -> Result { let Self { config, item, current_macro, } = self; let bon = NestedMeta::parse_meta_list(config.clone())? .iter() .find_map(|meta| match meta { NestedMeta::Meta(syn::Meta::NameValue(meta)) if meta.path.is_ident("crate") => { let path = &meta.value; Some(syn::Path::parse_mod_style.parse2(quote!(#path))) } _ => None, }) .transpose()? .unwrap_or_else(|| syn::parse_quote!(::bon)); let current_macro = syn::parse_quote!(#bon::#current_macro); let invocation_name = Self::unique_invocation_name(&item, ¤t_macro)?; let predicates = predicates.iter().enumerate().map(|(i, predicate)| { // We need to insert the recursion counter into the name so that // the name is unique on every recursive iteration of the cfg eval. let pred_id = format_ident!("{invocation_name}_{recursion_counter}_{i}"); quote!(#pred_id: #predicate) }); let expansion = quote! { #bon::__eval_cfg_callback! { {} #((#predicates))* #current_macro, #recursion_counter, ( #config ) #item } }; Ok(Expansion::Recurse(expansion)) } /// The macro `__eval_cfg_callback` needs to generate a use statement for /// every `cfg` predicate. To do that it needs to assign a unique name for /// every `use` statement so they doesn't collide with other items in /// the same scope and with each other. /// /// But.. How in the world can we generate a unique name for every `use` /// if proc macros are supposed to be stateless and deterministic? 😳 /// /// We could use a random number here, but that would make the output /// non-deterministic, which is not good for reproducible builds and /// generally may lead to some unexpected headaches 🤧. /// /// That's a silly problem, and here is a silly solution that doesn't /// work in 100% of the cases but it's probably good enough 😸. /// /// We just need to use some existing name as a source of uniqueness. /// The name of the item under the macro is a good candidate for that. /// If the item is a function, then we can use the function name as that /// reliable source of uniqueness. /// /// If the item is an `impl` block, then we have a bit of a problem because /// the `impl` block doesn't have a unique identifier attached to it, especially /// if the `self_ty` of the `impl` block isn't some simple syntax like a path. /// /// However, in most of the cases it will be a simple path, so its combination /// with the name of the first function in the `impl` block should be unique enough. fn unique_invocation_name(item: &syn::Item, current_macro: &syn::Path) -> Result { let path_to_ident = |path: &syn::Path| path.segments.iter().map(|segment| &segment.ident).join("_"); // Include the name of the proc macro in the unique name to avoid // collisions when different proc macros are placed on the same item // and they use this code to generate unique names. let macro_path_str = path_to_ident(current_macro); let item_name = match item { syn::Item::Fn(item) => item.sig.ident.to_string(), syn::Item::Impl(item) => { let self_ty = item .self_ty .as_path() .map(|path| path_to_ident(&path.path)) .unwrap_or_default(); let first_fn = item .items .iter() .find_map(|item| match item { syn::ImplItem::Fn(method) => Some(method.sig.ident.to_string()), _ => None, }) .unwrap_or_default(); format!("impl_{self_ty}_fn_{first_fn}") } _ => bail!(&Span::call_site(), "Unsupported item type"), }; Ok(format!("__eval_cfg_{macro_path_str}_{item_name}")) } } fn eval_cfgs(true_predicates: &BTreeSet, attrs: &mut Vec) -> Result { let mut cfg_attr_expansions = vec![]; for (i, attr) in attrs.iter().enumerate() { let syntax = match CfgSyntax::from_meta(&attr.meta)? { Some(syntax) => syntax, _ => continue, }; let expansion = match syntax { CfgSyntax::Cfg(predicate) => { if !true_predicates.contains(&predicate.to_string()) { // The cfg predicate is false. No need to keep iterating // because the entire syntax this attribute is attached to // should be removed. Signal the caller to remove it via `false`. return Ok(false); } // Just remove the attribute. It evaluated to `true` None } CfgSyntax::CfgAttr(cfg_attr) => { let predicate = cfg_attr.predicate.to_token_stream().to_string(); // We can't both iterate over the attributes and mutate them, // so collect the planned actions in a separate vector, and // do the mutations after the iteration. true_predicates .contains(&predicate) .then(|| cfg_attr.then_branch) } }; cfg_attr_expansions.push((i, expansion)); } // It's important to iterate in reverse order to avoid index invalidation for (i, metas) in cfg_attr_expansions.iter().rev() { let metas = if let Some(metas) = metas { metas } else { attrs.remove(*i); continue; }; let replacement = metas.iter().map(|meta| syn::parse_quote!(#[#meta])); attrs.splice(i..=i, replacement); } Ok(true) } bon-macros-3.7.1/src/normalization/cfg/parse.rs000064400000000000000000000055301046102023000175730ustar 00000000000000use crate::util::prelude::*; use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; mod kw { syn::custom_keyword!(__cfgs); } pub(crate) fn parse_predicate_results(tokens: TokenStream) -> Result> { let results: WrapOption = syn::parse2(tokens)?; Ok(results.0) } // Newtypes over an `Option` to be able to implement trait on it #[derive(Debug)] struct WrapOption(Option); /// Represents a special directive inserted at the beginning of the macro parameters /// that has the syntax `@cfgs(true, false, true)`. It delivers the results of cfg /// evaluations to the macro. #[derive(Debug)] pub(crate) struct PredicateResults { pub(crate) results: Vec, pub(crate) recursion_counter: usize, pub(crate) rest: TokenStream, } impl Parse for WrapOption { fn parse(input: ParseStream<'_>) -> syn::Result { if !input.peek(kw::__cfgs) { // We need to exhaust the input stream to avoid a "unexpected token" error input.parse::()?; return Ok(Self(None)); } input.parse::()?; let results; syn::parenthesized!(results in input); let recursion_counter: syn::LitInt = results.parse()?; let recursion_counter = recursion_counter.base10_parse::()?; results.parse::()?; let results: Vec = Punctuated::::parse_terminated(&results)? .into_iter() .map(|bool| bool.value) .collect(); let results = PredicateResults { results, recursion_counter, rest: input.parse()?, }; Ok(Self(Some(results))) } } pub(crate) enum CfgSyntax { Cfg(TokenStream), CfgAttr(CfgAttr), } impl CfgSyntax { pub(crate) fn from_meta(meta: &syn::Meta) -> Result> { let meta = match meta { syn::Meta::List(meta) => meta, _ => return Ok(None), }; if meta.path.is_ident("cfg") { return Ok(Some(Self::Cfg(meta.tokens.clone()))); } if meta.path.is_ident("cfg_attr") { let cfg_attr = syn::parse2(meta.tokens.clone())?; return Ok(Some(Self::CfgAttr(cfg_attr))); } Ok(None) } } pub(crate) struct CfgAttr { pub(crate) predicate: Box, pub(crate) then_branch: Punctuated, } impl Parse for CfgAttr { fn parse(input: ParseStream<'_>) -> syn::Result { let predicate = input.parse()?; input.parse::()?; let then_branch = Punctuated::::parse_terminated(input)?; Ok(Self { predicate, then_branch, }) } } bon-macros-3.7.1/src/normalization/cfg/visit.rs000064400000000000000000000061461046102023000176230ustar 00000000000000use crate::util::prelude::*; pub(crate) fn visit_attrs( item: &mut syn::Item, visit: impl FnMut(&mut Vec) -> Result, ) -> Result { VisitAttrs { visit }.visit_item(item) } struct VisitAttrs { visit: T, } impl VisitAttrs where T: FnMut(&mut Vec) -> Result, { fn visit(&mut self, attrs: &mut Vec) -> Result { (self.visit)(attrs) } fn visit_item(&mut self, item: &mut syn::Item) -> Result { match item { syn::Item::Fn(fn_item) => self.visit_item_fn(fn_item), syn::Item::Impl(impl_item) => self.visit_item_impl(impl_item), _ => Ok(()), } } fn visit_item_fn(&mut self, fn_item: &mut syn::ItemFn) -> Result { if !self.visit(&mut fn_item.attrs)? { bail!( fn_item, "This code should never be executed if there is a `#[cfg(...)]` attribute \ on the function itself, because if that cfg evaluates to `false`, \ no other proc macro on the item should be called." ) } self.visit_signature(&mut fn_item.sig) } fn visit_impl_item_fn(&mut self, fn_item: &mut syn::ImplItemFn) -> Result { if !self.visit(&mut fn_item.attrs)? { return Ok(false); } self.visit_signature(&mut fn_item.sig)?; Ok(true) } fn visit_signature(&mut self, sig: &mut syn::Signature) -> Result { sig.inputs.try_retain_mut(|arg| match arg { syn::FnArg::Typed(arg) => self.visit(&mut arg.attrs), syn::FnArg::Receiver(arg) => self.visit(&mut arg.attrs), })?; self.visit_generics(&mut sig.generics)?; if let Some(variadic) = &mut sig.variadic { if !self.visit(&mut variadic.attrs)? { sig.variadic = None; } } // Where clause doesn't support attributes yet. // There is an issue in rust-lang/rust: // https://github.com/rust-lang/rust/issues/115590 Ok(()) } fn visit_generics(&mut self, generics: &mut syn::Generics) -> Result { generics.params.try_retain_mut(|param| match param { syn::GenericParam::Type(param) => self.visit(&mut param.attrs), syn::GenericParam::Lifetime(param) => self.visit(&mut param.attrs), syn::GenericParam::Const(param) => self.visit(&mut param.attrs), }) } fn visit_item_impl(&mut self, impl_item: &mut syn::ItemImpl) -> Result { if !self.visit(&mut impl_item.attrs)? { bail!( impl_item, "This code should never be executed if there is a `#[cfg(...)]` attribute \ on the impl itself, because if that cfg evaluates to `false`, \ no other proc macro on the item should be called." ) } self.visit_generics(&mut impl_item.generics)?; impl_item.items.try_retain_mut(|item| match item { syn::ImplItem::Fn(fn_item) => self.visit_impl_item_fn(fn_item), _ => Ok(true), }) } } bon-macros-3.7.1/src/normalization/generics_namespace.rs000064400000000000000000000046041046102023000215360ustar 00000000000000use crate::util::prelude::*; use proc_macro2::TokenTree; use std::collections::BTreeSet; use syn::visit::Visit; #[derive(Debug, Default, Clone)] pub(crate) struct GenericsNamespace { /// Set of identifiers referenced in the syntax node. pub(crate) idents: BTreeSet, /// Set of lifetimes referenced in the syntax node. pub(crate) lifetimes: BTreeSet, } impl Visit<'_> for GenericsNamespace { fn visit_ident(&mut self, ident: &syn::Ident) { self.idents.insert(ident.to_string()); } fn visit_meta_list(&mut self, meta_list: &syn::MetaList) { syn::visit::visit_meta_list(self, meta_list); self.visit_token_stream(meta_list.tokens.clone()); } fn visit_lifetime(&mut self, lifetime: &syn::Lifetime) { self.lifetimes.insert(lifetime.ident.to_string()); } fn visit_item(&mut self, _item: &syn::Item) { // Don't recurse into child items. They don't inherit the parent item's generics. } } impl GenericsNamespace { pub(crate) fn unique_ident(&self, name: String) -> syn::Ident { let name = unique_name(&self.idents, name); syn::Ident::new(&name, Span::call_site()) } pub(crate) fn unique_lifetime(&self, name: String) -> String { unique_name(&self.lifetimes, name) } pub(crate) fn visit_token_stream(&mut self, token_stream: TokenStream) { let mut tokens = token_stream.into_iter().peekable(); while let Some(tt) = tokens.next() { match tt { TokenTree::Group(group) => { self.visit_token_stream(group.stream()); } TokenTree::Ident(ident) => { self.visit_ident(&ident); } TokenTree::Punct(punct) => { if punct.as_char() != '\'' { continue; } if let Some(TokenTree::Ident(ident)) = tokens.peek() { self.lifetimes.insert(ident.to_string()); tokens.next(); } } TokenTree::Literal(_) => {} } } } } /// Adds `_` suffix to the name to avoid conflicts with existing identifiers. pub(crate) fn unique_name(taken: &BTreeSet, mut ident: String) -> String { while taken.contains(&ident) { ident.push('_'); } ident } bon-macros-3.7.1/src/normalization/impl_traits.rs000064400000000000000000000050771046102023000202570ustar 00000000000000use super::GenericsNamespace; use crate::util::prelude::*; use syn::visit_mut::VisitMut; pub(crate) struct NormalizeImplTraits<'a> { namespace: &'a GenericsNamespace, } impl<'a> NormalizeImplTraits<'a> { pub(crate) fn new(namespace: &'a GenericsNamespace) -> Self { Self { namespace } } } impl VisitMut for NormalizeImplTraits<'_> { fn visit_impl_item_fn_mut(&mut self, fn_item: &mut syn::ImplItemFn) { // We are interested only in signatures of functions. Don't recurse // into the function's block. self.visit_signature_mut(&mut fn_item.sig); } fn visit_signature_mut(&mut self, signature: &mut syn::Signature) { let mut visitor = AssignTypeParams::new(self, &mut signature.generics); for arg in &mut signature.inputs { visitor.visit_fn_arg_mut(arg); } } } struct AssignTypeParams<'a> { base: &'a NormalizeImplTraits<'a>, generics: &'a mut syn::Generics, next_type_param_index: usize, } impl<'a> AssignTypeParams<'a> { fn new(base: &'a NormalizeImplTraits<'a>, generics: &'a mut syn::Generics) -> Self { Self { base, generics, next_type_param_index: 1, } } } impl VisitMut for AssignTypeParams<'_> { fn visit_item_mut(&mut self, _item: &mut syn::Item) { // Don't recurse into nested items because `impl Trait` isn't available there. } fn visit_signature_mut(&mut self, signature: &mut syn::Signature) { for arg in &mut signature.inputs { self.visit_type_mut(arg.ty_mut()); } } fn visit_type_mut(&mut self, ty: &mut syn::Type) { syn::visit_mut::visit_type_mut(self, ty); if !matches!(ty, syn::Type::ImplTrait(_)) { return; } let index = self.next_type_param_index; self.next_type_param_index += 1; let type_param = self.base.namespace.unique_ident(format!("I{index}")); let impl_trait = std::mem::replace(ty, syn::Type::Path(syn::parse_quote!(#type_param))); let impl_trait = match impl_trait { syn::Type::ImplTrait(impl_trait) => impl_trait, _ => { unreachable!("BUG: code higher validated that this is impl trait: {impl_trait:?}"); } }; self.generics .params .push(syn::GenericParam::Type(syn::parse_quote!(#type_param))); let bounds = impl_trait.bounds; self.generics .make_where_clause() .predicates .push(syn::parse_quote!(#type_param: #bounds)); } } bon-macros-3.7.1/src/normalization/lifetimes.rs000064400000000000000000000203111046102023000176750ustar 00000000000000use super::GenericsNamespace; use crate::util::prelude::*; use syn::visit::Visit; use syn::visit_mut::VisitMut; pub(crate) struct NormalizeLifetimes<'a> { namespace: &'a GenericsNamespace, } impl<'a> NormalizeLifetimes<'a> { pub(crate) fn new(namespace: &'a GenericsNamespace) -> Self { Self { namespace } } } impl VisitMut for NormalizeLifetimes<'_> { fn visit_item_impl_mut(&mut self, impl_block: &mut syn::ItemImpl) { for item in &mut impl_block.items { self.visit_impl_item_mut(item); } AssignLifetimes::new(self, "i", &mut impl_block.generics) .visit_type_mut(&mut impl_block.self_ty); } fn visit_impl_item_mut(&mut self, item: &mut syn::ImplItem) { if let syn::ImplItem::Fn(fn_item) = item { self.visit_signature_mut(&mut fn_item.sig); } } fn visit_item_fn_mut(&mut self, fn_item: &mut syn::ItemFn) { self.visit_signature_mut(&mut fn_item.sig); } fn visit_signature_mut(&mut self, signature: &mut syn::Signature) { let mut visitor = AssignLifetimes::new(self, "f", &mut signature.generics); for arg in &mut signature.inputs { visitor.visit_fn_arg_mut(arg); } let return_type = match &mut signature.output { syn::ReturnType::Type(_, return_type) => return_type, syn::ReturnType::Default => return, }; // Now perform lifetime elision for the lifetimes in the return type. // This code implements the logic described in the Rust reference: // https://doc.rust-lang.org/reference/lifetime-elision.html let elided_output_lifetime = signature .inputs .first() .and_then(|arg| { let receiver = arg.as_receiver()?; receiver.lifetime().or_else(|| match receiver.ty.as_ref() { syn::Type::Reference(reference) => reference.lifetime.as_ref(), _ => None, }) }) .or_else(|| { let lifetime = signature .inputs .iter() .filter_map(syn::FnArg::as_typed) .fold(LifetimeCollector::None, |mut acc, pat_type| { acc.visit_pat_type(pat_type); acc }); match lifetime { LifetimeCollector::Single(lifetime) => Some(lifetime), _ => None, } }); let elided_lifetime = match elided_output_lifetime { Some(elided_lifetime) => elided_lifetime, _ => return, }; ElideOutputLifetime { elided_lifetime }.visit_type_mut(return_type); } } struct AssignLifetimes<'a> { base: &'a NormalizeLifetimes<'a>, prefix: &'static str, /// Generics where the assigned lifetimes should be stored. generics: &'a mut syn::Generics, next_lifetime_index: usize, } impl<'a> AssignLifetimes<'a> { fn new( base: &'a NormalizeLifetimes<'a>, prefix: &'static str, generics: &'a mut syn::Generics, ) -> Self { Self { base, prefix, generics, next_lifetime_index: 1, } } } impl VisitMut for AssignLifetimes<'_> { fn visit_item_mut(&mut self, _item: &mut syn::Item) { // Don't recurse into nested items because lifetimes aren't available there. } fn visit_type_bare_fn_mut(&mut self, _bare_fn: &mut syn::TypeBareFn) { // Skip function pointers because anon lifetimes that appear in them // don't belong to the surrounding function signature. } fn visit_parenthesized_generic_arguments_mut( &mut self, _args: &mut syn::ParenthesizedGenericArguments, ) { // Skip Fn traits for the same reason as function pointers described higher. } fn visit_lifetime_mut(&mut self, lifetime: &mut syn::Lifetime) { if lifetime.ident == "_" { *lifetime = self.next_lifetime(); } } fn visit_type_reference_mut(&mut self, reference: &mut syn::TypeReference) { syn::visit_mut::visit_type_reference_mut(self, reference); reference .lifetime .get_or_insert_with(|| self.next_lifetime()); } fn visit_receiver_mut(&mut self, receiver: &mut syn::Receiver) { // If this is a `self: Type` syntax, then it's not a special case // and we can just visit the explicit type of the receiver as usual if receiver.colon_token.is_some() { syn::visit_mut::visit_type_mut(self, &mut receiver.ty); return; } let lifetime = match &mut receiver.reference { Some((_and, lifetime)) => lifetime, _ => return, }; if matches!(lifetime, Some(lifetime) if lifetime.ident != "_") { return; } let receiver_ty = match receiver.ty.as_mut() { syn::Type::Reference(receiver_ty) => receiver_ty, _ => return, }; let new_lifetime = self.next_lifetime(); *lifetime = Some(new_lifetime.clone()); receiver_ty.lifetime = Some(new_lifetime); } } impl AssignLifetimes<'_> { /// Make a lifetime with the next index. It's used to generate unique /// lifetimes for every occurrence of a reference with the anonymous /// lifetime. fn next_lifetime(&mut self) -> syn::Lifetime { let index = self.next_lifetime_index; self.next_lifetime_index += 1; let mut lifetime = self .base .namespace .unique_lifetime(format!("{}{index}", self.prefix)); // `syn::Lifetime::new` requires the string to start with the `'` character, // which is just discarded in that method's impl 🗿. lifetime.insert(0, '\''); let lifetime = syn::Lifetime::new(&lifetime, Span::call_site()); let lifetime_param = syn::LifetimeParam::new(lifetime.clone()); let lifetime_param = syn::GenericParam::Lifetime(lifetime_param); self.generics.params.insert(index - 1, lifetime_param); lifetime } } enum LifetimeCollector<'a> { None, Single(&'a syn::Lifetime), Multiple, } impl<'a> Visit<'a> for LifetimeCollector<'a> { fn visit_item(&mut self, _item: &syn::Item) { // Don't recurse into nested items because lifetimes aren't available there. } fn visit_type_bare_fn(&mut self, _bare_fn: &syn::TypeBareFn) { // Skip function pointers because anon lifetimes that appear in them // don't belong to the surrounding function signature. } fn visit_parenthesized_generic_arguments( &mut self, _args: &syn::ParenthesizedGenericArguments, ) { // Skip Fn traits for the same reason as function pointers described higher. } fn visit_lifetime(&mut self, lifetime: &'a syn::Lifetime) { match self { Self::None => *self = Self::Single(lifetime), Self::Single(_) => *self = Self::Multiple, Self::Multiple => {} } } } struct ElideOutputLifetime<'a> { elided_lifetime: &'a syn::Lifetime, } impl VisitMut for ElideOutputLifetime<'_> { fn visit_item_mut(&mut self, _item: &mut syn::Item) { // Don't recurse into nested items because lifetimes aren't available there. } fn visit_type_bare_fn_mut(&mut self, _bare_fn: &mut syn::TypeBareFn) { // Skip function pointers because anon lifetimes that appear in them // don't belong to the surrounding function signature. } fn visit_parenthesized_generic_arguments_mut( &mut self, _args: &mut syn::ParenthesizedGenericArguments, ) { // Skip Fn traits for the same reason as function pointers described higher. } fn visit_lifetime_mut(&mut self, lifetime: &mut syn::Lifetime) { if lifetime.ident == "_" { *lifetime = self.elided_lifetime.clone(); } } fn visit_type_reference_mut(&mut self, reference: &mut syn::TypeReference) { syn::visit_mut::visit_type_reference_mut(self, reference); reference .lifetime .get_or_insert_with(|| self.elided_lifetime.clone()); } } bon-macros-3.7.1/src/normalization/mod.rs000064400000000000000000000004311046102023000164740ustar 00000000000000mod cfg; mod generics_namespace; mod impl_traits; mod lifetimes; mod self_ty; mod syntax_variant; pub(crate) use cfg::*; pub(crate) use generics_namespace::*; pub(crate) use impl_traits::*; pub(crate) use lifetimes::*; pub(crate) use self_ty::*; pub(crate) use syntax_variant::*; bon-macros-3.7.1/src/normalization/self_ty.rs000064400000000000000000000046271046102023000173750ustar 00000000000000use crate::util::prelude::*; use syn::spanned::Spanned; use syn::visit_mut::VisitMut; pub(crate) struct NormalizeSelfTy<'a> { pub(crate) self_ty: &'a syn::Type, } impl VisitMut for NormalizeSelfTy<'_> { fn visit_item_mut(&mut self, _item: &mut syn::Item) { // Don't recurse into nested items because `Self` isn't available there. } fn visit_receiver_mut(&mut self, _receiver: &mut syn::Receiver) { // Don't recurse into the receiver. Keep it at `Self` as it was. // We normalize `Self` at a later stage. This is to keep the code // generated via `quote!()` using the simpler syntax of `self` without // an explicit type annotation, which requires that receiver's type // is left intact during normalization here. } fn visit_impl_item_fn_mut(&mut self, fn_item: &mut syn::ImplItemFn) { // We are interested only in signatures of functions. Don't recurse // into the function's block. self.visit_signature_mut(&mut fn_item.sig); } // TODO: this isn't all. We need to replace `Self` references in const generics // expressions as well. fn visit_type_mut(&mut self, ty: &mut syn::Type) { syn::visit_mut::visit_type_mut(self, ty); let ty_path = match ty { syn::Type::Path(ty_path) => ty_path, _ => return, }; if !ty_path.path.is_ident("Self") { return; } *ty = (*self.self_ty).clone(); } fn visit_type_path_mut(&mut self, type_path: &mut syn::TypePath) { syn::visit_mut::visit_type_path_mut(self, type_path); let syn::TypePath { qself, path } = type_path; let is_self_projection = qself.is_none() && path.starts_with_segment("Self") && path.segments.len() > 1; if !is_self_projection { return; } // There is no `.remove()` method in `Punctuated` // https://github.com/dtolnay/syn/issues/1314 path.segments = std::mem::take(&mut path.segments) .into_iter() .skip(1) .collect(); let span = type_path.span(); // QSelf doesn't implement `Parse` trait type_path.qself = Some(syn::QSelf { lt_token: syn::Token![<](span), ty: Box::new(self.self_ty.clone()), position: 0, as_token: None, gt_token: syn::Token![>](span), }); } } bon-macros-3.7.1/src/normalization/syntax_variant.rs000064400000000000000000000016431046102023000207750ustar 00000000000000/// Struct, that contains both the original syntax (unprocessed) and the normalized /// version. This is useful for code that needs access to both versions of the syntax. #[derive(Debug)] pub(crate) struct SyntaxVariant { /// Original syntax that was passed to the macro without any modifications. pub(crate) orig: T, /// The value that is equivalent to `orig`, but it underwent normalization. pub(crate) norm: T, } impl SyntaxVariant { pub(crate) fn apply_ref<'a, U>(&'a self, f: impl Fn(&'a T) -> U) -> SyntaxVariant { let orig = f(&self.orig); let norm = f(&self.norm); SyntaxVariant { orig, norm } } pub(crate) fn into_iter(self) -> impl Iterator> where T: IntoIterator, { self.orig .into_iter() .zip(self.norm) .map(|(orig, norm)| SyntaxVariant { orig, norm }) } } bon-macros-3.7.1/src/parsing/bon_crate_path.rs000064400000000000000000000034501046102023000174460ustar 00000000000000use crate::util::prelude::*; use darling::FromMeta; #[derive(Debug, Clone)] pub(crate) enum BonCratePath { Default, Explicit(syn::Path), } impl Default for BonCratePath { fn default() -> Self { Self::Default } } impl FromMeta for BonCratePath { fn from_meta(meta: &syn::Meta) -> Result { let path = super::parse_path_mod_style(meta)?; let prefix = &path .segments .first() .ok_or_else(|| err!(&path, "path must have at least one segment"))? .ident; let is_absolute = path.leading_colon.is_some() || prefix == "crate" || prefix == "$crate"; if is_absolute { return Ok(Self::Explicit(path)); } if prefix == "super" || prefix == "self" { bail!( &path, "path must not be relative; specify the path that starts with `crate::` \ instead; if you want to refer to a reexport from an external crate then \ use leading colons like `::crate_name::reexport::path::bon`" ) } let path_str = darling::util::path_to_string(&path); bail!( &path, "path must be absolute; if you want to refer to a reexport from an external \ crate then add leading colons like `::{path_str}`; if the path leads to a module \ in the current crate, then specify the absolute path with `crate` like \ `crate::reexport::path::bon` or `$crate::reexport::path::bon` (if within a macro)" ); } } impl ToTokens for BonCratePath { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::Default => tokens.extend(quote!(::bon)), Self::Explicit(path) => path.to_tokens(tokens), } } } bon-macros-3.7.1/src/parsing/const_.rs000064400000000000000000000132051046102023000157620ustar 00000000000000use super::reject_attrs; use crate::util::prelude::*; /// At the time of this writing `Rust` doesn't support calling closures in `const` /// contexts. This doesn't compile: /// /// ```compile_fail /// const { (|| {})() } /// ``` /// /// However, we need this to work because we need to wrap user-supplied expressions /// with closures. It gives us a guarantee that the user-supplied expression can't /// break out of the surrounding scope via a `return`, `break`, or `continue` inside /// of it. /// /// Therefore, we use this function to allow very simple expressions to still be /// embedded in the surrounding context while recommending to delegate to a function /// call if more complex expression is required. pub(crate) fn require_embeddable_const_expr(expr: &syn::Expr) -> Result { use require_embeddable_const_expr as recurse; fn recurse_block(block: &syn::Block) -> Result { let stmt = match block.stmts.as_slice() { [stmt] => stmt, [] => return Ok(()), _ => bail!( &block, "only a single expression in a block is allowed in this position" ), }; match stmt { syn::Stmt::Expr(expr, None) => recurse(expr), _ => bail!( &stmt, "this kind of statement is not allowed in this position; \ only a single expression is allowed in a block is allowed here" ), } } use syn::Expr::*; match expr { Array(arr) => { reject_attrs(&arr.attrs)?; arr.elems.iter().try_for_each(recurse)?; } Binary(binary) => { reject_attrs(&binary.attrs)?; recurse(&binary.left)?; recurse(&binary.right)?; } Block(block) => { reject_attrs(&block.attrs)?; recurse_block(&block.block)?; } Call(call) => { reject_attrs(&call.attrs)?; recurse(&call.func)?; call.args.iter().try_for_each(recurse)?; } Cast(cast) => { reject_attrs(&cast.attrs)?; recurse(&cast.expr)?; } Field(field) => { reject_attrs(&field.attrs)?; recurse(&field.base)?; } Group(group) => { reject_attrs(&group.attrs)?; recurse(&group.expr)?; } If(if_expr) => { reject_attrs(&if_expr.attrs)?; recurse(&if_expr.cond)?; recurse_block(&if_expr.then_branch)?; if let Some((_else, else_branch)) = &if_expr.else_branch { recurse(else_branch)?; } } Const(const_block) => { reject_attrs(&const_block.attrs)?; recurse_block(&const_block.block)?; } Index(index) => { reject_attrs(&index.attrs)?; recurse(&index.expr)?; recurse(&index.index)?; } Infer(infer) => reject_attrs(&infer.attrs)?, Lit(lit) => reject_attrs(&lit.attrs)?, Loop(loop_expr) => { reject_attrs(&loop_expr.attrs)?; recurse_block(&loop_expr.body)?; } Match(expr_match) => { reject_attrs(&expr_match.attrs)?; recurse(&expr_match.expr)?; expr_match.arms.iter().try_for_each(|arm| { reject_attrs(&arm.attrs)?; if let Some((_if, guard)) = &arm.guard { recurse(guard)?; } recurse(&arm.body) })?; } MethodCall(method_call) => { reject_attrs(&method_call.attrs)?; recurse(&method_call.receiver)?; method_call.args.iter().try_for_each(recurse)?; } Paren(paren) => { reject_attrs(&paren.attrs)?; recurse(&paren.expr)?; } Path(path) => reject_attrs(&path.attrs)?, Range(range) => { reject_attrs(&range.attrs)?; if let Some(start) = &range.start { recurse(start)?; } if let Some(end) = &range.end { recurse(end)?; } } Reference(reference) => { reject_attrs(&reference.attrs)?; recurse(&reference.expr)?; } Repeat(repeat) => { reject_attrs(&repeat.attrs)?; recurse(&repeat.expr)?; recurse(&repeat.len)?; } Struct(struct_expr) => { reject_attrs(&struct_expr.attrs)?; struct_expr.fields.iter().try_for_each(|field| { reject_attrs(&field.attrs)?; recurse(&field.expr) })?; if let Some(rest) = &struct_expr.rest { recurse(rest)?; } } Tuple(tuple) => { reject_attrs(&tuple.attrs)?; tuple.elems.iter().try_for_each(recurse)?; } Unary(unary) => { reject_attrs(&unary.attrs)?; recurse(&unary.expr)?; } Unsafe(unsafe_expr) => { reject_attrs(&unsafe_expr.attrs)?; recurse_block(&unsafe_expr.block)?; } While(while_expr) => { reject_attrs(&while_expr.attrs)?; recurse(&while_expr.cond)?; recurse_block(&while_expr.body)?; } _ => { bail!( &expr, "this kind of expression is not allowed in this position; \ if you need to use a complex expression such as this then \ move it into a separate `const fn` and call that function \ here instead" ) } } Ok(()) } bon-macros-3.7.1/src/parsing/docs.rs000064400000000000000000000037171046102023000154340ustar 00000000000000use super::SpannedKey; use crate::util::prelude::*; pub(crate) fn parse_docs_without_self_mentions( context: &'static str, meta: &syn::Meta, ) -> Result>> { let docs = parse_docs(meta)?; reject_self_mentions_in_docs(context, &docs)?; Ok(docs) } pub(crate) fn parse_docs(meta: &syn::Meta) -> Result>> { let meta = meta.require_list()?; meta.require_curly_braces_delim()?; let attrs = meta.parse_args_with(syn::Attribute::parse_outer)?; for attr in &attrs { if !attr.is_doc_expr() { bail!(attr, "expected a doc comment"); } } SpannedKey::new(&meta.path, attrs) } /// Validates the docs for the presence of `Self` mentions to prevent users from /// shooting themselves in the foot where they would think that `Self` resolves /// to the current item the docs were placed on, when in fact the docs are moved /// to a different context where `Self` has a different meaning. pub(crate) fn reject_self_mentions_in_docs( context: &'static str, attrs: &[syn::Attribute], ) -> Result { for attr in attrs { let doc = match attr.as_doc_expr() { Some(doc) => doc, _ => continue, }; let doc = match &doc { syn::Expr::Lit(doc) => doc, _ => continue, }; let doc = match &doc.lit { syn::Lit::Str(doc) => doc, _ => continue, }; let self_references = ["[`Self`]", "[Self]"]; if self_references .iter() .any(|self_ref| doc.value().contains(self_ref)) { bail!( &doc.span(), "the documentation should not reference `Self` because it will \ be moved to the {context} where `Self` changes meaning, which \ may confuse the reader of this code; use explicit type names instead.", ); } } Ok(()) } bon-macros-3.7.1/src/parsing/item_sig.rs000064400000000000000000000043141046102023000162760ustar 00000000000000use super::SpannedKey; use crate::util::prelude::*; use darling::FromMeta; /// "Item signature" is a set of parameters that configures some aspects of /// an item like a function, struct, struct field, module, trait. All of them /// have configurable properties that are specified here. #[derive(Debug, Clone, Default)] pub(crate) struct ItemSigConfig { pub(crate) name: Option>, pub(crate) vis: Option>, pub(crate) docs: Option>>, } impl ItemSigConfig { pub(crate) fn name(&self) -> Option<&syn::Ident> { self.name.as_ref().map(|name| &name.value) } pub(crate) fn vis(&self) -> Option<&syn::Visibility> { self.vis.as_ref().map(|vis| &vis.value) } pub(crate) fn docs(&self) -> Option<&[syn::Attribute]> { self.docs.as_ref().map(|docs| docs.value.as_slice()) } } pub(crate) struct ItemSigConfigParsing<'a> { pub(crate) meta: &'a syn::Meta, pub(crate) reject_self_mentions: Option<&'static str>, } impl ItemSigConfigParsing<'_> { pub(crate) fn parse(self) -> Result { let meta = self.meta; if let syn::Meta::NameValue(meta) = meta { let val = &meta.value; let name = syn::parse2(val.to_token_stream())?; return Ok(ItemSigConfig { name: Some(SpannedKey::new(&meta.path, name)?), vis: None, docs: None, }); } #[derive(Debug, FromMeta)] struct Full { name: Option>, vis: Option>, #[darling(default, with = super::parse_docs, map = Some)] doc: Option>>, } let full: Full = crate::parsing::parse_non_empty_paren_meta_list(meta)?; if let Some(context) = self.reject_self_mentions { if let Some(docs) = &full.doc { crate::parsing::reject_self_mentions_in_docs(context, docs)?; } } let config = ItemSigConfig { name: full.name, vis: full.vis, docs: full.doc, }; Ok(config) } } bon-macros-3.7.1/src/parsing/mod.rs000064400000000000000000000055621046102023000152630ustar 00000000000000mod bon_crate_path; mod const_; mod docs; mod item_sig; mod simple_closure; mod spanned_key; pub(crate) use bon_crate_path::*; pub(crate) use const_::*; pub(crate) use docs::*; pub(crate) use item_sig::*; pub(crate) use simple_closure::*; pub(crate) use spanned_key::*; use crate::util::prelude::*; use darling::FromMeta; use syn::parse::Parser; use syn::punctuated::Punctuated; use syn::spanned::Spanned; pub(crate) fn parse_non_empty_paren_meta_list(meta: &syn::Meta) -> Result { require_non_empty_paren_meta_list_or_name_value(meta)?; T::from_meta(meta) } pub(crate) fn require_non_empty_paren_meta_list_or_name_value(meta: &syn::Meta) -> Result { match meta { syn::Meta::List(meta) => { meta.require_parens_delim()?; if meta.tokens.is_empty() { bail!( &meta.delimiter.span().join(), "expected parameters in parentheses" ); } } syn::Meta::Path(path) => bail!( &meta, "this empty `{0}` attribute is unexpected; \ remove it or pass parameters in parentheses: \ `{0}(...)`", darling::util::path_to_string(path) ), syn::Meta::NameValue(_) => {} } Ok(()) } /// Utility for parsing with `#[darling(with = ...)]` attribute that allows to /// parse an arbitrary sequence of items inside of parentheses. For example /// `foo(a, b, c)`, where `a`, `b`, and `c` are of type `T` and `,` is represented /// by the token type `P`. pub(crate) fn parse_paren_meta_list_with_terminated( meta: &syn::Meta, ) -> Result> where T: syn::parse::Parse, P: syn::parse::Parse, { let item = std::any::type_name::(); let punct = std::any::type_name::

(); let name = |val: &str| { format!( "'{}'", val.rsplit("::").next().unwrap_or(val).to_lowercase() ) }; let meta = match meta { syn::Meta::List(meta) => meta, _ => bail!( &meta, "expected a list of {} separated by {}", name(item), name(punct), ), }; meta.require_parens_delim()?; let punctuated = Punctuated::parse_terminated.parse2(meta.tokens.clone())?; Ok(punctuated) } fn parse_path_mod_style(meta: &syn::Meta) -> Result { let expr = match meta { syn::Meta::NameValue(meta) => &meta.value, _ => bail!(meta, "expected a simple path, like `foo::bar`"), }; Ok(expr.require_path_mod_style()?.clone()) } pub(crate) fn reject_syntax(name: &'static str, syntax: &Option) -> Result { if let Some(syntax) = syntax { bail!(syntax, "{name} is not allowed here") } Ok(()) } pub(crate) fn reject_attrs(attrs: &[syn::Attribute]) -> Result { reject_syntax("attribute", &attrs.first()) } bon-macros-3.7.1/src/parsing/simple_closure.rs000064400000000000000000000052341046102023000175250ustar 00000000000000use super::{reject_attrs, reject_syntax}; use crate::util::prelude::*; use darling::FromMeta; /// Utility type for parsing simple closure syntax that only allows [`syn::PatIdent`] /// inputs and rejects any attributes and prefix keywords like `async`, `move`, `for` /// on the closure. #[derive(Debug)] pub(crate) struct SimpleClosure { pub(crate) inputs: Vec, pub(crate) body: Box, pub(crate) output: syn::ReturnType, } #[derive(Debug)] pub(crate) struct SimpleClosureInput { pub(crate) pat: syn::PatIdent, pub(crate) ty: Option>, } impl FromMeta for SimpleClosure { fn from_meta(meta: &syn::Meta) -> Result { let err = || { let path = darling::util::path_to_string(meta.path()); err!( meta, "expected a closure e.g. `{path} = |param: T| expression`" ) }; let meta = match meta { syn::Meta::NameValue(meta) => meta, _ => return Err(err()), }; let closure = match &meta.value { syn::Expr::Closure(closure) => closure, _ => return Err(err()), }; reject_syntax("`for<...>` syntax", &closure.lifetimes)?; reject_syntax("`const` keyword", &closure.constness)?; reject_syntax("`static` keyword", &closure.movability)?; reject_syntax("`async` keyword", &closure.asyncness)?; reject_syntax("`move` keyword", &closure.capture)?; reject_attrs(&closure.attrs)?; let inputs = closure .clone() .inputs .into_iter() .map(|input| match input { syn::Pat::Ident(pat) => SimpleClosureInput::from_pat_ident(pat), syn::Pat::Type(pat) => SimpleClosureInput::from_pat_type(pat), _ => bail!(&input, "expected a simple identifier pattern"), }) .collect::>()?; Ok(Self { inputs, body: closure.body.clone(), output: closure.output.clone(), }) } } impl SimpleClosureInput { fn from_pat_ident(pat: syn::PatIdent) -> Result { reject_attrs(&pat.attrs)?; reject_syntax("`ref` keyword", &pat.by_ref)?; Ok(Self { pat, ty: None }) } fn from_pat_type(input: syn::PatType) -> Result { reject_attrs(&input.attrs)?; let ident = match *input.pat { syn::Pat::Ident(pat) => Self::from_pat_ident(pat)?.pat, _ => bail!(&input.pat, "expected a simple identifier pattern"), }; Ok(Self { pat: ident, ty: Some(input.ty), }) } } bon-macros-3.7.1/src/parsing/spanned_key.rs000064400000000000000000000036071046102023000170020ustar 00000000000000use crate::util::prelude::*; use darling::FromMeta; use std::fmt; use std::ops::{Deref, DerefMut}; /// A type that stores the attribute key path information along with the parsed value. /// It is useful for error reporting. For example, if some key was unexpected, it's /// possible to point to the key's span in the error instead of the attribute's value. #[derive(Clone)] pub(crate) struct SpannedKey { pub(crate) key: syn::Ident, pub(crate) value: T, } impl SpannedKey { pub(crate) fn new(path: &syn::Path, value: T) -> Result { Ok(Self { key: path.require_ident()?.clone(), value, }) } pub(crate) fn into_value(self) -> T { self.value } pub(crate) fn key(&self) -> &syn::Ident { &self.key } pub(crate) fn with_value(self, value: U) -> SpannedKey { SpannedKey { value, key: self.key, } } pub(crate) fn map_value(self, map: impl FnOnce(T) -> U) -> SpannedKey { SpannedKey { value: map(self.value), key: self.key, } } } impl Default for SpannedKey { fn default() -> Self { Self { key: syn::Ident::new("__bon_autogenerated", Span::call_site()), value: T::default(), } } } impl FromMeta for SpannedKey { fn from_meta(meta: &syn::Meta) -> Result { let value = T::from_meta(meta)?; Self::new(meta.path(), value) } } impl fmt::Debug for SpannedKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.value, f) } } impl Deref for SpannedKey { type Target = T; fn deref(&self) -> &T { &self.value } } impl DerefMut for SpannedKey { fn deref_mut(&mut self) -> &mut T { &mut self.value } } bon-macros-3.7.1/src/privatize.rs000064400000000000000000000017021046102023000150460ustar 00000000000000use crate::util::prelude::*; use syn::parse::{ParseStream, Parser}; pub(crate) fn privatize_fn(input: TokenStream) -> TokenStream { transform .parse2(input) .unwrap_or_else(syn::Error::into_compile_error) } fn transform(input: ParseStream<'_>) -> syn::Result { let outer_attrs = syn::Attribute::parse_outer(input)?; let vis: syn::Visibility = input.parse()?; let mut sig: syn::Signature = input.parse()?; privatize_fn_name(&mut sig); let rest: TokenStream = input.parse()?; Ok(quote::quote! { #(#outer_attrs)* #vis #sig #rest }) } pub(crate) fn privatize_fn_name(sig: &mut syn::Signature) { // We don't generate a random name to ensure reproducible builds. Some of // `bon`'s users use custom build systems where the output of the macro is // cached and the stability of the output is depended upon. sig.ident = quote::format_ident!("__orig_{}", sig.ident.raw_name()); } bon-macros-3.7.1/src/tests/attr_setters.rs000064400000000000000000000127121046102023000167210ustar 00000000000000use super::assert_snapshot; use crate::util::prelude::*; #[test] fn setters_docs_and_vis() { let actual_tokens = crate::builder::generate_from_derive(quote! { #[builder(on(u8, setters(doc(default(skip)))))] struct Sut { /// Docs on the required field itself #[builder(setters( vis = "pub(in overridden)", doc { /// Docs on the required field setters. /// Multiline. } ))] required_field: u32, /// Docs on the optional field itself #[builder(setters( vis = "pub(in overridden)", doc { /// Docs on the optional field setters. /// Multiline. } ))] optional_field: Option, /// Docs on the default field itself #[builder( setters( vis = "pub(in overridden)", doc { /// Docs on the default field setters. /// Multiline. } ), default = 2 + 2 * 3 )] default_field: u32, /// Docs on the field itself #[builder( setters( some_fn( vis = "pub(in some_fn_overridden)", doc { /// Docs on some_fn /// Multiline. } ), option_fn( vis = "pub(in option_fn_overridden)", doc { /// Docs on option_fn /// Multiline. } ) ) )] optional_field_with_specific_overrides: Option, #[builder( setters( some_fn( vis = "pub(in some_fn_overridden)", doc { /// Docs on some_fn /// Multiline. } ), option_fn( vis = "pub(in option_fn_overridden)", doc { /// Docs on option_fn /// Multiline. } ) ), default = 2 + 2 * 3 )] default_field_with_specific_overrides: u32, #[builder(setters( doc { /// Common docs /// Multiline. }, vis = "pub(in overridden)", option_fn( vis = "pub(in option_fn_overridden)", doc { /// Docs on option_fn /// Multiline. } ) ))] optional_field_with_inherited_overrides: Option, #[builder( setters( doc { /// Common docs /// Multiline. }, vis = "pub(in overridden)", option_fn( vis = "pub(in option_fn_overridden)", doc { /// Docs on option_fn /// Multiline. } ) ), default = 2 + 2 * 3 )] default_field_with_inherited_overrides: u32, #[builder( setters(doc(default(skip))), default = 2 + 2 * 3 )] setters_doc_default_skip: u32, #[builder( setters( doc(default(skip)), doc { /// Custom docs /// Multiline. } ), default = 2 + 2 * 3 )] setters_doc_default_skip_and_custom_docs_block: u32, #[builder(default = 2 + 2 * 3)] setters_doc_default_skip_from_top_level_on: u8, } }); let mut actual: syn::File = syn::parse2(actual_tokens.clone()).unwrap(); // Sanitize the output. Keep only setters and remove their bodies. let builder_impl = actual .items .iter_mut() .find_map(|item| match item { syn::Item::Impl(impl_item) => { (impl_item.self_ty == syn::parse_quote!(SutBuilder)).then(|| impl_item) } _ => None, }) .unwrap_or_else(|| { panic!("No builder impl block found. Generated toekens:\n{actual_tokens}") }); builder_impl.items.retain_mut(|item| match item { syn::ImplItem::Fn(fn_item) => { if fn_item.sig.ident == "build" { return false; } // Remove noise attributes fn_item.attrs.retain(|attr| { ["allow", "inline"] .iter() .all(|ident| !attr.path().is_ident(ident)) }); fn_item.block = syn::parse_quote!({}); true } _ => true, }); assert_snapshot("setters_docs_and_vis", builder_impl); } bon-macros-3.7.1/src/tests/mod.rs000064400000000000000000000010241046102023000147470ustar 00000000000000mod attr_setters; mod syntax_errors; use crate::util::prelude::*; use expect_test::{expect_file, ExpectFile}; fn snapshot(test_name: &str) -> ExpectFile { let snapshot_path = format!( "{}/tests/snapshots/{test_name}.rs", env!("CARGO_MANIFEST_DIR") ); expect_file![snapshot_path] } #[track_caller] fn assert_snapshot(test_name: &'static str, actual: &dyn ToTokens) { let actual = prettyplease::unparse(&syn::parse2(actual.to_token_stream()).unwrap()); snapshot(test_name).assert_eq(&actual); } bon-macros-3.7.1/src/tests/syntax_errors.rs000064400000000000000000000024601046102023000171170ustar 00000000000000use super::snapshot; use crate::util::prelude::*; #[allow(non_camel_case_types)] #[derive(Copy, Clone)] enum MacroKind { #[allow(dead_code)] builder, bon, } #[track_caller] fn assert_builder_codegen( macro_kind: MacroKind, test_name: &'static str, params: TokenStream, item: TokenStream, ) { let sut = match macro_kind { MacroKind::builder => crate::builder::generate_from_attr, MacroKind::bon => crate::bon::generate, }; let actual = sut(params, item); let actual = syn::parse2(actual.clone()) .map(|actual| prettyplease::unparse(&actual)) // There is a syntax error, so we can't prettify it .unwrap_or_else(|_err| actual.to_string()); snapshot(test_name).assert_eq(&actual); } macro_rules! test_codegen { ( #[$test_name:ident] #[$kind:ident$( ( $( $params:tt )* ) )?] $( $item:tt )* ) => { #[test] fn $test_name() { assert_builder_codegen( MacroKind::$kind, stringify!($test_name), quote!( $( $( $params )* )?), quote!( $( $item )* ), ); } } } test_codegen! { #[bon_incomplete_if] #[bon] impl Sut { #[builder] fn sut() { if 1 } } } bon-macros-3.7.1/src/util/attrs.rs000064400000000000000000000027511046102023000151500ustar 00000000000000use syn::spanned::Spanned; pub(crate) trait AttributeExt { fn is_doc_expr(&self) -> bool; fn as_doc_expr(&self) -> Option<&syn::Expr>; fn to_allow(&self) -> Option; } impl AttributeExt for syn::Attribute { fn is_doc_expr(&self) -> bool { self.as_doc_expr().is_some() } fn as_doc_expr(&self) -> Option<&syn::Expr> { let attr = match &self.meta { syn::Meta::NameValue(attr) => attr, _ => return None, }; if !attr.path.is_ident("doc") { return None; } Some(&attr.value) } /// Returns `Some` if this is an `#[allow(...)]` or `#[expect(...)]` attribute. /// Turns an `#[expect(...)]` into `#[allow(...)]`, which is useful to make sure /// that macro doesn't trigger another warning that there is actually no /// instance of a lint warning under the `#[expect(...)]`. fn to_allow(&self) -> Option { if self.path().is_ident("allow") { return Some(self.clone()); } if !self.path().is_ident("expect") { return None; } // Turn an `expect` into allow let mut attr = self.clone(); let path = match &mut attr.meta { syn::Meta::Path(path) => path, syn::Meta::List(meta) => &mut meta.path, syn::Meta::NameValue(meta) => &mut meta.path, }; *path = syn::parse_quote_spanned!(path.span()=> allow); Some(attr) } } bon-macros-3.7.1/src/util/expr.rs000064400000000000000000000010661046102023000147670ustar 00000000000000use crate::util::prelude::*; pub(crate) trait ExprExt { fn require_path_mod_style(&self) -> Result<&syn::Path>; } impl ExprExt for syn::Expr { fn require_path_mod_style(&self) -> Result<&syn::Path> { let expr = match self { Self::Path(expr) => expr, _ => bail!(self, "expected a simple path, like `foo::bar`"), }; crate::parsing::reject_attrs(&expr.attrs)?; crate::parsing::reject_syntax(" syntax", &expr.qself)?; expr.path.require_mod_style()?; Ok(&expr.path) } } bon-macros-3.7.1/src/util/fn_arg.rs000064400000000000000000000017251046102023000152470ustar 00000000000000pub(crate) trait FnArgExt { fn attrs_mut(&mut self) -> &mut Vec; fn ty_mut(&mut self) -> &mut syn::Type; fn as_receiver(&self) -> Option<&syn::Receiver>; fn as_typed(&self) -> Option<&syn::PatType>; } impl FnArgExt for syn::FnArg { fn attrs_mut(&mut self) -> &mut Vec { match self { Self::Receiver(arg) => &mut arg.attrs, Self::Typed(arg) => &mut arg.attrs, } } fn ty_mut(&mut self) -> &mut syn::Type { match self { Self::Receiver(arg) => &mut arg.ty, Self::Typed(arg) => &mut arg.ty, } } fn as_receiver(&self) -> Option<&syn::Receiver> { match self { Self::Typed(_) => None, Self::Receiver(arg) => Some(arg), } } fn as_typed(&self) -> Option<&syn::PatType> { match self { Self::Typed(arg) => Some(arg), Self::Receiver(_) => None, } } } bon-macros-3.7.1/src/util/generic_param.rs000064400000000000000000000012221046102023000165770ustar 00000000000000pub(crate) trait GenericParamExt { fn to_generic_argument(&self) -> syn::GenericArgument; } impl GenericParamExt for syn::GenericParam { fn to_generic_argument(&self) -> syn::GenericArgument { match self { Self::Lifetime(param) => syn::GenericArgument::Lifetime(param.lifetime.clone()), Self::Type(param) => { let ident = ¶m.ident; syn::GenericArgument::Type(syn::parse_quote!(#ident)) } Self::Const(param) => { let ident = ¶m.ident; syn::GenericArgument::Const(syn::parse_quote!(#ident)) } } } } bon-macros-3.7.1/src/util/ide.rs000064400000000000000000000200661046102023000145530ustar 00000000000000// We must make sure this code never panics. It must be able to handle all possible inputs. #![deny( clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unreachable, clippy::unimplemented, clippy::todo, clippy::exit )] use crate::util::prelude::*; use proc_macro2::TokenTree; use syn::parse::{Parse, ParseStream, Parser}; use syn::{token, Token}; pub(crate) fn parse_comma_separated_meta(input: ParseStream<'_>) -> syn::Result> { let mut output = vec![]; while !input.is_empty() { let value = input.parse::()?; output.push(value); while !input.is_empty() { if input.peek(Token![,]) { input.parse::()?; break; } // Skip the invalid token (comma is expected only). input.parse::()?; } } Ok(output) } #[derive(Clone, Debug)] pub(crate) enum Meta { None, Path(syn::Path), List(MetaList), NameValue(MetaNameValue), } impl Parse for Meta { fn parse(input: ParseStream<'_>) -> syn::Result { let path = loop { let path = input.call(syn::Path::parse_mod_style).ok(); if let Some(path) = path { break path; } if input.parse::().is_err() { return Ok(Self::None); } }; let meta = if input.peek(token::Paren) { let content; syn::parenthesized!(content in input); Self::List(MetaList { path, tokens: content.parse()?, }) } else if input.peek(token::Bracket) { let content; syn::bracketed!(content in input); Self::List(MetaList { path, tokens: content.parse()?, }) } else if input.peek(token::Brace) { let content; syn::braced!(content in input); Self::List(MetaList { path, tokens: content.parse()?, }) } else if input.peek(Token![=]) { Self::NameValue(MetaNameValue { path, eq_token: input.parse()?, value: input.parse().ok(), }) } else { Self::Path(path) }; Ok(meta) } } #[derive(Clone, Debug)] pub(crate) struct MetaList { pub(crate) path: syn::Path, pub(crate) tokens: TokenStream, } #[derive(Clone, Debug)] pub(crate) struct MetaNameValue { pub(crate) path: syn::Path, #[allow(dead_code)] pub(crate) eq_token: syn::Token![=], #[allow(dead_code)] pub(crate) value: Option, } fn paths_from_meta(meta: Vec) -> Vec { meta.into_iter() .filter_map(|meta| match meta { Meta::Path(path) => Some(path), Meta::NameValue(meta) => Some(meta.path), Meta::List(meta) => Some(meta.path), Meta::None => None, }) .collect() } /// This is a highly experimental function that attempts to generate code that /// can be used by IDEs to provide hints and completions for attributes in macros. /// /// The idea is that we parse a potentially incomplete syntax of the macro invocation /// and then we generate a set of modules that contain `use` statements with the /// identifiers passed to the macro. /// /// By placing these input identifiers in the right places inside of `use` statements /// we can hint the IDEs to provide completions for the attributes based on what's /// available in the module the use statement references. pub(crate) fn generate_completion_triggers(meta: Vec) -> TokenStream { let bon = meta .iter() .find_map(|meta| match meta { Meta::NameValue(meta) if meta.path.is_ident("crate") => Some(meta.value.as_ref()), _ => None, }) .flatten() .and_then(|expr| Some(expr.require_path_mod_style().ok()?.clone())) .unwrap_or_else(|| syn::parse_quote!(::bon)); let completions = CompletionsSchema::with_children( "builder_top_level", vec![ CompletionsSchema::leaf("builder_type"), CompletionsSchema::leaf("start_fn"), CompletionsSchema::leaf("finish_fn"), CompletionsSchema::leaf("state_mod"), CompletionsSchema::leaf("on").set_custom_filter(|meta| { if let Some(first) = meta.first() { if let Meta::Path(path) = first { if path.is_ident("into") || path.is_ident("required") || path.is_ident("overwritable") { return; } } meta.remove(0); } }), CompletionsSchema::leaf("derive"), ], ); let completion_triggers = completions.generate_completion_triggers(&bon, meta, &[]); quote! { // The special `rust_analyzer` CFG is enabled only when Rust Analyzer is // running its code analysis. This allows us to provide code that is // useful only for Rust Analyzer for it to provide hints and completions. #[allow(unexpected_cfgs)] const _: () = { #[cfg(rust_analyzer)] { #completion_triggers } }; } } struct CompletionsSchema { key: &'static str, children: Vec, custom_filter: Option)>, } impl CompletionsSchema { fn leaf(key: &'static str) -> Self { Self { key, children: vec![], custom_filter: None, } } fn with_children(key: &'static str, children: Vec) -> Self { Self { key, children, custom_filter: None, } } fn set_custom_filter(mut self, custom_filter: fn(&mut Vec)) -> Self { self.custom_filter = Some(custom_filter); self } fn generate_completion_triggers( &self, bon: &syn::Path, mut meta: Vec, module_prefix: &[&syn::Ident], ) -> TokenStream { if let Some(custom_filter) = self.custom_filter { custom_filter(&mut meta); } let module_suffix = syn::Ident::new(self.key, Span::call_site()); let module_name = module_prefix .iter() .copied() .chain([&module_suffix]) .collect::>(); let child_completion_triggers = self .children .iter() .map(|child| { let child_metas = meta .iter() .filter_map(|meta| { let meta = match meta { Meta::List(meta) => meta, _ => return None, }; if !meta.path.is_ident(&child.key) { return None; } parse_comma_separated_meta.parse2(meta.tokens.clone()).ok() }) .concat(); child.generate_completion_triggers(bon, child_metas, &module_name) }) .collect::>(); let paths = paths_from_meta(meta); let module_name_snake_case = syn::Ident::new(&module_name.iter().join("_"), Span::call_site()); quote! { mod #module_name_snake_case { // We separately import everything from the IDE hints module // and then put the `paths` in the `use self::{ ... }` statement // to avoid Rust Analyzer from providing completions for the // `self` keyword in the `use` statement. It works because // `use self::self` is not a valid syntax. use #bon::__::ide #(::#module_name)* ::*; use self::{ #( #paths as _, )* }; } #(#child_completion_triggers)* } } } bon-macros-3.7.1/src/util/ident.rs000064400000000000000000000066471046102023000151260ustar 00000000000000use crate::util::prelude::*; use ident_case::RenameRule; pub(crate) trait IdentExt { /// Converts the ident (assumed to be in `snake_case`) to `PascalCase` without /// preserving its span. /// /// Span loss is intentional to work around the semantic token type assignment /// ambiguity that may be experienced by IDEs. For example, rust analyzer /// assigns different colors to identifiers according to their semantic meaning. /// /// If identifiers with the same span were used in different contexts such as /// in function name and struct name positions, then rust-analyzer would chose /// the semantic meaning for syntax highlighting of the input identifier randomly /// out of these two contexts. /// /// By not preserving the span, we can ensure that the semantic meaning of the /// produced identifier won't influence the syntax highlighting of the original /// identifier. fn snake_to_pascal_case(&self) -> Self; /// Same thing as `snake_to_pascal_case` but converts `PascalCase` to `snake_case`. fn pascal_to_snake_case(&self) -> Self; /// Creates a new ident with the given name and span. If the name starts with /// `r#` then automatically creates a raw ident. fn new_maybe_raw(name: &str, span: Span) -> Self; /// Returns the name of the identifier stripping the `r#` prefix if it exists. fn raw_name(&self) -> String; } impl IdentExt for syn::Ident { fn snake_to_pascal_case(&self) -> Self { // There are no pascal case keywords in Rust except for `Self`, which // is anyway not allowed even as a raw identifier: // https://internals.rust-lang.org/t/raw-identifiers-dont-work-for-all-identifiers/9094 // // So no need to handle raw identifiers here. let mut renamed = RenameRule::PascalCase.apply_to_field(self.raw_name()); // Make sure `Self` keyword isn't generated. // This may happen if the input was `self_`, for example. if renamed == "Self" { renamed.push('_'); } Self::new(&renamed, Span::call_site()) } fn pascal_to_snake_case(&self) -> Self { let renamed = RenameRule::SnakeCase.apply_to_variant(self.raw_name()); Self::new_maybe_raw(&renamed, Span::call_site()) } fn new_maybe_raw(name: &str, span: Span) -> Self { // If the ident is already raw (starts with `r#`) then just create a raw ident. if let Some(name) = name.strip_prefix("r#") { return Self::new_raw(name, span); } // ..otherwise validate if it is a valid identifier. // The `parse_str` method will return an error if the name is not a valid // identifier. if syn::parse_str::(name).is_ok() { return Self::new(name, span); } // Try to make it a raw ident by adding `r#` prefix. // This won't work for some keywords such as `super`, `crate`, // `Self`, which are not allowed as raw identifiers if syn::parse_str::(&format!("r#{name}")).is_ok() { return Self::new_raw(name, span); } // As the final fallback add a trailing `_` to create a valid identifier Self::new(&format!("{name}_"), span) } fn raw_name(&self) -> String { let name = self.to_string(); if let Some(raw) = name.strip_prefix("r#") { raw.to_owned() } else { name } } } bon-macros-3.7.1/src/util/item.rs000064400000000000000000000020331046102023000147420ustar 00000000000000pub(crate) trait ItemExt { fn attrs_mut(&mut self) -> Option<&mut Vec>; } impl ItemExt for syn::Item { fn attrs_mut(&mut self) -> Option<&mut Vec> { let attrs = match self { Self::Const(item) => &mut item.attrs, Self::Enum(item) => &mut item.attrs, Self::ExternCrate(item) => &mut item.attrs, Self::Fn(item) => &mut item.attrs, Self::ForeignMod(item) => &mut item.attrs, Self::Impl(item) => &mut item.attrs, Self::Macro(item) => &mut item.attrs, Self::Mod(item) => &mut item.attrs, Self::Static(item) => &mut item.attrs, Self::Struct(item) => &mut item.attrs, Self::Trait(item) => &mut item.attrs, Self::TraitAlias(item) => &mut item.attrs, Self::Type(item) => &mut item.attrs, Self::Union(item) => &mut item.attrs, Self::Use(item) => &mut item.attrs, _ => return None, }; Some(attrs) } } bon-macros-3.7.1/src/util/iterator.rs000064400000000000000000000034741046102023000156470ustar 00000000000000use crate::util::prelude::*; use std::fmt::Write; pub(crate) trait IteratorExt: Iterator + Sized { /// Based on itertools: /// fn join(mut self, sep: &str) -> String where Self::Item: std::fmt::Display, { let first = match self.next() { Some(first) => first, _ => return String::new(), }; // estimate lower bound of capacity needed let (lower, _) = self.size_hint(); let mut result = String::with_capacity(sep.len() * lower); write!(&mut result, "{first}").unwrap(); for elt in self { result.push_str(sep); write!(&mut result, "{elt}").unwrap(); } result } } impl IteratorExt for I {} pub(crate) trait IntoIteratorExt: IntoIterator + Sized { fn try_equals_with( self, other: O, compare: impl Fn(Self::Item, O::Item) -> Result, ) -> Result where O: IntoIterator, O::IntoIter: ExactSizeIterator, Self::IntoIter: ExactSizeIterator, { let me = self.into_iter(); let other = other.into_iter(); if me.len() != other.len() { return Ok(false); } for (a, b) in me.zip(other) { if !compare(a, b)? { return Ok(false); } } Ok(true) } fn concat(self) -> Self::Item where Self::Item: Extend<::Item> + IntoIterator + Default, { self.into_iter() .reduce(|mut a, b| { a.extend(b); a }) .unwrap_or_default() } } impl IntoIteratorExt for I {} bon-macros-3.7.1/src/util/meta_list.rs000064400000000000000000000032361046102023000157730ustar 00000000000000use crate::util::prelude::*; pub(crate) trait MetaListExt { fn require_parens_delim(&self) -> Result<()>; fn require_curly_braces_delim(&self) -> Result<()>; } impl MetaListExt for syn::MetaList { fn require_parens_delim(&self) -> Result<()> { require_delim(self, MacroDelimKind::Paren) } fn require_curly_braces_delim(&self) -> Result<()> { require_delim(self, MacroDelimKind::Brace) } } fn require_delim(meta: &syn::MetaList, expected: MacroDelimKind) -> Result<()> { let actual = MacroDelimKind::from_syn(&meta.delimiter); if actual == expected { return Ok(()); } let path = darling::util::path_to_string(&meta.path); bail!( meta, "wrong delimiter, expected {} e.g. `{path}{}`, but got {}: `{path}{}`", expected.name(), expected.example(), actual.name(), actual.example(), ); } #[derive(PartialEq, Eq, Clone, Copy)] enum MacroDelimKind { Paren, Brace, Bracket, } impl MacroDelimKind { fn from_syn(delim: &syn::MacroDelimiter) -> Self { match delim { syn::MacroDelimiter::Paren(_) => Self::Paren, syn::MacroDelimiter::Brace(_) => Self::Brace, syn::MacroDelimiter::Bracket(_) => Self::Bracket, } } fn name(self) -> &'static str { match self { Self::Paren => "parentheses", Self::Brace => "curly braces", Self::Bracket => "square brackets", } } fn example(self) -> &'static str { match self { Self::Paren => "(...)", Self::Brace => "{...}", Self::Bracket => "[...]", } } } bon-macros-3.7.1/src/util/mod.rs000064400000000000000000000043231046102023000145670ustar 00000000000000mod attrs; mod expr; mod fn_arg; mod generic_param; mod ident; mod item; mod iterator; mod meta_list; mod path; mod punctuated; mod ty; mod vec; mod visibility; pub(crate) mod ide; use prelude::*; pub(crate) mod prelude { pub(crate) use proc_macro2::{Span, TokenStream}; pub(crate) use quote::{format_ident, quote, quote_spanned, ToTokens}; /// The `Error` type in in this crate is supposed to act like `anyhow::Error` /// providing a simple way to create and return errors from format strings. /// /// See [`err!()`] and [`bail!()`] macros for creating errors. Right now this /// is just a reexport of [`darling::Error`] because that error already provides /// the anyhow-like error handling experience. pub(crate) use darling::Error; pub(crate) type Result = std::result::Result; pub(crate) use super::attrs::AttributeExt; pub(crate) use super::expr::ExprExt; pub(crate) use super::fn_arg::FnArgExt; pub(crate) use super::generic_param::GenericParamExt; pub(crate) use super::ident::IdentExt; pub(crate) use super::item::ItemExt; pub(crate) use super::iterator::{IntoIteratorExt, IteratorExt}; pub(crate) use super::meta_list::MetaListExt; pub(crate) use super::path::PathExt; pub(crate) use super::punctuated::PunctuatedExt; pub(crate) use super::ty::TypeExt; pub(crate) use super::vec::VecExt; pub(crate) use super::visibility::VisibilityExt; pub(crate) use super::{bail, err}; } /// Inspired by `anyhow::bail`, but returns a [`Result`] with [`darling::Error`]. /// It accepts the value that implements [`syn::spanned::Spanned`] to attach the /// span to the error. #[allow(edition_2024_expr_fragment_specifier)] macro_rules! bail { ($spanned:expr, $($tt:tt)*) => { return Err($crate::util::err!($spanned, $($tt)*)) }; } /// Inspired by `anyhow::anyhow`, but returns a [`darling::Error`]. /// It accepts the value that implements [`syn::spanned::Spanned`] to attach the /// span to the error. #[allow(edition_2024_expr_fragment_specifier)] macro_rules! err { ($spanned:expr, $($tt:tt)*) => { ::darling::Error::custom(format_args!($($tt)*)).with_span($spanned) }; } pub(crate) use {bail, err}; bon-macros-3.7.1/src/util/path.rs000064400000000000000000000017651046102023000147530ustar 00000000000000use crate::util::prelude::*; pub(crate) trait PathExt { fn starts_with_segment(&self, desired_segment: &str) -> bool; fn ends_with_segment(&self, desired_segment: &str) -> bool; /// Returns an error if this path has some generic arguments. fn require_mod_style(&self) -> Result; } impl PathExt for syn::Path { fn starts_with_segment(&self, desired_segment: &str) -> bool { self.segments .first() .map(|first| first.ident == desired_segment) .unwrap_or(false) } fn ends_with_segment(&self, desired_segment: &str) -> bool { self.segments .last() .map(|first| first.ident == desired_segment) .unwrap_or(false) } fn require_mod_style(&self) -> Result { if self .segments .iter() .any(|seg| seg.arguments != syn::PathArguments::None) { bail!(self, "expected a simple path e.g. `foo::bar`"); } Ok(()) } } bon-macros-3.7.1/src/util/punctuated.rs000064400000000000000000000015121046102023000161610ustar 00000000000000use crate::util::prelude::*; use syn::punctuated::Punctuated; pub(crate) trait PunctuatedExt { /// Remove all elements from the [`Punctuated`] that do not satisfy the predicate. /// Also bail on the first error that the predicate returns. fn try_retain_mut(&mut self, f: impl FnMut(&mut T) -> Result) -> Result; } impl PunctuatedExt for Punctuated where P: Default, { fn try_retain_mut(&mut self, mut try_predicate: impl FnMut(&mut T) -> Result) -> Result { // Unforunatelly, there is no builtin `retain` or `remove` in `Punctuated` // so we just re-create it from scratch. for mut pair in std::mem::take(self).into_pairs() { if try_predicate(pair.value_mut())? { self.extend([pair]); } } Ok(()) } } bon-macros-3.7.1/src/util/ty/match_types.rs000064400000000000000000000334221046102023000167660ustar 00000000000000use crate::util::iterator::IntoIteratorExt; use crate::util::prelude::*; use syn::spanned::Spanned; pub(crate) fn match_return_types( scrutinee: &syn::ReturnType, pattern: &syn::ReturnType, ) -> Result { match (scrutinee, pattern) { (syn::ReturnType::Default, syn::ReturnType::Default) => Ok(true), (syn::ReturnType::Default, syn::ReturnType::Type(_, pattern)) => { match_types(&syn::parse_quote!(()), pattern) } (syn::ReturnType::Type(_, scrutinee), syn::ReturnType::Default) => { Ok(**scrutinee == syn::parse_quote!(())) } (syn::ReturnType::Type(_, scrutinee), syn::ReturnType::Type(_, pattern)) => { match_types(scrutinee, pattern) } } } fn match_paths(scrutinee: &syn::Path, pattern: &syn::Path) -> Result { let verdict = scrutinee.leading_colon == pattern.leading_colon && scrutinee .segments .iter() .try_equals_with(&pattern.segments, |scrutinee, pattern| { let verdict = scrutinee.ident == pattern.ident && match_path_args(&scrutinee.arguments, &pattern.arguments)?; Ok(verdict) })?; Ok(verdict) } fn match_path_args(scrutinee: &syn::PathArguments, pattern: &syn::PathArguments) -> Result { use syn::PathArguments::*; let verdict = match (scrutinee, pattern) { (None, None) => true, (AngleBracketed(scrutinee), AngleBracketed(pattern)) => { match_angle_bracketed_generic_args(scrutinee, pattern)? } (Parenthesized(scrutinee), Parenthesized(pattern)) => { scrutinee .inputs .iter() .try_equals_with(&pattern.inputs, match_types)? && match_return_types(&scrutinee.output, &pattern.output)? } _ => false, }; Ok(verdict) } fn match_angle_bracketed_generic_args( scrutinee: &syn::AngleBracketedGenericArguments, pattern: &syn::AngleBracketedGenericArguments, ) -> Result { scrutinee .args .iter() .try_equals_with(&pattern.args, match_generic_args) } fn match_option( scrutinee: Option<&T>, pattern: Option<&T>, compare: impl Fn(&T, &T) -> Result, ) -> Result { match (scrutinee, &pattern) { (None, None) => Ok(true), (Some(scrutinee), Some(pattern)) => compare(scrutinee, pattern), _ => Ok(false), } } fn match_generic_args( scrutinee: &syn::GenericArgument, pattern: &syn::GenericArgument, ) -> Result { use syn::GenericArgument::*; let verdict = match pattern { Lifetime(pattern) => { if pattern.ident != "_" { return Err(unsupported_syntax_error( pattern, "Lifetimes are ignored during type pattern matching. \ Use an anonymous lifetime (`'_`) in this position instead. \ Named lifetime syntax in generic parameters", )); } matches!(scrutinee, Lifetime(_)) } Type(pattern) => { let scrutinee = match scrutinee { Type(scrutinee) => scrutinee, _ => return Ok(false), }; match_types(scrutinee, pattern)? } Const(pattern) => { let scrutinee = match scrutinee { Const(scrutinee) => scrutinee, _ => return Ok(false), }; match_exprs(scrutinee, pattern) } AssocType(pattern) => { let scrutinee = match scrutinee { AssocType(scrutinee) => scrutinee, _ => return Ok(false), }; scrutinee.ident == pattern.ident && match_types(&scrutinee.ty, &pattern.ty)? && match_option( scrutinee.generics.as_ref(), pattern.generics.as_ref(), match_angle_bracketed_generic_args, )? } AssocConst(pattern) => { let scrutinee = match scrutinee { AssocConst(scrutinee) => scrutinee, _ => return Ok(false), }; scrutinee.ident == pattern.ident && match_option( scrutinee.generics.as_ref(), pattern.generics.as_ref(), match_angle_bracketed_generic_args, )? && match_exprs(&scrutinee.value, &pattern.value) } _ => return Err(unsupported_syntax_error(&pattern, "this syntax")), }; Ok(verdict) } fn match_exprs(scrutinee: &syn::Expr, pattern: &syn::Expr) -> bool { matches!(pattern, syn::Expr::Infer(_)) || scrutinee == pattern } pub(crate) fn match_types(scrutinee: &syn::Type, pattern: &syn::Type) -> Result { use syn::Type::*; let pattern = pattern.peel(); if let Infer(_) = pattern { return Ok(true); } let scrutinee = scrutinee.peel(); let verdict = match pattern { Array(pattern) => { let scrutinee = match scrutinee { Array(scrutinee) => scrutinee, _ => return Ok(false), }; match_types(&scrutinee.elem, &pattern.elem)? && match_exprs(&scrutinee.len, &pattern.len) } Path(pattern) => { if let Some(qself) = &pattern.qself { return Err(unsupported_syntax_error(qself, " syntax")); } let scrutinee = match scrutinee { Path(scrutinee) => scrutinee, _ => return Ok(false), }; scrutinee.qself.is_none() && match_paths(&scrutinee.path, &pattern.path)? } Ptr(pattern) => { let scrutinee = match scrutinee { Ptr(scrutinee) => scrutinee, _ => return Ok(false), }; scrutinee.const_token == pattern.const_token && scrutinee.mutability == pattern.mutability && match_types(&scrutinee.elem, &pattern.elem)? } Reference(pattern) => { if let Some(lifetime) = &pattern.lifetime { return Err(unsupported_syntax_error( lifetime, "Lifetimes are ignored during type pattern matching. \ Explicit lifetime syntax", )); } let scrutinee = match scrutinee { Reference(scrutinee) => scrutinee, _ => return Ok(false), }; scrutinee.mutability == pattern.mutability && match_types(&scrutinee.elem, &pattern.elem)? } Slice(pattern) => { let scrutinee = match scrutinee { Slice(scrutinee) => scrutinee, _ => return Ok(false), }; match_types(&scrutinee.elem, &pattern.elem)? } Tuple(pattern) => { let scrutinee = match scrutinee { Tuple(scrutinee) => scrutinee, _ => return Ok(false), }; scrutinee .elems .iter() .try_equals_with(&pattern.elems, match_types)? } Never(_) => matches!(scrutinee, Never(_)), _ => return Err(unsupported_syntax_error(&pattern, "this syntax")), }; Ok(verdict) } fn unsupported_syntax_error(spanned: &impl Spanned, syntax: &str) -> Error { err!( spanned, "{syntax} is not supported in type patterns yet. If you have \ a use case for this, please open an issue at \ https://github.com/elastio/bon/issues.", ) } #[cfg(test)] mod tests { // One less `&` character to type in assertions #![allow(clippy::needless_pass_by_value)] use super::*; use syn::parse_quote as pq; #[track_caller] fn assert_match_types(scrutinee: syn::Type, pattern: syn::Type) { // Make sure pure wildcard matches everything assert!(scrutinee.matches(&pq!(_)).unwrap()); assert!(scrutinee.matches(&pattern).unwrap()); } #[track_caller] fn assert_not_match_types(scrutinee: syn::Type, pattern: syn::Type) { assert!(!scrutinee.matches(&pattern).unwrap()); } #[track_caller] fn assert_unsupported(pattern: syn::Type) { let scrutinee: syn::Type = syn::parse_quote!(()); let result = scrutinee.matches(&pattern); let err = result.unwrap_err().to_string(); assert!( err.contains("is not supported in type patterns yet"), "Error: {err}" ); } #[test] fn array() { assert_match_types(pq!([u8; 4]), pq!([u8; 4])); assert_match_types(pq!([u8; 4]), pq!([_; 4])); assert_match_types(pq!([u8; 4]), pq!([_; _])); assert_match_types(pq!([u8; 4]), pq!([u8; _])); assert_not_match_types(pq!([u8; 4]), pq!([u8; 5])); assert_not_match_types(pq!([u8; 4]), pq!([_; 5])); assert_not_match_types(pq!([u8; 4]), pq!([u16; 4])); assert_not_match_types(pq!([u8; 4]), pq!([u16; _])); assert_not_match_types(pq!([u8; 4]), pq!([_])); assert_not_match_types(pq!([u8; 4]), pq!([u8])); } #[test] fn path() { assert_match_types(pq!(bool), pq!(bool)); assert_match_types(pq!(foo::Bar), pq!(foo::Bar)); assert_match_types(pq!(crate::foo::Bar), pq!(crate::foo::Bar)); assert_match_types(pq!(super::foo::Bar), pq!(super::foo::Bar)); assert_not_match_types(pq!(::Bar), pq!(Bar)); assert_not_match_types(pq!(Bar), pq!(::Bar)); assert_not_match_types(pq!(super::foo::Bar), pq!(crate::foo::Bar)); assert_match_types(pq!(foo::Bar), pq!(foo::Bar<_>)); assert_match_types(pq!(foo::Bar), pq!(foo::Bar)); assert_match_types(pq!(foo::Bar), pq!(foo::Bar)); assert_match_types(pq!(foo::Bar), pq!(foo::Bar<_, bool>)); assert_match_types(pq!(foo::Bar), pq!(foo::Bar)); assert_match_types(pq!(foo::Bar), pq!(foo::Bar<_, _>)); assert_not_match_types(pq!(foo::Bar), pq!(foo::Bar)); assert_not_match_types(pq!(foo::Bar), pq!(foo::Bar)); assert_not_match_types(pq!(foo::Bar), pq!(foo::Bar)); assert_not_match_types(pq!(foo::Bar), pq!(foo::Bar<_, _>)); assert_not_match_types(pq!(foo::Foo), pq!(foo::Bar)); } #[test] fn ptr() { assert_match_types(pq!(*const u8), pq!(*const u8)); assert_match_types(pq!(*const u8), pq!(*const _)); assert_match_types(pq!(*mut u8), pq!(*mut u8)); assert_match_types(pq!(*mut u8), pq!(*mut _)); assert_not_match_types(pq!(*const u8), pq!(*mut u8)); assert_not_match_types(pq!(*const u8), pq!(*mut _)); assert_not_match_types(pq!(*mut u8), pq!(*const u8)); assert_not_match_types(pq!(*mut u8), pq!(*const _)); } #[test] fn reference() { assert_match_types(pq!(&u8), pq!(&u8)); assert_match_types(pq!(&u8), pq!(&_)); assert_match_types(pq!(&mut u8), pq!(&mut u8)); assert_match_types(pq!(&mut u8), pq!(&mut _)); assert_match_types(pq!(&'a u8), pq!(&_)); assert_match_types(pq!(&'_ u8), pq!(&_)); assert_match_types(pq!(&'static u8), pq!(&_)); assert_match_types(pq!(&'a mut u8), pq!(&mut _)); assert_match_types(pq!(&'_ mut u8), pq!(&mut _)); assert_match_types(pq!(&'static mut u8), pq!(&mut _)); assert_match_types(pq!(&'a u8), pq!(&u8)); assert_match_types(pq!(&'_ u8), pq!(&u8)); assert_match_types(pq!(&'static u8), pq!(&u8)); assert_match_types(pq!(&'a mut u8), pq!(&mut u8)); assert_match_types(pq!(&'_ mut u8), pq!(&mut u8)); assert_match_types(pq!(&'static mut u8), pq!(&mut u8)); assert_not_match_types(pq!(&u8), pq!(&mut u8)); assert_not_match_types(pq!(&u8), pq!(&mut _)); assert_not_match_types(pq!(&mut u8), pq!(&u8)); assert_not_match_types(pq!(&mut u8), pq!(&_)); } #[test] fn slice() { assert_match_types(pq!([u8]), pq!([u8])); assert_match_types(pq!([u8]), pq!([_])); assert_match_types(pq!(&[u8]), pq!(&[u8])); assert_match_types(pq!(&[u8]), pq!(&[_])); assert_match_types(pq!(&[u8]), pq!(&_)); assert_not_match_types(pq!([u8]), pq!([u16])); assert_not_match_types(pq!([u8]), pq!([u8; 4])); } #[test] fn tuple() { assert_match_types(pq!((u8, bool)), pq!((u8, bool))); assert_match_types(pq!((u8, bool)), pq!((_, _))); assert_match_types(pq!((u8, bool)), pq!((u8, _))); assert_match_types(pq!((u8, bool)), pq!((_, bool))); assert_match_types(pq!(()), pq!(())); assert_match_types(pq!((u8,)), pq!((u8,))); assert_match_types(pq!((u8,)), pq!((_,))); assert_not_match_types(pq!((u8, bool)), pq!((bool, u8))); assert_not_match_types(pq!((u8, bool)), pq!((u8, bool, u8))); assert_not_match_types(pq!((u8,)), pq!(())); assert_not_match_types(pq!(()), pq!((u8,))); } #[test] fn never() { assert_match_types(pq!(!), pq!(!)); assert_not_match_types(pq!(!), pq!(bool)); } #[test] fn unsupported() { assert_unsupported(pq!(dyn Trait)); assert_unsupported(pq!(dyn FnOnce())); assert_unsupported(pq!(impl Trait)); assert_unsupported(pq!(impl FnOnce())); assert_unsupported(pq!(fn())); assert_unsupported(pq!(&'a _)); assert_unsupported(pq!(&'_ _)); assert_unsupported(pq!(&'static _)); assert_unsupported(pq!(for<'a> Trait<'a>)); assert_unsupported(pq!(for<'a> fn())); assert_unsupported(pq!(::Foo)); } } bon-macros-3.7.1/src/util/ty/mod.rs000064400000000000000000000066421046102023000152310ustar 00000000000000mod match_types; use crate::util::prelude::*; use syn::punctuated::Punctuated; pub(crate) trait TypeExt { /// Try downcasting the type to [`syn::Type::Path`] fn as_path(&self) -> Option<&syn::TypePath>; /// Try downcasting the type to [`syn::Type::Path`]. If it has a [`syn::QSelf`] /// then this method will return `None`. fn as_path_no_qself(&self) -> Option<&syn::Path>; /// Detects if the type is [`Option`] and returns its generic type parameter fn option_type_param(&self) -> Option<&syn::Type>; /// Validates that this type is a generic type (path without [`syn::QSelf`]) /// which ends with the given last segment that passes the predicate /// `is_desired_last_segment`. fn as_generic_angle_bracketed_path( &self, is_desired_last_segment: impl FnOnce(&syn::Ident) -> bool, ) -> Option>; /// Heuristically detects if the type is [`Option`] fn is_option(&self) -> bool; /// Recursively strips the [`syn::Type::Group`] and [`syn::Type::Paren`] wrappers fn peel(&self) -> &Self; /// Returns `true` if the given type matches the pattern. The types match only if /// their tokens are equal or if they differ in the places where the pattern has /// a wildcard [`syn::Type::Infer`] e.g. `Vec` matches the pattern `Vec<_>`. /// /// Any wildcards in `Self` will not be specially handled. Only wildcards in `pattern` /// have semantic meaning. fn matches(&self, pattern: &syn::Type) -> Result; } impl TypeExt for syn::Type { fn as_path(&self) -> Option<&syn::TypePath> { match self.peel() { Self::Path(path) => Some(path), _ => None, } } fn as_path_no_qself(&self) -> Option<&syn::Path> { let path = self.as_path()?; if path.qself.is_some() { return None; } Some(&path.path) } fn option_type_param(&self) -> Option<&syn::Type> { let ty = self.as_generic_angle_bracketed_path(|last_segment| last_segment == "Option")?; if ty.args.len() != 1 { return None; } let arg = ty.args.first()?; let arg = match arg { syn::GenericArgument::Type(arg) => arg, _ => return None, }; Some(arg) } fn as_generic_angle_bracketed_path( &self, is_desired_last_segment: impl FnOnce(&syn::Ident) -> bool, ) -> Option> { let path = self.as_path_no_qself()?; let last_segment = path.segments.last()?; if !is_desired_last_segment(&last_segment.ident) { return None; } let args = match &last_segment.arguments { syn::PathArguments::AngleBracketed(args) => &args.args, _ => return None, }; Some(GenericAngleBracketedPath { path, args }) } fn is_option(&self) -> bool { self.option_type_param().is_some() } fn peel(&self) -> &Self { match self { Self::Group(group) => group.elem.peel(), Self::Paren(paren) => paren.elem.peel(), _ => self, } } fn matches(&self, pattern: &syn::Type) -> Result { match_types::match_types(self, pattern) } } pub(crate) struct GenericAngleBracketedPath<'a> { pub(crate) path: &'a syn::Path, pub(crate) args: &'a Punctuated, } bon-macros-3.7.1/src/util/vec.rs000064400000000000000000000012531046102023000145640ustar 00000000000000use crate::util::prelude::*; pub(crate) trait VecExt { /// Remove all elements from the [`Vec`] that do not satisfy the predicate. /// Also bail on the first error that the predicate returns. fn try_retain_mut(&mut self, f: impl FnMut(&mut T) -> Result) -> Result; } impl VecExt for Vec { fn try_retain_mut(&mut self, mut try_predicate: impl FnMut(&mut T) -> Result) -> Result { let mut i = 0; while i < self.len() { if try_predicate(self.get_mut(i).expect("BUG: index must be valid"))? { i += 1; } else { self.remove(i); } } Ok(()) } } bon-macros-3.7.1/src/util/visibility.rs000064400000000000000000000113621046102023000162000ustar 00000000000000use crate::util::prelude::*; pub(crate) trait VisibilityExt { /// Returns [`syn::Visibility`] that is equivalent to the current visibility /// but for an item that is inside of the child module. This basically does /// the following conversions. /// /// - `pub` -> `pub` (unchanged) /// - `pub(crate)` -> `pub(crate)` (unchanged) /// - `pub(self)` or ` ` (default private visibility) -> `pub(super)` /// - `pub(super)` -> `pub(in super::super)` /// - `pub(in relative::path)` -> `pub(in super::relative::path)` /// - `pub(in ::absolute::path)` -> `pub(in ::absolute::path)` (unchanged) /// - `pub(in crate::path)` -> `pub(in crate::path)` (unchanged) /// - `pub(in self::path)` -> `pub(in super::path)` /// - `pub(in super::path)` -> `pub(in super::super::path)` /// /// Note that absolute paths in `pub(in ...)` are not supported with Rust 2018+, /// according to the [Rust reference]: /// /// > Edition Differences: Starting with the 2018 edition, paths for pub(in path) /// > must start with crate, self, or super. The 2015 edition may also use paths /// > starting with :: or modules from the crate root. /// /// # Errors /// /// This function may return an error if it encounters some unexpected syntax. /// For example, some syntax that isn't known to the latest version of Rust /// this code was written for. /// /// [Rust reference]: https://doc.rust-lang.org/reference/visibility-and-privacy.html#pubin-path-pubcrate-pubsuper-and-pubself fn into_equivalent_in_child_module(self) -> Result; } impl VisibilityExt for syn::Visibility { fn into_equivalent_in_child_module(mut self) -> Result { match &mut self { Self::Public(_) => Ok(self), Self::Inherited => Ok(syn::parse_quote!(pub(super))), Self::Restricted(syn::VisRestricted { path, in_token: None, .. }) => { if path.is_ident("crate") { return Ok(self); } if path.is_ident("super") { return Ok(syn::parse_quote!(pub(in super::#path))); } if path.is_ident("self") { return Ok(syn::parse_quote!(pub(super))); } bail!( &self, "Expected either `crate` or `super` or `in some::path` inside of \ `pub(...)` but got something else. This may be because a new \ syntax for visibility was released in a newer Rust version, \ but this crate doesn't support it." ); } Self::Restricted(syn::VisRestricted { path, in_token: Some(_), .. }) => { if path.leading_colon.is_some() { return Ok(self); } if path.starts_with_segment("crate") { return Ok(self); } if let Some(first_segment) = path.segments.first_mut() { if first_segment.ident == "self" { let span = first_segment.ident.span(); *first_segment = syn::parse_quote_spanned!(span=>super); return Ok(self); } } path.segments.insert(0, syn::parse_quote!(super)); Ok(self) } } } } #[cfg(test)] mod tests { use super::*; use syn::parse_quote as pq; #[test] fn all_tests() { #[track_caller] // One less `&` character to type in assertions #[allow(clippy::needless_pass_by_value)] fn test(vis: syn::Visibility, expected: syn::Visibility) { let actual = vis.into_equivalent_in_child_module().unwrap(); assert!( actual == expected, "got:\nactual: {}\nexpected: {}", actual.to_token_stream(), expected.to_token_stream() ); } test(pq!(pub), pq!(pub)); test(pq!(pub(crate)), pq!(pub(crate))); test(pq!(pub(self)), pq!(pub(super))); test(pq!(), pq!(pub(super))); test(pq!(pub(super)), pq!(pub(in super::super))); test( pq!(pub(in relative::path)), pq!(pub(in super::relative::path)), ); test(pq!(pub(in crate::path)), pq!(pub(in crate::path))); test(pq!(pub(in self::path)), pq!(pub(in super::path))); test(pq!(pub(in super::path)), pq!(pub(in super::super::path))); test(pq!(pub(in ::absolute::path)), pq!(pub(in ::absolute::path))); } } bon-macros-3.7.1/tests/snapshots/bon_incomplete_if.rs000064400000000000000000000001571046102023000211040ustar 00000000000000:: core :: compile_error ! { "unexpected end of input, expected curly braces" } impl Sut { fn sut () { if 1 } }bon-macros-3.7.1/tests/snapshots/setters_docs_and_vis.rs000064400000000000000000000203211046102023000216300ustar 00000000000000#[allow(unused_parens)] #[automatically_derived] #[allow(deprecated)] impl SutBuilder { /**_**Required.**_ */ /// Docs on the required field setters. /// Multiline. pub(in overridden) fn required_field( mut self, value: u32, ) -> SutBuilder> where S::RequiredField: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::optional_field()) / [Option](Self::maybe_optional_field()) setters)._ */ /// Docs on the optional field setters. /// Multiline. pub(in overridden) fn optional_field( self, value: u32, ) -> SutBuilder> where S::OptionalField: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::optional_field()) / [Option](Self::maybe_optional_field()) setters)._ */ /// Docs on the optional field setters. /// Multiline. pub(in overridden) fn maybe_optional_field( mut self, value: Option, ) -> SutBuilder> where S::OptionalField: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::default_field()) / [Option](Self::maybe_default_field()) setters)._ _**Default:**_ ```2 + 2 * 3```. */ /// Docs on the default field setters. /// Multiline. pub(in overridden) fn default_field( self, value: u32, ) -> SutBuilder> where S::DefaultField: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::default_field()) / [Option](Self::maybe_default_field()) setters)._ _**Default:**_ ```2 + 2 * 3```. */ /// Docs on the default field setters. /// Multiline. pub(in overridden) fn maybe_default_field( mut self, value: Option, ) -> SutBuilder> where S::DefaultField: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::optional_field_with_specific_overrides()) / [Option](Self::maybe_optional_field_with_specific_overrides()) setters)._ */ /// Docs on some_fn /// Multiline. pub(in some_fn_overridden) fn optional_field_with_specific_overrides( self, value: u32, ) -> SutBuilder> where S::OptionalFieldWithSpecificOverrides: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::optional_field_with_specific_overrides()) / [Option](Self::maybe_optional_field_with_specific_overrides()) setters)._ */ /// Docs on option_fn /// Multiline. pub(in option_fn_overridden) fn maybe_optional_field_with_specific_overrides( mut self, value: Option, ) -> SutBuilder> where S::OptionalFieldWithSpecificOverrides: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::default_field_with_specific_overrides()) / [Option](Self::maybe_default_field_with_specific_overrides()) setters)._ _**Default:**_ ```2 + 2 * 3```. */ /// Docs on some_fn /// Multiline. pub(in some_fn_overridden) fn default_field_with_specific_overrides( self, value: u32, ) -> SutBuilder> where S::DefaultFieldWithSpecificOverrides: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::default_field_with_specific_overrides()) / [Option](Self::maybe_default_field_with_specific_overrides()) setters)._ _**Default:**_ ```2 + 2 * 3```. */ /// Docs on option_fn /// Multiline. pub(in option_fn_overridden) fn maybe_default_field_with_specific_overrides( mut self, value: Option, ) -> SutBuilder> where S::DefaultFieldWithSpecificOverrides: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::optional_field_with_inherited_overrides()) / [Option](Self::maybe_optional_field_with_inherited_overrides()) setters)._ */ /// Common docs /// Multiline. pub(in overridden) fn optional_field_with_inherited_overrides( self, value: u32, ) -> SutBuilder> where S::OptionalFieldWithInheritedOverrides: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::optional_field_with_inherited_overrides()) / [Option](Self::maybe_optional_field_with_inherited_overrides()) setters)._ */ /// Docs on option_fn /// Multiline. pub(in option_fn_overridden) fn maybe_optional_field_with_inherited_overrides( mut self, value: Option, ) -> SutBuilder> where S::OptionalFieldWithInheritedOverrides: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::default_field_with_inherited_overrides()) / [Option](Self::maybe_default_field_with_inherited_overrides()) setters)._ _**Default:**_ ```2 + 2 * 3```. */ /// Common docs /// Multiline. pub(in overridden) fn default_field_with_inherited_overrides( self, value: u32, ) -> SutBuilder> where S::DefaultFieldWithInheritedOverrides: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::default_field_with_inherited_overrides()) / [Option](Self::maybe_default_field_with_inherited_overrides()) setters)._ _**Default:**_ ```2 + 2 * 3```. */ /// Docs on option_fn /// Multiline. pub(in option_fn_overridden) fn maybe_default_field_with_inherited_overrides( mut self, value: Option, ) -> SutBuilder> where S::DefaultFieldWithInheritedOverrides: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::setters_doc_default_skip()) / [Option](Self::maybe_setters_doc_default_skip()) setters)._ */ fn setters_doc_default_skip( self, value: u32, ) -> SutBuilder> where S::SettersDocDefaultSkip: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::setters_doc_default_skip()) / [Option](Self::maybe_setters_doc_default_skip()) setters)._ */ fn maybe_setters_doc_default_skip( mut self, value: Option, ) -> SutBuilder> where S::SettersDocDefaultSkip: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::setters_doc_default_skip_and_custom_docs_block()) / [Option](Self::maybe_setters_doc_default_skip_and_custom_docs_block()) setters)._ */ /// Custom docs /// Multiline. fn setters_doc_default_skip_and_custom_docs_block( self, value: u32, ) -> SutBuilder> where S::SettersDocDefaultSkipAndCustomDocsBlock: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::setters_doc_default_skip_and_custom_docs_block()) / [Option](Self::maybe_setters_doc_default_skip_and_custom_docs_block()) setters)._ */ /// Custom docs /// Multiline. fn maybe_setters_doc_default_skip_and_custom_docs_block( mut self, value: Option, ) -> SutBuilder> where S::SettersDocDefaultSkipAndCustomDocsBlock: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::setters_doc_default_skip_from_top_level_on()) / [Option](Self::maybe_setters_doc_default_skip_from_top_level_on()) setters)._ */ fn setters_doc_default_skip_from_top_level_on( self, value: u8, ) -> SutBuilder> where S::SettersDocDefaultSkipFromTopLevelOn: sut_builder::IsUnset, {} /**_**Optional** ([Some](Self::setters_doc_default_skip_from_top_level_on()) / [Option](Self::maybe_setters_doc_default_skip_from_top_level_on()) setters)._ */ fn maybe_setters_doc_default_skip_from_top_level_on( mut self, value: Option, ) -> SutBuilder> where S::SettersDocDefaultSkipFromTopLevelOn: sut_builder::IsUnset, {} }