From ff213e8ef4c41785ea26913bb7c4f07d7c973de5 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 19 Sep 2024 19:26:26 +0000 Subject: [PATCH 001/119] Initial attempt --- .../builder_gen/member/into_conversion.rs | 2 +- bon-macros/src/builder/builder_gen/mod.rs | 49 ++++- .../src/builder/builder_gen/setter_methods.rs | 6 +- bon-macros/src/lib.rs | 182 ++++++++++++++++++ bon/src/private/mod.rs | 3 + bon/src/private/state.rs | 14 ++ 6 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 bon/src/private/state.rs diff --git a/bon-macros/src/builder/builder_gen/member/into_conversion.rs b/bon-macros/src/builder/builder_gen/member/into_conversion.rs index 1aa00adc..848c27e2 100644 --- a/bon-macros/src/builder/builder_gen/member/into_conversion.rs +++ b/bon-macros/src/builder/builder_gen/member/into_conversion.rs @@ -16,7 +16,7 @@ impl NamedMember { is_into_enabled(self.origin, &self.params, scrutinee, on_params) } - pub(crate) fn setter_method_core_name(&self) -> &syn::Ident { + pub(crate) fn public_ident(&self) -> &syn::Ident { self.params.name.as_ref().unwrap_or(&self.norm_ident) } } diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 90bab8cc..ecc72941 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -171,6 +171,7 @@ impl BuilderGenCtx { let builder_decl = self.builder_decl(); let builder_impl = self.builder_impl()?; let builder_derives = self.builder_derives(); + let builder_type_macro = self.builder_type_macro(); // -- Postprocessing -- // Here we parse all items back and add the `allow` attributes to them. @@ -178,6 +179,7 @@ impl BuilderGenCtx { #builder_decl #builder_derives #builder_impl + #builder_type_macro }; let mut other_items = other_items.items; @@ -196,6 +198,49 @@ impl BuilderGenCtx { }) } + fn builder_type_macro(&self) -> TokenStream2 { + let private_macro_ident = quote::format_ident!("__{}", self.builder_ident.raw_name()); + + let vis = &self.vis; + let builder_ident = &self.builder_ident; + + let member_types = self.named_members().map(|member| &member.norm_ty); + let generics_decl = &self.generics.decl_without_defaults; + let generic_args = &self.generics.args; + let where_clause = &self.generics.where_clause; + + let schema = self.named_members().map(|member| { + let member_ident = &member.public_ident(); + if member.is_optional() { + quote!(#member_ident: optional) + } else { + quote!(#member_ident: required) + } + }); + + quote! { + impl <#(#generics_decl,)*> + ::bon::private::state::Members for #builder_ident<#(#generic_args,)*> + #where_clause + { + type Members = (#(#member_types,)*); + } + + macro_rules! #private_macro_ident { + ($($tt:tt)*) => { + ::bon::__builder_type!( + #builder_ident { + #(#schema,)* + } + $($tt)* + ) + } + } + + #vis use #private_macro_ident as #builder_ident; + } + } + fn builder_impl(&self) -> Result { let finish_method = self.finish_method()?; let (setter_methods, items_for_rustdoc) = self.setter_methods()?; @@ -218,6 +263,8 @@ impl BuilderGenCtx { let vis = &self.vis; + let builder_traits_mod = quote::format_ident!("{}Traits", builder_ident.raw_name()); + Ok(quote! { #items_for_rustdoc @@ -610,7 +657,7 @@ impl BuilderGenCtx { quote::format_ident!( "{}__{}", self.builder_type.ident.raw_name(), - member.setter_method_core_name() + member.public_ident() ) } diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index 3c48435c..cbdfd9c2 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -51,7 +51,7 @@ impl<'a> MemberSettersCtx<'a> { }; Ok(self.setter_method(MemberSetterMethod { - method_name: self.member.setter_method_core_name().clone(), + method_name: self.member.public_ident().clone(), fn_params: quote!(value: #fn_param_type), overwrite_docs: None, body: SetterBody::Default { @@ -68,7 +68,7 @@ impl<'a> MemberSettersCtx<'a> { (quote!(#inner_type), quote!()) }; - let setter_method_name = self.member.setter_method_core_name().clone(); + let setter_method_name = self.member.public_ident().clone(); // Preserve the original identifier span to make IDE's "go to definition" work correctly let option_method_name = syn::Ident::new( @@ -197,7 +197,7 @@ impl<'a> MemberSettersCtx<'a> { } fn generate_docs_for_setter(&self) -> Vec { - let setter_core_name = self.member.setter_method_core_name(); + let setter_core_name = self.member.public_ident(); let start_fn_ident = &self.builder_gen.start_func.ident; let more = |start_fn_path: &std::fmt::Arguments<'_>| { diff --git a/bon-macros/src/lib.rs b/bon-macros/src/lib.rs index d3065f11..ed7753cf 100644 --- a/bon-macros/src/lib.rs +++ b/bon-macros/src/lib.rs @@ -316,3 +316,185 @@ pub fn __return_type(ret_ty: TokenStream, item: TokenStream) -> TokenStream { func.into_token_stream().into() } + +#[doc(hidden)] +#[proc_macro] +pub fn __gen_tuple_traits(total: TokenStream) -> TokenStream { + use crate::util::prelude::*; + use quote::quote; + + let total = syn::parse_macro_input!(total as syn::LitInt); + let total: u16 = total.base10_parse().unwrap(); + + let traits = (1..=total).map(|i| { + let tuple_trait = quote::format_ident!("Tuple{i}"); + let item_type = quote::format_ident!("T{i}"); + + let tuple_impls = (i..=total).map(|j| { + let generics = (1..=j).map(|k| quote::format_ident!("T{k}")); + let generics2 = generics.clone(); + + quote! { + impl<#(#generics,)*> #tuple_trait for (#(#generics2,)*) { + type #item_type = #item_type; + } + } + }); + + let maybe_super_trait = if i > 1 { + let prev = quote::format_ident!("Tuple{}", i - 1); + quote!(: #prev) + } else { + quote! {} + }; + + quote! { + trait #tuple_trait #maybe_super_trait { + type #item_type; + } + + #(#tuple_impls)* + } + }); + + traits.concat().into() +} + +#[doc(hidden)] +#[proc_macro] +pub fn __gen_tuple_traits2(total: TokenStream) -> TokenStream { + use quote::quote; + + let total = syn::parse_macro_input!(total as syn::LitInt); + let total: u16 = total.base10_parse().unwrap(); + + let items = (1..=total).map(|i| quote::format_ident!("T{i}")); + + let impls = (1..=total).map(|i| { + let covered = (1..=i).map(|j| quote::format_ident!("T{j}")); + let covered2 = covered.clone(); + let covered3 = covered.clone(); + let rest = (i + 1..=total).map(|j| quote::format_ident!("T{j}")); + + quote! { + impl<#(#covered,)*> Tuple for (#(#covered2,)*) { + #( type #covered3 = #covered3; )* + #( type #rest = N; )* + } + } + }); + + quote! { + pub trait Tuple { + #( type #items;)* + } + + #(#impls)* + } + .into() +} + +#[doc(hidden)] +#[proc_macro] +pub fn __builder_type(params: TokenStream) -> TokenStream { + use crate::util::prelude::*; + use quote::quote; + + enum MemberKind { + Required, + Optional, + } + + mod kw { + syn::custom_keyword!(required); + syn::custom_keyword!(optional); + } + + struct Input { + builder_ident: syn::Ident, + members: Vec<(syn::Ident, MemberKind)>, + set_members: Vec, + } + + impl syn::parse::Parse for Input { + fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { + let builder_ident = input.parse()?; + + let schema; + + syn::braced!(schema in input); + + let parse_members = |input: syn::parse::ParseStream<'_>| { + let key: syn::Ident = input.parse()?; + input.parse::()?; + + let lookahead = input.lookahead1(); + + let kind = if lookahead.peek(kw::required) { + input.parse::()?; + MemberKind::Required + } else if lookahead.peek(kw::optional) { + input.parse::()?; + MemberKind::Optional + } else { + return Err(lookahead.error()); + }; + + Ok((key, kind)) + }; + + let members = schema.parse_terminated(parse_members, syn::Token![,])?; + let members = Vec::from_iter(members); + + let set_members = input.parse_terminated(syn::Ident::parse, syn::Token![,])?; + let set_members = Vec::from_iter(set_members); + + Ok(Self { + builder_ident, + members, + set_members, + }) + } + } + + let input = syn::parse_macro_input!(params as Input); + + let Input { + builder_ident, + members, + + // TODO: validate only correct members are specified, + // maybe add completions + set_members, + } = input; + + let p = quote!(::bon::private); + + let state_types = members.iter().enumerate().map(|(i, (ident, kind))| { + let is_set = set_members.contains(ident); + + if is_set { + let tuple_item = quote::format_ident!("T{}", i + 1); + quote! { + #p::Set< + < + <#builder_ident as #p::state::Members>::Members + as + #p::state::Tuple + >::#tuple_item + > + } + } else { + match kind { + MemberKind::Required => quote! { #p::Unset<#p::Required> }, + MemberKind::Optional => quote! { #p::Unset<#p::Optional> }, + } + } + }); + + // TODO: deliver regular generics from input + quote! { + #builder_ident<(#(#state_types,)*)> + } + .into() +} diff --git a/bon/src/private/mod.rs b/bon/src/private/mod.rs index 015003d8..85edf795 100644 --- a/bon/src/private/mod.rs +++ b/bon/src/private/mod.rs @@ -9,6 +9,9 @@ clippy::missing_const_for_fn )] +/// Implementation details of the builder state +pub mod state; + /// Used to trigger deprecation warnings from the macros. pub mod deprecations; diff --git a/bon/src/private/state.rs b/bon/src/private/state.rs new file mode 100644 index 00000000..230cc40f --- /dev/null +++ b/bon/src/private/state.rs @@ -0,0 +1,14 @@ +pub trait Members { + type Members; +} + +bon_macros::__gen_tuple_traits2!(100); + +type N = never::Never; + +mod never { + /// Unnameable type. It's public, but under a private module. + #[doc(hidden)] + #[allow(unreachable_pub, unnameable_types, missing_debug_implementations)] + pub enum Never {} +} From 76e3b6be56a7cb30e0d81df104f53adba6e64c9c Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 20 Sep 2024 12:14:54 +0000 Subject: [PATCH 002/119] Smallfixes --- bon-macros/src/builder/builder_gen/member/mod.rs | 2 +- .../ui/compile_fail/positional_members.stderr | 16 ++++++++-------- website/guide/positional-members.md | 12 ++++++++---- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 4f8b8d4f..b5dde063 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -247,7 +247,7 @@ impl Member { if let Some(attr) = incorrect_order { bail!( &attr.span(), - "incorrect members oredering; the order of members must be the following:\n\ + "incorrect members ordering; the order of members must be the following:\n\ (1) members annotated with #[builder(start_fn)]\n\ (2) members annotated with #[builder(finish_fn)]\n\ (3) all other members in any order", diff --git a/bon/tests/integration/ui/compile_fail/positional_members.stderr b/bon/tests/integration/ui/compile_fail/positional_members.stderr index 34c0a47e..cce0f0e5 100644 --- a/bon/tests/integration/ui/compile_fail/positional_members.stderr +++ b/bon/tests/integration/ui/compile_fail/positional_members.stderr @@ -1,4 +1,4 @@ -error: incorrect members oredering; the order of members must be the following: +error: incorrect members ordering; the order of members must be the following: (1) members annotated with #[builder(start_fn)] (2) members annotated with #[builder(finish_fn)] (3) all other members in any order @@ -7,7 +7,7 @@ error: incorrect members oredering; the order of members must be the following: 8 | #[builder(start_fn)] | ^^^^^^^^ -error: incorrect members oredering; the order of members must be the following: +error: incorrect members ordering; the order of members must be the following: (1) members annotated with #[builder(start_fn)] (2) members annotated with #[builder(finish_fn)] (3) all other members in any order @@ -16,7 +16,7 @@ error: incorrect members oredering; the order of members must be the following: 17 | #[builder(start_fn)] | ^^^^^^^^ -error: incorrect members oredering; the order of members must be the following: +error: incorrect members ordering; the order of members must be the following: (1) members annotated with #[builder(start_fn)] (2) members annotated with #[builder(finish_fn)] (3) all other members in any order @@ -25,7 +25,7 @@ error: incorrect members oredering; the order of members must be the following: 24 | #[builder(start_fn)] | ^^^^^^^^ -error: incorrect members oredering; the order of members must be the following: +error: incorrect members ordering; the order of members must be the following: (1) members annotated with #[builder(start_fn)] (2) members annotated with #[builder(finish_fn)] (3) all other members in any order @@ -34,7 +34,7 @@ error: incorrect members oredering; the order of members must be the following: 31 | #[builder(finish_fn)] | ^^^^^^^^^ -error: incorrect members oredering; the order of members must be the following: +error: incorrect members ordering; the order of members must be the following: (1) members annotated with #[builder(start_fn)] (2) members annotated with #[builder(finish_fn)] (3) all other members in any order @@ -43,7 +43,7 @@ error: incorrect members oredering; the order of members must be the following: 39 | #[builder(start_fn)] | ^^^^^^^^ -error: incorrect members oredering; the order of members must be the following: +error: incorrect members ordering; the order of members must be the following: (1) members annotated with #[builder(start_fn)] (2) members annotated with #[builder(finish_fn)] (3) all other members in any order @@ -52,7 +52,7 @@ error: incorrect members oredering; the order of members must be the following: 47 | #[builder(finish_fn)] | ^^^^^^^^^ -error: incorrect members oredering; the order of members must be the following: +error: incorrect members ordering; the order of members must be the following: (1) members annotated with #[builder(start_fn)] (2) members annotated with #[builder(finish_fn)] (3) all other members in any order @@ -61,7 +61,7 @@ error: incorrect members oredering; the order of members must be the following: 55 | #[builder(start_fn)] | ^^^^^^^^ -error: incorrect members oredering; the order of members must be the following: +error: incorrect members ordering; the order of members must be the following: (1) members annotated with #[builder(start_fn)] (2) members annotated with #[builder(finish_fn)] (3) all other members in any order diff --git a/website/guide/positional-members.md b/website/guide/positional-members.md index 3cf5e657..95001b24 100644 --- a/website/guide/positional-members.md +++ b/website/guide/positional-members.md @@ -64,8 +64,10 @@ We can use a similar combination of the [top-level `#[builder(finish_fn = ...)]` use bon::Builder; #[derive(Builder)] -#[builder(start_fn = with_coordinates)] -#[builder(finish_fn = claim)] // [!code highlight] +#[builder( + start_fn = with_coordinates, + finish_fn = claim // [!code highlight] +)] struct Treasure { #[builder(start_fn)] x: u32, @@ -101,8 +103,10 @@ You may also combine these attributes with [`#[builder(into)]`](../reference/bui use bon::Builder; #[derive(Builder)] -#[builder(start_fn = with_coordinates)] -#[builder(finish_fn = claim)] +#[builder( + start_fn = with_coordinates, + finish_fn = claim // [!code focus] +)] struct Treasure { #[builder(start_fn)] x: u32, From 39ef481751c54795b2daceac1f68dd9baaddc4af Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 20 Sep 2024 20:04:08 +0000 Subject: [PATCH 003/119] Fix in TS --- website/.vitepress/theme/utils/versioning.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/.vitepress/theme/utils/versioning.ts b/website/.vitepress/theme/utils/versioning.ts index db72722a..43fe8c6d 100644 --- a/website/.vitepress/theme/utils/versioning.ts +++ b/website/.vitepress/theme/utils/versioning.ts @@ -1,4 +1,4 @@ -import { useRouter, withBase } from "vitepress"; +import { withBase } from "vitepress"; export const latestVersion = "v2"; export const versions = ["v2", "v1"]; From a3a08e7dc623201d5d4a19caada6499b5d08de73 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 21 Sep 2024 00:29:05 +0000 Subject: [PATCH 004/119] Brand new approach --- bon-macros/src/builder/builder_gen/mod.rs | 228 ++++++++++++------ .../src/builder/builder_gen/setter_methods.rs | 55 +++-- bon-macros/src/lib.rs | 1 - bon/src/private/mod.rs | 120 +++++---- e2e-tests/src/missing_docs_test.rs | 2 +- 5 files changed, 244 insertions(+), 162 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index ecc72941..0351884b 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -11,6 +11,7 @@ use builder_params::{BuilderDerives, OnParams}; use member::{Member, MemberOrigin, NamedMember, RawMember, StartFnArgMember}; use quote::{quote, ToTokens}; use setter_methods::{MemberSettersCtx, SettersReturnType}; +use std::hint::unreachable_unchecked; struct AssocMethodReceiverCtx { with_self_keyword: syn::Receiver, @@ -166,12 +167,25 @@ impl BuilderGenCtx { self.members.iter().filter_map(Member::as_start_fn_arg) } + fn builder_state_trait_ident(&self) -> syn::Ident { + quote::format_ident!("{}State", self.builder_ident.raw_name()) + } + pub(crate) fn output(self) -> Result { let mut start_func = self.start_func()?; let builder_decl = self.builder_decl(); let builder_impl = self.builder_impl()?; let builder_derives = self.builder_derives(); - let builder_type_macro = self.builder_type_macro(); + // let builder_type_macro = self.builder_type_macro(); + + // let all = quote! { + // #builder_decl + // #builder_derives + // #builder_impl + // #builder_type_macro + // }; + + // eprintln!("{}", all.to_string()); // -- Postprocessing -- // Here we parse all items back and add the `allow` attributes to them. @@ -179,7 +193,7 @@ impl BuilderGenCtx { #builder_decl #builder_derives #builder_impl - #builder_type_macro + // #builder_type_macro }; let mut other_items = other_items.items; @@ -248,23 +262,15 @@ impl BuilderGenCtx { let generics_decl = &self.generics.decl_without_defaults; let generic_args = &self.generics.args; let where_clause = &self.generics.where_clause; - let state_type_vars = self - .named_members() - .map(|member| &member.generic_var_ident) - .collect::>(); - let builder_ident = &self.builder_type.ident; + let builder_state_trait_ident = self.builder_state_trait_ident(); let allows = allow_warnings_on_member_types(); - let named_members_labels = self - .named_members() - .map(|member| self.members_label(member)); + let named_members_labels = self.named_members().map(|member| self.member_label(member)); let vis = &self.vis; - let builder_traits_mod = quote::format_ident!("{}Traits", builder_ident.raw_name()); - Ok(quote! { #items_for_rustdoc @@ -278,11 +284,11 @@ impl BuilderGenCtx { #[automatically_derived] impl< #(#generics_decl,)* - #(#state_type_vars,)* + BuilderState: #builder_state_trait_ident > #builder_ident< #(#generic_args,)* - (#(#state_type_vars,)*) + BuilderState > #where_clause { @@ -366,14 +372,6 @@ impl BuilderGenCtx { quote! { #receiver, } }); - let unset_state_literals = self.named_members().map(|member| { - if member.is_optional() { - quote!(::bon::private::Unset(::bon::private::Optional)) - } else { - quote!(::bon::private::Unset(::bon::private::Required)) - } - }); - let start_fn_params = self .start_fn_args() .map(|member| member.base.fn_input_param(&self.on_params)) @@ -392,6 +390,19 @@ impl BuilderGenCtx { let ide_hints = self.ide_hints(); + let named_members_init = self.named_members().map(|member| { + let member_ident = &member.norm_ident; + if member.is_optional() { + quote! { + #member_ident: None + } + } else { + quote! { + #member_ident: ::bon::private::MemberCell::uninit() + } + } + }); + let func = quote! { #(#docs)* #[inline(always)] @@ -416,7 +427,7 @@ impl BuilderGenCtx { __private_phantom: ::core::marker::PhantomData, #receiver_field_init #start_fn_args_field_init - __private_named_members: (#( #unset_state_literals, )*) + #(#named_members_init,)* } } }; @@ -473,9 +484,9 @@ impl BuilderGenCtx { // explanation for it, I just didn't care to research it yet ¯\_(ツ)_/¯. #(#types,)* - // A special case of zero members requires storing `___State` in phantom data + // A special case of zero members requires storing `BuilderState` in phantom data // otherwise it would be reported as an unused type parameter. - ::core::marker::PhantomData<___State> + ::core::marker::PhantomData )> } } @@ -522,15 +533,11 @@ impl BuilderGenCtx { let allows = allow_warnings_on_member_types(); - let initial_state_type_alias_ident = - quote::format_ident!("__{}InitialState", builder_ident.raw_name()); + let state_all_unset = quote::format_ident!("{}StateAllUnset", builder_ident.raw_name()); let unset_state_types = self.named_members().map(|member| { - if member.is_optional() { - quote!(::bon::private::Unset<::bon::private::Optional>) - } else { - quote!(::bon::private::Unset<::bon::private::Required>) - } + let label = self.member_label(member); + quote!(::bon::private::Unset<#label>) }); let mut start_fn_arg_types = self @@ -538,22 +545,86 @@ impl BuilderGenCtx { .map(|member| &member.base.norm_ty) .peekable(); - let start_fn_arg_types_field = start_fn_arg_types.peek().is_some().then(|| { + let start_fn_args_field = start_fn_arg_types.peek().is_some().then(|| { quote! { #[doc = #private_field_doc] __private_start_fn_args: (#(#start_fn_arg_types,)*), } }); + let member_fields = self.named_members().map(|member| { + let ident = &member.norm_ident; + + if let Some(ty) = member.as_optional_norm_ty() { + quote! { + #[doc = #private_field_doc] + #ident: ::core::option::Option<#ty> + } + } else { + let ty = &member.norm_ty; + let member_pascal = &member.norm_ident_pascal; + quote! { + #[doc = #private_field_doc] + #ident: ::bon::private::MemberCell + } + } + }); + + let builder_state_trait_ident = self.builder_state_trait_ident(); + let named_members_pascal_idents: Vec<_> = self + .named_members() + .map(|member| &member.norm_ident_pascal) + .collect(); + + let total_named_members = named_members_pascal_idents.len(); + + let state_transition_aliases = self.named_members().map(|member| { + let alias_name = quote::format_ident!( + "{}Set{}", + self.builder_ident.raw_name(), + member.norm_ident_pascal.raw_name() + ); + + let states = self.named_members().map(|other_member| { + if other_member.orig_ident == member.orig_ident { + let label = self.member_label(member); + quote! { + ::bon::private::Set<#label> + } + } else { + let member_pascal = &other_member.norm_ident_pascal; + quote! { + S::#member_pascal + } + } + }); + + if total_named_members == 1 { + return quote! { + #vis type #alias_name = ( #(#states,)* ); + }; + } + + quote! { + #vis type #alias_name = ( #(#states,)* ); + } + }); + quote! { - // This type alias exists just to shorten the type signature of - // the default generic argument of the builder struct. It's not - // really important for users to see what this type alias expands to. - // - // If they want to see how "bon works" they should just expand the - // macro manually where they'll see this type alias. - #[doc(hidden)] - #vis type #initial_state_type_alias_ident = (#(#unset_state_types,)*); + #( #state_transition_aliases )* + + #vis trait #builder_state_trait_ident { + #(type #named_members_pascal_idents: ::bon::private::MemberState; )* + } + + impl< #(#named_members_pascal_idents: ::bon::private::MemberState,)* > #builder_state_trait_ident + for ( #(#named_members_pascal_idents,)* ) + { + #( type #named_members_pascal_idents = #named_members_pascal_idents; )* + } + + /// Initial state of the builder where all named members are unset + #vis type #state_all_unset = (#(#unset_state_types,)*); #[must_use = #must_use_message] #(#docs)* @@ -570,7 +641,7 @@ impl BuilderGenCtx { )] #vis struct #builder_ident< #(#generics_decl,)* - ___State = #initial_state_type_alias_ident + BuilderState: #builder_state_trait_ident = #state_all_unset > #where_clause { @@ -582,10 +653,9 @@ impl BuilderGenCtx { __private_phantom: #phantom_data, #receiver_field - #start_fn_arg_types_field + #start_fn_args_field - #[doc = #private_field_doc] - __private_named_members: ___State + #( #member_fields, )* } } } @@ -612,14 +682,17 @@ impl BuilderGenCtx { } }; - let maybe_default = member + let expr = member .as_optional_norm_ty() - // For `Option` members we don't need any `unwrap_or_[else/default]`. - // The implementation of `From for Set>` already - // returns an `Option`. - .filter(|_| !member.norm_ty.is_option()) .map(|_| { - member + // For `Option` members we don't need any `unwrap_or_[else/default]`. + // The implementation of `From for Set>` already + // returns an `Option`. + if member.norm_ty.is_option() { + return None; + } + + let default = member .param_default() .flatten() .map(|default| { @@ -632,28 +705,31 @@ impl BuilderGenCtx { Result::<_>::Ok(quote! { .unwrap_or_else(|| #default) }) }) - .unwrap_or_else(|| Ok(quote! { .unwrap_or_default() })) + .unwrap_or_else(|| Ok(quote! { .unwrap_or_default() })); + + Some(default) }) - .transpose()?; - - let index = &member.index; - let set_state_type_param = member.set_state_type_param(); - let member_label = self.members_label(member); - - let expr = quote! { - ::bon::private::IntoSet::< - #set_state_type_param, - #member_label - >::into_set(self.__private_named_members.#index) - #maybe_default - }; + .map(Option::transpose) + .transpose()? + .map(|default| { + let ident = &member.norm_ident; + quote! { + self.#ident #default + } + }) + .unwrap_or_else(|| { + let ident = &member.norm_ident; + quote! { + self.#ident.into_inner() + } + }); Ok(expr) } /// Name of the dummy struct that is generated just to give a name for /// the member in the error message when `IntoSet` trait is not implemented. - fn members_label(&self, member: &NamedMember) -> syn::Ident { + fn member_label(&self, member: &NamedMember) -> syn::Ident { quote::format_ident!( "{}__{}", self.builder_type.ident.raw_name(), @@ -694,17 +770,15 @@ impl BuilderGenCtx { let finish_func_ident = &self.finish_func.ident; let output = &self.finish_func.output; - let where_bounds = self.named_members().map(|member| { - let member_type_var = &member.generic_var_ident; - let set_state_type_param = member.set_state_type_param(); - let member_label = self.members_label(member); - quote! { - #member_type_var: ::bon::private::IntoSet< - #set_state_type_param, - #member_label - > - } - }); + let where_bounds = self + .named_members() + .filter(|member| !member.is_optional()) + .map(|member| { + let member_pascal = &member.norm_ident_pascal; + quote! { + BuilderState::#member_pascal: ::bon::private::IsSet + } + }); let finish_fn_params = self .members diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index cbdfd9c2..b79f3029 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -55,7 +55,7 @@ impl<'a> MemberSettersCtx<'a> { fn_params: quote!(value: #fn_param_type), overwrite_docs: None, body: SetterBody::Default { - member_init: quote!(::bon::private::Set(value #maybe_into_call)), + member_init: quote!(::bon::private::MemberCell::init(value #maybe_into_call)), }, })) } @@ -91,7 +91,7 @@ impl<'a> MemberSettersCtx<'a> { more details.", )), body: SetterBody::Default { - member_init: quote!(::bon::private::Set(value #maybe_map_conv_call)), + member_init: quote!(value #maybe_map_conv_call), }, }, // We intentionally keep the name and signature of the setter method @@ -146,12 +146,18 @@ impl<'a> MemberSettersCtx<'a> { let builder_ident = &self.builder_gen.builder_type.ident; - let member_exprs = self.builder_gen.named_members().map(|other_member| { - if other_member.norm_ident == self.member.norm_ident { - return member_init.clone(); + let member_forwards = self.builder_gen.named_members().map(|other_member| { + let member_ident = &other_member.norm_ident; + if *member_ident == self.member.norm_ident { + return quote! { + #member_ident: #member_init + }; + } + + let ident = &other_member.norm_ident; + quote! { + #member_ident: self.#ident } - let index = &other_member.index; - quote!(self.__private_named_members.#index) }); quote! { @@ -159,17 +165,32 @@ impl<'a> MemberSettersCtx<'a> { __private_phantom: ::core::marker::PhantomData, #maybe_receiver_field #maybe_start_fn_args_field - __private_named_members: (#( #member_exprs, )*) + #( #member_forwards, )* } } } }; - let member_state_type = &self.member.generic_var_ident; - let SettersReturnType { - doc_true: ret_doc_true, - doc_false: ret_doc_false, - } = &self.return_type; + let member_pascal = &self.member.norm_ident_pascal; + // let SettersReturnType { + // doc_true: ret_doc_true, + // doc_false: ret_doc_false, + // } = &self.return_type; + let state_transition = quote::format_ident!( + "{}Set{}", + self.builder_gen.builder_ident.raw_name(), + self.member.norm_ident_pascal.raw_name() + ); + + let state_transition = if self.builder_gen.named_members().count() == 1 { + quote!(#state_transition) + } else { + quote!(#state_transition) + }; + + let builder_ident = &self.builder_gen.builder_ident; + + let generic_args = &self.builder_gen.generics.args; quote! { #( #docs )* @@ -183,13 +204,9 @@ impl<'a> MemberSettersCtx<'a> { clippy::impl_trait_in_params )] #[inline(always)] - // The `cfg_attr` condition is for `doc`, so we don't pay the price - // if invoking the `__return_type` macro in the usual case when the - // code is compiled outside of `rustdoc`. - #[cfg_attr(doc, bon::__return_type(#ret_doc_true))] - #vis fn #method_name(self, #fn_params) -> #ret_doc_false + #vis fn #method_name(self, #fn_params) -> #builder_ident<#(#generic_args,)* #state_transition> where - #member_state_type: ::bon::private::IsUnset, + BuilderState::#member_pascal: ::bon::private::IsUnset, { #body } diff --git a/bon-macros/src/lib.rs b/bon-macros/src/lib.rs index ed7753cf..6f722344 100644 --- a/bon-macros/src/lib.rs +++ b/bon-macros/src/lib.rs @@ -397,7 +397,6 @@ pub fn __gen_tuple_traits2(total: TokenStream) -> TokenStream { #[doc(hidden)] #[proc_macro] pub fn __builder_type(params: TokenStream) -> TokenStream { - use crate::util::prelude::*; use quote::quote; enum MemberKind { diff --git a/bon/src/private/mod.rs b/bon/src/private/mod.rs index 85edf795..9892c9d9 100644 --- a/bon/src/private/mod.rs +++ b/bon/src/private/mod.rs @@ -9,6 +9,9 @@ clippy::missing_const_for_fn )] +use core::marker::PhantomData; +use core::mem::MaybeUninit; + /// Implementation details of the builder state pub mod state; @@ -25,79 +28,78 @@ pub extern crate alloc; pub fn assert_clone() {} pub fn assert_debug() {} -/// Marker trait to denote the state of the member that is not set yet. -#[rustversion::attr( - since(1.78.0), - diagnostic::on_unimplemented( - message = "can't set the same member twice", - label = "this member was already set" - ) -)] -pub trait IsUnset {} - -#[derive(Debug, Clone)] -pub struct Required; - -#[derive(Debug, Clone)] -pub struct Optional; +#[derive(Debug)] +pub struct Unset(T); -/// The sole implementation of the [`IsUnset`] trait. -#[derive(Debug, Clone)] -pub struct Unset(pub T); - -impl IsUnset for Unset {} - -/// A trait used to transition optional members to the [`Set`] state. -/// -/// It also provides a better error message when the member is not set. -/// The `Member` generic parameter isn't used by the trait implementation, -/// it's used only as a label with the name of the member to specify which one -/// was not set. -#[rustversion::attr( - since(1.78.0), - diagnostic::on_unimplemented( - message = "can't finish building yet; the member `{Member}` was not set", - label = "the member `{Member}` was not set" - ) -)] -pub trait IntoSet { - fn into_set(self) -> T; +pub struct MemberCell { + state: PhantomData, + value: MaybeUninit, } -impl IntoSet for Set { - #[inline(always)] - fn into_set(self) -> T { - self.0 +impl MemberCell { + pub fn uninit() -> Self { + Self { + state: PhantomData, + value: MaybeUninit::uninit(), + } } } -impl IntoSet, Member> for Unset { - #[inline(always)] - fn into_set(self) -> Option { - None +impl MemberCell { + pub fn init(value: T) -> Self { + Self { + state: PhantomData, + value: MaybeUninit::new(value), + } } -} -/// Implemented by `Unset` and `Set` states of members, which are basically -/// all possible states of a member. + pub fn into_inner(self) -> T { + unsafe { + let value = self.value.assume_init_read(); + std::mem::forget(self); + value + } + } +} pub trait MemberState { - fn is_set(&self) -> bool; + fn is_set() -> bool; +} + +impl MemberState for Unset { + fn is_set() -> bool { + false + } } impl MemberState for Set { - #[inline(always)] - fn is_set(&self) -> bool { + fn is_set() -> bool { true } } -impl MemberState for Unset { - #[inline(always)] - fn is_set(&self) -> bool { - false +impl Drop for MemberCell { + fn drop(&mut self) { + if State::is_set() { + #[allow(unsafe_code)] + unsafe { + self.value.assume_init_drop(); + } + } } } +#[diagnostic::on_unimplemented(message = "The member {Self} was already set!")] +pub trait IsUnset {} +impl IsUnset for Unset {} + +#[derive(Debug)] +pub struct Set(T); + +#[diagnostic::on_unimplemented(message = "The member {Self} was not set!")] +pub trait IsSet {} + +impl IsSet for Set {} + /// This is all a big embarrassing workaround, please don't oversee 😳😳😳. /// /// Anyway, if you are curious what the hell is going on here, then here is @@ -203,16 +205,6 @@ macro_rules! __eval_cfg_callback { }; } -#[repr(transparent)] -#[derive(Clone)] -pub struct Set(pub T); - -impl core::fmt::Debug for Set { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - core::fmt::Debug::fmt(&self.0, f) - } -} - /// The `cfg` predicate evaluated to `true`, now push that information into /// the `$results` list. #[macro_export] diff --git a/e2e-tests/src/missing_docs_test.rs b/e2e-tests/src/missing_docs_test.rs index f8a274b3..b7107572 100644 --- a/e2e-tests/src/missing_docs_test.rs +++ b/e2e-tests/src/missing_docs_test.rs @@ -47,7 +47,7 @@ impl Struct { } /// [`GenericStruct`] docs -#[derive(bon::Builder)] +// #[derive(bon::Builder)] pub struct GenericStruct { // Docs on setters for struct fields are autogenerated // So missing docs here shouldn't be reported From 60bf26855d37e3d7ddcd61d928b6d0945245243b Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 21 Sep 2024 15:41:34 +0000 Subject: [PATCH 005/119] Next iteration --- .../src/builder/builder_gen/member/mod.rs | 12 - bon-macros/src/builder/builder_gen/mod.rs | 335 +++++++----------- .../src/builder/builder_gen/setter_methods.rs | 45 +-- bon-macros/src/lib.rs | 202 ----------- bon-macros/src/normalization/lifetimes.rs | 6 +- bon-macros/src/util/ident.rs | 28 +- bon-macros/src/util/mod.rs | 2 + bon-macros/src/util/visibility.rs | 64 ++++ bon/src/collections.rs | 151 ++++++++ bon/src/lib.rs | 193 +++------- bon/src/private/cfg_eval.rs | 137 +++++++ bon/src/private/member_cell.rs | 163 +++++++++ bon/src/private/mod.rs | 238 +++---------- bon/src/private/state.rs | 14 - .../integration/builder/name_conflicts.rs | 52 +++ bon/tests/integration/builder/raw_idents.rs | 18 +- website/guide/overview.md | 2 +- 17 files changed, 837 insertions(+), 825 deletions(-) create mode 100644 bon-macros/src/util/visibility.rs create mode 100644 bon/src/collections.rs create mode 100644 bon/src/private/cfg_eval.rs create mode 100644 bon/src/private/member_cell.rs delete mode 100644 bon/src/private/state.rs diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index b5dde063..ef1d19fd 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -5,7 +5,6 @@ use crate::util::prelude::*; use darling::util::SpannedValue; use darling::FromAttributes; use params::MemberParams; -use quote::quote; use std::fmt; #[derive(Debug, Clone, Copy)] @@ -153,17 +152,6 @@ impl NamedMember { self.as_optional_norm_ty().is_some() } - /// The type parameter for the `Set` type that corresponds to this member - pub(crate) fn set_state_type_param(&self) -> TokenStream2 { - let ty = &self.norm_ty; - let ty = self - .as_optional_norm_ty() - .map(|ty| quote!(Option<#ty>)) - .unwrap_or_else(|| quote!(#ty)); - - quote!(#ty) - } - pub(crate) fn param_default(&self) -> Option> { self.params .default diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 0351884b..6e60f953 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -9,9 +9,8 @@ pub(crate) mod input_struct; use crate::util::prelude::*; use builder_params::{BuilderDerives, OnParams}; use member::{Member, MemberOrigin, NamedMember, RawMember, StartFnArgMember}; -use quote::{quote, ToTokens}; -use setter_methods::{MemberSettersCtx, SettersReturnType}; -use std::hint::unreachable_unchecked; +use quote::quote; +use setter_methods::MemberSettersCtx; struct AssocMethodReceiverCtx { with_self_keyword: syn::Receiver, @@ -167,44 +166,57 @@ impl BuilderGenCtx { self.members.iter().filter_map(Member::as_start_fn_arg) } - fn builder_state_trait_ident(&self) -> syn::Ident { - quote::format_ident!("{}State", self.builder_ident.raw_name()) + fn builder_mod_ident(&self) -> syn::Ident { + self.builder_ident.pascal_to_snake_case() } pub(crate) fn output(self) -> Result { let mut start_func = self.start_func()?; - let builder_decl = self.builder_decl(); + let builder_decl = self.builder_decl()?; let builder_impl = self.builder_impl()?; let builder_derives = self.builder_derives(); - // let builder_type_macro = self.builder_type_macro(); - // let all = quote! { - // #builder_decl - // #builder_derives - // #builder_impl - // #builder_type_macro - // }; + let default_allows = syn::parse_quote!(#[allow( + // We have a `deprecated` lint on any `bon::private` items which we + // use in the generated code extensively + deprecated + )]); - // eprintln!("{}", all.to_string()); + 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: syn::File = syn::parse_quote! { + let other_items = quote! { #builder_decl #builder_derives #builder_impl - // #builder_type_macro }; + 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(self.allow_attrs.iter().cloned()); + attrs.extend(allows.clone()); } } - start_func.attrs.extend(self.allow_attrs); + start_func.attrs.extend(allows); Ok(MacroOutput { start_func, @@ -212,88 +224,36 @@ impl BuilderGenCtx { }) } - fn builder_type_macro(&self) -> TokenStream2 { - let private_macro_ident = quote::format_ident!("__{}", self.builder_ident.raw_name()); - - let vis = &self.vis; - let builder_ident = &self.builder_ident; - - let member_types = self.named_members().map(|member| &member.norm_ty); - let generics_decl = &self.generics.decl_without_defaults; - let generic_args = &self.generics.args; - let where_clause = &self.generics.where_clause; - - let schema = self.named_members().map(|member| { - let member_ident = &member.public_ident(); - if member.is_optional() { - quote!(#member_ident: optional) - } else { - quote!(#member_ident: required) - } - }); - - quote! { - impl <#(#generics_decl,)*> - ::bon::private::state::Members for #builder_ident<#(#generic_args,)*> - #where_clause - { - type Members = (#(#member_types,)*); - } - - macro_rules! #private_macro_ident { - ($($tt:tt)*) => { - ::bon::__builder_type!( - #builder_ident { - #(#schema,)* - } - $($tt)* - ) - } - } - - #vis use #private_macro_ident as #builder_ident; - } - } - fn builder_impl(&self) -> Result { let finish_method = self.finish_method()?; - let (setter_methods, items_for_rustdoc) = self.setter_methods()?; + let setter_methods = self + .named_members() + .map(|member| MemberSettersCtx::new(self, member).setter_methods()) + .collect::>>()?; 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 builder_state_trait_ident = self.builder_state_trait_ident(); + let builder_mod = self.builder_mod_ident(); let allows = allow_warnings_on_member_types(); - let named_members_labels = self.named_members().map(|member| self.member_label(member)); - - let vis = &self.vis; - Ok(quote! { - #items_for_rustdoc - - #( - #[allow(non_camel_case_types)] - #[doc(hidden)] - #vis struct #named_members_labels; - )* - #allows #[automatically_derived] impl< #(#generics_decl,)* - BuilderState: #builder_state_trait_ident + BuilderTypeState: #builder_mod::State > #builder_ident< #(#generic_args,)* - BuilderState + BuilderTypeState > #where_clause { #finish_method - #setter_methods + #(#setter_methods)* } }) } @@ -456,7 +416,7 @@ impl BuilderGenCtx { // 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 - quote!(::core::marker::PhantomData<#ty>) + quote!(fn() -> ::core::marker::PhantomData<#ty>) }); quote! { @@ -484,19 +444,20 @@ impl BuilderGenCtx { // explanation for it, I just didn't care to research it yet ¯\_(ツ)_/¯. #(#types,)* - // A special case of zero members requires storing `BuilderState` in phantom data - // otherwise it would be reported as an unused type parameter. - ::core::marker::PhantomData + // A special case of zero members requires storing `BuilderTypeState` in + // phantom data otherwise it would be reported as an unused type parameter. + fn() -> ::core::marker::PhantomData, )> } } - fn builder_decl(&self) -> TokenStream2 { + fn builder_decl(&self) -> Result { let vis = &self.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 builder_mod = self.builder_mod_ident(); let private_field_doc = "\ Please don't touch this field. It's an implementation \ @@ -533,13 +494,6 @@ impl BuilderGenCtx { let allows = allow_warnings_on_member_types(); - let state_all_unset = quote::format_ident!("{}StateAllUnset", builder_ident.raw_name()); - - let unset_state_types = self.named_members().map(|member| { - let label = self.member_label(member); - quote!(::bon::private::Unset<#label>) - }); - let mut start_fn_arg_types = self .start_fn_args() .map(|member| &member.base.norm_ty) @@ -565,12 +519,11 @@ impl BuilderGenCtx { let member_pascal = &member.norm_ident_pascal; quote! { #[doc = #private_field_doc] - #ident: ::bon::private::MemberCell + #ident: ::bon::private::MemberCell } } }); - let builder_state_trait_ident = self.builder_state_trait_ident(); let named_members_pascal_idents: Vec<_> = self .named_members() .map(|member| &member.norm_ident_pascal) @@ -578,12 +531,10 @@ impl BuilderGenCtx { let total_named_members = named_members_pascal_idents.len(); + let vis_child = self.vis.clone().into_equivalent_in_child_module()?; + let state_transition_aliases = self.named_members().map(|member| { - let alias_name = quote::format_ident!( - "{}Set{}", - self.builder_ident.raw_name(), - member.norm_ident_pascal.raw_name() - ); + let alias_name = quote::format_ident!("Set{}", member.norm_ident_pascal.raw_name()); let states = self.named_members().map(|other_member| { if other_member.orig_ident == member.orig_ident { @@ -599,32 +550,92 @@ impl BuilderGenCtx { } }); + let member_ident = member.public_ident(); + if total_named_members == 1 { + let docs = format!( + "Changes the type state of the builder to set the member `{member_ident}`.", + ); + return quote! { - #vis type #alias_name = ( #(#states,)* ); + #[doc = #docs] + #vis_child type #alias_name = ( #(#states,)* ); }; } + let docs = format!( + "Changes the type state of the builder to set the member `{member_ident}`. \ + The `S` parameter is the type state to update. If not specified \ + then [`AllUnset`] state is used, which results in all members being \ + unset except for `{member_ident}`.\n\n\ + [`AllUnset`]: self::AllUnset", + ); + quote! { - #vis type #alias_name = ( #(#states,)* ); + #[doc = #docs] + #vis_child type #alias_name = ( #(#states,)* ); } }); + let named_members_labels: Vec<_> = self + .named_members() + .map(|member| self.member_label(member)) + .collect(); - quote! { - #( #state_transition_aliases )* + let assoc_types_docs = self.named_members().map(|member| { + let ident = &member.public_ident(); + format!( + "Represents the state of the member `{ident}`.\n\n\ + See the [`State`] trait-level docs for details", + ) + }); - #vis trait #builder_state_trait_ident { - #(type #named_members_pascal_idents: ::bon::private::MemberState; )* - } + let builder_mod_docs = format!( + "Contains the traits and type aliases for manipulating \ + the type state of the {builder_ident}" + ); - impl< #(#named_members_pascal_idents: ::bon::private::MemberState,)* > #builder_state_trait_ident - for ( #(#named_members_pascal_idents,)* ) - { - #( type #named_members_pascal_idents = #named_members_pascal_idents; )* - } + Ok(quote! { + #[doc = #builder_mod_docs] + #vis mod #builder_mod { + #( #state_transition_aliases )* + + /// Represents the builder's type state that specifies which members are set and which are not. + /// + /// You can use the associated types of this trait to control the state of individual members + /// with the [`bon::IsSet`] and [`bon::IsUnset`] traits. + /// + /// [`bon::IsSet`]: ::bon::IsSet + /// [`bon::IsUnset]: ::bon::IsUnset + #vis_child trait State: ::core::marker::Sized { + #( + #[doc = #assoc_types_docs] + type #named_members_pascal_idents: ::bon::private::MemberState; + )* + } - /// Initial state of the builder where all named members are unset - #vis type #state_all_unset = (#(#unset_state_types,)*); + // Using `self::State` explicitly to avoid name conflicts with the + // members named `state` which would create a generic param named `State` + // that would shadow the trait `State` in the same scope. + impl< #(#named_members_pascal_idents: ::bon::private::MemberState,)* > self::State + for ( #(#named_members_pascal_idents,)* ) + { + #( type #named_members_pascal_idents = #named_members_pascal_idents; )* + } + + /// Initial state of the builder where all named members are unset + #vis_child type AllUnset = (#(::bon::private::Unset<#named_members_labels>,)*); + + #( + #[allow(non_camel_case_types)] + #[doc(hidden)] + #[deprecated = + "this type is an implementation detail and should not be \ + used directly; use the Set* type aliases to control the \ + state of members instead" + ] + #vis_child struct #named_members_labels; + )* + } #[must_use = #must_use_message] #(#docs)* @@ -641,7 +652,7 @@ impl BuilderGenCtx { )] #vis struct #builder_ident< #(#generics_decl,)* - BuilderState: #builder_state_trait_ident = #state_all_unset + BuilderTypeState: #builder_mod::State = #builder_mod::AllUnset > #where_clause { @@ -657,7 +668,7 @@ impl BuilderGenCtx { #( #member_fields, )* } - } + }) } fn member_expr(&self, member: &Member) -> Result { @@ -776,7 +787,7 @@ impl BuilderGenCtx { .map(|member| { let member_pascal = &member.norm_ident_pascal; quote! { - BuilderState::#member_pascal: ::bon::private::IsSet + BuilderTypeState::#member_pascal: ::bon::IsSet } }); @@ -813,102 +824,6 @@ impl BuilderGenCtx { } }) } - - fn setter_methods(&self) -> Result<(TokenStream2, TokenStream2)> { - let generics_decl = &self.generics.decl_without_defaults; - let generic_args = &self.generics.args; - let builder_ident = &self.builder_type.ident; - let where_clause = &self.generics.where_clause; - - let state_type_vars = self - .named_members() - .map(|member| &member.generic_var_ident) - .collect::>(); - - let allows = allow_warnings_on_member_types(); - - let next_state_trait_ident = - quote::format_ident!("__{}SetMember", builder_ident.raw_name()); - - let next_states_decls = self.named_members().map(|member| { - let member_pascal = &member.norm_ident_pascal; - quote! { - type #member_pascal; - } - }); - - let setters = self - .named_members() - .map(|member| { - let state_types = self.named_members().map(|other_member| { - if other_member.orig_ident == member.orig_ident { - let ty = member.set_state_type_param(); - quote!(::bon::private::Set<#ty>) - } else { - other_member.generic_var_ident.to_token_stream() - } - }); - - let member_pascal = &member.norm_ident_pascal; - - let next_state = quote! { - #builder_ident< - #(#generic_args,)* - (#(#state_types,)*) - > - }; - - let return_type = SettersReturnType { - doc_true: quote!(::#member_pascal), - doc_false: next_state.clone(), - }; - - let setter_methods = - MemberSettersCtx::new(self, member, return_type).setter_methods()?; - - let next_state = quote!(type #member_pascal = #next_state;); - - Ok((setter_methods, next_state)) - }) - .collect::>>()?; - let next_states_defs = setters.iter().map(|(_, next_state)| next_state); - - let items_for_rustdoc = quote! { - // This item is under `cfg(doc)` because it's used only to make the - // documentation less noisy (see `SettersReturnType` for more info). - #[cfg(doc)] - trait #next_state_trait_ident { - #(#next_states_decls)* - } - - // This item is under `cfg(doc)` because it's used only to make the - // documentation less noisy (see `SettersReturnType` for more info). - #[cfg(doc)] - #allows - #[automatically_derived] - impl< - #(#generics_decl,)* - #(#state_type_vars,)* - > - #next_state_trait_ident - for - #builder_ident< - #(#generic_args,)* - (#(#state_type_vars,)*) - > - #where_clause - { - #(#next_states_defs)* - } - }; - - let setter_methods = setters - .into_iter() - .map(|(setter_methods, _)| setter_methods) - .concat(); - - Ok((setter_methods, items_for_rustdoc)) - } } pub(crate) fn generic_param_to_arg(param: &syn::GenericParam) -> syn::GenericArgument { diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index b79f3029..8f660af2 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -2,36 +2,19 @@ use super::{BuilderGenCtx, NamedMember}; use crate::util::prelude::*; use quote::quote; -/// Specifies the return type of the setter method. It is conditioned by the -/// `cfg(doc)`. If `cfg(doc)` is enabled, we want to generate a shorter type -/// signature that doesn't clutter the docs. -/// -/// However, such type signature uses an associated type of a trait which makes -/// it much slower to compile when the code is built outside of `rustdoc`. -/// -/// So the `doc_false` variant is used as an easier to compile alternative, -/// but still the equivalent of the same return type. -pub(crate) struct SettersReturnType { - pub(crate) doc_true: TokenStream2, - pub(crate) doc_false: TokenStream2, -} - pub(crate) struct MemberSettersCtx<'a> { builder_gen: &'a BuilderGenCtx, member: &'a NamedMember, - return_type: SettersReturnType, } impl<'a> MemberSettersCtx<'a> { pub(crate) fn new( builder_gen: &'a BuilderGenCtx, member: &'a NamedMember, - return_type: SettersReturnType, ) -> Self { Self { builder_gen, member, - return_type, } } @@ -55,7 +38,7 @@ impl<'a> MemberSettersCtx<'a> { fn_params: quote!(value: #fn_param_type), overwrite_docs: None, body: SetterBody::Default { - member_init: quote!(::bon::private::MemberCell::init(value #maybe_into_call)), + member_init: quote!(::bon::private::MemberCell::new(value #maybe_into_call)), }, })) } @@ -172,24 +155,22 @@ impl<'a> MemberSettersCtx<'a> { }; let member_pascal = &self.member.norm_ident_pascal; - // let SettersReturnType { - // doc_true: ret_doc_true, - // doc_false: ret_doc_false, - // } = &self.return_type; - let state_transition = quote::format_ident!( - "{}Set{}", - self.builder_gen.builder_ident.raw_name(), - self.member.norm_ident_pascal.raw_name() - ); - let state_transition = if self.builder_gen.named_members().count() == 1 { - quote!(#state_transition) + let state_transition = + quote::format_ident!("Set{}", self.member.norm_ident_pascal.raw_name()); + + let builder_mod = &self.builder_gen.builder_mod_ident(); + let generic_param = if self.builder_gen.named_members().count() == 1 { + quote!() } else { - quote!(#state_transition) + quote!() }; - let builder_ident = &self.builder_gen.builder_ident; + let state_transition = quote! { + #builder_mod::#state_transition #generic_param + }; + let builder_ident = &self.builder_gen.builder_ident; let generic_args = &self.builder_gen.generics.args; quote! { @@ -206,7 +187,7 @@ impl<'a> MemberSettersCtx<'a> { #[inline(always)] #vis fn #method_name(self, #fn_params) -> #builder_ident<#(#generic_args,)* #state_transition> where - BuilderState::#member_pascal: ::bon::private::IsUnset, + BuilderTypeState::#member_pascal: ::bon::IsUnset, { #body } diff --git a/bon-macros/src/lib.rs b/bon-macros/src/lib.rs index 6f722344..214eb1c9 100644 --- a/bon-macros/src/lib.rs +++ b/bon-macros/src/lib.rs @@ -19,7 +19,6 @@ mod normalization; mod util; use proc_macro::TokenStream; -use quote::ToTokens; /// Generates a builder for the function or method it's placed on. /// @@ -296,204 +295,3 @@ pub fn set(input: TokenStream) -> TokenStream { collections::set::generate(entries).into() } - -/// Private proc macro! Don't use it directly, it's an implementation detail. -/// -/// This macro takes a function and overrides its return type with the provided one. -/// It's used in combination with `cfg_attr` to conditionally change the return type -/// of a function based on the `cfg(doc)` value. -#[doc(hidden)] -#[proc_macro_attribute] -pub fn __return_type(ret_ty: TokenStream, item: TokenStream) -> TokenStream { - let mut func: syn::ItemFn = match syn::parse(item.clone()) { - Ok(func) => func, - Err(err) => return error::error_into_token_stream(err.into(), item.into()).into(), - }; - - let ret_ty = proc_macro2::TokenStream::from(ret_ty); - - func.sig.output = syn::parse_quote!(-> #ret_ty); - - func.into_token_stream().into() -} - -#[doc(hidden)] -#[proc_macro] -pub fn __gen_tuple_traits(total: TokenStream) -> TokenStream { - use crate::util::prelude::*; - use quote::quote; - - let total = syn::parse_macro_input!(total as syn::LitInt); - let total: u16 = total.base10_parse().unwrap(); - - let traits = (1..=total).map(|i| { - let tuple_trait = quote::format_ident!("Tuple{i}"); - let item_type = quote::format_ident!("T{i}"); - - let tuple_impls = (i..=total).map(|j| { - let generics = (1..=j).map(|k| quote::format_ident!("T{k}")); - let generics2 = generics.clone(); - - quote! { - impl<#(#generics,)*> #tuple_trait for (#(#generics2,)*) { - type #item_type = #item_type; - } - } - }); - - let maybe_super_trait = if i > 1 { - let prev = quote::format_ident!("Tuple{}", i - 1); - quote!(: #prev) - } else { - quote! {} - }; - - quote! { - trait #tuple_trait #maybe_super_trait { - type #item_type; - } - - #(#tuple_impls)* - } - }); - - traits.concat().into() -} - -#[doc(hidden)] -#[proc_macro] -pub fn __gen_tuple_traits2(total: TokenStream) -> TokenStream { - use quote::quote; - - let total = syn::parse_macro_input!(total as syn::LitInt); - let total: u16 = total.base10_parse().unwrap(); - - let items = (1..=total).map(|i| quote::format_ident!("T{i}")); - - let impls = (1..=total).map(|i| { - let covered = (1..=i).map(|j| quote::format_ident!("T{j}")); - let covered2 = covered.clone(); - let covered3 = covered.clone(); - let rest = (i + 1..=total).map(|j| quote::format_ident!("T{j}")); - - quote! { - impl<#(#covered,)*> Tuple for (#(#covered2,)*) { - #( type #covered3 = #covered3; )* - #( type #rest = N; )* - } - } - }); - - quote! { - pub trait Tuple { - #( type #items;)* - } - - #(#impls)* - } - .into() -} - -#[doc(hidden)] -#[proc_macro] -pub fn __builder_type(params: TokenStream) -> TokenStream { - use quote::quote; - - enum MemberKind { - Required, - Optional, - } - - mod kw { - syn::custom_keyword!(required); - syn::custom_keyword!(optional); - } - - struct Input { - builder_ident: syn::Ident, - members: Vec<(syn::Ident, MemberKind)>, - set_members: Vec, - } - - impl syn::parse::Parse for Input { - fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { - let builder_ident = input.parse()?; - - let schema; - - syn::braced!(schema in input); - - let parse_members = |input: syn::parse::ParseStream<'_>| { - let key: syn::Ident = input.parse()?; - input.parse::()?; - - let lookahead = input.lookahead1(); - - let kind = if lookahead.peek(kw::required) { - input.parse::()?; - MemberKind::Required - } else if lookahead.peek(kw::optional) { - input.parse::()?; - MemberKind::Optional - } else { - return Err(lookahead.error()); - }; - - Ok((key, kind)) - }; - - let members = schema.parse_terminated(parse_members, syn::Token![,])?; - let members = Vec::from_iter(members); - - let set_members = input.parse_terminated(syn::Ident::parse, syn::Token![,])?; - let set_members = Vec::from_iter(set_members); - - Ok(Self { - builder_ident, - members, - set_members, - }) - } - } - - let input = syn::parse_macro_input!(params as Input); - - let Input { - builder_ident, - members, - - // TODO: validate only correct members are specified, - // maybe add completions - set_members, - } = input; - - let p = quote!(::bon::private); - - let state_types = members.iter().enumerate().map(|(i, (ident, kind))| { - let is_set = set_members.contains(ident); - - if is_set { - let tuple_item = quote::format_ident!("T{}", i + 1); - quote! { - #p::Set< - < - <#builder_ident as #p::state::Members>::Members - as - #p::state::Tuple - >::#tuple_item - > - } - } else { - match kind { - MemberKind::Required => quote! { #p::Unset<#p::Required> }, - MemberKind::Optional => quote! { #p::Unset<#p::Optional> }, - } - } - }); - - // TODO: deliver regular generics from input - quote! { - #builder_ident<(#(#state_types,)*)> - } - .into() -} diff --git a/bon-macros/src/normalization/lifetimes.rs b/bon-macros/src/normalization/lifetimes.rs index cb4b9fe6..d3cae200 100644 --- a/bon-macros/src/normalization/lifetimes.rs +++ b/bon-macros/src/normalization/lifetimes.rs @@ -9,7 +9,7 @@ impl VisitMut for NormalizeLifetimes { fn visit_item_impl_mut(&mut self, impl_block: &mut syn::ItemImpl) { syn::visit_mut::visit_item_impl_mut(self, impl_block); - AssignLifetimes::new("i", &mut impl_block.generics).visit_type_mut(&mut impl_block.self_ty); + AssignLifetimes::new("impl", &mut impl_block.generics).visit_type_mut(&mut impl_block.self_ty); } fn visit_impl_item_fn_mut(&mut self, fn_item: &mut syn::ImplItemFn) { @@ -19,7 +19,7 @@ impl VisitMut for NormalizeLifetimes { } fn visit_signature_mut(&mut self, signature: &mut syn::Signature) { - let mut visitor = AssignLifetimes::new("f", &mut signature.generics); + let mut visitor = AssignLifetimes::new("fn", &mut signature.generics); for arg in &mut signature.inputs { visitor.visit_fn_arg_mut(arg); } @@ -152,7 +152,7 @@ impl AssignLifetimes<'_> { let index = self.next_lifetime_index; self.next_lifetime_index += 1; - let lifetime = format!("'__{}{index}", self.prefix); + let lifetime = format!("'{}{index}", self.prefix); let lifetime = syn::Lifetime::new(&lifetime, Span::call_site()); let lifetime_param = syn::LifetimeParam::new(lifetime.clone()); diff --git a/bon-macros/src/util/ident.rs b/bon-macros/src/util/ident.rs index f9d5919a..9b43f4ee 100644 --- a/bon-macros/src/util/ident.rs +++ b/bon-macros/src/util/ident.rs @@ -18,6 +18,7 @@ pub(crate) trait IdentExt { /// produced identifier won't influence the syntax highlighting of the original /// identifier. fn snake_to_pascal_case(&self) -> Self; + 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. @@ -38,12 +39,33 @@ impl IdentExt for syn::Ident { 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#") { - Self::new_raw(name, span) - } else { - Self::new(name, span) + 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 { diff --git a/bon-macros/src/util/mod.rs b/bon-macros/src/util/mod.rs index 7c3a1182..b5d5b25d 100644 --- a/bon-macros/src/util/mod.rs +++ b/bon-macros/src/util/mod.rs @@ -7,6 +7,7 @@ mod path; mod punctuated; mod ty; mod vec; +mod visibility; pub(crate) mod ide; @@ -35,6 +36,7 @@ pub(crate) mod prelude { 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}; } diff --git a/bon-macros/src/util/visibility.rs b/bon-macros/src/util/visibility.rs new file mode 100644 index 00000000..d5ed7077 --- /dev/null +++ b/bon-macros/src/util/visibility.rs @@ -0,0 +1,64 @@ +use 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) + /// - ` ` (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) + /// + /// # 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. + 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))); + } + + 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); + } + + path.segments.insert(0, syn::parse_quote!(super)); + + Ok(self) + } + } + } +} diff --git a/bon/src/collections.rs b/bon/src/collections.rs new file mode 100644 index 00000000..b5b0b186 --- /dev/null +++ b/bon/src/collections.rs @@ -0,0 +1,151 @@ +/// Same as [`std::vec!`] but converts each element with [`Into`]. +/// +/// **WARNING:** it's not recommended to import this macro into scope. Reference it +/// using the full path (`bon::vec![]`) to avoid confusion with the [`std::vec!`] macro. +/// +/// A good example of the use case for this macro is when you want to create a +/// [`Vec`] where part of the items are hard-coded string literals of type +/// `&str` and the other part is made of dynamic [`String`] values. +/// +/// ``` +/// fn convert_media(input_extension: &str, output_extension: &str) -> std::io::Result<()> { +/// let ffmpeg_args: Vec = bon::vec![ +/// "-i", +/// format!("input.{input_extension}"), +/// "-y", +/// format!("output.{output_extension}"), +/// ]; +/// +/// std::process::Command::new("ffmpeg").args(ffmpeg_args).output()?; +/// +/// Ok(()) +/// } +/// ``` +/// +/// This macro doesn't support `vec![expr; N]` syntax, since it's simpler to +/// just write `vec![expr.into(); N]` using [`std::vec!`] instead. +#[macro_export] +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +#[allow(edition_2024_expr_fragment_specifier)] +macro_rules! vec { + () => ($crate::private::alloc::vec::Vec::new()); + ($($item:expr),+ $(,)?) => ($crate::private::alloc::vec![$(::core::convert::Into::into($item)),+ ]); +} + +/// Creates a fixed-size array literal with each element converted with [`Into`]. +/// +/// You'll probably need a hint for the target type of items in the array if the +/// compiler can't infer it from its usage. +/// +/// This is similar in spirit to the [`bon::vec!`] macro, but it's for arrays. +/// See [`bon::vec!`] docs for details. +/// +/// Same example as in [`bon::vec!`], but using this macro. It works with array +/// as well because [`Command::args`] accepts any value that implements [`IntoIterator`]: +/// +/// ``` +/// fn convert_media(input_extension: &str, output_extension: &str) -> std::io::Result<()> { +/// let ffmpeg_args: [String; 4] = bon::arr![ +/// "-i", +/// format!("input.{input_extension}"), +/// "-y", +/// format!("output.{output_extension}"), +/// ]; +/// +/// std::process::Command::new("ffmpeg").args(ffmpeg_args).output()?; +/// +/// Ok(()) +/// } +/// ``` +/// +/// This macro doesn't support `[expr; N]` syntax, since it's simpler to +/// just write `[expr.into(); N]` instead. +/// +/// [`Command::args`]: std::process::Command::args +/// [`bon::vec!`]: crate::vec +#[macro_export] +#[allow(edition_2024_expr_fragment_specifier)] +macro_rules! arr { + () => ([]); + ($($item:expr),+ $(,)?) => ([$(::core::convert::Into::into($item)),+]); +} + +#[cfg(test)] +mod tests { + #[cfg(feature = "alloc")] + use crate::private::alloc::{string::String, vec::Vec}; + use core::num::NonZeroU8; + + #[cfg(feature = "alloc")] + #[test] + fn arr_of_strings() { + let actual: [String; 3] = crate::arr!["foo", "bar", "baz"]; + assert_eq!(actual, ["foo", "bar", "baz"]); + + let actual: [String; 0] = crate::arr![]; + assert!(actual.is_empty()); + } + + #[test] + fn arr_of_numbers() { + let actual: [u8; 2] = crate::arr![NonZeroU8::new(1).unwrap(), NonZeroU8::new(2).unwrap()]; + assert_eq!(actual, [1, 2]); + + let actual: [u8; 0] = crate::arr![]; + assert!(actual.is_empty()); + } + + #[cfg(feature = "alloc")] + #[test] + fn vec_smoke() { + let actual: Vec = crate::vec!["foo", "bar", "baz"]; + assert_eq!(actual, ["foo", "bar", "baz"]); + + let actual: Vec = crate::vec![]; + assert!(actual.is_empty()); + } + + #[cfg(feature = "std")] + #[test] + fn map_smoke() { + use std::collections::{BTreeMap, HashMap}; + + let hash_strings: HashMap = crate::map! { + "Hello": "World", + "Goodbye": "Mars", + }; + + assert_eq!(hash_strings["Hello"], "World"); + assert_eq!(hash_strings["Goodbye"], "Mars"); + + let tree_strings: BTreeMap = crate::map! { + "Hello": "World", + "Goodbye": "Mars", + }; + + assert_eq!(tree_strings["Hello"], "World"); + assert_eq!(tree_strings["Goodbye"], "Mars"); + } + + #[cfg(feature = "std")] + #[test] + fn set_smoke() { + use std::collections::BTreeSet; + use std::collections::HashSet; + + let hash_strings: HashSet = crate::set!["Hello", "World", "Goodbye", "Mars"]; + + assert!(hash_strings.contains("Hello")); + assert!(hash_strings.contains("World")); + assert!(hash_strings.contains("Goodbye")); + assert!(hash_strings.contains("Mars")); + + let tree_strings: BTreeSet = crate::set!["Hello", "World", "Goodbye", "Mars"]; + + assert!(tree_strings.contains("Hello")); + assert!(tree_strings.contains("World")); + assert!(tree_strings.contains("Goodbye")); + assert!(tree_strings.contains("Mars")); + } +} diff --git a/bon/src/lib.rs b/bon/src/lib.rs index 64a3c7af..2276a51c 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -1,162 +1,67 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] +// We mark all items from the `private` module as deprecated to signal that they are +// implementation details and should not be used directly. Unfortunately, this triggers +// the deprecation warnings within this crate itself everywhere we use them, so we just +// suppress this lint for the entire crate. +#![allow(deprecated)] -pub use bon_macros::*; - -/// Symbols used by macros. They are not stable and are considered an implementation detail. -/// Don't use them! #[doc(hidden)] +#[deprecated = "the items from this module are an implementation detail; they should \ + not be used directly; 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://discord.gg/QcBYSamw4c)"] pub mod private; -/// Same as [`std::vec!`] but converts each element with [`Into`]. +/// Small utility declarative macros for creating colletions with [`Into`] conversions. +mod collections; + +/// Rexport all macros from the proc-macro crate. +pub use bon_macros::*; + +use private::sealed::Sealed; + +/// Marker trait that indicates that the member is not set, i.e. none of its setters were called. /// -/// **WARNING:** it's not recommended to import this macro into scope. Reference it -/// using the full path (`bon::vec![]`) to avoid confusion with the [`std::vec!`] macro. +/// You should use this trait bound, for example, if you want to extend the builder with custom +/// setters. /// -/// A good example of the use case for this macro is when you want to create a -/// [`Vec`] where part of the items are hard-coded string literals of type -/// `&str` and the other part is made of dynamic [`String`] values. +/// **Example:** /// /// ``` -/// fn convert_media(input_extension: &str, output_extension: &str) -> std::io::Result<()> { -/// let ffmpeg_args: Vec = bon::vec![ -/// "-i", -/// format!("input.{input_extension}"), -/// "-y", -/// format!("output.{output_extension}"), -/// ]; -/// -/// std::process::Command::new("ffmpeg").args(ffmpeg_args).output()?; -/// -/// Ok(()) +/// #[derive(bon::Builder)] +/// struct Example { +/// x: i32, +/// y: i32, /// } -/// ``` /// -/// This macro doesn't support `vec![expr; N]` syntax, since it's simpler to -/// just write `vec![expr.into(); N]` using [`std::vec!`] instead. -#[macro_export] -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] -#[allow(edition_2024_expr_fragment_specifier)] -macro_rules! vec { - () => ($crate::private::alloc::vec::Vec::new()); - ($($item:expr),+ $(,)?) => ($crate::private::alloc::vec![$(::core::convert::Into::into($item)),+ ]); -} - -/// Creates a fixed-size array literal with each element converted with [`Into`]. -/// -/// You'll probably need a hint for the target type of items in the array if the -/// compiler can't infer it from its usage. -/// -/// This is similar in spirit to the [`bon::vec!`] macro, but it's for arrays. -/// See [`bon::vec!`] docs for details. -/// -/// Same example as in [`bon::vec!`], but using this macro. It works with array -/// as well because [`Command::args`] accepts any value that implements [`IntoIterator`]: +/// // Import the type aliases for transforming the builder's type state +/// use example_builder::{SetX, SetY}; /// -/// ``` -/// fn convert_media(input_extension: &str, output_extension: &str) -> std::io::Result<()> { -/// let ffmpeg_args: [String; 4] = bon::arr![ -/// "-i", -/// format!("input.{input_extension}"), -/// "-y", -/// format!("output.{output_extension}"), -/// ]; -/// -/// std::process::Command::new("ffmpeg").args(ffmpeg_args).output()?; +/// impl ExampleBuilder { +/// fn x_doubled(value: i32) -> ExampleBuilder> +/// where +/// // The code won't compile without this bound +/// State::X: bon::IsUnset, +/// { +/// self.x(value * 2) +/// } /// -/// Ok(()) +/// fn y_doubled(value: i32) -> ExampleBuilder> +/// where +/// // The code won't compile without this bound +/// State::Y: bon::IsUnset, +/// { +/// self.y(value * 2) +/// } /// } /// ``` -/// -/// This macro doesn't support `[expr; N]` syntax, since it's simpler to -/// just write `[expr.into(); N]` instead. -/// -/// [`Command::args`]: std::process::Command::args -/// [`bon::vec!`]: crate::vec -#[macro_export] -#[allow(edition_2024_expr_fragment_specifier)] -macro_rules! arr { - () => ([]); - ($($item:expr),+ $(,)?) => ([$(::core::convert::Into::into($item)),+]); -} - -#[cfg(test)] -mod tests { - #[cfg(feature = "alloc")] - use crate::private::alloc::{string::String, vec::Vec}; - use core::num::NonZeroU8; - - #[cfg(feature = "alloc")] - #[test] - fn arr_of_strings() { - let actual: [String; 3] = crate::arr!["foo", "bar", "baz"]; - assert_eq!(actual, ["foo", "bar", "baz"]); - - let actual: [String; 0] = crate::arr![]; - assert!(actual.is_empty()); - } - - #[test] - fn arr_of_numbers() { - let actual: [u8; 2] = crate::arr![NonZeroU8::new(1).unwrap(), NonZeroU8::new(2).unwrap()]; - assert_eq!(actual, [1, 2]); - - let actual: [u8; 0] = crate::arr![]; - assert!(actual.is_empty()); - } - - #[cfg(feature = "alloc")] - #[test] - fn vec_smoke() { - let actual: Vec = crate::vec!["foo", "bar", "baz"]; - assert_eq!(actual, ["foo", "bar", "baz"]); - - let actual: Vec = crate::vec![]; - assert!(actual.is_empty()); - } - - #[cfg(feature = "std")] - #[test] - fn map_smoke() { - use std::collections::{BTreeMap, HashMap}; - - let hash_strings: HashMap = crate::map! { - "Hello": "World", - "Goodbye": "Mars", - }; - - assert_eq!(hash_strings["Hello"], "World"); - assert_eq!(hash_strings["Goodbye"], "Mars"); - - let tree_strings: BTreeMap = crate::map! { - "Hello": "World", - "Goodbye": "Mars", - }; - - assert_eq!(tree_strings["Hello"], "World"); - assert_eq!(tree_strings["Goodbye"], "Mars"); - } - - #[cfg(feature = "std")] - #[test] - fn set_smoke() { - use std::collections::BTreeSet; - use std::collections::HashSet; - - let hash_strings: HashSet = crate::set!["Hello", "World", "Goodbye", "Mars"]; - - assert!(hash_strings.contains("Hello")); - assert!(hash_strings.contains("World")); - assert!(hash_strings.contains("Goodbye")); - assert!(hash_strings.contains("Mars")); - - let tree_strings: BTreeSet = crate::set!["Hello", "World", "Goodbye", "Mars"]; +#[diagnostic::on_unimplemented(message = "The member {Self} was already set!")] +pub trait IsUnset: Sealed {} - assert!(tree_strings.contains("Hello")); - assert!(tree_strings.contains("World")); - assert!(tree_strings.contains("Goodbye")); - assert!(tree_strings.contains("Mars")); - } -} +/// Marker trait that indicates that the member is set, i.e. at least one of its setters was called. +// TODO: add examples (they would require having custom renames and visibility overrides for default setters) +#[diagnostic::on_unimplemented(message = "The member {Self} was not set!")] +pub trait IsSet: Sealed {} diff --git a/bon/src/private/cfg_eval.rs b/bon/src/private/cfg_eval.rs new file mode 100644 index 00000000..1a47d8bb --- /dev/null +++ b/bon/src/private/cfg_eval.rs @@ -0,0 +1,137 @@ +/// This is all a big embarrassing workaround, please don't oversee 😳😳😳. +/// +/// Anyway, if you are curious what the hell is going on here, then here is +/// an explanation 😸. So... where to start 🤔. Ah! The problem! +/// +/// ## The problem +/// +/// Proc macro attributes (like `#[builder]`) see all the `#[cfg(...)]` and `#[cfg_attr(...)]` +/// attributes unexpanded. For example, if you write smth like this: +/// +/// ``` +/// #[bon::builder] +/// fn func( +/// #[cfg(windows)] +/// windows_only_param: u32, +/// ) {} +/// +/// ``` +/// +/// then the `#[builder]` macro will see the full `#[cfg(...)]` attribute with +/// the `windows_only_param` it is attached to verbatim. The `#[cfg(...)]` isn't +/// removed by the time the `#[builder]`'s macro expansion is invoked. +/// +/// It is a problem because the `#[builder]` macro needs to know the exact list +/// of members it has to generate setters for. It doesn't know whether +/// the `windows` predicate evaluates to `true` or `false`, especially if this was +/// a more complex predicate. So it can't decide whether to generate a setter for +/// the `windows_only_param` or not. +/// +/// ## The solution +/// +/// This macro allows us to evaluate the `cfg` predicates by using a variation of +/// [the trick] shared by @recatek. +/// +/// When the `#[builder]` macro finds any usage of `#[cfg(...)]` or `#[cfg_attr(...)]` +/// it generates a call to this macro with all `cfg` predicates collected from the +/// item it was placed on. The `#[builder]` macro deduplicates and sorts the `cfg` +/// predicates and passes them as `$pred` to this macro. +/// +/// This macro then dispatches to `__eval_cfg_callback_true` or `__eval_cfg_callback_false` +/// by defining a conditional `use ...` statement for each predicate and collects the +/// results of the evaluation in the `$results` list. +/// +/// For the last call to this macro (when no more `$pred` are left) the macro calls back +/// to the proc macro attribute that called it with the results of the evaluation and +/// the original parameters and the item which are passed through via the `$rest` macro variable. +/// +/// [the trick]: https://users.rust-lang.org/t/supporting-or-evaluating-cfg-in-proc-macro-parameters/93240/2 +#[macro_export] +#[doc(hidden)] +macro_rules! __eval_cfg_callback { + ( + { $($results:tt)* } + ( $pred_id:ident: $($pred:tt)* ) + $($rest:tt)* + ) => { + // The `pred_id` is required to be a unique identifier for the current + // predicate evaluation so that we can use it in a `use` statement to define + // a new unique name for the macro to call. + #[cfg($($pred)*)] + #[doc(hidden)] + #[allow(deprecated)] + use $crate::__eval_cfg_callback_true as $pred_id; + + #[cfg(not($($pred)*))] + #[doc(hidden)] + #[allow(deprecated)] + use $crate::__eval_cfg_callback_false as $pred_id; + + // The trick here is that `$pred_id` now resolves either to + // `__eval_cfg_callback_true` or `__eval_cfg_callback_false` + // depending on the evaluation of the cfg predicate, so by + // invoking it as a macro, that macro internally pushes either + // `true` or `false` to the `$results` list. + $pred_id! { + { $($results)* } + $($rest)* + } + }; + + // The terminal case for the recursion when there are no more predicates left. + // We have collected all the results of the cfg evaluations and now we can + // delegate them to the proc macro attribute that called this macro. + ( + // The results of the cfg evaluation + { $($results:tt)* } + + // The proc macro attribute to invoke with the results + $final_macro:path, + + // The number of times this macro was called recursively from the proc macro + $recursion_counter:literal, + + // Parameters to pass to the proc macro attribute after the cfg results + ( $($macro_params:tt)* ) + + // The item to attach the proc macro attribute to + $($item:tt)* + ) => { + // The special `__cfgs(...)` prefix is parsed by the proc macro attribute + // to get the results of the cfg evaluations. + #[$final_macro(__cfgs($recursion_counter, $($results)*) $($macro_params)*)] + $($item)* + }; +} + +/// The `cfg` predicate evaluated to `true`, now push that information into +/// the `$results` list. +#[macro_export] +#[doc(hidden)] +macro_rules! __eval_cfg_callback_true { + ( + { $($results:tt)* } + $($tt:tt)* + ) => { + $crate::__eval_cfg_callback! { + { $($results)* true, } + $($tt)* + } + }; +} + +/// The `cfg` predicate evaluated to `false`, now push that information into +/// the `$results` list. +#[macro_export] +#[doc(hidden)] +macro_rules! __eval_cfg_callback_false { + ( + { $($results:tt)* } + $($tt:tt)* + ) => { + $crate::__eval_cfg_callback! { + { $($results)* false, } + $($tt)* + } + }; +} diff --git a/bon/src/private/member_cell.rs b/bon/src/private/member_cell.rs new file mode 100644 index 00000000..4e5ceb2c --- /dev/null +++ b/bon/src/private/member_cell.rs @@ -0,0 +1,163 @@ +use super::MemberState; +use crate::{IsSet, IsUnset}; +use core::fmt; +use core::marker::PhantomData; +use core::mem::MaybeUninit; + +/// This is an optimized version of the [`Option`] type that encodes the state +/// of [`Some`] or [`None`] at compile time, and thus it's a completely zero-cost +/// transparent wrapper over the inner value of type `T`. +/// +/// The `State` generic type determines whether the value was set or not. It can +/// either implement the [`IsSet`] trait or the [`IsUnset`] trait but never both (!). +/// +/// This requirement that [`IsSet`] and [`IsUnset`] are mutually exclusive is +/// enforced by the type system and also the fact that these traits are sealed, +/// so nothing outside of this module can implement them. +/// +/// The [`MemberState`] trait bound is required because it's needed in the [`Drop`] +/// implementation which does a conditional [`drop`] based on the state of the value +/// (i.e., only when it [`IsSet`]). This is basically a workaround for the lack of +/// specialization in Rust, and that the compiler still conservatively assumes that +/// [`IsSet`] and [`IsUnset`] trait implementations can possible overlap even though +/// they are sealed. +#[doc(hidden)] +#[deprecated = "this type is an implementation detail and should not be used directly; \ + 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://discord.gg/QcBYSamw4c)"] +#[must_use] +pub struct MemberCell { + /// The [`PhantomData`] uses an `fn()` pointer to signify that this type + /// doesn't hold an instance of `State`. + state: PhantomData State>, + + value: MaybeUninit, +} + +impl Drop for MemberCell { + fn drop(&mut self) { + if State::is_set() { + // SAFETY: this is safe. The `value` is guaranteed to be initialized + // because `State::is_set()` returns `true` only when it implements + // the `IsSet` trait. The `IsSet` trait is sealed and implemented + // only for the `Set` type, and there is only one way to create + // a `MaybeCell` for `IsSet` state - via the `MaybeCell::new` + // constructor that initializes the `value` field. + // + // Also the `MaybeCell::into_inner` method runs `mem::forget` on the + // `MaybeCell` instance once it consumed the inner value, so the + // `drop` method won't be invoked in that case. + #[allow(unsafe_code)] + unsafe { + // MSRV: we can't use `MaybeUninit::assume_init_drop` here because + // it is only available since Rust 1.60.0 (or MSRV is 1.59.0) + core::ptr::drop_in_place(self.value.as_mut_ptr()); + } + } + } +} + +impl MemberCell { + /// Creates a new [`MemberCell`] with an uninitialized value. This is only + /// possible when the `State` implements the [`IsUnset`] trait. + #[inline(always)] + pub fn uninit() -> Self { + Self { + state: PhantomData, + value: MaybeUninit::uninit(), + } + } +} + +impl MemberCell { + /// Creates a new [`MemberCell`] initialized with the specified value. This is + /// only possible when the `State` implements the [`IsSet`] trait. + #[inline(always)] + pub fn new(value: T) -> Self { + Self { + state: PhantomData, + value: MaybeUninit::new(value), + } + } + + /// Returns a reference to the value if it's set, otherwise `None`. + #[inline(always)] + pub fn into_inner(self) -> T { + // SAFETY: this is safe. The `value` is guaranteed to be initialized + // by the `MemberCell::new` constructor. There is no other way to + // create a `MemberCell` where `State: IsSet`. The trait implementation + // if `IsSet` and `IsUnset` are guaranteed to be mutually exclusive. + // They are sealed and implemented for Set/Unset types respectively. + #[allow(unsafe_code)] + unsafe { + // MSRV: we can't use `MaybeUninit::assume_init_read` here because + // it is only available since Rust 1.60.0 (or MSRV is 1.59.0) + let value = self.value.as_ptr().read(); + + // SAFETY: Make sure `drop` doesn't run to avoid double drop + // now that we have the `value` moved out of the `MaybeUninit`. + core::mem::forget(self); + + value + } + } +} + +impl MemberCell { + #[inline(always)] + fn try_get(&self) -> Option<&T> { + if State::is_set() { + // SAFETY: this is safe. The `value` is guaranteed to be initialized + // by the `MemberCell::new` constructor. There is no other way to + // create a `MemberCell` where `State: IsSet`. The trait implementation + // if `IsSet` and `IsUnset` are guaranteed to be mutually exclusive. + // They are sealed and implemented for Set/Unset types respectively. + #[allow(unsafe_code)] + unsafe { + return Some(self.value.assume_init_ref()); + } + } + + None + } +} + +impl fmt::Debug for MemberCell +where + State: MemberState, + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(value) = self.try_get() { + fmt::Debug::fmt(value, f) + } else { + f.write_str("Unset") + } + } +} + +impl Clone for MemberCell +where + State: MemberState, + T: Clone, +{ + #[inline(always)] + fn clone(&self) -> Self { + // `map_or_else` reads works, and writing a raw `if let` here may make + // it easier for the compiler to optimize this code out. + #[allow(clippy::option_if_let_else)] + if let Some(value) = self.try_get() { + Self { + state: PhantomData, + value: MaybeUninit::new(value.clone()), + } + } else { + Self { + state: PhantomData, + value: MaybeUninit::uninit(), + } + } + } +} diff --git a/bon/src/private/mod.rs b/bon/src/private/mod.rs index 9892c9d9..782f821b 100644 --- a/bon/src/private/mod.rs +++ b/bon/src/private/mod.rs @@ -6,14 +6,12 @@ // Marking every potential function as `const` is a bit too much. // Especially, this doesn't play well with our MSRV. Trait bounds // aren't allowed on const functions in older Rust versions. - clippy::missing_const_for_fn -)] - -use core::marker::PhantomData; -use core::mem::MaybeUninit; + clippy::missing_const_for_fn, -/// Implementation details of the builder state -pub mod state; + // We use `deprecated` as a sign to the user that they shouldn't use + // the type as it's an internal implementation detail. + deprecated, +)] /// Used to trigger deprecation warnings from the macros. pub mod deprecations; @@ -21,218 +19,66 @@ pub mod deprecations; /// Used for providing better IDE hints (completions and syntax highlighting). pub mod ide; +mod cfg_eval; +mod member_cell; + +pub(crate) mod sealed { + // The purpose of the `Sealed` trait **is** to be unnameable from outside the crate. + #[allow(unnameable_types)] + pub trait Sealed: Sized {} + + impl Sealed for super::Unset {} + impl Sealed for super::Set {} +} + +pub use member_cell::MemberCell; + +use core::fmt; +use sealed::Sealed; + /// Used to implement the `alloc` feature. #[cfg(feature = "alloc")] pub extern crate alloc; pub fn assert_clone() {} -pub fn assert_debug() {} +pub fn assert_debug() {} +#[doc(hidden)] +#[deprecated = "this type is an implementation detail and should not be used directly; \ + 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://discord.gg/QcBYSamw4c)"] #[derive(Debug)] pub struct Unset(T); -pub struct MemberCell { - state: PhantomData, - value: MaybeUninit, -} +impl crate::IsUnset for Unset {} -impl MemberCell { - pub fn uninit() -> Self { - Self { - state: PhantomData, - value: MaybeUninit::uninit(), - } - } -} +#[doc(hidden)] +#[deprecated = "this type is an implementation detail and should not be used directly; \ + 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://discord.gg/QcBYSamw4c)"] +#[derive(Debug)] +pub struct Set(T); -impl MemberCell { - pub fn init(value: T) -> Self { - Self { - state: PhantomData, - value: MaybeUninit::new(value), - } - } +impl crate::IsSet for Set {} - pub fn into_inner(self) -> T { - unsafe { - let value = self.value.assume_init_read(); - std::mem::forget(self); - value - } - } -} -pub trait MemberState { +pub trait MemberState: Sealed { fn is_set() -> bool; } impl MemberState for Unset { + #[inline(always)] fn is_set() -> bool { false } } impl MemberState for Set { + #[inline(always)] fn is_set() -> bool { true } } - -impl Drop for MemberCell { - fn drop(&mut self) { - if State::is_set() { - #[allow(unsafe_code)] - unsafe { - self.value.assume_init_drop(); - } - } - } -} - -#[diagnostic::on_unimplemented(message = "The member {Self} was already set!")] -pub trait IsUnset {} -impl IsUnset for Unset {} - -#[derive(Debug)] -pub struct Set(T); - -#[diagnostic::on_unimplemented(message = "The member {Self} was not set!")] -pub trait IsSet {} - -impl IsSet for Set {} - -/// This is all a big embarrassing workaround, please don't oversee 😳😳😳. -/// -/// Anyway, if you are curious what the hell is going on here, then here is -/// an explanation 😸. So... where to start 🤔. Ah! The problem! -/// -/// ## The problem -/// -/// Proc macro attributes (like `#[builder]`) see all the `#[cfg(...)]` and `#[cfg_attr(...)]` -/// attributes unexpanded. For example, if you write smth like this: -/// -/// ``` -/// #[bon::builder] -/// fn func( -/// #[cfg(windows)] -/// windows_only_param: u32, -/// ) {} -/// -/// ``` -/// -/// then the `#[builder]` macro will see the full `#[cfg(...)]` attribute with -/// the `windows_only_param` it is attached to verbatim. The `#[cfg(...)]` isn't -/// removed by the time the `#[builder]`'s macro expansion is invoked. -/// -/// It is a problem because the `#[builder]` macro needs to know the exact list -/// of members it has to generate setters for. It doesn't know whether -/// the `windows` predicate evaluates to `true` or `false`, especially if this was -/// a more complex predicate. So it can't decide whether to generate a setter for -/// the `windows_only_param` or not. -/// -/// ## The solution -/// -/// This macro allows us to evaluate the `cfg` predicates by using a variation of -/// [the trick] shared by @recatek. -/// -/// When the `#[builder]` macro finds any usage of `#[cfg(...)]` or `#[cfg_attr(...)]` -/// it generates a call to this macro with all `cfg` predicates collected from the -/// item it was placed on. The `#[builder]` macro deduplicates and sorts the `cfg` -/// predicates and passes them as `$pred` to this macro. -/// -/// This macro then dispatches to `__eval_cfg_callback_true` or `__eval_cfg_callback_false` -/// by defining a conditional `use ...` statement for each predicate and collects the -/// results of the evaluation in the `$results` list. -/// -/// For the last call to this macro (when no more `$pred` are left) the macro calls back -/// to the proc macro attribute that called it with the results of the evaluation and -/// the original parameters and the item which are passed through via the `$rest` macro variable. -/// -/// [the trick]: https://users.rust-lang.org/t/supporting-or-evaluating-cfg-in-proc-macro-parameters/93240/2 -#[macro_export] -#[doc(hidden)] -macro_rules! __eval_cfg_callback { - ( - { $($results:tt)* } - ( $pred_id:ident: $($pred:tt)* ) - $($rest:tt)* - ) => { - // The `pred_id` is required to be a unique identifier for the current - // predicate evaluation so that we can use it in a `use` statement to define - // a new unique name for the macro to call. - #[cfg($($pred)*)] - #[doc(hidden)] - use $crate::__eval_cfg_callback_true as $pred_id; - - #[cfg(not($($pred)*))] - #[doc(hidden)] - use $crate::__eval_cfg_callback_false as $pred_id; - - // The trick here is that `$pred_id` now resolves either to - // `__eval_cfg_callback_true` or `__eval_cfg_callback_false` - // depending on the evaluation of the cfg predicate, so by - // invoking it as a macro, that macro internally pushes either - // `true` or `false` to the `$results` list. - - $pred_id! { - { $($results)* } - $($rest)* - } - }; - - // The terminal case for the recursion when there are no more predicates left. - // We have collected all the results of the cfg evaluations and now we can - // delegate them to the proc macro attribute that called this macro. - ( - // The results of the cfg evaluation - { $($results:tt)* } - - // The proc macro attribute to invoke with the results - $final_macro:path, - - // The number of times this macro was called recursively from the proc macro - $recursion_counter:literal, - - // Parameters to pass to the proc macro attribute after the cfg results - ( $($macro_params:tt)* ) - - // The item to attach the proc macro attribute to - $($item:tt)* - ) => { - // The special `__cfgs(...)` prefix is parsed by the proc macro attribute - // to get the results of the cfg evaluations. - #[$final_macro(__cfgs($recursion_counter, $($results)*) $($macro_params)*)] - $($item)* - }; -} - -/// The `cfg` predicate evaluated to `true`, now push that information into -/// the `$results` list. -#[macro_export] -#[doc(hidden)] -macro_rules! __eval_cfg_callback_true { - ( - { $($results:tt)* } - $($tt:tt)* - ) => { - $crate::__eval_cfg_callback! { - { $($results)* true, } - $($tt)* - } - }; -} - -/// The `cfg` predicate evaluated to `false`, now push that information into -/// the `$results` list. -#[macro_export] -#[doc(hidden)] -macro_rules! __eval_cfg_callback_false { - ( - { $($results:tt)* } - $($tt:tt)* - ) => { - $crate::__eval_cfg_callback! { - { $($results)* false, } - $($tt)* - } - }; -} diff --git a/bon/src/private/state.rs b/bon/src/private/state.rs deleted file mode 100644 index 230cc40f..00000000 --- a/bon/src/private/state.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub trait Members { - type Members; -} - -bon_macros::__gen_tuple_traits2!(100); - -type N = never::Never; - -mod never { - /// Unnameable type. It's public, but under a private module. - #[doc(hidden)] - #[allow(unreachable_pub, unnameable_types, missing_debug_implementations)] - pub enum Never {} -} diff --git a/bon/tests/integration/builder/name_conflicts.rs b/bon/tests/integration/builder/name_conflicts.rs index 5066828f..5d37d183 100644 --- a/bon/tests/integration/builder/name_conflicts.rs +++ b/bon/tests/integration/builder/name_conflicts.rs @@ -35,3 +35,55 @@ fn member_and_type_named_the_same_struct() { let _ = Sut::builder().user(User).build(); Sut::sut().user(User).call(); } + +mod member_names_state { + use crate::prelude::*; + + #[test] + fn test_free_fn() { + #[builder] + fn sut(state: u32, member_state: u32, unset: u32, all_unset: u32) { + let _ = (state, member_state, unset, all_unset); + } + + sut().state(1).member_state(2).unset(3).all_unset(4).call() + } + + #[test] + fn test_struct() { + #[derive(Builder)] + struct Sut { + state: u32, + member_state: u32, + unset: u32, + all_unset: u32, + } + + let _ = Sut::builder() + .state(1) + .member_state(2) + .unset(3) + .all_unset(4) + .build(); + } + + #[test] + fn test_assoc_method() { + struct Sut; + + #[bon] + impl Sut { + #[builder] + fn sut(state: u32, member_state: u32, unset: u32, all_unset: u32) { + let _ = (state, member_state, unset, all_unset); + } + } + + Sut::sut() + .state(1) + .member_state(2) + .unset(3) + .all_unset(4) + .call(); + } +} diff --git a/bon/tests/integration/builder/raw_idents.rs b/bon/tests/integration/builder/raw_idents.rs index 90d71499..d4f65d6e 100644 --- a/bon/tests/integration/builder/raw_idents.rs +++ b/bon/tests/integration/builder/raw_idents.rs @@ -17,12 +17,13 @@ fn struct_case() { assert_eq!(actual.r#type, 42); assert_eq!(actual.other, 100); - #[derive(Builder)] - #[builder(builder_type = r#type)] - #[allow(clippy::items_after_statements)] - struct Sut {} + // TODO: add builder_mod overrides + // #[derive(Builder)] + // #[builder(builder_type = r#type)] + // #[allow(clippy::items_after_statements)] + // struct Sut {} - let _: r#type = Sut::builder(); + // let _: r#type = Sut::builder(); } #[test] @@ -35,8 +36,9 @@ fn fn_case() { r#type().r#type(42).r#while(100).call(); - #[builder(builder_type = r#type)] - fn sut() {} + // TODO: add builder_mod overrides + // #[builder(builder_type = r#type)] + // fn sut() {} - let _: r#type = sut(); + // let _: r#type = sut(); } diff --git a/website/guide/overview.md b/website/guide/overview.md index b9e6939e..b99d89bb 100644 --- a/website/guide/overview.md +++ b/website/guide/overview.md @@ -274,7 +274,7 @@ If you can't figure something out, consult the docs and maybe use that search ` From 01ce57a5c58e7a3a46ad8e1692b807acb0781f88 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 21 Sep 2024 19:41:11 +0000 Subject: [PATCH 006/119] Fix lints --- .../builder/builder_gen/builder_derives.rs | 103 ++-- .../src/builder/builder_gen/builder_params.rs | 12 + .../{input_func.rs => input_fn.rs} | 115 ++-- .../src/builder/builder_gen/input_struct.rs | 85 ++- .../builder_gen/member/into_conversion.rs | 4 - .../src/builder/builder_gen/member/mod.rs | 9 +- bon-macros/src/builder/builder_gen/mod.rs | 533 ++++++------------ bon-macros/src/builder/builder_gen/models.rs | 243 ++++++++ .../src/builder/builder_gen/setter_methods.rs | 31 +- .../src/builder/{item_func.rs => item_fn.rs} | 24 +- bon-macros/src/builder/item_impl.rs | 40 +- bon-macros/src/builder/item_struct.rs | 6 +- bon-macros/src/builder/mod.rs | 4 +- bon-macros/src/util/generic_param.rs | 19 + bon-macros/src/util/mod.rs | 2 + bon/src/lib.rs | 4 +- bon/src/private/member_cell.rs | 5 - bon/tests/integration/builder/mod.rs | 6 +- .../integration/builder/name_conflicts.rs | 3 +- .../integration/builder/positional_members.rs | 6 +- .../ui/compile_fail/builder_derives.stderr | 114 +++- .../integration/ui/compile_fail/errors.stderr | 43 +- .../ui/compile_fail/warnings.stderr | 8 +- 23 files changed, 815 insertions(+), 604 deletions(-) rename bon-macros/src/builder/builder_gen/{input_func.rs => input_fn.rs} (85%) create mode 100644 bon-macros/src/builder/builder_gen/models.rs rename bon-macros/src/builder/{item_func.rs => item_fn.rs} (62%) create mode 100644 bon-macros/src/util/generic_param.rs diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index f72638db..03c65320 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -2,6 +2,7 @@ use super::builder_params::BuilderDerives; use super::BuilderGenCtx; use crate::builder::builder_gen::Member; use crate::util::prelude::*; +use darling::ast::GenericParamExt; use quote::quote; impl BuilderGenCtx { @@ -34,6 +35,32 @@ impl BuilderGenCtx { .map(Box::as_ref) } + /// 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. + fn where_clause_for_derive(&self, target_trait_bounds: &TokenStream2) -> TokenStream2 { + let target_trait_bounds_predicates = self + .generics + .decl_without_defaults + .iter() + .filter_map(syn::GenericParam::as_type_param) + .map(|param| { + let ident = ¶m.ident; + quote! { + #ident: #target_trait_bounds + } + }); + + let base_predicates = self.generics.where_clause_predicates(); + + quote! { + where + #( #base_predicates, )* + #( #target_trait_bounds_predicates, )* + } + } + fn derive_clone(&self) -> TokenStream2 { let generics_decl = &self.generics.decl_without_defaults; let generic_args = &self.generics.args; @@ -53,31 +80,31 @@ impl BuilderGenCtx { } }); - let builder_where_clause_predicates = self.generics.where_clause_predicates(); - + let where_clause = self.where_clause_for_derive(&clone); + let builder_mod_ident = &self.builder_mod.ident; let builder_component_types = self.builder_component_types(); quote! { #[automatically_derived] - impl < + impl< #(#generics_decl,)* - ___State + BuilderTypeState: #builder_mod_ident::State > - #clone for #builder_ident < + #clone for #builder_ident< #(#generic_args,)* - ___State + BuilderTypeState > - where - #(#builder_where_clause_predicates,)* - ___State: #clone, + #where_clause { fn clone(&self) -> Self { + // These assertions improve error messages by pointing directly to the + // types that don't implement the target trait in the user's code. #(::bon::private::assert_clone::<#builder_component_types>();)* Self { __private_phantom: ::core::marker::PhantomData, #clone_receiver #clone_start_fn_args - __private_named_members: self.__private_named_members.clone(), + __private_named_members: #clone::clone(&self.__private_named_members), } } } @@ -85,36 +112,15 @@ impl BuilderGenCtx { } fn derive_debug(&self) -> TokenStream2 { - let generics_decl = &self.generics.decl_without_defaults; - let generic_args = &self.generics.args; - let builder_ident = &self.builder_type.ident; - - let debug = quote!(::core::fmt::Debug); - - let format_receiver = self.receiver().map(|_| { - quote! { - output.field("self", &self.__private_receiver); - } - }); - - let builder_where_clause_predicates = self.generics.where_clause_predicates(); - let builder_component_types = self.builder_component_types(); - - let builder_ident_str = builder_ident.to_string(); - - let state_type_vars = self - .named_members() - .map(|member| &member.generic_var_ident) - .collect::>(); - let format_members = self.members.iter().filter_map(|member| { match member { Member::Named(member) => { let member_index = &member.index; - let member_ident_str = member.orig_ident.to_string(); + let member_ident_str = member.public_ident().to_string(); + let member_pascal = &member.norm_ident_pascal; Some(quote! { // Skip members that are not set to reduce noise - if self.__private_named_members.#member_index.is_set() { + if ::is_set() { output.field( #member_ident_str, &self.__private_named_members.#member_index @@ -140,21 +146,36 @@ impl BuilderGenCtx { } }); + let format_receiver = self.receiver().map(|_| { + quote! { + output.field("self", &self.__private_receiver); + } + }); + + let debug = quote!(::core::fmt::Debug); + let where_clause = self.where_clause_for_derive(&debug); + let builder_mod_ident = &self.builder_mod.ident; + let generics_decl = &self.generics.decl_without_defaults; + let generic_args = &self.generics.args; + let builder_ident = &self.builder_type.ident; + let builder_ident_str = builder_ident.to_string(); + let builder_component_types = self.builder_component_types(); + quote! { #[automatically_derived] - impl < + impl< #(#generics_decl,)* - #(#state_type_vars,)* + BuilderTypeState: #builder_mod_ident::State > - #debug for #builder_ident < + #debug for #builder_ident< #(#generic_args,)* - (#(#state_type_vars,)*) + BuilderTypeState > - where - #(#builder_where_clause_predicates,)* - #(#state_type_vars: ::bon::private::MemberState + ::core::fmt::Debug,)* + #where_clause { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + // These assertions improve error messages by pointing directly to the + // types that don't implement the target trait in the user's code. #(::bon::private::assert_debug::<#builder_component_types>();)* let mut output = f.debug_struct(#builder_ident_str); diff --git a/bon-macros/src/builder/builder_gen/builder_params.rs b/bon-macros/src/builder/builder_gen/builder_params.rs index 2fd4ed3a..b6af8df7 100644 --- a/bon-macros/src/builder/builder_gen/builder_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params.rs @@ -24,6 +24,15 @@ fn parse_builder_type(meta: &syn::Meta) -> Result { .parse() } +fn parse_builder_mod(meta: &syn::Meta) -> Result { + ItemParamsParsing { + meta, + allow_vis: false, + reject_self_mentions: Some("buuilder module"), + } + .parse() +} + #[derive(Debug, FromMeta)] pub(crate) struct BuilderParams { #[darling(default, with = parse_finish_fn)] @@ -32,6 +41,9 @@ pub(crate) struct BuilderParams { #[darling(default, with = parse_builder_type)] pub(crate) builder_type: ItemParams, + #[darling(default, with = parse_builder_mod)] + pub(crate) builder_mod: ItemParams, + #[darling(multiple)] pub(crate) on: Vec, diff --git a/bon-macros/src/builder/builder_gen/input_func.rs b/bon-macros/src/builder/builder_gen/input_fn.rs similarity index 85% rename from bon-macros/src/builder/builder_gen/input_func.rs rename to bon-macros/src/builder/builder_gen/input_fn.rs index 1bbe6e16..d0a64000 100644 --- a/bon-macros/src/builder/builder_gen/input_func.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -1,10 +1,10 @@ use super::builder_params::BuilderParams; use super::{ - generic_param_to_arg, AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFunc, - FinishFuncBody, Generics, Member, MemberOrigin, RawMember, StartFunc, + AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, + Member, MemberOrigin, RawMember, StartFn, }; use crate::builder::builder_gen::builder_params::ItemParams; -use crate::builder::builder_gen::BuilderType; +use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams}; use crate::normalization::NormalizeSelfTy; use crate::util::prelude::*; use darling::util::SpannedValue; @@ -17,7 +17,7 @@ use syn::visit::Visit; use syn::visit_mut::VisitMut; #[derive(Debug, FromMeta)] -pub(crate) struct FuncInputParams { +pub(crate) struct FnInputParams { expose_positional_fn: Option>, #[darling(flatten)] @@ -62,11 +62,11 @@ impl FromMeta for ExposePositionalFnParams { } } -pub(crate) struct FuncInputCtx { - pub(crate) orig_func: syn::ItemFn, - pub(crate) norm_func: syn::ItemFn, +pub(crate) struct FnInputCtx { + pub(crate) orig_fn: syn::ItemFn, + pub(crate) norm_fn: syn::ItemFn, pub(crate) impl_ctx: Option>, - pub(crate) params: FuncInputParams, + pub(crate) params: FnInputParams, } pub(crate) struct ImplCtx { @@ -79,7 +79,7 @@ pub(crate) struct ImplCtx { pub(crate) allow_attrs: Vec, } -impl FuncInputCtx { +impl FnInputCtx { fn self_ty_prefix(&self) -> Option { let prefix = self .impl_ctx @@ -99,7 +99,7 @@ impl FuncInputCtx { } fn assoc_method_receiver_ctx(&self) -> Option { - let receiver = self.norm_func.sig.receiver()?; + let receiver = self.norm_fn.sig.receiver()?; let self_ty = &self.impl_ctx.as_deref()?.self_ty; let mut without_self_keyword = receiver.ty.clone(); @@ -114,13 +114,13 @@ impl FuncInputCtx { fn generics(&self) -> Generics { let impl_ctx = self.impl_ctx.as_ref(); - let norm_func_params = &self.norm_func.sig.generics.params; + let norm_fn_params = &self.norm_fn.sig.generics.params; let params = impl_ctx - .map(|impl_ctx| merge_generic_params(&impl_ctx.generics.params, norm_func_params)) - .unwrap_or_else(|| norm_func_params.iter().cloned().collect()); + .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.norm_func.sig.generics.where_clause.clone(), + self.norm_fn.sig.generics.where_clause.clone(), impl_ctx.and_then(|impl_ctx| impl_ctx.generics.where_clause.clone()), ]; @@ -146,16 +146,16 @@ impl FuncInputCtx { return quote::format_ident!("{}Builder", self.self_ty_prefix().unwrap_or_default()); } - let pascal_case_func = self.norm_func.sig.ident.snake_to_pascal_case(); + let pascal_case_fn = self.norm_fn.sig.ident.snake_to_pascal_case(); quote::format_ident!( - "{}{pascal_case_func}Builder", + "{}{pascal_case_fn}Builder", self.self_ty_prefix().unwrap_or_default(), ) } - pub(crate) fn adapted_func(&self) -> Result { - let mut orig = self.orig_func.clone(); + pub(crate) fn adapted_fn(&self) -> Result { + let mut orig = self.orig_fn.clone(); let params = self.params.expose_positional_fn.as_ref(); @@ -166,7 +166,7 @@ impl FuncInputCtx { .clone() // If exposing of positional fn is enabled without an explicit // visibility, then just use the visibility of the original function. - .unwrap_or_else(|| self.norm_func.vis.clone()) + .unwrap_or_else(|| self.norm_fn.vis.clone()) }) // By default we change the positional function's visibility to private // to avoid exposing it to the surrounding code. The surrounding code is @@ -253,7 +253,7 @@ impl FuncInputCtx { } fn is_method_new(&self) -> bool { - self.impl_ctx.is_some() && self.norm_func.sig.ident == "new" + self.impl_ctx.is_some() && self.norm_fn.sig.ident == "new" } pub(crate) fn into_builder_gen_ctx(self) -> Result { @@ -267,7 +267,7 @@ impl FuncInputCtx { the type of `Self` and properly generate the builder struct \ definition adjacently to the impl block."; - if let Some(receiver) = &self.orig_func.sig.receiver() { + if let Some(receiver) = &self.orig_fn.sig.receiver() { bail!( &receiver.self_token, "Function contains a `self` parameter {explanation}" @@ -275,7 +275,7 @@ impl FuncInputCtx { } let mut ctx = FindSelfReference::default(); - ctx.visit_item_fn(&self.orig_func); + ctx.visit_item_fn(&self.orig_fn); if let Some(self_span) = ctx.self_span { bail!( &self_span, @@ -290,8 +290,8 @@ impl FuncInputCtx { func.sig.inputs.iter().filter_map(syn::FnArg::as_typed) } - let members = typed_args(&self.norm_func) - .zip(typed_args(&self.orig_func)) + let members = typed_args(&self.norm_fn) + .zip(typed_args(&self.orig_fn)) .map(|(norm_arg, orig_arg)| { let pat = match norm_arg.pat.as_ref() { syn::Pat::Ident(pat) => pat, @@ -315,8 +315,8 @@ impl FuncInputCtx { let generics = self.generics(); - let finish_func_body = FnCallBody { - sig: self.adapted_func()?.sig, + let finish_fn_body = FnCallBody { + sig: self.adapted_fn()?.sig, impl_ctx: self.impl_ctx.clone(), }; @@ -324,19 +324,19 @@ impl FuncInputCtx { // Special case for `new` methods. We rename them to `builder` // since this is the name that is used in the builder pattern - let start_func_ident = if is_method_new { + let start_fn_ident = if is_method_new { quote::format_ident!("builder") } else { - self.norm_func.sig.ident.clone() + self.norm_fn.sig.ident.clone() }; let ItemParams { - name: finish_func_ident, + name: finish_fn_ident, vis: _, - docs: finish_func_docs, + docs: finish_fn_docs, } = self.params.base.finish_fn; - let finish_func_ident = finish_func_ident.unwrap_or_else(|| { + let finish_fn_ident = finish_fn_ident.unwrap_or_else(|| { // For `new` methods the `build` finisher is more conventional if is_method_new { quote::format_ident!("build") @@ -345,24 +345,24 @@ impl FuncInputCtx { } }); - let finish_func_docs = finish_func_docs.unwrap_or_else(|| { + let finish_fn_docs = finish_fn_docs.unwrap_or_else(|| { vec![syn::parse_quote! { /// Finishes building and performs the requested action. }] }); - let finish_func = FinishFunc { - ident: finish_func_ident, - unsafety: self.norm_func.sig.unsafety, - asyncness: self.norm_func.sig.asyncness, - must_use: get_must_use_attribute(&self.norm_func.attrs)?, - body: Box::new(finish_func_body), - output: self.norm_func.sig.output, - attrs: finish_func_docs, + let finish_fn = FinishFn { + ident: finish_fn_ident, + unsafety: self.norm_fn.sig.unsafety, + asyncness: self.norm_fn.sig.asyncness, + must_use: get_must_use_attribute(&self.norm_fn.attrs)?, + body: Box::new(finish_fn_body), + output: self.norm_fn.sig.output, + attrs: finish_fn_docs, }; let fn_allows = self - .norm_func + .norm_fn .attrs .iter() .filter_map(syn::Attribute::to_allow); @@ -375,15 +375,15 @@ impl FuncInputCtx { .chain(fn_allows) .collect(); - let start_func = StartFunc { - ident: start_func_ident, + let start_fn = StartFn { + ident: start_fn_ident, // No override for visibility for the start fn is provided here. // It's supposed to be the same as the original function's visibility. vis: None, attrs: self - .norm_func + .norm_fn .attrs .into_iter() .filter(<_>::is_doc) @@ -393,18 +393,18 @@ impl FuncInputCtx { // target function itself. We don't need to duplicate the generics // from the impl block here. generics: Some(Generics::new( - Vec::from_iter(self.norm_func.sig.generics.params), - self.norm_func.sig.generics.where_clause, + Vec::from_iter(self.norm_fn.sig.generics.params), + self.norm_fn.sig.generics.where_clause, )), }; - let builder_type = BuilderType { + let builder_type = BuilderTypeParams { ident: builder_ident, derives: self.params.base.derive, docs: self.params.base.builder_type.docs, }; - let ctx = BuilderGenCtx { + BuilderGenCtx::new(BuilderGenCtxParams { members, allow_attrs, @@ -413,14 +413,13 @@ impl FuncInputCtx { assoc_method_ctx: receiver, generics, - vis: self.norm_func.vis, + vis: self.norm_fn.vis, builder_type, - start_func, - finish_func, - }; - - Ok(ctx) + builder_mod: self.params.base.builder_mod, + start_fn, + finish_fn, + }) } } @@ -429,7 +428,7 @@ struct FnCallBody { impl_ctx: Option>, } -impl FinishFuncBody for FnCallBody { +impl FinishFnBody for FnCallBody { fn generate(&self, members: &[Member]) -> TokenStream2 { let asyncness = &self.sig.asyncness; let maybe_await = asyncness.is_some().then(|| quote!(.await)); @@ -445,7 +444,7 @@ impl FinishFuncBody for FnCallBody { .params .iter() .filter(|arg| !matches!(arg, syn::GenericParam::Lifetime(_))) - .map(generic_param_to_arg); + .map(syn::GenericParam::to_generic_argument); let prefix = self .sig @@ -456,13 +455,13 @@ impl FinishFuncBody for FnCallBody { Some(quote!(<#self_ty>::)) }); - let func_ident = &self.sig.ident; + let fn_ident = &self.sig.ident; // The variables with values of members are in scope for this expression. let member_vars = members.iter().map(Member::orig_ident); quote! { - #prefix #func_ident::<#(#generic_args,)*>( + #prefix #fn_ident::<#(#generic_args,)*>( #( #member_vars ),* ) #maybe_await diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 23c54a1d..5d444125 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -1,9 +1,9 @@ use super::builder_params::{BuilderParams, ItemParams, ItemParamsParsing}; use super::{ - AssocMethodCtx, BuilderGenCtx, FinishFunc, FinishFuncBody, Generics, Member, MemberOrigin, - RawMember, StartFunc, + AssocMethodCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, Member, MemberOrigin, + RawMember, StartFn, }; -use crate::builder::builder_gen::BuilderType; +use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams}; use crate::util::prelude::*; use darling::FromMeta; use quote::quote; @@ -74,7 +74,7 @@ impl StructInputCtx { .generics .params .iter() - .map(super::generic_param_to_arg); + .map(syn::GenericParam::to_generic_argument); let struct_ident = &orig_struct.ident; let struct_ty = syn::parse_quote!(#struct_ident<#(#generic_args),*>); @@ -98,20 +98,6 @@ impl StructInputCtx { } pub(crate) fn into_builder_gen_ctx(self) -> Result { - let builder_type = { - let ItemParams { name, vis: _, docs } = self.params.base.builder_type; - - let builder_ident = name.unwrap_or_else(|| { - quote::format_ident!("{}Builder", self.norm_struct.ident.raw_name()) - }); - - BuilderType { - derives: self.params.base.derive.clone(), - ident: builder_ident, - docs, - } - }; - fn fields(struct_item: &syn::ItemStruct) -> Result<&syn::FieldsNamed> { match &struct_item.fields { syn::Fields::Named(fields) => Ok(fields), @@ -149,46 +135,46 @@ impl StructInputCtx { self.norm_struct.generics.where_clause.clone(), ); - let finish_func_body = StructLiteralBody { + let finish_fn_body = StructLiteralBody { struct_ident: self.norm_struct.ident.clone(), }; let ItemParams { - name: start_func_ident, - vis: start_func_vis, - docs: start_func_docs, + name: start_fn_ident, + vis: start_fn_vis, + docs: start_fn_docs, } = self.params.start_fn; - let start_func_ident = start_func_ident + let start_fn_ident = start_fn_ident .unwrap_or_else(|| syn::Ident::new("builder", self.norm_struct.ident.span())); let ItemParams { - name: finish_func_ident, + name: finish_fn_ident, vis: _, - docs: finish_func_docs, + docs: finish_fn_docs, } = self.params.base.finish_fn; - let finish_func_ident = - finish_func_ident.unwrap_or_else(|| syn::Ident::new("build", start_func_ident.span())); + let finish_fn_ident = + finish_fn_ident.unwrap_or_else(|| syn::Ident::new("build", start_fn_ident.span())); let struct_ty = &self.struct_ty; - let finish_func = FinishFunc { - ident: finish_func_ident, + let finish_fn = FinishFn { + ident: finish_fn_ident, unsafety: None, asyncness: None, must_use: Some(syn::parse_quote! { #[must_use = "building a struct without using it is likely a bug"] }), - body: Box::new(finish_func_body), + body: Box::new(finish_fn_body), output: syn::parse_quote!(-> #struct_ty), - attrs: finish_func_docs.unwrap_or_else(|| { + attrs: finish_fn_docs.unwrap_or_else(|| { vec![syn::parse_quote! { /// Finishes building and returns the requested object }] }), }; - let start_func_docs = start_func_docs.unwrap_or_else(|| { + let start_fn_docs = start_fn_docs.unwrap_or_else(|| { let docs = format!( "Create an instance of [`{}`] using the builder syntax", self.norm_struct.ident @@ -197,10 +183,10 @@ impl StructInputCtx { vec![syn::parse_quote!(#[doc = #docs])] }); - let start_func = StartFunc { - ident: start_func_ident, - vis: start_func_vis, - attrs: start_func_docs, + let start_fn = StartFn { + ident: start_fn_ident, + vis: start_fn_vis, + attrs: start_fn_docs, generics: None, }; @@ -216,7 +202,21 @@ impl StructInputCtx { .filter_map(syn::Attribute::to_allow) .collect(); - let ctx = BuilderGenCtx { + let builder_type = { + let ItemParams { name, vis: _, docs } = self.params.base.builder_type; + + let builder_ident = name.unwrap_or_else(|| { + quote::format_ident!("{}Builder", self.norm_struct.ident.raw_name()) + }); + + BuilderTypeParams { + derives: self.params.base.derive, + ident: builder_ident, + docs, + } + }; + + BuilderGenCtx::new(BuilderGenCtxParams { members, allow_attrs, @@ -228,11 +228,10 @@ impl StructInputCtx { vis: self.norm_struct.vis, builder_type, - start_func, - finish_func, - }; - - Ok(ctx) + builder_mod: self.params.base.builder_mod, + start_fn, + finish_fn, + }) } } @@ -240,7 +239,7 @@ struct StructLiteralBody { struct_ident: syn::Ident, } -impl FinishFuncBody for StructLiteralBody { +impl FinishFnBody for StructLiteralBody { fn generate(&self, member_exprs: &[Member]) -> TokenStream2 { let Self { struct_ident } = self; diff --git a/bon-macros/src/builder/builder_gen/member/into_conversion.rs b/bon-macros/src/builder/builder_gen/member/into_conversion.rs index 848c27e2..6c5bb9ca 100644 --- a/bon-macros/src/builder/builder_gen/member/into_conversion.rs +++ b/bon-macros/src/builder/builder_gen/member/into_conversion.rs @@ -15,10 +15,6 @@ impl NamedMember { is_into_enabled(self.origin, &self.params, scrutinee, on_params) } - - pub(crate) fn public_ident(&self) -> &syn::Ident { - self.params.name.as_ref().unwrap_or(&self.norm_ident) - } } impl PositionalFnArgMember { diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index ef1d19fd..3cdd8f6f 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -76,10 +76,6 @@ pub(crate) struct NamedMember { /// Original type of the member (not normalized) pub(crate) orig_ty: Box, - /// The name of the type variable that can be used as the type of this - /// member in contexts where it should be generic. - pub(crate) generic_var_ident: syn::Ident, - /// Parameters configured by the user explicitly via attributes pub(crate) params: MemberParams, } @@ -139,6 +135,10 @@ impl NamedMember { Ok(()) } + pub(crate) fn public_ident(&self) -> &syn::Ident { + self.params.name.as_ref().unwrap_or(&self.norm_ident) + } + fn as_optional_with_ty<'a>(&'a self, ty: &'a syn::Type) -> Option<&'a syn::Type> { ty.option_type_param() .or_else(|| (self.params.default.is_some()).then(|| ty)) @@ -267,7 +267,6 @@ impl Member { let me = NamedMember { index: named_count.into(), origin, - generic_var_ident: quote::format_ident!("__{}", norm_ident_pascal), norm_ident_pascal, orig_ident, norm_ident, diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 6e60f953..191d83f7 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -1,155 +1,23 @@ mod builder_derives; mod builder_params; mod member; +mod models; mod setter_methods; -pub(crate) mod input_func; +pub(crate) mod input_fn; pub(crate) mod input_struct; use crate::util::prelude::*; -use builder_params::{BuilderDerives, OnParams}; use member::{Member, MemberOrigin, NamedMember, RawMember, StartFnArgMember}; +use models::{ + AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, + StartFn, +}; use quote::quote; use setter_methods::MemberSettersCtx; -struct AssocMethodReceiverCtx { - with_self_keyword: syn::Receiver, - without_self_keyword: Box, -} - -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. - self_ty: Box, - - /// Present only if the method has a receiver, i.e. `self` or `&self` or - /// `&mut self` or `self: ExplicitType`. - receiver: Option, -} - -pub(crate) struct BuilderGenCtx { - 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. - allow_attrs: Vec, - on_params: Vec, - - generics: Generics, - vis: syn::Visibility, - assoc_method_ctx: Option, - - builder_type: BuilderType, - start_func: StartFunc, - finish_func: FinishFunc, -} - -struct FinishFunc { - ident: syn::Ident, - - /// Additional attributes to apply to the item - attrs: Vec, - - unsafety: Option, - asyncness: Option, - /// - must_use: Option, - body: Box, - output: syn::ReturnType, -} - -struct StartFunc { - ident: syn::Ident, - - /// Additional attributes to apply to the item - attrs: Vec, - - /// Overrides the common generics - generics: Option, - - /// If present overrides the automatic visibility - vis: Option, -} - -struct BuilderType { - ident: syn::Ident, - - derives: BuilderDerives, - - /// Optional docs override - docs: Option>, -} - -pub(crate) trait FinishFuncBody { - /// 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, members: &[Member]) -> TokenStream2; -} - -struct Generics { - 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. - decl_with_defaults: Vec, - - /// Generic parameters without default values in them. This is suitable for - /// use as generics in function signatures or impl blocks. - 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. - args: Vec, -} - -impl Generics { - 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(generic_param_to_arg) - .collect(); - - Self { - where_clause, - decl_with_defaults, - decl_without_defaults, - args, - } - } - - fn where_clause_predicates(&self) -> impl Iterator { - self.where_clause - .as_ref() - .into_iter() - .flat_map(|clause| &clause.predicates) - } -} - pub(crate) struct MacroOutput { - pub(crate) start_func: syn::ItemFn, + pub(crate) start_fn: syn::ItemFn, pub(crate) other_items: TokenStream2, } @@ -166,13 +34,10 @@ impl BuilderGenCtx { self.members.iter().filter_map(Member::as_start_fn_arg) } - fn builder_mod_ident(&self) -> syn::Ident { - self.builder_ident.pascal_to_snake_case() - } - pub(crate) fn output(self) -> Result { - let mut start_func = self.start_func()?; - let builder_decl = self.builder_decl()?; + let mut start_fn = self.start_fn()?; + let builder_mod = self.builder_mod(); + let builder_decl = self.builder_decl(); let builder_impl = self.builder_impl()?; let builder_derives = self.builder_derives(); @@ -187,6 +52,7 @@ impl BuilderGenCtx { // -- Postprocessing -- // Here we parse all items back and add the `allow` attributes to them. let other_items = quote! { + #builder_mod #builder_decl #builder_derives #builder_impl @@ -216,16 +82,16 @@ impl BuilderGenCtx { } } - start_func.attrs.extend(allows); + start_fn.attrs.extend(allows); Ok(MacroOutput { - start_func, + start_fn, other_items: quote!(#(#other_items)*), }) } fn builder_impl(&self) -> Result { - let finish_method = self.finish_method()?; + let finish_fn = self.finish_fn()?; let setter_methods = self .named_members() .map(|member| MemberSettersCtx::new(self, member).setter_methods()) @@ -235,7 +101,7 @@ impl BuilderGenCtx { let generic_args = &self.generics.args; let where_clause = &self.generics.where_clause; let builder_ident = &self.builder_type.ident; - let builder_mod = self.builder_mod_ident(); + let builder_mod = &self.builder_mod.ident; let allows = allow_warnings_on_member_types(); @@ -252,16 +118,12 @@ impl BuilderGenCtx { > #where_clause { - #finish_method + #finish_fn #(#setter_methods)* } }) } - fn start_func_generics(&self) -> &Generics { - self.start_func.generics.as_ref().unwrap_or(&self.generics) - } - /// Generates code that has no meaning to the compiler, but it helps /// IDEs to provide better code highlighting, completions and other /// hints. @@ -297,13 +159,13 @@ impl BuilderGenCtx { } } - fn start_func(&self) -> Result { + fn start_fn(&self) -> Result { let builder_ident = &self.builder_type.ident; - let docs = &self.start_func.attrs; - let vis = self.start_func.vis.as_ref().unwrap_or(&self.vis); + let docs = &self.start_fn.attrs; + let vis = self.start_fn.vis.as_ref().unwrap_or(&self.vis); - let start_func_ident = &self.start_func.ident; + 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 @@ -312,7 +174,7 @@ impl BuilderGenCtx { // in the where clause. Research `darling`'s lifetime tracking API and // maybe implement this in the future - let generics = self.start_func_generics(); + 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; @@ -350,16 +212,11 @@ impl BuilderGenCtx { let ide_hints = self.ide_hints(); - let named_members_init = self.named_members().map(|member| { - let member_ident = &member.norm_ident; + let named_members_init_exprs = self.named_members().map(|member| { if member.is_optional() { - quote! { - #member_ident: None - } + quote!(None) } else { - quote! { - #member_ident: ::bon::private::MemberCell::uninit() - } + quote!(::bon::private::MemberCell::uninit()) } }); @@ -375,7 +232,7 @@ impl BuilderGenCtx { // const operations. clippy::missing_const_for_fn )] - #vis fn #start_func_ident<#(#generics_decl),*>( + #vis fn #start_fn_ident<#(#generics_decl),*>( #receiver #(#start_fn_params,)* ) -> #builder_ident<#(#generic_args,)*> @@ -387,7 +244,7 @@ impl BuilderGenCtx { __private_phantom: ::core::marker::PhantomData, #receiver_field_init #start_fn_args_field_init - #(#named_members_init,)* + __private_named_members: (#( #named_members_init_exprs, )*) } } }; @@ -451,96 +308,16 @@ impl BuilderGenCtx { } } - fn builder_decl(&self) -> Result { - let vis = &self.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 builder_mod = self.builder_mod_ident(); - - let private_field_doc = "\ - Please don't touch this field. It's an implementation \ - detail that is exempt from the API stability guarantees. \ - This field couldn't be hidden using Rust's privacy syntax. \ - The details about this are described in [the blog post]\ - (https://elastio.github.io/bon/blog/the-weird-of-function-local-types-in-rust). - "; - - let receiver_field = self.receiver().map(|receiver| { - let ty = &receiver.without_self_keyword; - quote! { - #[doc = #private_field_doc] - __private_receiver: #ty, - } - }); - - let must_use_message = format!( - "the builder does nothing until you call `{}()` on it to finish building", - self.finish_func.ident - ); - - let docs = self.builder_type.docs.clone().unwrap_or_else(|| { - let doc = format!( - "Use builder syntax to set the required parameters and finish \ - by calling the method [`Self::{}()`].", - self.finish_func.ident - ); - - vec![syn::parse_quote! { - #[doc = #doc] - }] - }); - - let allows = allow_warnings_on_member_types(); - - let mut start_fn_arg_types = self - .start_fn_args() - .map(|member| &member.base.norm_ty) - .peekable(); - - let start_fn_args_field = start_fn_arg_types.peek().is_some().then(|| { - quote! { - #[doc = #private_field_doc] - __private_start_fn_args: (#(#start_fn_arg_types,)*), - } - }); - - let member_fields = self.named_members().map(|member| { - let ident = &member.norm_ident; - - if let Some(ty) = member.as_optional_norm_ty() { - quote! { - #[doc = #private_field_doc] - #ident: ::core::option::Option<#ty> - } - } else { - let ty = &member.norm_ty; - let member_pascal = &member.norm_ident_pascal; - quote! { - #[doc = #private_field_doc] - #ident: ::bon::private::MemberCell - } - } - }); - - let named_members_pascal_idents: Vec<_> = self - .named_members() - .map(|member| &member.norm_ident_pascal) - .collect(); - - let total_named_members = named_members_pascal_idents.len(); - - let vis_child = self.vis.clone().into_equivalent_in_child_module()?; - - let state_transition_aliases = self.named_members().map(|member| { - let alias_name = quote::format_ident!("Set{}", member.norm_ident_pascal.raw_name()); + fn state_transition_aliases(&self) -> impl Iterator + '_ { + let vis_child = &self.builder_mod.vis_child; + let is_single_member = self.named_members().take(2).count() == 1; + self.named_members().map(move |member| { let states = self.named_members().map(|other_member| { if other_member.orig_ident == member.orig_ident { - let label = self.member_label(member); + let ident = &member.public_ident(); quote! { - ::bon::private::Set<#label> + ::bon::private::Set } } else { let member_pascal = &other_member.norm_ident_pascal; @@ -551,15 +328,16 @@ impl BuilderGenCtx { }); let member_ident = member.public_ident(); + let alias_ident = quote::format_ident!("Set{}", member.norm_ident_pascal.raw_name()); - if total_named_members == 1 { + if is_single_member { let docs = format!( "Changes the type state of the builder to set the member `{member_ident}`.", ); return quote! { #[doc = #docs] - #vis_child type #alias_name = ( #(#states,)* ); + #vis_child type #alias_ident = ( #(#states,)* ); }; } @@ -573,13 +351,24 @@ impl BuilderGenCtx { quote! { #[doc = #docs] - #vis_child type #alias_name = ( #(#states,)* ); + #vis_child type #alias_ident = ( #(#states,)* ); } - }); - let named_members_labels: Vec<_> = self + }) + } + + fn builder_mod(&self) -> TokenStream2 { + let vis_top = &self.vis; + let vis_child = &self.builder_mod.vis_child; + let vis_child_child = &self.builder_mod.vis_child_child; + + let builder_mod_docs = &self.builder_mod.docs; + let builder_mod_ident = &self.builder_mod.ident; + let state_transition_aliases = self.state_transition_aliases().collect::>(); + + let named_members_idents = self .named_members() - .map(|member| self.member_label(member)) - .collect(); + .map(NamedMember::public_ident) + .collect::>(); let assoc_types_docs = self.named_members().map(|member| { let ident = &member.public_ident(); @@ -589,14 +378,14 @@ impl BuilderGenCtx { ) }); - let builder_mod_docs = format!( - "Contains the traits and type aliases for manipulating \ - the type state of the {builder_ident}" - ); + let named_members_pascal = self + .named_members() + .map(|member| &member.norm_ident_pascal) + .collect::>(); - Ok(quote! { - #[doc = #builder_mod_docs] - #vis mod #builder_mod { + quote! { + #( #builder_mod_docs )* + #vis_top mod #builder_mod_ident { #( #state_transition_aliases )* /// Represents the builder's type state that specifies which members are set and which are not. @@ -609,34 +398,121 @@ impl BuilderGenCtx { #vis_child trait State: ::core::marker::Sized { #( #[doc = #assoc_types_docs] - type #named_members_pascal_idents: ::bon::private::MemberState; + type #named_members_pascal: ::bon::private::MemberState; )* } // Using `self::State` explicitly to avoid name conflicts with the // members named `state` which would create a generic param named `State` // that would shadow the trait `State` in the same scope. - impl< #(#named_members_pascal_idents: ::bon::private::MemberState,)* > self::State - for ( #(#named_members_pascal_idents,)* ) + impl< #(#named_members_pascal: ::bon::private::MemberState,)* > self::State + for ( #(#named_members_pascal,)* ) { - #( type #named_members_pascal_idents = #named_members_pascal_idents; )* + #( type #named_members_pascal = #named_members_pascal; )* } /// Initial state of the builder where all named members are unset - #vis_child type AllUnset = (#(::bon::private::Unset<#named_members_labels>,)*); - - #( - #[allow(non_camel_case_types)] - #[doc(hidden)] - #[deprecated = - "this type is an implementation detail and should not be \ - used directly; use the Set* type aliases to control the \ - state of members instead" - ] - #vis_child struct #named_members_labels; - )* + #vis_child type AllUnset = ( + #(::bon::private::Unset,)* + ); + + mod members { + #( + #[allow( + non_camel_case_types, + + // This is intentional. We don't want users to touch + // the implementation details of the builder's type + // signature like this one. They should use only the + // exported items of the parent module. + unnameable_types, + )] + #[doc(hidden)] + #[deprecated = + "this type is an implementation detail and should not be \ + used directly; use the Set* type aliases to control the \ + state of members instead" + ] + #vis_child_child struct #named_members_idents; + )* + } + } + } + } + + fn builder_decl(&self) -> TokenStream2 { + let vis = &self.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 builder_mod = &self.builder_mod.ident; + + let private_field_attrs = quote! { + // The fields can't be hidden using Rust's privacy syntax. + // The details about this are described in [the blog post] + // (https://elastio.github.io/bon/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. + #[doc(hidden)] + #[deprecated = + "this field is an implementation detail; it should not be used directly; \ + 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://discord.gg/QcBYSamw4c)"] + }; + + let receiver_field = self.receiver().map(|receiver| { + let ty = &receiver.without_self_keyword; + quote! { + #private_field_attrs + __private_receiver: #ty, + } + }); + + let must_use_message = format!( + "the builder does nothing until you call `{}()` on it to finish building", + self.finish_fn.ident + ); + + let allows = allow_warnings_on_member_types(); + + let mut start_fn_arg_types = self + .start_fn_args() + .map(|member| &member.base.norm_ty) + .peekable(); + + let start_fn_args_field = start_fn_arg_types.peek().is_some().then(|| { + quote! { + #private_field_attrs + __private_start_fn_args: (#(#start_fn_arg_types,)*), } + }); + + let named_members_types = self.named_members().map(|member| { + member + .as_optional_norm_ty() + .map(|ty| { + quote! { + ::core::option::Option<#ty> + } + }) + .unwrap_or_else(|| { + let ty = &member.norm_ty; + let member_pascal = &member.norm_ident_pascal; + quote! { + ::bon::private::MemberCell + } + }) + }); + let docs = &self.builder_type.docs; + + quote! { #[must_use = #must_use_message] #(#docs)* #allows @@ -656,22 +532,19 @@ impl BuilderGenCtx { > #where_clause { - // We could use `#[cfg(not(rust_analyzer))]` to hide these. - // 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. - #[doc = #private_field_doc] + #private_field_attrs __private_phantom: #phantom_data, #receiver_field #start_fn_args_field - #( #member_fields, )* + #private_field_attrs + __private_named_members: ( #(#named_members_types,)* ) } - }) + } } - fn member_expr(&self, member: &Member) -> Result { + fn finish_fn_member_expr(&self, member: &Member) -> Result { let member = match member { Member::Named(member) => member, Member::Skipped(member) => { @@ -679,7 +552,7 @@ impl BuilderGenCtx { .value .as_ref() .as_ref() - .map(|value| quote! { #value }) + .map(|value| quote! { (|| #value)() }) .unwrap_or_else(|| quote! { ::core::default::Default::default() }); return Ok(expr); @@ -693,7 +566,7 @@ impl BuilderGenCtx { } }; - let expr = member + let suffix = member .as_optional_norm_ty() .map(|_| { // For `Option` members we don't need any `unwrap_or_[else/default]`. @@ -722,38 +595,21 @@ impl BuilderGenCtx { }) .map(Option::transpose) .transpose()? - .map(|default| { - let ident = &member.norm_ident; - quote! { - self.#ident #default - } - }) - .unwrap_or_else(|| { - let ident = &member.norm_ident; - quote! { - self.#ident.into_inner() - } - }); + .unwrap_or_else(|| Some(quote! { .into_inner() })); - Ok(expr) - } + let index = &member.index; - /// Name of the dummy struct that is generated just to give a name for - /// the member in the error message when `IntoSet` trait is not implemented. - fn member_label(&self, member: &NamedMember) -> syn::Ident { - quote::format_ident!( - "{}__{}", - self.builder_type.ident.raw_name(), - member.public_ident() - ) + Ok(quote! { + self.__private_named_members.#index #suffix + }) } - fn finish_method(&self) -> Result { + fn finish_fn(&self) -> Result { let members_vars_decls = self .members .iter() .map(|member| { - let expr = self.member_expr(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 @@ -772,15 +628,6 @@ impl BuilderGenCtx { }) .collect::>>()?; - let body = &self.finish_func.body.generate(&self.members); - let asyncness = &self.finish_func.asyncness; - let unsafety = &self.finish_func.unsafety; - let must_use = &self.finish_func.must_use; - let attrs = &self.finish_func.attrs; - let vis = &self.vis; - let finish_func_ident = &self.finish_func.ident; - let output = &self.finish_func.output; - let where_bounds = self .named_members() .filter(|member| !member.is_optional()) @@ -798,6 +645,15 @@ impl BuilderGenCtx { .map(|member| member.fn_input_param(&self.on_params)) .collect::>>()?; + let body = &self.finish_fn.body.generate(&self.members); + let asyncness = &self.finish_fn.asyncness; + let unsafety = &self.finish_fn.unsafety; + let must_use = &self.finish_fn.must_use; + let attrs = &self.finish_fn.attrs; + let vis = &self.vis; + let finish_fn_ident = &self.finish_fn.ident; + let output = &self.finish_fn.output; + Ok(quote! { #(#attrs)* #[inline(always)] @@ -812,10 +668,7 @@ impl BuilderGenCtx { clippy::future_not_send, )] #must_use - #vis #asyncness #unsafety fn #finish_func_ident( - self, - #(#finish_fn_params,)* - ) #output + #vis #asyncness #unsafety fn #finish_fn_ident(self, #(#finish_fn_params,)*) #output where #(#where_bounds,)* { @@ -826,22 +679,6 @@ impl BuilderGenCtx { } } -pub(crate) fn generic_param_to_arg(param: &syn::GenericParam) -> syn::GenericArgument { - match param { - syn::GenericParam::Lifetime(param) => { - syn::GenericArgument::Lifetime(param.lifetime.clone()) - } - syn::GenericParam::Type(param) => { - let ident = ¶m.ident; - syn::GenericArgument::Type(syn::parse_quote!(#ident)) - } - syn::GenericParam::Const(param) => { - let ident = ¶m.ident; - syn::GenericArgument::Const(syn::parse_quote!(#ident)) - } - } -} - fn allow_warnings_on_member_types() -> TokenStream2 { quote! { // This warning may occur when the original unnormalized syntax was diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs new file mode 100644 index 00000000..4c8ee301 --- /dev/null +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -0,0 +1,243 @@ +use super::builder_params::{BuilderDerives, ItemParams, OnParams}; +use super::member::Member; +use crate::util::prelude::*; + +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, members: &[Member]) -> TokenStream2; +} + +pub(super) struct AssocMethodReceiverCtx { + 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 FinishFn { + pub(super) ident: syn::Ident, + + /// Additional attributes to apply to the item + pub(super) attrs: Vec, + + pub(super) unsafety: Option, + pub(super) asyncness: Option, + /// + pub(super) must_use: Option, + pub(super) body: Box, + pub(super) output: syn::ReturnType, +} + +pub(super) struct StartFn { + pub(super) ident: syn::Ident, + + /// Additional attributes to apply to the item + pub(super) attrs: Vec, + + /// Overrides the common generics + pub(super) generics: Option, + + /// If present overrides the automatic visibility + pub(super) vis: Option, +} + +pub(super) struct BuilderType { + pub(super) ident: syn::Ident, + pub(super) derives: BuilderDerives, + pub(super) docs: Vec, +} + +pub(super) struct BuilderMod { + pub(super) ident: syn::Ident, + pub(super) docs: Vec, + + /// Visibility equivalent to the [`BuilderGenCtx::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) 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) 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) on_params: Vec, + + pub(super) generics: Generics, + + /// Visibility of the generated items + pub(super) vis: syn::Visibility, + + pub(super) assoc_method_ctx: Option, + + pub(super) builder_type: BuilderType, + pub(super) builder_mod: BuilderMod, + pub(super) start_fn: StartFn, + pub(super) finish_fn: FinishFn, +} + +pub(super) struct BuilderGenCtxParams { + pub(super) members: Vec, + + pub(super) allow_attrs: Vec, + pub(super) on_params: Vec, + + pub(super) generics: Generics, + pub(super) vis: syn::Visibility, + pub(super) assoc_method_ctx: Option, + + pub(super) builder_type: BuilderTypeParams, + pub(super) builder_mod: ItemParams, + pub(super) start_fn: StartFn, + pub(super) finish_fn: FinishFn, +} + +pub(super) struct BuilderTypeParams { + pub(super) ident: syn::Ident, + pub(super) derives: BuilderDerives, + pub(super) docs: Option>, +} + +impl BuilderGenCtx { + pub(super) fn new(params: BuilderGenCtxParams) -> Result { + let BuilderGenCtxParams { + members, + allow_attrs, + on_params, + generics, + vis, + assoc_method_ctx, + builder_type, + builder_mod, + start_fn, + finish_fn, + } = params; + + let vis_child = vis.clone().into_equivalent_in_child_module()?; + let vis_child_child = vis_child.clone().into_equivalent_in_child_module()?; + + let builder_mod = BuilderMod { + vis_child, + vis_child_child, + + ident: builder_mod + .name + .unwrap_or_else(|| builder_type.ident.pascal_to_snake_case()), + + docs: builder_mod.docs.unwrap_or_else(|| { + let docs = format!( + "Contains the traits and type aliases for manipulating \ + the type state of the {}", + builder_type.ident + ); + + vec![syn::parse_quote!(#[doc = #docs])] + }), + }; + + let builder_type = BuilderType { + docs: builder_type.docs.unwrap_or_else(|| { + let doc = format!( + "Use builder syntax to set the required parameters and finish \ + by calling the method [`Self::{}()`].", + finish_fn.ident + ); + + vec![syn::parse_quote! { + #[doc = #doc] + }] + }), + derives: builder_type.derives, + ident: builder_type.ident, + }; + + Ok(Self { + members, + allow_attrs, + on_params, + generics, + vis, + assoc_method_ctx, + builder_type, + builder_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) + } +} diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index 8f660af2..52926e21 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -8,10 +8,7 @@ pub(crate) struct MemberSettersCtx<'a> { } impl<'a> MemberSettersCtx<'a> { - pub(crate) fn new( - builder_gen: &'a BuilderGenCtx, - member: &'a NamedMember, - ) -> Self { + pub(crate) fn new(builder_gen: &'a BuilderGenCtx, member: &'a NamedMember) -> Self { Self { builder_gen, member, @@ -129,18 +126,12 @@ impl<'a> MemberSettersCtx<'a> { let builder_ident = &self.builder_gen.builder_type.ident; - let member_forwards = self.builder_gen.named_members().map(|other_member| { - let member_ident = &other_member.norm_ident; - if *member_ident == self.member.norm_ident { - return quote! { - #member_ident: #member_init - }; - } - - let ident = &other_member.norm_ident; - quote! { - #member_ident: self.#ident + let member_exprs = self.builder_gen.named_members().map(|other_member| { + if other_member.norm_ident == self.member.norm_ident { + return member_init.clone(); } + let index = &other_member.index; + quote!(self.__private_named_members.#index) }); quote! { @@ -148,7 +139,7 @@ impl<'a> MemberSettersCtx<'a> { __private_phantom: ::core::marker::PhantomData, #maybe_receiver_field #maybe_start_fn_args_field - #( #member_forwards, )* + __private_named_members: (#( #member_exprs, )*) } } } @@ -159,8 +150,8 @@ impl<'a> MemberSettersCtx<'a> { let state_transition = quote::format_ident!("Set{}", self.member.norm_ident_pascal.raw_name()); - let builder_mod = &self.builder_gen.builder_mod_ident(); - let generic_param = if self.builder_gen.named_members().count() == 1 { + let builder_mod = &self.builder_gen.builder_mod.ident; + let generic_param = if self.builder_gen.named_members().take(2).count() == 1 { quote!() } else { quote!() @@ -170,7 +161,7 @@ impl<'a> MemberSettersCtx<'a> { #builder_mod::#state_transition #generic_param }; - let builder_ident = &self.builder_gen.builder_ident; + let builder_ident = &self.builder_gen.builder_type.ident; let generic_args = &self.builder_gen.generics.args; quote! { @@ -196,7 +187,7 @@ impl<'a> MemberSettersCtx<'a> { fn generate_docs_for_setter(&self) -> Vec { let setter_core_name = self.member.public_ident(); - let start_fn_ident = &self.builder_gen.start_func.ident; + let start_fn_ident = &self.builder_gen.start_fn.ident; let more = |start_fn_path: &std::fmt::Arguments<'_>| { format!(" See {start_fn_path} for more info.") diff --git a/bon-macros/src/builder/item_func.rs b/bon-macros/src/builder/item_fn.rs similarity index 62% rename from bon-macros/src/builder/item_func.rs rename to bon-macros/src/builder/item_fn.rs index d96e4d70..f403c9d9 100644 --- a/bon-macros/src/builder/item_func.rs +++ b/bon-macros/src/builder/item_fn.rs @@ -1,37 +1,37 @@ -use super::builder_gen::input_func::{FuncInputCtx, FuncInputParams}; +use super::builder_gen::input_fn::{FnInputCtx, FnInputParams}; use super::builder_gen::MacroOutput; use crate::util::prelude::*; use quote::quote; use syn::visit_mut::VisitMut; -pub(crate) fn generate(params: FuncInputParams, orig_func: syn::ItemFn) -> Result { - let mut norm_func = orig_func.clone(); +pub(crate) fn generate(params: FnInputParams, orig_fn: syn::ItemFn) -> Result { + let mut norm_fn = orig_fn.clone(); - crate::normalization::NormalizeLifetimes.visit_item_fn_mut(&mut norm_func); - crate::normalization::NormalizeImplTraits.visit_item_fn_mut(&mut norm_func); + crate::normalization::NormalizeLifetimes.visit_item_fn_mut(&mut norm_fn); + crate::normalization::NormalizeImplTraits.visit_item_fn_mut(&mut norm_fn); - let ctx = FuncInputCtx { - orig_func, - norm_func, + let ctx = FnInputCtx { + orig_fn, + norm_fn, impl_ctx: None, params, }; - let adapted_func = ctx.adapted_func()?; + let adapted_fn = ctx.adapted_fn()?; let MacroOutput { - start_func, + start_fn, other_items, } = ctx.into_builder_gen_ctx()?.output()?; Ok(quote! { - #start_func + #start_fn #other_items // Keep original function at the end. It seems like rust-analyzer // does better job of highlighting syntax when it is here. Assuming // this is because rust-analyzer prefers the last occurrence of the // span when highlighting. - #adapted_func + #adapted_fn }) } diff --git a/bon-macros/src/builder/item_impl.rs b/bon-macros/src/builder/item_impl.rs index 1deec50e..e0a80126 100644 --- a/bon-macros/src/builder/item_impl.rs +++ b/bon-macros/src/builder/item_impl.rs @@ -1,4 +1,4 @@ -use super::builder_gen::input_func::{FuncInputCtx, FuncInputParams, ImplCtx}; +use super::builder_gen::input_fn::{FnInputCtx, FnInputParams, ImplCtx}; use crate::util::prelude::*; use darling::ast::NestedMeta; use darling::FromMeta; @@ -11,7 +11,7 @@ pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result, Vec<_>) = + 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, @@ -24,7 +24,7 @@ pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result Result Result norm_func, + let norm_fn = match norm_item { + syn::ImplItem::Fn(norm_fn) => norm_fn, _ => unreachable!(), }; - let orig_func = match orig_item { - syn::ImplItem::Fn(orig_func) => orig_func, + let orig_fn = match orig_item { + syn::ImplItem::Fn(orig_fn) => orig_fn, _ => unreachable!(), }; - let norm_func = impl_item_fn_into_fn_item(norm_func)?; - let orig_func = impl_item_fn_into_fn_item(orig_func)?; + let norm_fn = impl_item_fn_into_fn_item(norm_fn)?; + let orig_fn = impl_item_fn_into_fn_item(orig_fn)?; - let meta = orig_func + let meta = orig_fn .attrs .iter() .filter(|attr| attr.path().is_ident("builder")) @@ -102,24 +102,24 @@ pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result>(); - let params = FuncInputParams::from_list(&meta)?; + let params = FnInputParams::from_list(&meta)?; - let ctx = FuncInputCtx { - orig_func, - norm_func, + let ctx = FnInputCtx { + orig_fn, + norm_fn, impl_ctx: Some(impl_ctx.clone()), params, }; - Result::<_>::Ok((ctx.adapted_func()?, ctx.into_builder_gen_ctx()?.output()?)) + Result::<_>::Ok((ctx.adapted_fn()?, ctx.into_builder_gen_ctx()?.output()?)) }) .collect::>>()?; - let new_impl_items = outputs.iter().flat_map(|(adapted_func, output)| { - let start_func = &output.start_func; + let new_impl_items = outputs.iter().flat_map(|(adapted_fn, output)| { + let start_fn = &output.start_fn; [ - syn::parse_quote!(#start_func), - syn::parse_quote!(#adapted_func), + syn::parse_quote!(#start_fn), + syn::parse_quote!(#adapted_fn), ] }); diff --git a/bon-macros/src/builder/item_struct.rs b/bon-macros/src/builder/item_struct.rs index 3e252b71..4b42e310 100644 --- a/bon-macros/src/builder/item_struct.rs +++ b/bon-macros/src/builder/item_struct.rs @@ -8,11 +8,11 @@ pub(crate) fn generate(orig_struct: syn::ItemStruct) -> Result { let ctx = StructInputCtx::new(orig_struct)?; let MacroOutput { - mut start_func, + mut start_fn, other_items, } = ctx.into_builder_gen_ctx()?.output()?; - let impl_generics = std::mem::take(&mut start_func.sig.generics); + let impl_generics = std::mem::take(&mut start_fn.sig.generics); let (generics_decl, generic_args, where_clause) = impl_generics.split_for_impl(); @@ -21,7 +21,7 @@ pub(crate) fn generate(orig_struct: syn::ItemStruct) -> Result { impl #generics_decl #struct_ident #generic_args #where_clause { - #start_func + #start_fn } #other_items diff --git a/bon-macros/src/builder/mod.rs b/bon-macros/src/builder/mod.rs index 88bf3710..e0c9cea5 100644 --- a/bon-macros/src/builder/mod.rs +++ b/bon-macros/src/builder/mod.rs @@ -2,7 +2,7 @@ mod builder_gen; pub(crate) mod item_impl; -mod item_func; +mod item_fn; mod item_struct; use crate::normalization::{ExpandCfg, ExpansionOutput}; @@ -67,7 +67,7 @@ fn try_generate_from_attr(params: TokenStream2, item: TokenStream2) -> Result item_func::generate(FromMeta::from_list(nested_meta)?, item_fn)?, + syn::Item::Fn(item_fn) => item_fn::generate(FromMeta::from_list(nested_meta)?, item_fn)?, _ => bail!( &Span::call_site(), "only `fn` items are supported by the `#[bon::builder]` attribute" diff --git a/bon-macros/src/util/generic_param.rs b/bon-macros/src/util/generic_param.rs new file mode 100644 index 00000000..198533e0 --- /dev/null +++ b/bon-macros/src/util/generic_param.rs @@ -0,0 +1,19 @@ +pub(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)) + } + } + } +} diff --git a/bon-macros/src/util/mod.rs b/bon-macros/src/util/mod.rs index b5d5b25d..3b862aad 100644 --- a/bon-macros/src/util/mod.rs +++ b/bon-macros/src/util/mod.rs @@ -1,5 +1,6 @@ mod attrs; mod fn_arg; +mod generic_param; mod ident; mod item; mod iterator; @@ -29,6 +30,7 @@ pub(crate) mod prelude { pub(crate) use super::attrs::AttributeExt; 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}; diff --git a/bon/src/lib.rs b/bon/src/lib.rs index 2276a51c..691e7e14 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -8,8 +8,8 @@ #![allow(deprecated)] #[doc(hidden)] -#[deprecated = "the items from this module are an implementation detail; they should \ - not be used directly; if you found yourself needing it, then you are probably \ +#[deprecated = "the items from the `bon::private` module are an implementation detail; \ + they should not be used directly; if you found a need for this, 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://discord.gg/QcBYSamw4c)"] diff --git a/bon/src/private/member_cell.rs b/bon/src/private/member_cell.rs index 4e5ceb2c..a4eb3565 100644 --- a/bon/src/private/member_cell.rs +++ b/bon/src/private/member_cell.rs @@ -22,11 +22,6 @@ use core::mem::MaybeUninit; /// [`IsSet`] and [`IsUnset`] trait implementations can possible overlap even though /// they are sealed. #[doc(hidden)] -#[deprecated = "this type is an implementation detail and should not be used directly; \ - 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://discord.gg/QcBYSamw4c)"] #[must_use] pub struct MemberCell { /// The [`PhantomData`] uses an `fn()` pointer to signify that this type diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index f5b56ce5..54ade867 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -43,7 +43,7 @@ fn lifetime_elision() { #[cfg(feature = "std")] #[tokio::test] -async fn async_func() { +async fn async_fn() { #[builder] async fn sut(arg: u32) -> u32 { std::future::ready(arg).await @@ -55,7 +55,7 @@ async fn async_func() { #[cfg(feature = "std")] #[tokio::test] -async fn async_func_with_future_arg() { +async fn async_fn_with_future_arg() { #[builder] async fn sut(fut: Fut) -> Fut::Output { fut.await @@ -73,7 +73,7 @@ async fn async_func_with_future_arg() { #[test] #[allow(unsafe_code)] -fn unsafe_func() { +fn unsafe_fn() { #[builder] unsafe fn sut(arg: bool) { let _ = arg; diff --git a/bon/tests/integration/builder/name_conflicts.rs b/bon/tests/integration/builder/name_conflicts.rs index 5d37d183..46234bdd 100644 --- a/bon/tests/integration/builder/name_conflicts.rs +++ b/bon/tests/integration/builder/name_conflicts.rs @@ -46,12 +46,13 @@ mod member_names_state { let _ = (state, member_state, unset, all_unset); } - sut().state(1).member_state(2).unset(3).all_unset(4).call() + sut().state(1).member_state(2).unset(3).all_unset(4).call(); } #[test] fn test_struct() { #[derive(Builder)] + #[allow(dead_code)] struct Sut { state: u32, member_state: u32, diff --git a/bon/tests/integration/builder/positional_members.rs b/bon/tests/integration/builder/positional_members.rs index 0f554a1b..09be178b 100644 --- a/bon/tests/integration/builder/positional_members.rs +++ b/bon/tests/integration/builder/positional_members.rs @@ -57,7 +57,7 @@ mod smoke { }"#]], ); - let _ = Sut::builder(true, 'c', "str"); + let _actual = Sut::builder(true, 'c', "str"); } #[test] @@ -90,7 +90,7 @@ mod smoke { expect![[r#"(true, 'c', None, 99, "1", "2")"#]], ); - let _ = sut(true, 'c', "str"); + let _actual = sut(true, 'c', "str"); } #[test] @@ -139,7 +139,7 @@ mod smoke { expect![[r#"(true, 'c', None, 99, "1", "2")"#]], ); - let _ = Sut::sut(true, 'c', "str"); + let _actual = Sut::sut(true, 'c', "str"); assert_debug_eq( Sut.with_self(true).named(99).call("1"), diff --git a/bon/tests/integration/ui/compile_fail/builder_derives.stderr b/bon/tests/integration/ui/compile_fail/builder_derives.stderr index cd7d0b9d..a2ad686e 100644 --- a/bon/tests/integration/ui/compile_fail/builder_derives.stderr +++ b/bon/tests/integration/ui/compile_fail/builder_derives.stderr @@ -15,6 +15,22 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` 4 | struct NoTraitImpls; | +error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied + --> tests/integration/ui/compile_fail/builder_derives.rs:5:10 + | +5 | #[derive(Builder)] + | ^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls`, which is required by `(MemberCell<::NonDebug, NoTraitImpls>, MemberCell<::X, u32>): Clone` + | + = note: required for `MemberCell<::NonDebug, NoTraitImpls>` to implement `Clone` + = note: required because it appears within the type `(MemberCell<::NonDebug, NoTraitImpls>, MemberCell<<... as State>::X, ...>)` + = note: consider using `--verbose` to print the full type name to the console + = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` + | +3 + #[derive(Clone)] +4 | struct NoTraitImpls; + | + error[E0277]: `NoTraitImpls` doesn't implement `Debug` --> tests/integration/ui/compile_fail/builder_derives.rs:8:16 | @@ -26,14 +42,32 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` note: required by a bound in `assert_debug` --> src/private/mod.rs | - | pub fn assert_debug() {} - | ^^^^^^^^^^^^^^^^ required by this bound in `assert_debug` + | pub fn assert_debug() {} + | ^^^^^^^^^^ required by this bound in `assert_debug` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] 4 | struct NoTraitImpls; | +error[E0277]: `NoTraitImpls` doesn't implement `Debug` + --> tests/integration/ui/compile_fail/builder_derives.rs:5:10 + | +5 | #[derive(Builder)] + | ^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` + | + = help: the trait `Debug` is not implemented for `NoTraitImpls`, which is required by `MemberCell<::NonDebug, NoTraitImpls>: Debug` + = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` + = help: the trait `Debug` is implemented for `MemberCell` + = note: required for `MemberCell<::NonDebug, NoTraitImpls>` to implement `Debug` + = note: required for the cast from `&MemberCell<::NonDebug, NoTraitImpls>` to `&dyn Debug` + = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` + | +3 + #[derive(Debug)] +4 | struct NoTraitImpls; + | + error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied --> tests/integration/ui/compile_fail/builder_derives.rs:13:38 | @@ -51,6 +85,22 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` 4 | struct NoTraitImpls; | +error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied + --> tests/integration/ui/compile_fail/builder_derives.rs:12:1 + | +12 | #[builder(derive(Clone, Debug))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls`, which is required by `(MemberCell<::NonDebug, NoTraitImpls>, MemberCell<::X, u32>): Clone` + | + = note: required for `MemberCell<::NonDebug, NoTraitImpls>` to implement `Clone` + = note: required because it appears within the type `(MemberCell<::NonDebug, NoTraitImpls>, MemberCell<<... as State>::X, ...>)` + = note: consider using `--verbose` to print the full type name to the console + = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` + | +3 + #[derive(Clone)] +4 | struct NoTraitImpls; + | + error[E0277]: `NoTraitImpls` doesn't implement `Debug` --> tests/integration/ui/compile_fail/builder_derives.rs:13:38 | @@ -62,8 +112,26 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` note: required by a bound in `assert_debug` --> src/private/mod.rs | - | pub fn assert_debug() {} - | ^^^^^^^^^^^^^^^^ required by this bound in `assert_debug` + | pub fn assert_debug() {} + | ^^^^^^^^^^ required by this bound in `assert_debug` +help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` + | +3 + #[derive(Debug)] +4 | struct NoTraitImpls; + | + +error[E0277]: `NoTraitImpls` doesn't implement `Debug` + --> tests/integration/ui/compile_fail/builder_derives.rs:12:1 + | +12 | #[builder(derive(Clone, Debug))] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` + | + = help: the trait `Debug` is not implemented for `NoTraitImpls`, which is required by `MemberCell<::NonDebug, NoTraitImpls>: Debug` + = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` + = help: the trait `Debug` is implemented for `MemberCell` + = note: required for `MemberCell<::NonDebug, NoTraitImpls>` to implement `Debug` + = note: required for the cast from `&MemberCell<::NonDebug, NoTraitImpls>` to `&dyn Debug` + = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] @@ -87,6 +155,22 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` 4 | struct NoTraitImpls; | +error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied + --> tests/integration/ui/compile_fail/builder_derives.rs:15:1 + | +15 | #[bon] + | ^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls`, which is required by `(MemberCell<::NonDebug, NoTraitImpls>, MemberCell<::X, u32>): Clone` + | + = note: required for `MemberCell<::NonDebug, NoTraitImpls>` to implement `Clone` + = note: required because it appears within the type `(MemberCell<::NonDebug, NoTraitImpls>, MemberCell<<... as State>::X, ...>)` + = note: consider using `--verbose` to print the full type name to the console + = note: this error originates in the attribute macro `bon` (in Nightly builds, run with -Z macro-backtrace for more info) +help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` + | +3 + #[derive(Clone)] +4 | struct NoTraitImpls; + | + error[E0277]: `NoTraitImpls` doesn't implement `Debug` --> tests/integration/ui/compile_fail/builder_derives.rs:18:46 | @@ -98,8 +182,26 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` note: required by a bound in `assert_debug` --> src/private/mod.rs | - | pub fn assert_debug() {} - | ^^^^^^^^^^^^^^^^ required by this bound in `assert_debug` + | pub fn assert_debug() {} + | ^^^^^^^^^^ required by this bound in `assert_debug` +help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` + | +3 + #[derive(Debug)] +4 | struct NoTraitImpls; + | + +error[E0277]: `NoTraitImpls` doesn't implement `Debug` + --> tests/integration/ui/compile_fail/builder_derives.rs:15:1 + | +15 | #[bon] + | ^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` + | + = help: the trait `Debug` is not implemented for `NoTraitImpls`, which is required by `MemberCell<::NonDebug, NoTraitImpls>: Debug` + = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` + = help: the trait `Debug` is implemented for `MemberCell` + = note: required for `MemberCell<::NonDebug, NoTraitImpls>` to implement `Debug` + = note: required for the cast from `&MemberCell<::NonDebug, NoTraitImpls>` to `&dyn Debug` + = note: this error originates in the attribute macro `bon` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr index ecb9a4cc..9a25d16a 100644 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ b/bon/tests/integration/ui/compile_fail/errors.stderr @@ -193,72 +193,67 @@ error[E0599]: no method named `y` found for struct `SkipGeneratesNoSetterBuilder 22 | SkipGeneratesNoSetter::builder().y(42).build(); | ^ method not found in `SkipGeneratesNoSetterBuilder` -error[E0277]: can't finish building yet; the member `ExampleBuilder__y` was not set +error[E0277]: The member Unset was not set! --> tests/integration/ui/compile_fail/errors.rs:34:37 | 34 | let _ = Example::builder().x(1).build(); - | ^^^^^ the member `ExampleBuilder__y` was not set + | ^^^^^ the trait `IsSet` is not implemented for `Unset` | - = help: the trait `IntoSet` is not implemented for `Unset` - = help: the trait `IntoSet, ExampleBuilder__y>` is implemented for `Unset` -note: required by a bound in `ExampleBuilder::<(__X, __Y, __Z)>::build` + = help: the trait `IsSet` is implemented for `Set` +note: required by a bound in `ExampleBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:24:14 | 24 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `ExampleBuilder::<(__X, __Y, __Z)>::build` + | ^^^^^^^ required by this bound in `ExampleBuilder::::build` 25 | struct Example { | ------- required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: can't finish building yet; the member `ExampleBuilder__renamed` was not set +error[E0277]: The member Unset was not set! --> tests/integration/ui/compile_fail/errors.rs:34:37 | 34 | let _ = Example::builder().x(1).build(); - | ^^^^^ the member `ExampleBuilder__renamed` was not set + | ^^^^^ the trait `IsSet` is not implemented for `Unset` | - = help: the trait `IntoSet` is not implemented for `Unset` - = help: the trait `IntoSet, ExampleBuilder__renamed>` is implemented for `Unset` -note: required by a bound in `ExampleBuilder::<(__X, __Y, __Z)>::build` + = help: the trait `IsSet` is implemented for `Set` +note: required by a bound in `ExampleBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:24:14 | 24 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `ExampleBuilder::<(__X, __Y, __Z)>::build` + | ^^^^^^^ required by this bound in `ExampleBuilder::::build` 25 | struct Example { | ------- required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: can't set the same member twice +error[E0277]: The member Set was already set! --> tests/integration/ui/compile_fail/errors.rs:37:37 | 37 | let _ = Example::builder().y(1).y(2); - | ^ this member was already set + | ^ the trait `IsUnset` is not implemented for `Set` | - = help: the trait `IsUnset` is not implemented for `Set` = help: the trait `IsUnset` is implemented for `Unset` -note: required by a bound in `ExampleBuilder::<(__X, __Y, __Z)>::y` +note: required by a bound in `ExampleBuilder::::y` --> tests/integration/ui/compile_fail/errors.rs:24:14 | 24 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `ExampleBuilder::<(__X, __Y, __Z)>::y` + | ^^^^^^^ required by this bound in `ExampleBuilder::::y` ... 27 | y: u32, | - required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: can't finish building yet; the member `SutBuilder__arg1` was not set +error[E0277]: The member Unset was not set! --> tests/integration/ui/compile_fail/errors.rs:47:32 | 47 | let _ = Sut::builder().build(); - | ^^^^^ the member `SutBuilder__arg1` was not set + | ^^^^^ the trait `IsSet` is not implemented for `Unset` | - = help: the trait `IntoSet, SutBuilder__arg1>` is not implemented for `Unset` - = help: the trait `IntoSet, SutBuilder__arg1>` is implemented for `Unset` - = help: for that trait implementation, expected `Optional`, found `Required` -note: required by a bound in `SutBuilder::<(__Arg1,)>::build` + = help: the trait `IsSet` is implemented for `Set` +note: required by a bound in `SutBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:42:18 | 42 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `SutBuilder::<(__Arg1,)>::build` + | ^^^^^^^ required by this bound in `SutBuilder::::build` 43 | struct Sut { | --- required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/bon/tests/integration/ui/compile_fail/warnings.stderr b/bon/tests/integration/ui/compile_fail/warnings.stderr index e675e446..83dcc440 100644 --- a/bon/tests/integration/ui/compile_fail/warnings.stderr +++ b/bon/tests/integration/ui/compile_fail/warnings.stderr @@ -52,7 +52,7 @@ help: use `let _ = ...` to ignore the resulting value 33 | let _ = Example::builder().x(1); | +++++++ -error: unused return value of `ExampleBuilder::<(__X, __Y)>::build` that must be used +error: unused return value of `ExampleBuilder::::build` that must be used --> tests/integration/ui/compile_fail/warnings.rs:34:9 | 34 | Example::builder().x(1).y(2).build(); @@ -64,7 +64,7 @@ help: use `let _ = ...` to ignore the resulting value 34 | let _ = Example::builder().x(1).y(2).build(); | +++++++ -error: unused return value of `ExampleMustUseBuilder::call` that must be used +error: unused return value of `ExampleMustUseBuilder::::call` that must be used --> tests/integration/ui/compile_fail/warnings.rs:36:9 | 36 | Example::must_use().call(); @@ -75,7 +75,7 @@ help: use `let _ = ...` to ignore the resulting value 36 | let _ = Example::must_use().call(); | +++++++ -error: unused return value of `MustUseBuilder::call` that must be used +error: unused return value of `MustUseBuilder::::call` that must be used --> tests/integration/ui/compile_fail/warnings.rs:38:9 | 38 | must_use().call(); @@ -97,7 +97,7 @@ help: use `let _ = ...` to ignore the resulting value 39 | let _ = __orig_must_use(); | +++++++ -error: unused return value of `MustUseUnderCfgBuilder::call` that must be used +error: unused return value of `MustUseUnderCfgBuilder::::call` that must be used --> tests/integration/ui/compile_fail/warnings.rs:47:9 | 47 | must_use_under_cfg().call(); From 8e12077372b9729f10c455f0522526b037aca331 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 21 Sep 2024 21:02:13 +0000 Subject: [PATCH 007/119] Better error messages --- .../builder/builder_gen/builder_derives.rs | 54 ++- bon/src/lib.rs | 16 +- bon/src/private/derives.rs | 43 +++ bon/src/private/member_cell.rs | 28 +- bon/src/private/mod.rs | 8 +- .../ui/compile_fail/builder_derives.rs | 32 +- .../ui/compile_fail/builder_derives.stderr | 314 ++++++++++++------ .../integration/ui/compile_fail/errors.stderr | 20 +- 8 files changed, 375 insertions(+), 140 deletions(-) create mode 100644 bon/src/private/derives.rs diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index 03c65320..4d89e3c6 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -82,7 +82,27 @@ impl BuilderGenCtx { let where_clause = self.where_clause_for_derive(&clone); let builder_mod_ident = &self.builder_mod.ident; - let builder_component_types = self.builder_component_types(); + + let clone_named_members = self.named_members().map(|member| { + let member_index = &member.index; + + // The type hints here are necessary to get better error messages + // that point directly to the types that don't implement `Clone` + // in the input code using the span info from the type hints. + let clone_fn = member + .as_optional_norm_ty() + .map(|ty| quote!(clone_optional_member::<#ty>)) + .unwrap_or_else(|| { + let ty = &member.norm_ty; + quote!(clone_required_member::<_, #ty>) + }); + + quote! { + ::bon::private::derives::#clone_fn( + &self.__private_named_members.#member_index + ) + } + }); quote! { #[automatically_derived] @@ -97,14 +117,21 @@ impl BuilderGenCtx { #where_clause { fn clone(&self) -> Self { - // These assertions improve error messages by pointing directly to the - // types that don't implement the target trait in the user's code. - #(::bon::private::assert_clone::<#builder_component_types>();)* Self { __private_phantom: ::core::marker::PhantomData, #clone_receiver #clone_start_fn_args - __private_named_members: #clone::clone(&self.__private_named_members), + + // 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 `(...huge tuple type...)` to implement `Clone` + // ``` + __private_named_members: ( + #( #clone_named_members, )* + ), } } } @@ -118,12 +145,22 @@ impl BuilderGenCtx { let member_index = &member.index; let member_ident_str = member.public_ident().to_string(); let member_pascal = &member.norm_ident_pascal; + + let debug_fn = member.as_optional_norm_ty() + .map(|ty| quote!(debug_optional_member::<#ty>)) + .unwrap_or_else(|| { + let ty = &member.norm_ty; + quote!(debug_required_member::<_, #ty>) + }); + Some(quote! { // Skip members that are not set to reduce noise if ::is_set() { output.field( #member_ident_str, - &self.__private_named_members.#member_index + ::bon::private::derives::#debug_fn( + &self.__private_named_members.#member_index + ) ); } }) @@ -159,7 +196,6 @@ impl BuilderGenCtx { let generic_args = &self.generics.args; let builder_ident = &self.builder_type.ident; let builder_ident_str = builder_ident.to_string(); - let builder_component_types = self.builder_component_types(); quote! { #[automatically_derived] @@ -174,10 +210,6 @@ impl BuilderGenCtx { #where_clause { fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - // These assertions improve error messages by pointing directly to the - // types that don't implement the target trait in the user's code. - #(::bon::private::assert_debug::<#builder_component_types>();)* - let mut output = f.debug_struct(#builder_ident_str); #format_receiver diff --git a/bon/src/lib.rs b/bon/src/lib.rs index 691e7e14..b363a409 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -58,10 +58,22 @@ use private::sealed::Sealed; /// } /// } /// ``` -#[diagnostic::on_unimplemented(message = "The member {Self} was already set!")] +#[rustversion::attr( + since(1.78.0), + diagnostic::on_unimplemented( + message = "the following member was already set: `{Self}`, but this method requires it to be unset", + label = "the following member was already set: `{Self}`, but this method requires it to be unset", + ) +)] pub trait IsUnset: Sealed {} /// Marker trait that indicates that the member is set, i.e. at least one of its setters was called. // TODO: add examples (they would require having custom renames and visibility overrides for default setters) -#[diagnostic::on_unimplemented(message = "The member {Self} was not set!")] +#[rustversion::attr( + since(1.78.0), + diagnostic::on_unimplemented( + message = "the following member was not set: `{Self}`, but this method requires it to be set", + label = "the following member was not set: `{Self}`, but this method requires it to be set", + ) +)] pub trait IsSet: Sealed {} diff --git a/bon/src/private/derives.rs b/bon/src/private/derives.rs new file mode 100644 index 00000000..ed92cdaf --- /dev/null +++ b/bon/src/private/derives.rs @@ -0,0 +1,43 @@ +//! Utility functions for improving error messages in builder's derive implementations. +//! +//! These free functions are simple wrappers over the respective traits. They allow the +//! generated code to pass the concrete type of the member using the turbofish syntax, +//! which improves the compile errors when the member's type `T` doesn't implement +//! the target trait. +//! +//! E.g. they remove the messages that reference to the internal [`MemberCell`] +//! type completely like: +//! +//! ```not-rust +//! required for `MemberCell<...>` to implement `Clone` +//! ``` +//! +//! They also improve the spans of error messages because compiler knows that it needs to +//! point to the origin of the offending type (member's type T) from the turbofish +//! syntax to where the type came from (original code written by the user). +use super::{MemberCell, MemberState}; +use core::fmt::Debug; + +#[inline(always)] +pub fn clone_optional_member(member: &Option) -> Option { + member.clone() +} + +#[inline(always)] +pub fn clone_required_member( + member: &MemberCell, +) -> MemberCell { + member.clone() +} + +#[inline(always)] +pub fn debug_optional_member(member: &Option) -> &dyn Debug { + member +} + +#[inline(always)] +pub fn debug_required_member( + member: &MemberCell, +) -> &dyn Debug { + member +} diff --git a/bon/src/private/member_cell.rs b/bon/src/private/member_cell.rs index a4eb3565..cb226493 100644 --- a/bon/src/private/member_cell.rs +++ b/bon/src/private/member_cell.rs @@ -119,20 +119,6 @@ impl MemberCell { } } -impl fmt::Debug for MemberCell -where - State: MemberState, - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(value) = self.try_get() { - fmt::Debug::fmt(value, f) - } else { - f.write_str("Unset") - } - } -} - impl Clone for MemberCell where State: MemberState, @@ -156,3 +142,17 @@ where } } } + +impl fmt::Debug for MemberCell +where + State: MemberState, + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(value) = self.try_get() { + fmt::Debug::fmt(value, f) + } else { + f.write_str("Unset") + } + } +} diff --git a/bon/src/private/mod.rs b/bon/src/private/mod.rs index 782f821b..53786605 100644 --- a/bon/src/private/mod.rs +++ b/bon/src/private/mod.rs @@ -19,6 +19,8 @@ pub mod deprecations; /// Used for providing better IDE hints (completions and syntax highlighting). pub mod ide; +pub mod derives; + mod cfg_eval; mod member_cell; @@ -31,7 +33,7 @@ pub(crate) mod sealed { impl Sealed for super::Set {} } -pub use member_cell::MemberCell; +pub use member_cell::*; use core::fmt; use sealed::Sealed; @@ -52,7 +54,9 @@ pub fn assert_debug() {} #[derive(Debug)] pub struct Unset(T); -impl crate::IsUnset for Unset {} +impl crate::IsUnset for Unset { + // type MemberName =; +} #[doc(hidden)] #[deprecated = "this type is an implementation detail and should not be used directly; \ diff --git a/bon/tests/integration/ui/compile_fail/builder_derives.rs b/bon/tests/integration/ui/compile_fail/builder_derives.rs index 76de8155..4ef6127a 100644 --- a/bon/tests/integration/ui/compile_fail/builder_derives.rs +++ b/bon/tests/integration/ui/compile_fail/builder_derives.rs @@ -5,17 +5,43 @@ struct NoTraitImpls; #[derive(Builder)] #[builder(derive(Clone, Debug))] struct StructContainsNonTrait { - non_debug: NoTraitImpls, + no_impls: NoTraitImpls, + + no_impl_optional: Option, + + #[builder(default = NoTraitImpls)] + no_impl_optional_2: NoTraitImpls, + x: u32, } #[builder(derive(Clone, Debug))] -fn fn_contains_non_trait(_non_debug: NoTraitImpls, _x: u32) {} +fn fn_contains_non_trait( + _no_impls: NoTraitImpls, + + _no_impl_optional: Option, + + #[builder(default = NoTraitImpls)] // + _no_impl_optional_2: NoTraitImpls, + + _x: u32, +) { +} #[bon] impl StructContainsNonTrait { #[builder(derive(Clone, Debug))] - fn method_contains_non_trait(_non_debug: NoTraitImpls, _x: u32) {} + fn method_contains_non_trait( + _no_impls: NoTraitImpls, + + _no_impl_optional: Option, + + #[builder(default = NoTraitImpls)] // + _no_impl_optional_2: NoTraitImpls, + + _x: u32, + ) { + } } fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/builder_derives.stderr b/bon/tests/integration/ui/compile_fail/builder_derives.stderr index a2ad686e..e044023f 100644 --- a/bon/tests/integration/ui/compile_fail/builder_derives.stderr +++ b/bon/tests/integration/ui/compile_fail/builder_derives.stderr @@ -1,14 +1,14 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:8:16 + --> tests/integration/ui/compile_fail/builder_derives.rs:8:15 | -8 | non_debug: NoTraitImpls, - | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` +8 | no_impls: NoTraitImpls, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | -note: required by a bound in `assert_clone` - --> src/private/mod.rs +note: required by a bound in `clone_required_member` + --> src/private/derives.rs | - | pub fn assert_clone() {} - | ^^^^^ required by this bound in `assert_clone` + | pub fn clone_required_member( + | ^^^^^ required by this bound in `clone_required_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | 3 + #[derive(Clone)] @@ -16,34 +16,52 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:5:10 - | -5 | #[derive(Builder)] - | ^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls`, which is required by `(MemberCell<::NonDebug, NoTraitImpls>, MemberCell<::X, u32>): Clone` - | - = note: required for `MemberCell<::NonDebug, NoTraitImpls>` to implement `Clone` - = note: required because it appears within the type `(MemberCell<::NonDebug, NoTraitImpls>, MemberCell<<... as State>::X, ...>)` - = note: consider using `--verbose` to print the full type name to the console - = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) + --> tests/integration/ui/compile_fail/builder_derives.rs:10:30 + | +10 | no_impl_optional: Option, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` + | +note: required by a bound in `clone_optional_member` + --> src/private/derives.rs + | + | pub fn clone_optional_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_optional_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` - | -3 + #[derive(Clone)] -4 | struct NoTraitImpls; - | + | +3 + #[derive(Clone)] +4 | struct NoTraitImpls; + | + +error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied + --> tests/integration/ui/compile_fail/builder_derives.rs:13:25 + | +13 | no_impl_optional_2: NoTraitImpls, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` + | +note: required by a bound in `clone_optional_member` + --> src/private/derives.rs + | + | pub fn clone_optional_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_optional_member` +help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` + | +3 + #[derive(Clone)] +4 | struct NoTraitImpls; + | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:8:16 + --> tests/integration/ui/compile_fail/builder_derives.rs:8:15 | -8 | non_debug: NoTraitImpls, - | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` +8 | no_impls: NoTraitImpls, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` -note: required by a bound in `assert_debug` - --> src/private/mod.rs +note: required by a bound in `debug_required_member` + --> src/private/derives.rs | - | pub fn assert_debug() {} - | ^^^^^^^^^^ required by this bound in `assert_debug` + | pub fn debug_required_member( + | ^^^^^ required by this bound in `debug_required_member` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] @@ -51,34 +69,54 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:5:10 - | -5 | #[derive(Builder)] - | ^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` - | - = help: the trait `Debug` is not implemented for `NoTraitImpls`, which is required by `MemberCell<::NonDebug, NoTraitImpls>: Debug` - = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` - = help: the trait `Debug` is implemented for `MemberCell` - = note: required for `MemberCell<::NonDebug, NoTraitImpls>` to implement `Debug` - = note: required for the cast from `&MemberCell<::NonDebug, NoTraitImpls>` to `&dyn Debug` - = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) + --> tests/integration/ui/compile_fail/builder_derives.rs:10:30 + | +10 | no_impl_optional: Option, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` + | + = help: the trait `Debug` is not implemented for `NoTraitImpls` + = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` +note: required by a bound in `debug_optional_member` + --> src/private/derives.rs + | + | pub fn debug_optional_member(member: &Option) -> &dyn Debug { + | ^^^^^ required by this bound in `debug_optional_member` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` - | -3 + #[derive(Debug)] -4 | struct NoTraitImpls; - | + | +3 + #[derive(Debug)] +4 | struct NoTraitImpls; + | + +error[E0277]: `NoTraitImpls` doesn't implement `Debug` + --> tests/integration/ui/compile_fail/builder_derives.rs:13:25 + | +13 | no_impl_optional_2: NoTraitImpls, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` + | + = help: the trait `Debug` is not implemented for `NoTraitImpls` + = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` +note: required by a bound in `debug_optional_member` + --> src/private/derives.rs + | + | pub fn debug_optional_member(member: &Option) -> &dyn Debug { + | ^^^^^ required by this bound in `debug_optional_member` +help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` + | +3 + #[derive(Debug)] +4 | struct NoTraitImpls; + | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:13:38 + --> tests/integration/ui/compile_fail/builder_derives.rs:20:16 | -13 | fn fn_contains_non_trait(_non_debug: NoTraitImpls, _x: u32) {} - | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` +20 | _no_impls: NoTraitImpls, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | -note: required by a bound in `assert_clone` - --> src/private/mod.rs +note: required by a bound in `clone_required_member` + --> src/private/derives.rs | - | pub fn assert_clone() {} - | ^^^^^ required by this bound in `assert_clone` + | pub fn clone_required_member( + | ^^^^^ required by this bound in `clone_required_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | 3 + #[derive(Clone)] @@ -86,34 +124,71 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:12:1 + --> tests/integration/ui/compile_fail/builder_derives.rs:22:31 + | +22 | _no_impl_optional: Option, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | -12 | #[builder(derive(Clone, Debug))] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls`, which is required by `(MemberCell<::NonDebug, NoTraitImpls>, MemberCell<::X, u32>): Clone` +note: required by a bound in `clone_optional_member` + --> src/private/derives.rs | - = note: required for `MemberCell<::NonDebug, NoTraitImpls>` to implement `Clone` - = note: required because it appears within the type `(MemberCell<::NonDebug, NoTraitImpls>, MemberCell<<... as State>::X, ...>)` - = note: consider using `--verbose` to print the full type name to the console - = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info) + | pub fn clone_optional_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_optional_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | 3 + #[derive(Clone)] 4 | struct NoTraitImpls; | +error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied + --> tests/integration/ui/compile_fail/builder_derives.rs:25:26 + | +25 | _no_impl_optional_2: NoTraitImpls, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` + | +note: required by a bound in `clone_optional_member` + --> src/private/derives.rs + | + | pub fn clone_optional_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_optional_member` +help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` + | +3 + #[derive(Clone)] +4 | struct NoTraitImpls; + | + +error[E0277]: `NoTraitImpls` doesn't implement `Debug` + --> tests/integration/ui/compile_fail/builder_derives.rs:20:16 + | +20 | _no_impls: NoTraitImpls, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` + | + = help: the trait `Debug` is not implemented for `NoTraitImpls` + = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` +note: required by a bound in `debug_required_member` + --> src/private/derives.rs + | + | pub fn debug_required_member( + | ^^^^^ required by this bound in `debug_required_member` +help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` + | +3 + #[derive(Debug)] +4 | struct NoTraitImpls; + | + error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:13:38 + --> tests/integration/ui/compile_fail/builder_derives.rs:22:31 | -13 | fn fn_contains_non_trait(_non_debug: NoTraitImpls, _x: u32) {} - | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` +22 | _no_impl_optional: Option, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` -note: required by a bound in `assert_debug` - --> src/private/mod.rs +note: required by a bound in `debug_optional_member` + --> src/private/derives.rs | - | pub fn assert_debug() {} - | ^^^^^^^^^^ required by this bound in `assert_debug` + | pub fn debug_optional_member(member: &Option) -> &dyn Debug { + | ^^^^^ required by this bound in `debug_optional_member` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] @@ -121,17 +196,18 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:12:1 + --> tests/integration/ui/compile_fail/builder_derives.rs:25:26 | -12 | #[builder(derive(Clone, Debug))] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` +25 | _no_impl_optional_2: NoTraitImpls, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | - = help: the trait `Debug` is not implemented for `NoTraitImpls`, which is required by `MemberCell<::NonDebug, NoTraitImpls>: Debug` + = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` - = help: the trait `Debug` is implemented for `MemberCell` - = note: required for `MemberCell<::NonDebug, NoTraitImpls>` to implement `Debug` - = note: required for the cast from `&MemberCell<::NonDebug, NoTraitImpls>` to `&dyn Debug` - = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info) +note: required by a bound in `debug_optional_member` + --> src/private/derives.rs + | + | pub fn debug_optional_member(member: &Option) -> &dyn Debug { + | ^^^^^ required by this bound in `debug_optional_member` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] @@ -139,16 +215,33 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:18:46 + --> tests/integration/ui/compile_fail/builder_derives.rs:35:20 + | +35 | _no_impls: NoTraitImpls, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` + | +note: required by a bound in `clone_required_member` + --> src/private/derives.rs + | + | pub fn clone_required_member( + | ^^^^^ required by this bound in `clone_required_member` +help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` + | +3 + #[derive(Clone)] +4 | struct NoTraitImpls; + | + +error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied + --> tests/integration/ui/compile_fail/builder_derives.rs:37:35 | -18 | fn method_contains_non_trait(_non_debug: NoTraitImpls, _x: u32) {} - | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` +37 | _no_impl_optional: Option, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | -note: required by a bound in `assert_clone` - --> src/private/mod.rs +note: required by a bound in `clone_optional_member` + --> src/private/derives.rs | - | pub fn assert_clone() {} - | ^^^^^ required by this bound in `assert_clone` + | pub fn clone_optional_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_optional_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | 3 + #[derive(Clone)] @@ -156,15 +249,16 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:15:1 + --> tests/integration/ui/compile_fail/builder_derives.rs:40:30 | -15 | #[bon] - | ^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls`, which is required by `(MemberCell<::NonDebug, NoTraitImpls>, MemberCell<::X, u32>): Clone` +40 | _no_impl_optional_2: NoTraitImpls, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | - = note: required for `MemberCell<::NonDebug, NoTraitImpls>` to implement `Clone` - = note: required because it appears within the type `(MemberCell<::NonDebug, NoTraitImpls>, MemberCell<<... as State>::X, ...>)` - = note: consider using `--verbose` to print the full type name to the console - = note: this error originates in the attribute macro `bon` (in Nightly builds, run with -Z macro-backtrace for more info) +note: required by a bound in `clone_optional_member` + --> src/private/derives.rs + | + | pub fn clone_optional_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_optional_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | 3 + #[derive(Clone)] @@ -172,18 +266,37 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:18:46 + --> tests/integration/ui/compile_fail/builder_derives.rs:35:20 + | +35 | _no_impls: NoTraitImpls, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` + | + = help: the trait `Debug` is not implemented for `NoTraitImpls` + = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` +note: required by a bound in `debug_required_member` + --> src/private/derives.rs + | + | pub fn debug_required_member( + | ^^^^^ required by this bound in `debug_required_member` +help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` + | +3 + #[derive(Debug)] +4 | struct NoTraitImpls; + | + +error[E0277]: `NoTraitImpls` doesn't implement `Debug` + --> tests/integration/ui/compile_fail/builder_derives.rs:37:35 | -18 | fn method_contains_non_trait(_non_debug: NoTraitImpls, _x: u32) {} - | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` +37 | _no_impl_optional: Option, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` -note: required by a bound in `assert_debug` - --> src/private/mod.rs +note: required by a bound in `debug_optional_member` + --> src/private/derives.rs | - | pub fn assert_debug() {} - | ^^^^^^^^^^ required by this bound in `assert_debug` + | pub fn debug_optional_member(member: &Option) -> &dyn Debug { + | ^^^^^ required by this bound in `debug_optional_member` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] @@ -191,17 +304,18 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:15:1 + --> tests/integration/ui/compile_fail/builder_derives.rs:40:30 | -15 | #[bon] - | ^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` +40 | _no_impl_optional_2: NoTraitImpls, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | - = help: the trait `Debug` is not implemented for `NoTraitImpls`, which is required by `MemberCell<::NonDebug, NoTraitImpls>: Debug` + = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` - = help: the trait `Debug` is implemented for `MemberCell` - = note: required for `MemberCell<::NonDebug, NoTraitImpls>` to implement `Debug` - = note: required for the cast from `&MemberCell<::NonDebug, NoTraitImpls>` to `&dyn Debug` - = note: this error originates in the attribute macro `bon` (in Nightly builds, run with -Z macro-backtrace for more info) +note: required by a bound in `debug_optional_member` + --> src/private/derives.rs + | + | pub fn debug_optional_member(member: &Option) -> &dyn Debug { + | ^^^^^ required by this bound in `debug_optional_member` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr index 9a25d16a..c8908c73 100644 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ b/bon/tests/integration/ui/compile_fail/errors.stderr @@ -193,12 +193,13 @@ error[E0599]: no method named `y` found for struct `SkipGeneratesNoSetterBuilder 22 | SkipGeneratesNoSetter::builder().y(42).build(); | ^ method not found in `SkipGeneratesNoSetterBuilder` -error[E0277]: The member Unset was not set! +error[E0277]: the following member was not set: `Unset`, but this method requires it to be set --> tests/integration/ui/compile_fail/errors.rs:34:37 | 34 | let _ = Example::builder().x(1).build(); - | ^^^^^ the trait `IsSet` is not implemented for `Unset` + | ^^^^^ the following member was not set: `Unset`, but this method requires it to be set | + = help: the trait `IsSet` is not implemented for `Unset` = help: the trait `IsSet` is implemented for `Set` note: required by a bound in `ExampleBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:24:14 @@ -209,12 +210,13 @@ note: required by a bound in `ExampleBuilder::::build` | ------- required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: The member Unset was not set! +error[E0277]: the following member was not set: `Unset`, but this method requires it to be set --> tests/integration/ui/compile_fail/errors.rs:34:37 | 34 | let _ = Example::builder().x(1).build(); - | ^^^^^ the trait `IsSet` is not implemented for `Unset` + | ^^^^^ the following member was not set: `Unset`, but this method requires it to be set | + = help: the trait `IsSet` is not implemented for `Unset` = help: the trait `IsSet` is implemented for `Set` note: required by a bound in `ExampleBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:24:14 @@ -225,12 +227,13 @@ note: required by a bound in `ExampleBuilder::::build` | ------- required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: The member Set was already set! +error[E0277]: the following member was already set: `Set`, but this method requires it to be unset --> tests/integration/ui/compile_fail/errors.rs:37:37 | 37 | let _ = Example::builder().y(1).y(2); - | ^ the trait `IsUnset` is not implemented for `Set` + | ^ the following member was already set: `Set`, but this method requires it to be unset | + = help: the trait `IsUnset` is not implemented for `Set` = help: the trait `IsUnset` is implemented for `Unset` note: required by a bound in `ExampleBuilder::::y` --> tests/integration/ui/compile_fail/errors.rs:24:14 @@ -242,12 +245,13 @@ note: required by a bound in `ExampleBuilder::::y` | - required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: The member Unset was not set! +error[E0277]: the following member was not set: `Unset`, but this method requires it to be set --> tests/integration/ui/compile_fail/errors.rs:47:32 | 47 | let _ = Sut::builder().build(); - | ^^^^^ the trait `IsSet` is not implemented for `Unset` + | ^^^^^ the following member was not set: `Unset`, but this method requires it to be set | + = help: the trait `IsSet` is not implemented for `Unset` = help: the trait `IsSet` is implemented for `Set` note: required by a bound in `SutBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:42:18 From 3d3c823eda622178c1a7acb44cc829b835584ebd Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 22 Sep 2024 12:12:39 +0000 Subject: [PATCH 008/119] Update tests and reduce the types in PhantomData --- .../builder/builder_gen/builder_derives.rs | 13 ------------ bon-macros/src/builder/builder_gen/mod.rs | 13 ++++++++++-- bon/src/lib.rs | 4 ++-- .../integration/builder/builder_derives.rs | 20 +++---------------- 4 files changed, 16 insertions(+), 34 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index 4d89e3c6..f30b5f99 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -22,19 +22,6 @@ impl BuilderGenCtx { tokens } - fn builder_component_types(&self) -> impl Iterator { - let receiver_ty = self - .receiver() - .map(|receiver| &receiver.without_self_keyword); - - let member_types = self.named_members().map(|member| &member.norm_ty); - - std::iter::empty() - .chain(receiver_ty) - .chain(member_types) - .map(Box::as_ref) - } - /// 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 diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 191d83f7..006b3f3b 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -253,7 +253,16 @@ impl BuilderGenCtx { } fn phantom_data(&self) -> TokenStream2 { - let member_types = self.members.iter().map(Member::norm_ty); + let member_types = self.members.iter().filter_map(|member| { + match member { + // The types of these members already appear in the struct in the types + // of __private_named_members and __private_start_fn_args fields. + Member::Named(_) | Member::StartFnArg(_) => None, + Member::FinishFnArg(member) => Some(member.norm_ty.as_ref()), + Member::Skipped(member) => Some(member.norm_ty.as_ref()), + } + }); + let receiver_ty = self .assoc_method_ctx .as_ref() @@ -394,7 +403,7 @@ impl BuilderGenCtx { /// with the [`bon::IsSet`] and [`bon::IsUnset`] traits. /// /// [`bon::IsSet`]: ::bon::IsSet - /// [`bon::IsUnset]: ::bon::IsUnset + /// [`bon::IsUnset`]: ::bon::IsUnset #vis_child trait State: ::core::marker::Sized { #( #[doc = #assoc_types_docs] diff --git a/bon/src/lib.rs b/bon/src/lib.rs index b363a409..347c0435 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -41,7 +41,7 @@ use private::sealed::Sealed; /// use example_builder::{SetX, SetY}; /// /// impl ExampleBuilder { -/// fn x_doubled(value: i32) -> ExampleBuilder> +/// fn x_doubled(self, value: i32) -> ExampleBuilder> /// where /// // The code won't compile without this bound /// State::X: bon::IsUnset, @@ -49,7 +49,7 @@ use private::sealed::Sealed; /// self.x(value * 2) /// } /// -/// fn y_doubled(value: i32) -> ExampleBuilder> +/// fn y_doubled(self, value: i32) -> ExampleBuilder> /// where /// // The code won't compile without this bound /// State::Y: bon::IsUnset, diff --git a/bon/tests/integration/builder/builder_derives.rs b/bon/tests/integration/builder/builder_derives.rs index a071bb66..c47c1c1d 100644 --- a/bon/tests/integration/builder/builder_derives.rs +++ b/bon/tests/integration/builder/builder_derives.rs @@ -13,14 +13,7 @@ fn smoke_fn() { assert_debug_eq( actual, - expect![[r#" - SutBuilder { - _arg1: true, - _arg3: Some( - "value", - ), - _arg4: None, - }"#]], + expect![[r#"SutBuilder { arg1: true, arg3: Some("value"), arg4: None }"#]], ); } @@ -43,14 +36,7 @@ fn smoke_struct() { assert_debug_eq( actual, - expect![[r#" - SutBuilder { - _arg1: true, - _arg3: Some( - "value", - ), - _arg4: None, - }"#]], + expect![[r#"SutBuilder { arg1: true, arg3: Some("value"), arg4: None }"#]], ); } @@ -119,7 +105,7 @@ fn skipped_members() { let actual = Sut::builder().arg1(true).clone(); - assert_debug_eq(actual, expect!["SutBuilder { _arg1: true }"]); + assert_debug_eq(actual, expect!["SutBuilder { arg1: true }"]); } #[test] From 525052efc7fafc3bd3a3524250a4ad22d8d963bf Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 22 Sep 2024 13:44:27 +0000 Subject: [PATCH 009/119] Improve error messages in derives --- .../builder/builder_gen/builder_derives.rs | 34 ++- bon-macros/src/normalization/impl_traits.rs | 4 +- bon-macros/src/normalization/lifetimes.rs | 4 +- bon/src/private/derives.rs | 5 + .../integration/builder/builder_derives.rs | 193 ++++++++++------ .../ui/compile_fail/builder_derives.rs | 17 +- .../ui/compile_fail/builder_derives.stderr | 213 ++++++++++++++---- e2e-tests/src/lib.rs | 7 + 8 files changed, 344 insertions(+), 133 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index f30b5f99..cbe258e5 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -55,15 +55,24 @@ impl BuilderGenCtx { let clone = quote!(::core::clone::Clone); - let clone_receiver = self.receiver().map(|_| { + let clone_receiver = self.receiver().map(|receiver| { + let ty = &receiver.without_self_keyword; quote! { - __private_receiver: #clone::clone(&self.__private_receiver), + __private_receiver: <#ty as #clone>::clone(&self.__private_receiver), } }); let clone_start_fn_args = self.start_fn_args().next().map(|_| { + let clone_start_fn_args = self.start_fn_args().map(|arg| { + let ty = &arg.base.norm_ty; + let index = &arg.index; + quote! { + <#ty as #clone>::clone(&self.__private_start_fn_args.#index) + } + }); + quote! { - __private_start_fn_args: #clone::clone(&self.__private_start_fn_args), + __private_start_fn_args: ( #(#clone_start_fn_args,)* ), } }); @@ -116,9 +125,7 @@ impl BuilderGenCtx { // ``` // required for `(...huge tuple type...)` to implement `Clone` // ``` - __private_named_members: ( - #( #clone_named_members, )* - ), + __private_named_members: ( #( #clone_named_members, )* ), } } } @@ -155,10 +162,13 @@ impl BuilderGenCtx { Member::StartFnArg(member) => { let member_index = &member.index; let member_ident_str = member.base.ident.to_string(); + let member_ty = &member.base.norm_ty; Some(quote! { output.field( #member_ident_str, - &self.__private_start_fn_args.#member_index + ::bon::private::derives::as_dyn_debug::<#member_ty>( + &self.__private_start_fn_args.#member_index + ) ); }) } @@ -170,9 +180,15 @@ impl BuilderGenCtx { } }); - let format_receiver = self.receiver().map(|_| { + let format_receiver = self.receiver().map(|receiver| { + let ty = &receiver.without_self_keyword; quote! { - output.field("self", &self.__private_receiver); + output.field( + "self", + ::bon::private::derives::as_dyn_debug::<#ty>( + &self.__private_receiver + ) + ); } }); diff --git a/bon-macros/src/normalization/impl_traits.rs b/bon-macros/src/normalization/impl_traits.rs index 7cdd4640..d40b6026 100644 --- a/bon-macros/src/normalization/impl_traits.rs +++ b/bon-macros/src/normalization/impl_traits.rs @@ -28,7 +28,7 @@ impl<'a> AssignTypeParams<'a> { fn new(generics: &'a mut syn::Generics) -> Self { Self { generics, - next_type_param_index: 0, + next_type_param_index: 1, } } } @@ -54,7 +54,7 @@ impl VisitMut for AssignTypeParams<'_> { let index = self.next_type_param_index; self.next_type_param_index += 1; - let type_param = quote::format_ident!("__{index}"); + let type_param = quote::format_ident!("ImplTrait{index}"); let impl_trait = std::mem::replace(ty, syn::Type::Path(syn::parse_quote!(#type_param))); let impl_trait = match impl_trait { diff --git a/bon-macros/src/normalization/lifetimes.rs b/bon-macros/src/normalization/lifetimes.rs index d3cae200..0aaaf59e 100644 --- a/bon-macros/src/normalization/lifetimes.rs +++ b/bon-macros/src/normalization/lifetimes.rs @@ -79,7 +79,7 @@ impl<'a> AssignLifetimes<'a> { Self { prefix, generics, - next_lifetime_index: 0, + next_lifetime_index: 1, } } } @@ -157,7 +157,7 @@ impl AssignLifetimes<'_> { let lifetime_param = syn::LifetimeParam::new(lifetime.clone()); let lifetime_param = syn::GenericParam::Lifetime(lifetime_param); - self.generics.params.insert(index, lifetime_param); + self.generics.params.insert(index - 1, lifetime_param); lifetime } diff --git a/bon/src/private/derives.rs b/bon/src/private/derives.rs index ed92cdaf..d96e09b6 100644 --- a/bon/src/private/derives.rs +++ b/bon/src/private/derives.rs @@ -41,3 +41,8 @@ pub fn debug_required_member( ) -> &dyn Debug { member } + +#[inline(always)] +pub fn as_dyn_debug(member: &T) -> &dyn Debug { + member +} diff --git a/bon/tests/integration/builder/builder_derives.rs b/bon/tests/integration/builder/builder_derives.rs index c47c1c1d..5ab1cd09 100644 --- a/bon/tests/integration/builder/builder_derives.rs +++ b/bon/tests/integration/builder/builder_derives.rs @@ -120,103 +120,162 @@ fn empty_builder() { assert_debug_eq(actual, expect!["SutBuilder"]); } -#[test] -fn positional_members_struct() { - #[derive(Builder)] - #[builder(derive(Clone, Debug))] - #[allow(dead_code)] - struct Sut { - #[builder(start_fn)] - start_fn_arg: bool, +mod generics { + use crate::prelude::*; - #[builder(finish_fn)] - finish_fn_arg: &'static str, + #[test] + fn test_struct() { + #[derive(Builder)] + #[builder(derive(Clone, Debug))] + struct Sut { + _arg1: T, + } - named: u32, - } + let actual = Sut::builder().arg1(42).clone(); - let actual = Sut::builder(true); + assert_debug_eq(actual, expect!["SutBuilder { arg1: 42 }"]); + } - assert_debug_eq(actual.clone(), expect!["SutBuilder { start_fn_arg: true }"]); + #[test] + fn test_free_fn() { + #[builder(derive(Clone, Debug))] + fn sut(_arg1: T) {} - assert_debug_eq( - actual.named(42).clone(), - expect!["SutBuilder { start_fn_arg: true, named: 42 }"], - ); -} + let actual = sut::().arg1(42).clone(); -#[test] -fn positional_members_fn() { - #[builder(derive(Clone, Debug))] - #[allow(unused_variables)] - fn sut( - #[builder(start_fn)] start_fn_arg: bool, - #[builder(finish_fn)] finish_fn_arg: &'static str, - named: u32, - ) { + assert_debug_eq(actual, expect!["SutBuilder { arg1: 42 }"]); } - let actual = sut(true); + #[test] + fn test_assoc_method() { + #[derive(Clone, Debug)] + struct Sut(T); - assert_debug_eq(actual.clone(), expect!["SutBuilder { start_fn_arg: true }"]); + #[bon] + impl Sut { + #[builder(derive(Clone, Debug))] + fn sut(_arg1: U) {} - assert_debug_eq( - actual.named(42).clone(), - expect!["SutBuilder { start_fn_arg: true, named: 42 }"], - ); + #[builder(derive(Clone, Debug))] + fn with_self(&self, _arg1: U) { + let _ = self; + } + } + + let actual = Sut::<()>::sut::().arg1(42).clone(); + + assert_debug_eq(actual, expect!["SutSutBuilder { arg1: 42 }"]); + + let actual = Sut(true).with_self::().arg1(42).clone(); + + assert_debug_eq( + actual, + expect!["SutWithSelfBuilder { self: Sut(true), arg1: 42 }"], + ); + } } -#[test] -fn positional_members_impl_block() { - #[derive(Debug)] - struct Sut; +mod positional_members { + use crate::prelude::*; - #[bon] - #[allow(unused_variables)] - impl Sut { + #[test] + fn test_struct() { + #[derive(Builder)] #[builder(derive(Clone, Debug))] - fn sut( - #[builder(start_fn)] start_fn_arg: bool, - #[builder(finish_fn)] finish_fn_arg: &'static str, + #[allow(dead_code)] + struct Sut { + #[builder(start_fn)] + start_fn_arg: bool, + + #[builder(finish_fn)] + finish_fn_arg: &'static str, + named: u32, - ) { } + let actual = Sut::builder(true); + + assert_debug_eq(actual.clone(), expect!["SutBuilder { start_fn_arg: true }"]); + + assert_debug_eq( + actual.named(42).clone(), + expect!["SutBuilder { start_fn_arg: true, named: 42 }"], + ); + } + + #[test] + fn test_free_fn() { #[builder(derive(Clone, Debug))] - fn with_self( - &self, + #[allow(unused_variables)] + fn sut( #[builder(start_fn)] start_fn_arg: bool, #[builder(finish_fn)] finish_fn_arg: &'static str, named: u32, ) { - let _ = self; } - } - let actual = Sut::sut(true); + let actual = sut(true); - assert_debug_eq( - actual.clone(), - expect!["SutSutBuilder { start_fn_arg: true }"], - ); - assert_debug_eq( - actual.named(42).clone(), - expect!["SutSutBuilder { start_fn_arg: true, named: 42 }"], - ); + assert_debug_eq(actual.clone(), expect!["SutBuilder { start_fn_arg: true }"]); - let actual = Sut.with_self(true); + assert_debug_eq( + actual.named(42).clone(), + expect!["SutBuilder { start_fn_arg: true, named: 42 }"], + ); + } - assert_debug_eq( - actual.clone(), - expect!["SutWithSelfBuilder { self: Sut, start_fn_arg: true }"], - ); - assert_debug_eq( - actual.named(42).clone(), - expect![[r#" + #[test] + fn test_assoc_method() { + #[derive(Debug)] + struct Sut; + + #[bon] + #[allow(unused_variables)] + impl Sut { + #[builder(derive(Clone, Debug))] + fn sut( + #[builder(start_fn)] start_fn_arg: bool, + #[builder(finish_fn)] finish_fn_arg: &'static str, + named: u32, + ) { + } + + #[builder(derive(Clone, Debug))] + fn with_self( + &self, + #[builder(start_fn)] start_fn_arg: bool, + #[builder(finish_fn)] finish_fn_arg: &'static str, + named: u32, + ) { + let _ = self; + } + } + + let actual = Sut::sut(true); + + assert_debug_eq( + actual.clone(), + expect!["SutSutBuilder { start_fn_arg: true }"], + ); + assert_debug_eq( + actual.named(42).clone(), + expect!["SutSutBuilder { start_fn_arg: true, named: 42 }"], + ); + + let actual = Sut.with_self(true); + + assert_debug_eq( + actual.clone(), + expect!["SutWithSelfBuilder { self: Sut, start_fn_arg: true }"], + ); + assert_debug_eq( + actual.named(42).clone(), + expect![[r#" SutWithSelfBuilder { self: Sut, start_fn_arg: true, named: 42, }"#]], - ); + ); + } } diff --git a/bon/tests/integration/ui/compile_fail/builder_derives.rs b/bon/tests/integration/ui/compile_fail/builder_derives.rs index 4ef6127a..331a5124 100644 --- a/bon/tests/integration/ui/compile_fail/builder_derives.rs +++ b/bon/tests/integration/ui/compile_fail/builder_derives.rs @@ -5,7 +5,10 @@ struct NoTraitImpls; #[derive(Builder)] #[builder(derive(Clone, Debug))] struct StructContainsNonTrait { - no_impls: NoTraitImpls, + #[builder(start_fn)] + no_impl_start_fn: NoTraitImpls, + + no_impls_required: NoTraitImpls, no_impl_optional: Option, @@ -17,7 +20,10 @@ struct StructContainsNonTrait { #[builder(derive(Clone, Debug))] fn fn_contains_non_trait( - _no_impls: NoTraitImpls, + #[builder(start_fn)] // + _no_impl_start_fn: NoTraitImpls, + + _no_impls_required: NoTraitImpls, _no_impl_optional: Option, @@ -32,7 +38,12 @@ fn fn_contains_non_trait( impl StructContainsNonTrait { #[builder(derive(Clone, Debug))] fn method_contains_non_trait( - _no_impls: NoTraitImpls, + self, + + #[builder(start_fn)] + _no_impl_start_fn: NoTraitImpls, + + _no_impls_required: NoTraitImpls, _no_impl_optional: Option, diff --git a/bon/tests/integration/ui/compile_fail/builder_derives.stderr b/bon/tests/integration/ui/compile_fail/builder_derives.stderr index e044023f..7259161d 100644 --- a/bon/tests/integration/ui/compile_fail/builder_derives.stderr +++ b/bon/tests/integration/ui/compile_fail/builder_derives.stderr @@ -1,24 +1,36 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:8:15 + --> tests/integration/ui/compile_fail/builder_derives.rs:9:23 | -8 | no_impls: NoTraitImpls, - | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` +9 | no_impl_start_fn: NoTraitImpls, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | -note: required by a bound in `clone_required_member` - --> src/private/derives.rs - | - | pub fn clone_required_member( - | ^^^^^ required by this bound in `clone_required_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | +3 + #[derive(Clone)] +4 | struct NoTraitImpls; + | + +error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied + --> tests/integration/ui/compile_fail/builder_derives.rs:11:24 + | +11 | no_impls_required: NoTraitImpls, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` + | +note: required by a bound in `clone_required_member` + --> src/private/derives.rs + | + | pub fn clone_required_member( + | ^^^^^ required by this bound in `clone_required_member` +help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` + | 3 + #[derive(Clone)] 4 | struct NoTraitImpls; | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:10:30 + --> tests/integration/ui/compile_fail/builder_derives.rs:13:30 | -10 | no_impl_optional: Option, +13 | no_impl_optional: Option, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_optional_member` @@ -33,9 +45,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:13:25 + --> tests/integration/ui/compile_fail/builder_derives.rs:16:25 | -13 | no_impl_optional_2: NoTraitImpls, +16 | no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_optional_member` @@ -50,18 +62,18 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:8:15 + --> tests/integration/ui/compile_fail/builder_derives.rs:9:23 | -8 | no_impls: NoTraitImpls, - | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` +9 | no_impl_start_fn: NoTraitImpls, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` -note: required by a bound in `debug_required_member` +note: required by a bound in `as_dyn_debug` --> src/private/derives.rs | - | pub fn debug_required_member( - | ^^^^^ required by this bound in `debug_required_member` + | pub fn as_dyn_debug(member: &T) -> &dyn Debug { + | ^^^^^ required by this bound in `as_dyn_debug` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] @@ -69,9 +81,28 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:10:30 + --> tests/integration/ui/compile_fail/builder_derives.rs:11:24 + | +11 | no_impls_required: NoTraitImpls, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` + | + = help: the trait `Debug` is not implemented for `NoTraitImpls` + = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` +note: required by a bound in `debug_required_member` + --> src/private/derives.rs | -10 | no_impl_optional: Option, + | pub fn debug_required_member( + | ^^^^^ required by this bound in `debug_required_member` +help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` + | +3 + #[derive(Debug)] +4 | struct NoTraitImpls; + | + +error[E0277]: `NoTraitImpls` doesn't implement `Debug` + --> tests/integration/ui/compile_fail/builder_derives.rs:13:30 + | +13 | no_impl_optional: Option, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` @@ -88,9 +119,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:13:25 + --> tests/integration/ui/compile_fail/builder_derives.rs:16:25 | -13 | no_impl_optional_2: NoTraitImpls, +16 | no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` @@ -107,10 +138,22 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:20:16 + --> tests/integration/ui/compile_fail/builder_derives.rs:24:24 | -20 | _no_impls: NoTraitImpls, - | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` +24 | _no_impl_start_fn: NoTraitImpls, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` + | +help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` + | +3 + #[derive(Clone)] +4 | struct NoTraitImpls; + | + +error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied + --> tests/integration/ui/compile_fail/builder_derives.rs:26:25 + | +26 | _no_impls_required: NoTraitImpls, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_required_member` --> src/private/derives.rs @@ -124,9 +167,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:22:31 + --> tests/integration/ui/compile_fail/builder_derives.rs:28:31 | -22 | _no_impl_optional: Option, +28 | _no_impl_optional: Option, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_optional_member` @@ -141,9 +184,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:25:26 + --> tests/integration/ui/compile_fail/builder_derives.rs:31:26 | -25 | _no_impl_optional_2: NoTraitImpls, +31 | _no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_optional_member` @@ -158,10 +201,29 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:20:16 + --> tests/integration/ui/compile_fail/builder_derives.rs:24:24 | -20 | _no_impls: NoTraitImpls, - | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` +24 | _no_impl_start_fn: NoTraitImpls, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` + | + = help: the trait `Debug` is not implemented for `NoTraitImpls` + = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` +note: required by a bound in `as_dyn_debug` + --> src/private/derives.rs + | + | pub fn as_dyn_debug(member: &T) -> &dyn Debug { + | ^^^^^ required by this bound in `as_dyn_debug` +help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` + | +3 + #[derive(Debug)] +4 | struct NoTraitImpls; + | + +error[E0277]: `NoTraitImpls` doesn't implement `Debug` + --> tests/integration/ui/compile_fail/builder_derives.rs:26:25 + | +26 | _no_impls_required: NoTraitImpls, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` @@ -177,9 +239,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:22:31 + --> tests/integration/ui/compile_fail/builder_derives.rs:28:31 | -22 | _no_impl_optional: Option, +28 | _no_impl_optional: Option, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` @@ -196,9 +258,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:25:26 + --> tests/integration/ui/compile_fail/builder_derives.rs:31:26 | -25 | _no_impl_optional_2: NoTraitImpls, +31 | _no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` @@ -214,11 +276,29 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` 4 | struct NoTraitImpls; | +error[E0277]: the trait bound `StructContainsNonTrait: Clone` is not satisfied + --> tests/integration/ui/compile_fail/builder_derives.rs:38:6 + | +38 | impl StructContainsNonTrait { + | ^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `StructContainsNonTrait` + +error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied + --> tests/integration/ui/compile_fail/builder_derives.rs:44:28 + | +44 | _no_impl_start_fn: NoTraitImpls, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` + | +help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` + | +3 + #[derive(Clone)] +4 | struct NoTraitImpls; + | + error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:35:20 + --> tests/integration/ui/compile_fail/builder_derives.rs:46:29 | -35 | _no_impls: NoTraitImpls, - | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` +46 | _no_impls_required: NoTraitImpls, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_required_member` --> src/private/derives.rs @@ -232,9 +312,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:37:35 + --> tests/integration/ui/compile_fail/builder_derives.rs:48:35 | -37 | _no_impl_optional: Option, +48 | _no_impl_optional: Option, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_optional_member` @@ -249,9 +329,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:40:30 + --> tests/integration/ui/compile_fail/builder_derives.rs:51:30 | -40 | _no_impl_optional_2: NoTraitImpls, +51 | _no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_optional_member` @@ -265,11 +345,44 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` 4 | struct NoTraitImpls; | +error[E0277]: `StructContainsNonTrait` doesn't implement `Debug` + --> tests/integration/ui/compile_fail/builder_derives.rs:38:6 + | +38 | impl StructContainsNonTrait { + | ^^^^^^^^^^^^^^^^^^^^^^ `StructContainsNonTrait` cannot be formatted using `{:?}` + | + = help: the trait `Debug` is not implemented for `StructContainsNonTrait` + = note: add `#[derive(Debug)]` to `StructContainsNonTrait` or manually `impl Debug for StructContainsNonTrait` +note: required by a bound in `as_dyn_debug` + --> src/private/derives.rs + | + | pub fn as_dyn_debug(member: &T) -> &dyn Debug { + | ^^^^^ required by this bound in `as_dyn_debug` + +error[E0277]: `NoTraitImpls` doesn't implement `Debug` + --> tests/integration/ui/compile_fail/builder_derives.rs:44:28 + | +44 | _no_impl_start_fn: NoTraitImpls, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` + | + = help: the trait `Debug` is not implemented for `NoTraitImpls` + = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` +note: required by a bound in `as_dyn_debug` + --> src/private/derives.rs + | + | pub fn as_dyn_debug(member: &T) -> &dyn Debug { + | ^^^^^ required by this bound in `as_dyn_debug` +help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` + | +3 + #[derive(Debug)] +4 | struct NoTraitImpls; + | + error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:35:20 + --> tests/integration/ui/compile_fail/builder_derives.rs:46:29 | -35 | _no_impls: NoTraitImpls, - | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` +46 | _no_impls_required: NoTraitImpls, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` @@ -285,9 +398,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:37:35 + --> tests/integration/ui/compile_fail/builder_derives.rs:48:35 | -37 | _no_impl_optional: Option, +48 | _no_impl_optional: Option, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` @@ -304,9 +417,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:40:30 + --> tests/integration/ui/compile_fail/builder_derives.rs:51:30 | -40 | _no_impl_optional_2: NoTraitImpls, +51 | _no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index 62ed539f..5fec2cc7 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -122,6 +122,13 @@ pub fn greet( format!("Hello {name} with age {age}!") } +#[builder] +pub fn fn_with_impl_trait( + #[builder] _arg1: impl std::fmt::Debug + Clone, + #[builder] _arg2: impl std::fmt::Debug, +) { +} + #[builder] pub fn many_function_parameters( _id: Option<&str>, From 1f2208c00abc796a4381911509cc016c4021f7b6 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 22 Sep 2024 14:03:25 +0000 Subject: [PATCH 010/119] Add builder_mod overrides --- bon-macros/src/builder/builder_gen/models.rs | 29 ++++++++++++++++++-- bon/tests/integration/builder/raw_idents.rs | 13 +++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 4c8ee301..3acd7f47 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -149,13 +149,36 @@ impl BuilderGenCtx { let vis_child = vis.clone().into_equivalent_in_child_module()?; let vis_child_child = vis_child.clone().into_equivalent_in_child_module()?; + let builder_mod_ident_overridden = builder_mod.name.is_some(); + let builder_mod_ident = builder_mod + .name + .unwrap_or_else(|| builder_type.ident.pascal_to_snake_case()); + + if builder_type.ident == builder_mod_ident { + if builder_mod_ident_overridden { + bail!( + &builder_mod_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(builder_mod = ...)]`" + ); + } + let builder_mod = BuilderMod { vis_child, vis_child_child, - ident: builder_mod - .name - .unwrap_or_else(|| builder_type.ident.pascal_to_snake_case()), + ident: builder_mod_ident, docs: builder_mod.docs.unwrap_or_else(|| { let docs = format!( diff --git a/bon/tests/integration/builder/raw_idents.rs b/bon/tests/integration/builder/raw_idents.rs index d4f65d6e..b57cfdb6 100644 --- a/bon/tests/integration/builder/raw_idents.rs +++ b/bon/tests/integration/builder/raw_idents.rs @@ -18,12 +18,15 @@ fn struct_case() { assert_eq!(actual.other, 100); // TODO: add builder_mod overrides - // #[derive(Builder)] - // #[builder(builder_type = r#type)] - // #[allow(clippy::items_after_statements)] - // struct Sut {} + #[derive(Builder)] + #[builder(builder_type = r#type, builder_mod = r#mod)] + #[allow(clippy::items_after_statements, dead_code)] + struct Sut { + r#while: u32, + } - // let _: r#type = Sut::builder(); + let _actual: r#type = Sut::builder(); + let _actual: r#type = Sut::builder().r#while(32); } #[test] From bedc8f13ad8bda38d9f3dbdae3c321090d962412 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 22 Sep 2024 15:09:55 +0000 Subject: [PATCH 011/119] cargo fmt --- bon-macros/src/builder/builder_gen/input_fn.rs | 7 +------ bon-macros/src/builder/item_impl.rs | 5 +---- bon-macros/src/normalization/lifetimes.rs | 3 ++- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index d0a64000..ce42f492 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -382,12 +382,7 @@ impl FnInputCtx { // It's supposed to be the same as the original function's visibility. vis: None, - attrs: self - .norm_fn - .attrs - .into_iter() - .filter(<_>::is_doc) - .collect(), + attrs: self.norm_fn.attrs.into_iter().filter(<_>::is_doc).collect(), // Override on the start fn to use the the generics from the // target function itself. We don't need to duplicate the generics diff --git a/bon-macros/src/builder/item_impl.rs b/bon-macros/src/builder/item_impl.rs index e0a80126..46351024 100644 --- a/bon-macros/src/builder/item_impl.rs +++ b/bon-macros/src/builder/item_impl.rs @@ -117,10 +117,7 @@ pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result Date: Sun, 22 Sep 2024 16:03:35 +0000 Subject: [PATCH 012/119] improve visibility syntax --- .../src/builder/builder_gen/builder_params.rs | 4 +- bon-macros/src/util/meta_list.rs | 29 ++++++++++++++ bon-macros/src/util/mod.rs | 3 ++ bon-macros/src/util/parse.rs | 39 +++++++++++++++++++ website/reference/builder.md | 12 +++--- 5 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 bon-macros/src/util/meta_list.rs create mode 100644 bon-macros/src/util/parse.rs diff --git a/bon-macros/src/builder/builder_gen/builder_params.rs b/bon-macros/src/builder/builder_gen/builder_params.rs index b6af8df7..a05b0dfc 100644 --- a/bon-macros/src/builder/builder_gen/builder_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params.rs @@ -28,7 +28,7 @@ fn parse_builder_mod(meta: &syn::Meta) -> Result { ItemParamsParsing { meta, allow_vis: false, - reject_self_mentions: Some("buuilder module"), + reject_self_mentions: Some("builder module"), } .parse() } @@ -189,6 +189,8 @@ impl ItemParamsParsing<'_> { #[derive(Debug, FromMeta)] struct Full { name: Option, + + #[darling(with = crate::util::parse::visibility, map = Some)] vis: Option, docs: Option, } diff --git a/bon-macros/src/util/meta_list.rs b/bon-macros/src/util/meta_list.rs new file mode 100644 index 00000000..bb165d81 --- /dev/null +++ b/bon-macros/src/util/meta_list.rs @@ -0,0 +1,29 @@ +use crate::util::prelude::*; + +pub(crate) trait MetaListExt { + fn require_paren_delim(&self) -> Result<()>; +} + +impl MetaListExt for syn::MetaList { + fn require_paren_delim(&self) -> Result<()> { + if matches!(self.delimiter, syn::MacroDelimiter::Paren(_)) { + return Ok(()); + } + + let path = darling::util::path_to_string(&self.path); + + bail!( + self, + "wrong delimiter, expected parentheses e.g. `{path}(...)`, but got {}", + delim_example(&path, &self.delimiter), + ); + } +} + +fn delim_example(path: &str, delimiter: &syn::MacroDelimiter) -> String { + match delimiter { + syn::MacroDelimiter::Paren(_) => format!("`{path}(...)`"), + syn::MacroDelimiter::Brace(_) => format!("`{path}{{...}}`"), + syn::MacroDelimiter::Bracket(_) => format!("`{path}[...]`"), + } +} diff --git a/bon-macros/src/util/mod.rs b/bon-macros/src/util/mod.rs index 3b862aad..21d0ae00 100644 --- a/bon-macros/src/util/mod.rs +++ b/bon-macros/src/util/mod.rs @@ -4,6 +4,7 @@ mod generic_param; mod ident; mod item; mod iterator; +mod meta_list; mod path; mod punctuated; mod ty; @@ -11,6 +12,7 @@ mod vec; mod visibility; pub(crate) mod ide; +pub(crate) mod parse; use prelude::*; @@ -34,6 +36,7 @@ pub(crate) mod prelude { 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; diff --git a/bon-macros/src/util/parse.rs b/bon-macros/src/util/parse.rs new file mode 100644 index 00000000..ab11222f --- /dev/null +++ b/bon-macros/src/util/parse.rs @@ -0,0 +1,39 @@ +use crate::util::prelude::*; +use std::fmt::Display; + +pub(crate) fn visibility(meta: &syn::Meta) -> Result { + let error = |prefix: &dyn Display, path: &str| { + err!( + meta, + "{prefix}; use the following syntax to \ + specify the visibility instead: `{path}(pub(...))`; if you intended \ + to specify private visibility, then use `{path}(pub(self))`" + ) + }; + + let meta = match meta { + syn::Meta::NameValue(name_val) => { + let path = darling::util::path_to_string(&name_val.path); + return Err(error( + &format_args!("`{path} = ...` syntax is not supported"), + &path, + )); + } + syn::Meta::Path(path) => { + let path = darling::util::path_to_string(path); + return Err(error(&"missing visibility value", &path)); + } + syn::Meta::List(meta) => meta, + }; + + meta.require_paren_delim()?; + + if meta.tokens.is_empty() { + let path = darling::util::path_to_string(&meta.path); + return Err(error(&"missing visibility value in parentheses", &path)); + } + + let visibility = syn::parse2::(meta.tokens.clone())?; + + Ok(visibility) +} diff --git a/website/reference/builder.md b/website/reference/builder.md index bf19bf20..d0d8ac9c 100644 --- a/website/reference/builder.md +++ b/website/reference/builder.md @@ -314,9 +314,9 @@ Usually you'd want the underlying positional function to be hidden to provide on This attribute can take several forms. - Simple: `#[builder(expose_positional_fn = identifier)]`. Sets only the name of the positional function. -- Verbose: `#[builder(expose_positional_fn(name = identifier, vis = "visibility"))]`. +- Verbose: `#[builder(expose_positional_fn(name = identifier, vis(visibility)))]`. Allows setting both the name and the visibility of the positional function. - Each key is optional. The `vis` must be specified as a string literal e.g. `"pub(crate)"`, `"pub"` or `""` (empty string means private visibility). + Each key is optional. The `vis` must be specified in parentheses e.g. `vis(pub)`, `vis(pub(crate))`. For private visibility use the syntax `vis(pub(self))`. If `vis` parameter is not specified, then the visibility of the exposed positional function will be the same as specified on the function that the `#[builder]` was applied to. @@ -477,9 +477,9 @@ The default name for this method is `builder`, and the default visibility is the This attribute can take several forms. - Simple: `#[builder(start_fn = identifier)]`. Overrides only the name of the "start" method. -- Verbose: `#[builder(start_fn(name = identifier, vis = "visibility"))]`. +- Verbose: `#[builder(start_fn(name = identifier, vis(visibility)]`. Allows overriding both the name and the visibility of the "start" method. - Each key is optional. The `vis` must be specified as a string literal e.g. `"pub(crate)"`, `"pub"` or `""` (empty string means private visibility). + Each key is optional. The `vis` must be specified in parentheses e.g. `vis(pub)`, `vis(pub(crate))`. For private visibility use the syntax `vis(pub(self))`. **Example:** @@ -503,9 +503,9 @@ User::init() // [!code highlight] use bon::Builder; // `User::init()` method will have `pub(crate)` visibility // [!code highlight] -// Use `vis = ""` to make it fully private instead // [!code highlight] +// Use `vis(pub(self))` to make it fully private instead // [!code highlight] #[derive(Builder)] -#[builder(start_fn(name = init, vis = "pub(crate)"))] // [!code highlight] +#[builder(start_fn(name = init, vis(pub(crate))))] // [!code highlight] pub struct User { id: u32 } From c917ad805eaf287dcbcf5836fa8aa2b43518916a Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 22 Sep 2024 18:12:19 +0000 Subject: [PATCH 013/119] Add visibility overrides for all items, make the builder state trait sealed --- .../src/builder/builder_gen/builder_params.rs | 10 -- .../src/builder/builder_gen/input_fn.rs | 4 +- .../src/builder/builder_gen/input_struct.rs | 7 +- bon-macros/src/builder/builder_gen/mod.rs | 30 +++-- bon-macros/src/builder/builder_gen/models.rs | 119 ++++++++++-------- bon-macros/src/util/visibility.rs | 46 ++++++- bon/src/private/mod.rs | 26 ++-- .../integration/ui/compile_fail/errors.stderr | 8 +- 8 files changed, 154 insertions(+), 96 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/builder_params.rs b/bon-macros/src/builder/builder_gen/builder_params.rs index a05b0dfc..dce2af91 100644 --- a/bon-macros/src/builder/builder_gen/builder_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params.rs @@ -9,7 +9,6 @@ use syn::visit::Visit; fn parse_finish_fn(meta: &syn::Meta) -> Result { ItemParamsParsing { meta, - allow_vis: false, reject_self_mentions: Some("builder struct's impl block"), } .parse() @@ -18,7 +17,6 @@ fn parse_finish_fn(meta: &syn::Meta) -> Result { fn parse_builder_type(meta: &syn::Meta) -> Result { ItemParamsParsing { meta, - allow_vis: false, reject_self_mentions: Some("builder struct"), } .parse() @@ -27,7 +25,6 @@ fn parse_builder_type(meta: &syn::Meta) -> Result { fn parse_builder_mod(meta: &syn::Meta) -> Result { ItemParamsParsing { meta, - allow_vis: false, reject_self_mentions: Some("builder module"), } .parse() @@ -151,7 +148,6 @@ pub(crate) struct ItemParams { pub(crate) struct ItemParamsParsing<'a> { pub(crate) meta: &'a syn::Meta, - pub(crate) allow_vis: bool, pub(crate) reject_self_mentions: Option<&'static str>, } @@ -159,12 +155,6 @@ impl ItemParamsParsing<'_> { pub(crate) fn parse(self) -> Result { let params = Self::params_from_meta(self.meta)?; - if !self.allow_vis { - if let Some(vis) = ¶ms.vis { - bail!(vis, "visibility can't be overridden for this item"); - } - } - if let Some(context) = self.reject_self_mentions { if let Some(docs) = ¶ms.docs { super::reject_self_mentions_in_docs(context, docs)?; diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index ce42f492..4853a281 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -332,7 +332,7 @@ impl FnInputCtx { let ItemParams { name: finish_fn_ident, - vis: _, + vis: finish_fn_vis, docs: finish_fn_docs, } = self.params.base.finish_fn; @@ -353,6 +353,7 @@ impl FnInputCtx { let finish_fn = FinishFn { ident: finish_fn_ident, + vis: finish_fn_vis, unsafety: self.norm_fn.sig.unsafety, asyncness: self.norm_fn.sig.asyncness, must_use: get_must_use_attribute(&self.norm_fn.attrs)?, @@ -397,6 +398,7 @@ impl FnInputCtx { ident: builder_ident, derives: self.params.base.derive, docs: self.params.base.builder_type.docs, + vis: self.params.base.builder_type.vis, }; BuilderGenCtx::new(BuilderGenCtxParams { diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 5d444125..eea40481 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -21,7 +21,6 @@ pub(crate) struct StructInputParams { fn parse_start_fn(meta: &syn::Meta) -> Result { ItemParamsParsing { meta, - allow_vis: true, reject_self_mentions: None, } .parse() @@ -150,7 +149,7 @@ impl StructInputCtx { let ItemParams { name: finish_fn_ident, - vis: _, + vis: finish_fn_vis, docs: finish_fn_docs, } = self.params.base.finish_fn; @@ -160,6 +159,7 @@ impl StructInputCtx { let struct_ty = &self.struct_ty; let finish_fn = FinishFn { ident: finish_fn_ident, + vis: finish_fn_vis, unsafety: None, asyncness: None, must_use: Some(syn::parse_quote! { @@ -203,7 +203,7 @@ impl StructInputCtx { .collect(); let builder_type = { - let ItemParams { name, vis: _, docs } = self.params.base.builder_type; + let ItemParams { name, vis, docs } = self.params.base.builder_type; let builder_ident = name.unwrap_or_else(|| { quote::format_ident!("{}Builder", self.norm_struct.ident.raw_name()) @@ -213,6 +213,7 @@ impl StructInputCtx { derives: self.params.base.derive, ident: builder_ident, docs, + vis, } }; diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 006b3f3b..1d59f6a8 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -366,7 +366,7 @@ impl BuilderGenCtx { } fn builder_mod(&self) -> TokenStream2 { - let vis_top = &self.vis; + let vis_mod = &self.builder_mod.vis; let vis_child = &self.builder_mod.vis_child; let vis_child_child = &self.builder_mod.vis_child_child; @@ -394,7 +394,7 @@ impl BuilderGenCtx { quote! { #( #builder_mod_docs )* - #vis_top mod #builder_mod_ident { + #vis_mod mod #builder_mod_ident { #( #state_transition_aliases )* /// Represents the builder's type state that specifies which members are set and which are not. @@ -409,15 +409,23 @@ impl BuilderGenCtx { #[doc = #assoc_types_docs] type #named_members_pascal: ::bon::private::MemberState; )* + + fn __sealed(_: self::sealed::Sealed); + } + + mod sealed { + #vis_child_child enum Sealed {} } // Using `self::State` explicitly to avoid name conflicts with the // members named `state` which would create a generic param named `State` // that would shadow the trait `State` in the same scope. - impl< #(#named_members_pascal: ::bon::private::MemberState,)* > self::State - for ( #(#named_members_pascal,)* ) + impl<#( #named_members_pascal: ::bon::private::MemberState, )*> + self::State for ( #(#named_members_pascal,)* ) { #( type #named_members_pascal = #named_members_pascal; )* + + fn __sealed(_: self::sealed::Sealed) {} } /// Initial state of the builder where all named members are unset @@ -442,7 +450,7 @@ impl BuilderGenCtx { used directly; use the Set* type aliases to control the \ state of members instead" ] - #vis_child_child struct #named_members_idents; + #vis_child_child enum #named_members_idents {} )* } } @@ -450,7 +458,7 @@ impl BuilderGenCtx { } fn builder_decl(&self) -> TokenStream2 { - let vis = &self.vis; + 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; @@ -535,7 +543,7 @@ impl BuilderGenCtx { // by the big PhantomData type generated by the macro clippy::type_complexity )] - #vis struct #builder_ident< + #builder_vis struct #builder_ident< #(#generics_decl,)* BuilderTypeState: #builder_mod::State = #builder_mod::AllUnset > @@ -659,7 +667,11 @@ impl BuilderGenCtx { let unsafety = &self.finish_fn.unsafety; let must_use = &self.finish_fn.must_use; let attrs = &self.finish_fn.attrs; - let vis = &self.vis; + let finish_fn_vis = self + .finish_fn + .vis + .as_ref() + .unwrap_or(&self.builder_type.vis); let finish_fn_ident = &self.finish_fn.ident; let output = &self.finish_fn.output; @@ -677,7 +689,7 @@ impl BuilderGenCtx { clippy::future_not_send, )] #must_use - #vis #asyncness #unsafety fn #finish_fn_ident(self, #(#finish_fn_params,)*) #output + #finish_fn_vis #asyncness #unsafety fn #finish_fn_ident(self, #(#finish_fn_params,)*) #output where #(#where_bounds,)* { diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 3acd7f47..03e45dfa 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -27,6 +27,9 @@ pub(super) struct AssocMethodCtx { pub(super) struct FinishFn { pub(super) ident: syn::Ident, + /// Visibility override specified by the user + pub(super) vis: Option, + /// Additional attributes to apply to the item pub(super) attrs: Vec, @@ -53,21 +56,29 @@ pub(super) struct StartFn { pub(super) struct BuilderType { pub(super) ident: syn::Ident, + + /// Visibility of the builder module itself. + pub(super) vis: syn::Visibility, + pub(super) derives: BuilderDerives, pub(super) docs: Vec, } pub(super) struct BuilderMod { pub(super) ident: syn::Ident, - pub(super) docs: Vec, - /// Visibility equivalent to the [`BuilderGenCtx::vis`], but for items + /// 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 { @@ -127,6 +138,7 @@ pub(super) struct BuilderGenCtxParams { pub(super) struct BuilderTypeParams { pub(super) ident: syn::Ident, + pub(super) vis: Option, pub(super) derives: BuilderDerives, pub(super) docs: Option>, } @@ -146,52 +158,10 @@ impl BuilderGenCtx { finish_fn, } = params; - let vis_child = vis.clone().into_equivalent_in_child_module()?; - let vis_child_child = vis_child.clone().into_equivalent_in_child_module()?; - - let builder_mod_ident_overridden = builder_mod.name.is_some(); - let builder_mod_ident = builder_mod - .name - .unwrap_or_else(|| builder_type.ident.pascal_to_snake_case()); - - if builder_type.ident == builder_mod_ident { - if builder_mod_ident_overridden { - bail!( - &builder_mod_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(builder_mod = ...)]`" - ); - } - - let builder_mod = BuilderMod { - vis_child, - vis_child_child, - - ident: builder_mod_ident, - - docs: builder_mod.docs.unwrap_or_else(|| { - let docs = format!( - "Contains the traits and type aliases for manipulating \ - the type state of the {}", - builder_type.ident - ); - - vec![syn::parse_quote!(#[doc = #docs])] - }), - }; - let builder_type = BuilderType { + ident: builder_type.ident, + vis: builder_type.vis.unwrap_or_else(|| vis.clone()), + derives: builder_type.derives, docs: builder_type.docs.unwrap_or_else(|| { let doc = format!( "Use builder syntax to set the required parameters and finish \ @@ -203,8 +173,59 @@ impl BuilderGenCtx { #[doc = #doc] }] }), - derives: builder_type.derives, - ident: builder_type.ident, + }; + + let builder_mod = { + let ident_overridden = builder_mod.name.is_some(); + let ident = builder_mod + .name + .unwrap_or_else(|| builder_type.ident.pascal_to_snake_case()); + + if builder_type.ident == ident { + if 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(builder_mod = ...)]`" + ); + } + + let vis = builder_mod.vis.unwrap_or_else(|| builder_type.vis.clone()); + + // 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 type 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()?; + + BuilderMod { + vis, + vis_child, + vis_child_child, + + ident, + + docs: builder_mod.docs.unwrap_or_else(|| { + let docs = format!( + "Contains the traits and type aliases for manipulating \ + the type state of the {}", + builder_type.ident + ); + + vec![syn::parse_quote!(#[doc = #docs])] + }), + } }; Ok(Self { diff --git a/bon-macros/src/util/visibility.rs b/bon-macros/src/util/visibility.rs index d5ed7077..74567327 100644 --- a/bon-macros/src/util/visibility.rs +++ b/bon-macros/src/util/visibility.rs @@ -7,16 +7,25 @@ pub(crate) trait VisibilityExt { /// /// - `pub` -> `pub` (unchanged) /// - `pub(crate)` -> `pub(crate)` (unchanged) - /// - ` ` (default private visibility) -> `pub(super)` + /// - `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 ::absolute::path)` -> `pub(in ::absolute::path)` (unchange) + /// + /// 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; } @@ -38,6 +47,10 @@ impl VisibilityExt for syn::Visibility { 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 \ @@ -62,3 +75,32 @@ impl VisibilityExt for syn::Visibility { } } } + +#[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_eq!(actual, expected); + } + + 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 ::absolute::path)), pq!(pub(in ::absolute::path))); + } +} diff --git a/bon/src/private/mod.rs b/bon/src/private/mod.rs index 53786605..c0f478d2 100644 --- a/bon/src/private/mod.rs +++ b/bon/src/private/mod.rs @@ -46,41 +46,31 @@ pub fn assert_clone() {} pub fn assert_debug() {} #[doc(hidden)] -#[deprecated = "this type is an implementation detail and should not be used directly; \ - 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://discord.gg/QcBYSamw4c)"] #[derive(Debug)] -pub struct Unset(T); +pub struct Unset(Name); -impl crate::IsUnset for Unset { - // type MemberName =; -} +impl crate::IsUnset for Unset {} #[doc(hidden)] -#[deprecated = "this type is an implementation detail and should not be used directly; \ - 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://discord.gg/QcBYSamw4c)"] #[derive(Debug)] -pub struct Set(T); +pub struct Set(Name); -impl crate::IsSet for Set {} +impl crate::IsSet for Set {} +/// Allows to statically check if a member is set or not. +/// This is basically a utility to do compile-time downcasts. pub trait MemberState: Sealed { fn is_set() -> bool; } -impl MemberState for Unset { +impl MemberState for Unset { #[inline(always)] fn is_set() -> bool { false } } -impl MemberState for Set { +impl MemberState for Set { #[inline(always)] fn is_set() -> bool { true diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr index c8908c73..cb634a4b 100644 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ b/bon/tests/integration/ui/compile_fail/errors.stderr @@ -200,7 +200,7 @@ error[E0277]: the following member was not set: `Unset`, but this method requ | ^^^^^ the following member was not set: `Unset`, but this method requires it to be set | = help: the trait `IsSet` is not implemented for `Unset` - = help: the trait `IsSet` is implemented for `Set` + = help: the trait `IsSet` is implemented for `Set` note: required by a bound in `ExampleBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:24:14 | @@ -217,7 +217,7 @@ error[E0277]: the following member was not set: `Unset`, but this metho | ^^^^^ the following member was not set: `Unset`, but this method requires it to be set | = help: the trait `IsSet` is not implemented for `Unset` - = help: the trait `IsSet` is implemented for `Set` + = help: the trait `IsSet` is implemented for `Set` note: required by a bound in `ExampleBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:24:14 | @@ -234,7 +234,7 @@ error[E0277]: the following member was already set: `Set`, but this method re | ^ the following member was already set: `Set`, but this method requires it to be unset | = help: the trait `IsUnset` is not implemented for `Set` - = help: the trait `IsUnset` is implemented for `Unset` + = help: the trait `IsUnset` is implemented for `Unset` note: required by a bound in `ExampleBuilder::::y` --> tests/integration/ui/compile_fail/errors.rs:24:14 | @@ -252,7 +252,7 @@ error[E0277]: the following member was not set: `Unset`, but this method r | ^^^^^ the following member was not set: `Unset`, but this method requires it to be set | = help: the trait `IsSet` is not implemented for `Unset` - = help: the trait `IsSet` is implemented for `Set` + = help: the trait `IsSet` is implemented for `Set` note: required by a bound in `SutBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:42:18 | From 0359055a1b217bc2668d5a878c37ec9e937e8648 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 22 Sep 2024 18:24:12 +0000 Subject: [PATCH 014/119] Rearrange error message --- bon/src/lib.rs | 8 ++++---- .../integration/ui/compile_fail/errors.stderr | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bon/src/lib.rs b/bon/src/lib.rs index 347c0435..28791e9e 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -61,8 +61,8 @@ use private::sealed::Sealed; #[rustversion::attr( since(1.78.0), diagnostic::on_unimplemented( - message = "the following member was already set: `{Self}`, but this method requires it to be unset", - label = "the following member was already set: `{Self}`, but this method requires it to be unset", + message = "the member `{Self}` was already set, but this method requires it to be unset", + label = "the member `{Self}` was already set, but this method requires it to be unset", ) )] pub trait IsUnset: Sealed {} @@ -72,8 +72,8 @@ pub trait IsUnset: Sealed {} #[rustversion::attr( since(1.78.0), diagnostic::on_unimplemented( - message = "the following member was not set: `{Self}`, but this method requires it to be set", - label = "the following member was not set: `{Self}`, but this method requires it to be set", + message = "the member `{Self}` was not set, but this method requires it to be set", + label = "the member `{Self}` was not set, but this method requires it to be set", ) )] pub trait IsSet: Sealed {} diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr index cb634a4b..bb48ff07 100644 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ b/bon/tests/integration/ui/compile_fail/errors.stderr @@ -193,11 +193,11 @@ error[E0599]: no method named `y` found for struct `SkipGeneratesNoSetterBuilder 22 | SkipGeneratesNoSetter::builder().y(42).build(); | ^ method not found in `SkipGeneratesNoSetterBuilder` -error[E0277]: the following member was not set: `Unset`, but this method requires it to be set +error[E0277]: the member `Unset` was not set, but this method requires it to be set --> tests/integration/ui/compile_fail/errors.rs:34:37 | 34 | let _ = Example::builder().x(1).build(); - | ^^^^^ the following member was not set: `Unset`, but this method requires it to be set + | ^^^^^ the member `Unset` was not set, but this method requires it to be set | = help: the trait `IsSet` is not implemented for `Unset` = help: the trait `IsSet` is implemented for `Set` @@ -210,11 +210,11 @@ note: required by a bound in `ExampleBuilder::::build` | ------- required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the following member was not set: `Unset`, but this method requires it to be set +error[E0277]: the member `Unset` was not set, but this method requires it to be set --> tests/integration/ui/compile_fail/errors.rs:34:37 | 34 | let _ = Example::builder().x(1).build(); - | ^^^^^ the following member was not set: `Unset`, but this method requires it to be set + | ^^^^^ the member `Unset` was not set, but this method requires it to be set | = help: the trait `IsSet` is not implemented for `Unset` = help: the trait `IsSet` is implemented for `Set` @@ -227,11 +227,11 @@ note: required by a bound in `ExampleBuilder::::build` | ------- required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the following member was already set: `Set`, but this method requires it to be unset +error[E0277]: the member `Set` was already set, but this method requires it to be unset --> tests/integration/ui/compile_fail/errors.rs:37:37 | 37 | let _ = Example::builder().y(1).y(2); - | ^ the following member was already set: `Set`, but this method requires it to be unset + | ^ the member `Set` was already set, but this method requires it to be unset | = help: the trait `IsUnset` is not implemented for `Set` = help: the trait `IsUnset` is implemented for `Unset` @@ -245,11 +245,11 @@ note: required by a bound in `ExampleBuilder::::y` | - required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) -error[E0277]: the following member was not set: `Unset`, but this method requires it to be set +error[E0277]: the member `Unset` was not set, but this method requires it to be set --> tests/integration/ui/compile_fail/errors.rs:47:32 | 47 | let _ = Sut::builder().build(); - | ^^^^^ the following member was not set: `Unset`, but this method requires it to be set + | ^^^^^ the member `Unset` was not set, but this method requires it to be set | = help: the trait `IsSet` is not implemented for `Unset` = help: the trait `IsSet` is implemented for `Set` From 1974c1a6d52be5ee0966292f2a89fd5f28a1be9e Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 22 Sep 2024 21:49:41 +0000 Subject: [PATCH 015/119] Fix cfg attrs with doc comments --- bon-macros/src/normalization/cfg/mod.rs | 2 +- bon/tests/integration/builder/cfgs.rs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bon-macros/src/normalization/cfg/mod.rs b/bon-macros/src/normalization/cfg/mod.rs index 4e990904..e7621279 100644 --- a/bon-macros/src/normalization/cfg/mod.rs +++ b/bon-macros/src/normalization/cfg/mod.rs @@ -73,7 +73,7 @@ impl ExpandCfg { for attr in attrs { let cfg_syntax = match CfgSyntax::from_meta(&attr.meta)? { Some(cfg_syntax) => cfg_syntax, - None => return Ok(true), + None => continue, }; let predicate = match cfg_syntax { diff --git a/bon/tests/integration/builder/cfgs.rs b/bon/tests/integration/builder/cfgs.rs index 0ced1a31..355d4f65 100644 --- a/bon/tests/integration/builder/cfgs.rs +++ b/bon/tests/integration/builder/cfgs.rs @@ -10,6 +10,7 @@ fn struct_smoke() { #[cfg_attr(all(), allow(dead_code))] arg1: bool, + /// doc comment #[cfg(not(all()))] arg1: u32, @@ -33,6 +34,7 @@ fn struct_with_params() { #[cfg(all())] arg1: bool, + /// doc comment #[cfg(not(all()))] arg1: u32, @@ -63,6 +65,7 @@ fn fn_smoke() { #[cfg_attr(all(), allow(dead_code))] arg1: bool, + /// doc comment #[cfg(not(all()))] arg1: u32, #[cfg(any())] arg1: String, @@ -80,6 +83,7 @@ fn fn_with_params() { fn sut( #[cfg(all())] arg1: bool, + /// doc comment #[cfg(not(all()))] arg1: u32, #[cfg_attr(all(), builder(default))] arg2: [u8; 4], @@ -119,6 +123,7 @@ fn impl_block() { #[cfg_attr(all(), allow(dead_code))] arg1: bool, + /// doc comment #[cfg(not(all()))] arg1: u32, #[cfg(any())] arg1: String, @@ -131,6 +136,7 @@ fn impl_block() { fn sut_with_params( #[cfg(all())] arg1: bool, + /// doc comment #[cfg(not(all()))] arg1: u32, #[cfg_attr(all(), builder(default))] arg2: [u8; 4], From eb13c35dfce757645077f43d02c79585c3e1c6ee Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 24 Sep 2024 00:05:13 +0000 Subject: [PATCH 016/119] Renamings, add `NamedMemberState` trait --- bon-macros/src/builder/builder_gen/mod.rs | 26 +++++++++------- .../src/builder/builder_gen/setter_methods.rs | 2 +- bon/src/private/derives.rs | 12 ++++---- bon/src/private/{member_cell.rs => member.rs} | 30 +++++++++---------- bon/src/private/mod.rs | 25 +++++++++------- 5 files changed, 53 insertions(+), 42 deletions(-) rename bon/src/private/{member_cell.rs => member.rs} (85%) diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 1d59f6a8..e97e85bb 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -216,7 +216,7 @@ impl BuilderGenCtx { if member.is_optional() { quote!(None) } else { - quote!(::bon::private::MemberCell::uninit()) + quote!(::bon::private::Member::unset()) } }); @@ -407,7 +407,9 @@ impl BuilderGenCtx { #vis_child trait State: ::core::marker::Sized { #( #[doc = #assoc_types_docs] - type #named_members_pascal: ::bon::private::MemberState; + type #named_members_pascal: ::bon::private::NamedMemberState< + self::members::#named_members_idents + >; )* fn __sealed(_: self::sealed::Sealed); @@ -420,7 +422,11 @@ impl BuilderGenCtx { // Using `self::State` explicitly to avoid name conflicts with the // members named `state` which would create a generic param named `State` // that would shadow the trait `State` in the same scope. - impl<#( #named_members_pascal: ::bon::private::MemberState, )*> + impl<#( + #named_members_pascal: ::bon::private::NamedMemberState< + self::members::#named_members_idents + >, + )*> self::State for ( #(#named_members_pascal,)* ) { #( type #named_members_pascal = #named_members_pascal; )* @@ -433,6 +439,12 @@ impl BuilderGenCtx { #(::bon::private::Unset,)* ); + #[deprecated = + "this is an implementation detail and should not be \ + used directly; use the Set* type aliases to control the \ + state of members instead" + ] + #[doc(hidden)] mod members { #( #[allow( @@ -444,12 +456,6 @@ impl BuilderGenCtx { // exported items of the parent module. unnameable_types, )] - #[doc(hidden)] - #[deprecated = - "this type is an implementation detail and should not be \ - used directly; use the Set* type aliases to control the \ - state of members instead" - ] #vis_child_child enum #named_members_idents {} )* } @@ -522,7 +528,7 @@ impl BuilderGenCtx { let ty = &member.norm_ty; let member_pascal = &member.norm_ident_pascal; quote! { - ::bon::private::MemberCell + ::bon::private::Member } }) }); diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index 52926e21..640f49e1 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -35,7 +35,7 @@ impl<'a> MemberSettersCtx<'a> { fn_params: quote!(value: #fn_param_type), overwrite_docs: None, body: SetterBody::Default { - member_init: quote!(::bon::private::MemberCell::new(value #maybe_into_call)), + member_init: quote!(::bon::private::Member::set(value #maybe_into_call)), }, })) } diff --git a/bon/src/private/derives.rs b/bon/src/private/derives.rs index d96e09b6..8ab72872 100644 --- a/bon/src/private/derives.rs +++ b/bon/src/private/derives.rs @@ -5,17 +5,17 @@ //! which improves the compile errors when the member's type `T` doesn't implement //! the target trait. //! -//! E.g. they remove the messages that reference to the internal [`MemberCell`] +//! E.g. they remove the messages that reference to the internal [`Member`] //! type completely like: //! //! ```not-rust -//! required for `MemberCell<...>` to implement `Clone` +//! required for `Member<...>` to implement `Clone` //! ``` //! //! They also improve the spans of error messages because compiler knows that it needs to //! point to the origin of the offending type (member's type T) from the turbofish //! syntax to where the type came from (original code written by the user). -use super::{MemberCell, MemberState}; +use super::{Member, MemberState}; use core::fmt::Debug; #[inline(always)] @@ -25,8 +25,8 @@ pub fn clone_optional_member(member: &Option) -> Option { #[inline(always)] pub fn clone_required_member( - member: &MemberCell, -) -> MemberCell { + member: &Member, +) -> Member { member.clone() } @@ -37,7 +37,7 @@ pub fn debug_optional_member(member: &Option) -> &dyn Debug { #[inline(always)] pub fn debug_required_member( - member: &MemberCell, + member: &Member, ) -> &dyn Debug { member } diff --git a/bon/src/private/member_cell.rs b/bon/src/private/member.rs similarity index 85% rename from bon/src/private/member_cell.rs rename to bon/src/private/member.rs index cb226493..965dd2aa 100644 --- a/bon/src/private/member_cell.rs +++ b/bon/src/private/member.rs @@ -23,7 +23,7 @@ use core::mem::MaybeUninit; /// they are sealed. #[doc(hidden)] #[must_use] -pub struct MemberCell { +pub struct Member { /// The [`PhantomData`] uses an `fn()` pointer to signify that this type /// doesn't hold an instance of `State`. state: PhantomData State>, @@ -31,7 +31,7 @@ pub struct MemberCell { value: MaybeUninit, } -impl Drop for MemberCell { +impl Drop for Member { fn drop(&mut self) { if State::is_set() { // SAFETY: this is safe. The `value` is guaranteed to be initialized @@ -54,11 +54,11 @@ impl Drop for MemberCell { } } -impl MemberCell { - /// Creates a new [`MemberCell`] with an uninitialized value. This is only +impl Member { + /// Creates a new [`Member`] with an uninitialized value. This is only /// possible when the `State` implements the [`IsUnset`] trait. #[inline(always)] - pub fn uninit() -> Self { + pub fn unset() -> Self { Self { state: PhantomData, value: MaybeUninit::uninit(), @@ -66,11 +66,11 @@ impl MemberCell { } } -impl MemberCell { - /// Creates a new [`MemberCell`] initialized with the specified value. This is +impl Member { + /// Creates a new [`Member`] initialized with the specified value. This is /// only possible when the `State` implements the [`IsSet`] trait. #[inline(always)] - pub fn new(value: T) -> Self { + pub fn set(value: T) -> Self { Self { state: PhantomData, value: MaybeUninit::new(value), @@ -81,8 +81,8 @@ impl MemberCell { #[inline(always)] pub fn into_inner(self) -> T { // SAFETY: this is safe. The `value` is guaranteed to be initialized - // by the `MemberCell::new` constructor. There is no other way to - // create a `MemberCell` where `State: IsSet`. The trait implementation + // by the `Member::new` constructor. There is no other way to + // create a `Member` where `State: IsSet`. The trait implementation // if `IsSet` and `IsUnset` are guaranteed to be mutually exclusive. // They are sealed and implemented for Set/Unset types respectively. #[allow(unsafe_code)] @@ -100,13 +100,13 @@ impl MemberCell { } } -impl MemberCell { +impl Member { #[inline(always)] fn try_get(&self) -> Option<&T> { if State::is_set() { // SAFETY: this is safe. The `value` is guaranteed to be initialized - // by the `MemberCell::new` constructor. There is no other way to - // create a `MemberCell` where `State: IsSet`. The trait implementation + // by the `Member::new` constructor. There is no other way to + // create a `Member` where `State: IsSet`. The trait implementation // if `IsSet` and `IsUnset` are guaranteed to be mutually exclusive. // They are sealed and implemented for Set/Unset types respectively. #[allow(unsafe_code)] @@ -119,7 +119,7 @@ impl MemberCell { } } -impl Clone for MemberCell +impl Clone for Member where State: MemberState, T: Clone, @@ -143,7 +143,7 @@ where } } -impl fmt::Debug for MemberCell +impl fmt::Debug for Member where State: MemberState, T: fmt::Debug, diff --git a/bon/src/private/mod.rs b/bon/src/private/mod.rs index c0f478d2..dc44d827 100644 --- a/bon/src/private/mod.rs +++ b/bon/src/private/mod.rs @@ -22,36 +22,30 @@ pub mod ide; pub mod derives; mod cfg_eval; -mod member_cell; +mod member; pub(crate) mod sealed { // The purpose of the `Sealed` trait **is** to be unnameable from outside the crate. #[allow(unnameable_types)] pub trait Sealed: Sized {} - impl Sealed for super::Unset {} - impl Sealed for super::Set {} + impl Sealed for super::Unset {} + impl Sealed for super::Set {} } -pub use member_cell::*; +pub use member::*; -use core::fmt; use sealed::Sealed; /// Used to implement the `alloc` feature. #[cfg(feature = "alloc")] pub extern crate alloc; -pub fn assert_clone() {} -pub fn assert_debug() {} - -#[doc(hidden)] #[derive(Debug)] pub struct Unset(Name); impl crate::IsUnset for Unset {} -#[doc(hidden)] #[derive(Debug)] pub struct Set(Name); @@ -76,3 +70,14 @@ impl MemberState for Set { true } } + +#[rustversion::attr( + since(1.78.0), + diagnostic::on_unimplemented( + message = "expected type state for the member `{Name}`, but found `{Self}`", + label = "expected type state for the member `{Name}`, but found `{Self}`", + ) +)] +pub trait NamedMemberState: MemberState {} +impl NamedMemberState for Set {} +impl NamedMemberState for Unset {} From a1ddac50ef17423276694fe530642fe66c4e4b22 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 24 Sep 2024 18:38:14 +0000 Subject: [PATCH 017/119] `Option` approach --- bon-macros/src/builder/builder_gen/mod.rs | 155 +++++++++++------- .../src/builder/builder_gen/setter_methods.rs | 34 +--- 2 files changed, 105 insertions(+), 84 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index e97e85bb..c31ca4ec 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -13,7 +13,7 @@ use models::{ AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, StartFn, }; -use quote::quote; +use quote::{quote, ToTokens}; use setter_methods::MemberSettersCtx; pub(crate) struct MacroOutput { @@ -92,6 +92,7 @@ impl BuilderGenCtx { fn builder_impl(&self) -> Result { let finish_fn = self.finish_fn()?; + let transition_type_state_fn = self.transition_type_state_fn(); let setter_methods = self .named_members() .map(|member| MemberSettersCtx::new(self, member).setter_methods()) @@ -120,6 +121,7 @@ impl BuilderGenCtx { { #finish_fn #(#setter_methods)* + #transition_type_state_fn } }) } @@ -159,6 +161,43 @@ impl BuilderGenCtx { } } + fn transition_type_state_fn(&self) -> TokenStream2 { + let builder_ident = &self.builder_type.ident; + let builder_mod = &self.builder_mod.ident; + + let maybe_receiver_field = self + .receiver() + .map(|_| quote!(__private_receiver: self.__private_receiver,)); + + let maybe_start_fn_args_field = self + .start_fn_args() + .next() + .map(|_| quote!(__private_start_fn_args: self.__private_start_fn_args,)); + + let generic_args = &self.generics.args; + + quote! { + #[deprecated = + "this method is an implementation detail; it should not be used directly; \ + 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://discord.gg/QcBYSamw4c)" + ] + #[inline(always)] + fn __private_transition_type_state<__NewBuilderState: #builder_mod::State>(self) + -> #builder_ident<#(#generic_args,)* __NewBuilderState> + { + #builder_ident { + __private_phantom: ::core::marker::PhantomData, + #maybe_receiver_field + #maybe_start_fn_args_field + __private_named_members: self.__private_named_members, + } + } + } + } + fn start_fn(&self) -> Result { let builder_ident = &self.builder_type.ident; @@ -212,13 +251,18 @@ impl BuilderGenCtx { let ide_hints = self.ide_hints(); - let named_members_init_exprs = self.named_members().map(|member| { - if member.is_optional() { - quote!(None) - } else { - quote!(::bon::private::Member::unset()) + //`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 { + quote!(::core::default::Default::default()) + } else { + let none = quote!(::core::option::Option::None); + let nones = self.named_members().map(|_| &none); + quote! { + (#(#nones,)*) } - }); + }; let func = quote! { #(#docs)* @@ -244,7 +288,7 @@ impl BuilderGenCtx { __private_phantom: ::core::marker::PhantomData, #receiver_field_init #start_fn_args_field_init - __private_named_members: (#( #named_members_init_exprs, )*) + __private_named_members: #named_members_field_init, } } }; @@ -517,20 +561,10 @@ impl BuilderGenCtx { }); let named_members_types = self.named_members().map(|member| { - member - .as_optional_norm_ty() - .map(|ty| { - quote! { - ::core::option::Option<#ty> - } - }) - .unwrap_or_else(|| { - let ty = &member.norm_ty; - let member_pascal = &member.norm_ident_pascal; - quote! { - ::bon::private::Member - } - }) + let ty = member.as_optional_norm_ty().unwrap_or(&member.norm_ty); + quote! { + ::core::option::Option<#ty> + } }); let docs = &self.builder_type.docs; @@ -589,42 +623,53 @@ impl BuilderGenCtx { } }; - let suffix = member - .as_optional_norm_ty() - .map(|_| { - // For `Option` members we don't need any `unwrap_or_[else/default]`. - // The implementation of `From for Set>` already - // returns an `Option`. - if member.norm_ty.is_option() { - return None; - } + let index = &member.index; - let default = member - .param_default() - .flatten() - .map(|default| { - let has_into = member.param_into(&self.on_params)?; - let default = if has_into { - quote! { ::core::convert::Into::into((|| #default)()) } - } else { - quote! { #default } - }; - - Result::<_>::Ok(quote! { .unwrap_or_else(|| #default) }) - }) - .unwrap_or_else(|| Ok(quote! { .unwrap_or_default() })); - - Some(default) - }) - .map(Option::transpose) - .transpose()? - .unwrap_or_else(|| Some(quote! { .into_inner() })); + let member_field = quote! { + self.__private_named_members.#index + }; - let index = &member.index; + // 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.norm_ty.is_option() { + return Ok(member_field.to_token_stream()); + } - Ok(quote! { - self.__private_named_members.#index #suffix - }) + let default = match member.param_default() { + Some(Some(default)) => { + let has_into = member.param_into(&self.on_params)?; + let default = if has_into { + quote! { ::core::convert::Into::into((|| #default)()) } + } else { + quote! { #default } + }; + + quote! { + ::core::option::Option::unwrap_or_else(#member_field, || #default) + } + } + Some(None) => { + quote! { + ::core::option::Option::unwrap_or_default(#member_field) + } + } + None => { + quote! { + unsafe { + // SAFETY: we know that the member is set because we are in + // the `finish` function because this method uses the trait + // bounds 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). + ::core::option::Option::unwrap_unchecked(#member_field) + } + } + } + }; + + Ok(default) } fn finish_fn(&self) -> Result { diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index 640f49e1..89c7a1cf 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -35,7 +35,7 @@ impl<'a> MemberSettersCtx<'a> { fn_params: quote!(value: #fn_param_type), overwrite_docs: None, body: SetterBody::Default { - member_init: quote!(::bon::private::Member::set(value #maybe_into_call)), + member_init: quote!(Some(value #maybe_into_call)), }, })) } @@ -113,34 +113,10 @@ impl<'a> MemberSettersCtx<'a> { let body = match body { SetterBody::Custom(body) => body, SetterBody::Default { member_init } => { - let maybe_receiver_field = self - .builder_gen - .receiver() - .map(|_| quote!(__private_receiver: self.__private_receiver,)); - - let maybe_start_fn_args_field = self - .builder_gen - .start_fn_args() - .next() - .map(|_| quote!(__private_start_fn_args: self.__private_start_fn_args,)); - - let builder_ident = &self.builder_gen.builder_type.ident; - - let member_exprs = self.builder_gen.named_members().map(|other_member| { - if other_member.norm_ident == self.member.norm_ident { - return member_init.clone(); - } - let index = &other_member.index; - quote!(self.__private_named_members.#index) - }); - + let index = &self.member.index; quote! { - #builder_ident { - __private_phantom: ::core::marker::PhantomData, - #maybe_receiver_field - #maybe_start_fn_args_field - __private_named_members: (#( #member_exprs, )*) - } + self.__private_named_members.#index = #member_init; + self.__private_transition_type_state() } } }; @@ -176,7 +152,7 @@ impl<'a> MemberSettersCtx<'a> { clippy::impl_trait_in_params )] #[inline(always)] - #vis fn #method_name(self, #fn_params) -> #builder_ident<#(#generic_args,)* #state_transition> + #vis fn #method_name(mut self, #fn_params) -> #builder_ident<#(#generic_args,)* #state_transition> where BuilderTypeState::#member_pascal: ::bon::IsUnset, { From 6dc1aa12cb715a6600d7fd71df60d9aee4fac363 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 24 Sep 2024 21:00:25 +0000 Subject: [PATCH 018/119] Refactor, add `mutable` members support --- .../builder/builder_gen/builder_derives.rs | 13 +- .../src/builder/builder_gen/builder_params.rs | 36 ++- .../src/builder/builder_gen/input_fn.rs | 2 +- .../src/builder/builder_gen/input_struct.rs | 2 +- .../builder_gen/member/into_conversion.rs | 78 +++--- .../src/builder/builder_gen/member/mod.rs | 60 ++++- .../builder_gen/member/params/blanket.rs | 81 ++++++ .../member/{params.rs => params/mod.rs} | 19 +- bon-macros/src/builder/builder_gen/mod.rs | 240 +++++++++--------- .../src/builder/builder_gen/setter_methods.rs | 54 +++- bon/tests/integration/builder/cfgs.rs | 12 +- .../integration/ui/compile_fail/errors.stderr | 16 +- .../ui/compile_fail/warnings.stderr | 8 +- 13 files changed, 394 insertions(+), 227 deletions(-) create mode 100644 bon-macros/src/builder/builder_gen/member/params/blanket.rs rename bon-macros/src/builder/builder_gen/member/{params.rs => params/mod.rs} (87%) diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index cbe258e5..08ca47b1 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -104,11 +104,11 @@ impl BuilderGenCtx { #[automatically_derived] impl< #(#generics_decl,)* - BuilderTypeState: #builder_mod_ident::State + BuilderState: #builder_mod_ident::State > #clone for #builder_ident< #(#generic_args,)* - BuilderTypeState + BuilderState > #where_clause { @@ -140,7 +140,8 @@ impl BuilderGenCtx { let member_ident_str = member.public_ident().to_string(); let member_pascal = &member.norm_ident_pascal; - let debug_fn = member.as_optional_norm_ty() + let debug_fn = member + .as_optional_norm_ty() .map(|ty| quote!(debug_optional_member::<#ty>)) .unwrap_or_else(|| { let ty = &member.norm_ty; @@ -149,7 +150,7 @@ impl BuilderGenCtx { Some(quote! { // Skip members that are not set to reduce noise - if ::is_set() { + if ::is_set() { output.field( #member_ident_str, ::bon::private::derives::#debug_fn( @@ -204,11 +205,11 @@ impl BuilderGenCtx { #[automatically_derived] impl< #(#generics_decl,)* - BuilderTypeState: #builder_mod_ident::State + BuilderState: #builder_mod_ident::State > #debug for #builder_ident< #(#generic_args,)* - BuilderTypeState + BuilderState > #where_clause { diff --git a/bon-macros/src/builder/builder_gen/builder_params.rs b/bon-macros/src/builder/builder_gen/builder_params.rs index dce2af91..d0b290c3 100644 --- a/bon-macros/src/builder/builder_gen/builder_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params.rs @@ -62,6 +62,7 @@ pub(crate) struct BuilderDerives { pub(crate) struct OnParams { pub(crate) type_pattern: syn::Type, pub(crate) into: darling::util::Flag, + pub(crate) mutable: darling::util::Flag, } impl Parse for OnParams { @@ -74,17 +75,26 @@ impl Parse for OnParams { #[derive(FromMeta)] struct Parsed { into: darling::util::Flag, + mutable: darling::util::Flag, } - let Parsed { into } = Parsed::from_meta(&syn::parse_quote!(on(#rest)))?; - - if !into.is_present() { - return Err(syn::Error::new_spanned( - &rest, - "this #[builder(on(type_pattern, ...))] contains no options to override \ - the default behavior for the selected setters like `into`, so it \ - does nothing", - )); + let parsed = Parsed::from_meta(&syn::parse_quote!(on(#rest)))?; + + { + // Validate that at least some option was enabled. + // This lives in a separate block to make sure that if a new + // field is added to `Parsed` and unused here, then a compiler + // warning is emitted. + let Parsed { into, mutable } = &parsed; + + if !into.is_present() && !mutable.is_present() { + return Err(syn::Error::new_spanned( + &rest, + "this #[builder(on(type_pattern, ...))] contains no options to override \ + the default behavior for the selected setters like `into`, so it \ + does nothing", + )); + } } struct FindAttr { @@ -119,7 +129,13 @@ impl Parse for OnParams { "BUG: the type pattern does not match itself: {type_pattern:#?}" ); - Ok(Self { type_pattern, into }) + let Parsed { into, mutable } = parsed; + + Ok(Self { + type_pattern, + into, + mutable, + }) } } diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index 4853a281..44d5e9e5 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -311,7 +311,7 @@ impl FnInputCtx { }) .collect::>>()?; - let members = Member::from_raw(MemberOrigin::FnArg, members)?; + let members = Member::from_raw(&self.params.base.on, MemberOrigin::FnArg, members)?; let generics = self.generics(); diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index eea40481..0fb0b4dc 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -127,7 +127,7 @@ impl StructInputCtx { }) .collect::>>()?; - let members = Member::from_raw(MemberOrigin::StructField, members)?; + let members = Member::from_raw(&self.params.base.on, MemberOrigin::StructField, members)?; let generics = Generics::new( self.norm_struct.generics.params.iter().cloned().collect(), diff --git a/bon-macros/src/builder/builder_gen/member/into_conversion.rs b/bon-macros/src/builder/builder_gen/member/into_conversion.rs index 6c5bb9ca..7e472d30 100644 --- a/bon-macros/src/builder/builder_gen/member/into_conversion.rs +++ b/bon-macros/src/builder/builder_gen/member/into_conversion.rs @@ -1,10 +1,11 @@ -use super::{MemberOrigin, MemberParams, NamedMember, PositionalFnArgMember}; +use super::params::{BlanketParamName, EvalBlanketFlagParam}; +use super::{NamedMember, PositionalFnArgMember}; use crate::builder::builder_gen::builder_params::OnParams; use crate::util::prelude::*; use quote::{quote, ToTokens}; impl NamedMember { - pub(crate) fn param_into(&self, on_params: &[OnParams]) -> Result { + pub(super) fn merge_param_into(&mut self, on_params: &[OnParams]) -> Result { // 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 @@ -13,75 +14,60 @@ impl NamedMember { .as_optional_with_ty(&self.orig_ty) .unwrap_or(&self.orig_ty); - is_into_enabled(self.origin, &self.params, scrutinee, on_params) + self.params.into = EvalBlanketFlagParam { + on_params, + param_name: BlanketParamName::Into, + member_params: &self.params, + scrutinee, + origin: self.origin, + } + .eval()?; + + Ok(()) } } impl PositionalFnArgMember { - pub(crate) fn param_into(&self, on_params: &[OnParams]) -> Result { + pub(crate) fn merge_param_into(&mut self, on_params: &[OnParams]) -> 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.orig_ty.as_ref(); - is_into_enabled(self.origin, &self.params, scrutinee, on_params) + self.params.into = EvalBlanketFlagParam { + on_params, + param_name: BlanketParamName::Into, + member_params: &self.params, + scrutinee, + origin: self.origin, + } + .eval()?; + + Ok(()) } - pub(crate) fn fn_input_param(&self, on_params: &[OnParams]) -> Result { - let has_into = self.param_into(on_params)?; + pub(crate) fn fn_input_param(&self) -> TokenStream2 { + let has_into = self.params.into.is_present(); let norm_ty = &self.norm_ty; let ident = &self.ident; - let input = if has_into { + if has_into { quote! { #ident: impl Into<#norm_ty> } } else { quote! { #ident: #norm_ty } - }; - - Ok(input) + } } - pub(crate) fn maybe_into_ident_expr(&self, on_params: &[OnParams]) -> Result { - let has_into = self.param_into(on_params)?; + pub(crate) fn maybe_into_ident_expr(&self) -> TokenStream2 { + let has_into = self.params.into.is_present(); let ident = &self.ident; - let expr = if has_into { + if has_into { quote! { ::core::convert::Into::into(#ident) } } else { ident.to_token_stream() - }; - - Ok(expr) - } -} - -fn is_into_enabled( - origin: MemberOrigin, - member_params: &MemberParams, - scrutinee: &syn::Type, - on_params: &[OnParams], -) -> Result { - let verdict_from_defaults = on_params - .iter() - .map(|params| Ok((params, scrutinee.matches(¶ms.type_pattern)?))) - .collect::>>()? - .into_iter() - .filter(|(_, matched)| *matched) - .any(|(params, _)| params.into.is_present()); - - let verdict_from_override = member_params.into.is_present(); - - if verdict_from_defaults && verdict_from_override { - bail!( - &member_params.into.span(), - "this `#[builder(into)]` attribute is redundant, because `into` \ - is already implied for this member via the `#[builder(on(...))]` \ - at the top of the {}", - origin.parent_construct(), - ); + } } - - Ok(verdict_from_override || verdict_from_defaults) } diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 3cdd8f6f..60dded20 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -1,6 +1,7 @@ mod into_conversion; mod params; +use super::builder_params::OnParams; use crate::util::prelude::*; use darling::util::SpannedValue; use darling::FromAttributes; @@ -158,6 +159,23 @@ impl NamedMember { .as_ref() .map(|default| default.as_ref().as_ref()) } + + pub(crate) fn is_stateful(&self) -> bool { + !self.is_optional() || !self.params.mutable.is_present() + } + + pub(crate) fn merge_param_mutable(&mut self, on_params: &[OnParams]) -> Result { + self.params.mutable = params::EvalBlanketFlagParam { + on_params, + param_name: params::BlanketParamName::Mutable, + member_params: &self.params, + scrutinee: &self.norm_ty, + origin: self.origin, + } + .eval()?; + + Ok(()) + } } pub(crate) struct RawMember<'a> { @@ -174,6 +192,7 @@ impl Member { // (there is an other lint that checks for this). #[allow(single_use_lifetimes)] pub(crate) fn from_raw<'a>( + on_params: &[OnParams], origin: MemberOrigin, members: impl IntoIterator>, ) -> Result> { @@ -190,21 +209,23 @@ impl Member { let mut output = vec![]; - let start_fn_args = (0..).map_while(|index| { - let (member, params) = members.next_if(|(_, params)| params.start_fn.is_present())?; - let base = PositionalFnArgMember::new(origin, member, params); - Some(Self::StartFnArg(StartFnArgMember { + for index in 0.. { + let next = members.next_if(|(_, params)| params.start_fn.is_present()); + let (member, params) = match next { + Some(item) => item, + None => break, + }; + let base = PositionalFnArgMember::new(origin, member, on_params, params)?; + output.push(Self::StartFnArg(StartFnArgMember { base, index: index.into(), })) - }); - - output.extend(start_fn_args); + } while let Some((member, params)) = members.next_if(|(_, params)| params.finish_fn.is_present()) { - let member = PositionalFnArgMember::new(origin, member, params); + let member = PositionalFnArgMember::new(origin, member, on_params, params)?; output.push(Self::FinishFnArg(member)); } @@ -264,7 +285,7 @@ impl Member { let norm_ident = syn::Ident::new_maybe_raw(norm_ident, orig_ident.span()); let norm_ident_pascal = norm_ident.snake_to_pascal_case(); - let me = NamedMember { + let mut member = NamedMember { index: named_count.into(), origin, norm_ident_pascal, @@ -276,9 +297,11 @@ impl Member { docs, }; - me.validate()?; + member.merge_param_into(on_params)?; + member.merge_param_mutable(on_params)?; + member.validate()?; - output.push(Self::Named(me)); + output.push(Self::Named(member)); named_count += 1; } @@ -328,7 +351,12 @@ impl Member { } impl PositionalFnArgMember { - fn new(origin: MemberOrigin, member: RawMember<'_>, params: MemberParams) -> Self { + fn new( + origin: MemberOrigin, + member: RawMember<'_>, + on_params: &[OnParams], + params: MemberParams, + ) -> Result { let RawMember { attrs: _, ident, @@ -336,12 +364,16 @@ impl PositionalFnArgMember { orig_ty, } = member; - Self { + let mut me = Self { origin, ident, norm_ty, orig_ty, params, - } + }; + + me.merge_param_into(on_params)?; + + Ok(me) } } diff --git a/bon-macros/src/builder/builder_gen/member/params/blanket.rs b/bon-macros/src/builder/builder_gen/member/params/blanket.rs new file mode 100644 index 00000000..2e8132ea --- /dev/null +++ b/bon-macros/src/builder/builder_gen/member/params/blanket.rs @@ -0,0 +1,81 @@ +use super::MemberParams; +use crate::builder::builder_gen::builder_params::OnParams; +use crate::builder::builder_gen::member::MemberOrigin; +use crate::util::prelude::*; +use std::fmt; + +pub(crate) enum BlanketParamName { + Into, + Mutable, +} + +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::Mutable => fmt::Display::fmt(&super::ParamName::Mutable, f), + } + } +} + +impl BlanketParamName { + fn value_in_on_params(&self, on_params: &OnParams) -> darling::util::Flag { + match self { + Self::Into => on_params.into, + Self::Mutable => on_params.mutable, + } + } + + fn value_in_member_params(&self, member_params: &MemberParams) -> darling::util::Flag { + match self { + Self::Into => member_params.into, + Self::Mutable => member_params.mutable, + } + } +} + +pub(crate) struct EvalBlanketFlagParam<'a> { + pub(crate) on_params: &'a [OnParams], + pub(crate) param_name: BlanketParamName, + pub(crate) member_params: &'a MemberParams, + pub(crate) scrutinee: &'a syn::Type, + pub(crate) origin: MemberOrigin, +} + +impl EvalBlanketFlagParam<'_> { + pub(crate) fn eval(self) -> Result { + let Self { + on_params, + param_name, + member_params, + scrutinee, + origin, + } = self; + + let verdict_from_on_params = on_params + .iter() + .map(|params| Ok((params, scrutinee.matches(¶ms.type_pattern)?))) + .collect::>>()? + .into_iter() + .filter(|(_, matched)| *matched) + .map(|(params, _)| param_name.value_in_on_params(¶ms)) + .find(|flag| flag.is_present()); + + let value_in_member = param_name.value_in_member_params(member_params); + let flag = match (verdict_from_on_params, 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) + } +} diff --git a/bon-macros/src/builder/builder_gen/member/params.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs similarity index 87% rename from bon-macros/src/builder/builder_gen/member/params.rs rename to bon-macros/src/builder/builder_gen/member/params/mod.rs index 4c320991..09d5f525 100644 --- a/bon-macros/src/builder/builder_gen/member/params.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -1,4 +1,9 @@ -use super::MemberOrigin; +mod blanket; + +pub(crate) use blanket::{BlanketParamName, EvalBlanketFlagParam}; + +use super::{MemberOrigin, RawMember}; +use crate::builder::builder_gen::builder_params::OnParams; use crate::util::prelude::*; use darling::util::SpannedValue; use std::fmt; @@ -32,6 +37,14 @@ pub(crate) struct MemberParams { /// gets its own setter methods. pub(crate) start_fn: darling::util::Flag, pub(crate) finish_fn: darling::util::Flag, + + /// 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) mutable: darling::util::Flag, } #[derive(PartialEq, Eq, Clone, Copy)] @@ -42,6 +55,7 @@ enum ParamName { Skip, StartFn, FinishFn, + Mutable, } impl fmt::Display for ParamName { @@ -53,6 +67,7 @@ impl fmt::Display for ParamName { Self::Skip => "skip", Self::StartFn => "start_fn", Self::FinishFn => "finish_fn", + Self::Mutable => "mutable", }; f.write_str(str) } @@ -93,6 +108,7 @@ impl MemberParams { name, finish_fn, start_fn, + mutable, } = self; let attrs = [ @@ -102,6 +118,7 @@ impl MemberParams { (skip.is_some(), ParamName::Skip), (start_fn.is_present(), ParamName::StartFn), (finish_fn.is_present(), ParamName::FinishFn), + (mutable.is_present(), ParamName::Mutable), ]; attrs diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index c31ca4ec..1b3f3412 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -8,7 +8,9 @@ pub(crate) mod input_fn; pub(crate) mod input_struct; use crate::util::prelude::*; -use member::{Member, MemberOrigin, NamedMember, RawMember, StartFnArgMember}; +use member::{ + Member, MemberOrigin, NamedMember, PositionalFnArgMember, RawMember, StartFnArgMember, +}; use models::{ AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, StartFn, @@ -34,11 +36,15 @@ impl BuilderGenCtx { self.members.iter().filter_map(Member::as_start_fn_arg) } + 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 mut start_fn = self.start_fn(); let builder_mod = self.builder_mod(); let builder_decl = self.builder_decl(); - let builder_impl = self.builder_impl()?; + let builder_impl = self.builder_impl(); let builder_derives = self.builder_derives(); let default_allows = syn::parse_quote!(#[allow( @@ -90,13 +96,12 @@ impl BuilderGenCtx { }) } - fn builder_impl(&self) -> Result { - let finish_fn = self.finish_fn()?; + fn builder_impl(&self) -> TokenStream2 { + let finish_fn = self.finish_fn(); let transition_type_state_fn = self.transition_type_state_fn(); let setter_methods = self .named_members() - .map(|member| MemberSettersCtx::new(self, member).setter_methods()) - .collect::>>()?; + .map(|member| MemberSettersCtx::new(self, member).setter_methods()); let generics_decl = &self.generics.decl_without_defaults; let generic_args = &self.generics.args; @@ -106,16 +111,16 @@ impl BuilderGenCtx { let allows = allow_warnings_on_member_types(); - Ok(quote! { + quote! { #allows #[automatically_derived] impl< #(#generics_decl,)* - BuilderTypeState: #builder_mod::State + BuilderState: #builder_mod::State > #builder_ident< #(#generic_args,)* - BuilderTypeState + BuilderState > #where_clause { @@ -123,7 +128,7 @@ impl BuilderGenCtx { #(#setter_methods)* #transition_type_state_fn } - }) + } } /// Generates code that has no meaning to the compiler, but it helps @@ -198,7 +203,7 @@ impl BuilderGenCtx { } } - fn start_fn(&self) -> Result { + fn start_fn(&self) -> syn::ItemFn { let builder_ident = &self.builder_type.ident; let docs = &self.start_fn.attrs; @@ -235,15 +240,14 @@ impl BuilderGenCtx { let start_fn_params = self .start_fn_args() - .map(|member| member.base.fn_input_param(&self.on_params)) - .collect::>>()?; + .map(|member| member.base.fn_input_param()); - let start_fn_arg_exprs = self + let mut start_fn_arg_exprs = self .start_fn_args() - .map(|member| member.base.maybe_into_ident_expr(&self.on_params)) - .collect::>>()?; + .map(|member| member.base.maybe_into_ident_expr()) + .peekable(); - let start_fn_args_field_init = (!start_fn_arg_exprs.is_empty()).then(|| { + let start_fn_args_field_init = start_fn_arg_exprs.peek().is_some().then(|| { quote! { __private_start_fn_args: (#(#start_fn_arg_exprs,)*), } @@ -254,7 +258,7 @@ impl BuilderGenCtx { //`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 { + let named_members_field_init = if self.named_members().take(13).count() <= 12 { quote!(::core::default::Default::default()) } else { let none = quote!(::core::option::Option::None); @@ -264,7 +268,7 @@ impl BuilderGenCtx { } }; - let func = quote! { + syn::parse_quote! { #(#docs)* #[inline(always)] #[allow( @@ -291,9 +295,7 @@ impl BuilderGenCtx { __private_named_members: #named_members_field_init, } } - }; - - Ok(syn::parse_quote!(#func)) + } } fn phantom_data(&self) -> TokenStream2 { @@ -354,59 +356,70 @@ impl BuilderGenCtx { // explanation for it, I just didn't care to research it yet ¯\_(ツ)_/¯. #(#types,)* - // A special case of zero members requires storing `BuilderTypeState` in + // A special case of zero members requires storing `BuilderState` in // phantom data otherwise it would be reported as an unused type parameter. - fn() -> ::core::marker::PhantomData, + fn() -> ::core::marker::PhantomData, )> } } - fn state_transition_aliases(&self) -> impl Iterator + '_ { + fn state_transition_aliases(&self) -> TokenStream2 { let vis_child = &self.builder_mod.vis_child; - let is_single_member = self.named_members().take(2).count() == 1; - - self.named_members().map(move |member| { - let states = self.named_members().map(|other_member| { - if other_member.orig_ident == member.orig_ident { - let ident = &member.public_ident(); - quote! { - ::bon::private::Set - } - } else { - let member_pascal = &other_member.norm_ident_pascal; - quote! { - S::#member_pascal + + let stateful_members = self.stateful_members().collect::>(); + let is_single_member = stateful_members.len() == 1; + + stateful_members + .iter() + .map(|member| { + let states = stateful_members.iter().map(|other_member| { + if other_member.orig_ident == member.orig_ident { + let ident = &member.public_ident(); + quote! { + ::bon::private::Set + } + } else { + let member_pascal = &other_member.norm_ident_pascal; + quote! { + S::#member_pascal + } } + }); + + let member_ident = member.public_ident(); + let alias_ident = + quote::format_ident!("Set{}", member.norm_ident_pascal.raw_name()); + + if is_single_member { + let docs = format!( + "Changes the type state of the builder to set the member \ + `{member_ident}`.", + ); + + return quote! { + #[doc = #docs] + #vis_child type #alias_ident = ( #(#states,)* ); + }; } - }); - - let member_ident = member.public_ident(); - let alias_ident = quote::format_ident!("Set{}", member.norm_ident_pascal.raw_name()); - if is_single_member { let docs = format!( - "Changes the type state of the builder to set the member `{member_ident}`.", + "Changes the type state of the builder to set the member `{member_ident}`. \ + The `S` parameter is the type state to update. If not specified \ + then [`AllUnset`] state is used, which results in all members being \ + unset except for `{member_ident}`.\n\n\ + [`AllUnset`]: self::AllUnset", ); - return quote! { + quote! { #[doc = #docs] - #vis_child type #alias_ident = ( #(#states,)* ); - }; - } - - let docs = format!( - "Changes the type state of the builder to set the member `{member_ident}`. \ - The `S` parameter is the type state to update. If not specified \ - then [`AllUnset`] state is used, which results in all members being \ - unset except for `{member_ident}`.\n\n\ - [`AllUnset`]: self::AllUnset", - ); - - quote! { - #[doc = #docs] - #vis_child type #alias_ident = ( #(#states,)* ); - } - }) + #vis_child type #alias_ident< + S: self::State = self::AllUnset + > = ( + #(#states,)* + ); + } + }) + .concat() } fn builder_mod(&self) -> TokenStream2 { @@ -416,14 +429,14 @@ impl BuilderGenCtx { let builder_mod_docs = &self.builder_mod.docs; let builder_mod_ident = &self.builder_mod.ident; - let state_transition_aliases = self.state_transition_aliases().collect::>(); + let state_transition_aliases = self.state_transition_aliases(); - let named_members_idents = self - .named_members() + let stateful_members_idents = self + .stateful_members() .map(NamedMember::public_ident) .collect::>(); - let assoc_types_docs = self.named_members().map(|member| { + let assoc_types_docs = self.stateful_members().map(|member| { let ident = &member.public_ident(); format!( "Represents the state of the member `{ident}`.\n\n\ @@ -431,15 +444,15 @@ impl BuilderGenCtx { ) }); - let named_members_pascal = self - .named_members() + let stateful_members_pascal = self + .stateful_members() .map(|member| &member.norm_ident_pascal) .collect::>(); quote! { #( #builder_mod_docs )* #vis_mod mod #builder_mod_ident { - #( #state_transition_aliases )* + #state_transition_aliases /// Represents the builder's type state that specifies which members are set and which are not. /// @@ -451,8 +464,8 @@ impl BuilderGenCtx { #vis_child trait State: ::core::marker::Sized { #( #[doc = #assoc_types_docs] - type #named_members_pascal: ::bon::private::NamedMemberState< - self::members::#named_members_idents + type #stateful_members_pascal: ::bon::private::NamedMemberState< + self::members::#stateful_members_idents >; )* @@ -467,20 +480,20 @@ impl BuilderGenCtx { // members named `state` which would create a generic param named `State` // that would shadow the trait `State` in the same scope. impl<#( - #named_members_pascal: ::bon::private::NamedMemberState< - self::members::#named_members_idents + #stateful_members_pascal: ::bon::private::NamedMemberState< + self::members::#stateful_members_idents >, )*> - self::State for ( #(#named_members_pascal,)* ) + self::State for ( #(#stateful_members_pascal,)* ) { - #( type #named_members_pascal = #named_members_pascal; )* + #( type #stateful_members_pascal = #stateful_members_pascal; )* fn __sealed(_: self::sealed::Sealed) {} } /// Initial state of the builder where all named members are unset #vis_child type AllUnset = ( - #(::bon::private::Unset,)* + #(::bon::private::Unset,)* ); #[deprecated = @@ -500,7 +513,7 @@ impl BuilderGenCtx { // exported items of the parent module. unnameable_types, )] - #vis_child_child enum #named_members_idents {} + #vis_child_child enum #stateful_members_idents {} )* } } @@ -585,7 +598,7 @@ impl BuilderGenCtx { )] #builder_vis struct #builder_ident< #(#generics_decl,)* - BuilderTypeState: #builder_mod::State = #builder_mod::AllUnset + BuilderState: #builder_mod::State = #builder_mod::AllUnset > #where_clause { @@ -601,25 +614,23 @@ impl BuilderGenCtx { } } - fn finish_fn_member_expr(&self, member: &Member) -> Result { + fn finish_fn_member_expr(member: &Member) -> TokenStream2 { let member = match member { Member::Named(member) => member, Member::Skipped(member) => { - let expr = member + return member .value .as_ref() .as_ref() .map(|value| quote! { (|| #value)() }) .unwrap_or_else(|| quote! { ::core::default::Default::default() }); - - return Ok(expr); } Member::StartFnArg(member) => { let index = &member.index; - return Ok(quote! { self.__private_start_fn_args.#index }); + return quote! { self.__private_start_fn_args.#index }; } Member::FinishFnArg(member) => { - return member.maybe_into_ident_expr(&self.on_params); + return member.maybe_into_ident_expr(); } }; @@ -632,12 +643,12 @@ impl BuilderGenCtx { // 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.norm_ty.is_option() { - return Ok(member_field.to_token_stream()); + return member_field.to_token_stream(); } - let default = match member.param_default() { + match member.param_default() { Some(Some(default)) => { - let has_into = member.param_into(&self.on_params)?; + let has_into = member.params.into.is_present(); let default = if has_into { quote! { ::core::convert::Into::into((|| #default)()) } } else { @@ -667,34 +678,28 @@ impl BuilderGenCtx { } } } - }; - - Ok(default) + } } - fn finish_fn(&self) -> Result { - let members_vars_decls = self - .members - .iter() - .map(|member| { - let expr = self.finish_fn_member_expr(member)?; - let var_ident = member.orig_ident(); + fn finish_fn(&self) -> TokenStream2 { + 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. - let ty = member.norm_ty(); - - Ok(quote! { - let #var_ident: #ty = #expr; - }) - }) - .collect::>>()?; + // 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. + let ty = member.norm_ty(); + + quote! { + let #var_ident: #ty = #expr; + } + }); let where_bounds = self .named_members() @@ -702,7 +707,7 @@ impl BuilderGenCtx { .map(|member| { let member_pascal = &member.norm_ident_pascal; quote! { - BuilderTypeState::#member_pascal: ::bon::IsSet + BuilderState::#member_pascal: ::bon::IsSet } }); @@ -710,8 +715,7 @@ impl BuilderGenCtx { .members .iter() .filter_map(Member::as_finish_fn_arg) - .map(|member| member.fn_input_param(&self.on_params)) - .collect::>>()?; + .map(PositionalFnArgMember::fn_input_param); let body = &self.finish_fn.body.generate(&self.members); let asyncness = &self.finish_fn.asyncness; @@ -726,7 +730,7 @@ impl BuilderGenCtx { let finish_fn_ident = &self.finish_fn.ident; let output = &self.finish_fn.output; - Ok(quote! { + quote! { #(#attrs)* #[inline(always)] #[allow( @@ -747,7 +751,7 @@ impl BuilderGenCtx { #(#members_vars_decls)* #body } - }) + } } } diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index 89c7a1cf..134c677d 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -15,14 +15,14 @@ impl<'a> MemberSettersCtx<'a> { } } - pub(crate) fn setter_methods(&self) -> Result { + pub(crate) fn setter_methods(&self) -> TokenStream2 { let member_type = self.member.norm_ty.as_ref(); if let Some(inner_type) = self.member.as_optional_norm_ty() { return self.setters_for_optional_member(inner_type); } - let has_into = self.member.param_into(&self.builder_gen.on_params)?; + let has_into = self.member.params.into.is_present(); let (fn_param_type, maybe_into_call) = if has_into { (quote!(impl Into<#member_type>), quote!(.into())) @@ -30,18 +30,18 @@ impl<'a> MemberSettersCtx<'a> { (quote!(#member_type), quote!()) }; - Ok(self.setter_method(MemberSetterMethod { + self.setter_method(MemberSetterMethod { method_name: self.member.public_ident().clone(), fn_params: quote!(value: #fn_param_type), overwrite_docs: None, body: SetterBody::Default { member_init: quote!(Some(value #maybe_into_call)), }, - })) + }) } - fn setters_for_optional_member(&self, inner_type: &syn::Type) -> Result { - let has_into = self.member.param_into(&self.builder_gen.on_params)?; + fn setters_for_optional_member(&self, inner_type: &syn::Type) -> TokenStream2 { + let has_into = self.member.params.into.is_present(); let (inner_type, maybe_map_conv_call) = if has_into { (quote!(impl Into<#inner_type>), quote!(.map(Into::into))) } else { @@ -88,10 +88,10 @@ impl<'a> MemberSettersCtx<'a> { }, ]; - Ok(methods + methods .into_iter() .map(|method| self.setter_method(method)) - .collect()) + .concat() } fn setter_method(&self, method: MemberSetterMethod) -> TokenStream2 { @@ -114,9 +114,18 @@ impl<'a> MemberSettersCtx<'a> { SetterBody::Custom(body) => body, SetterBody::Default { member_init } => { let index = &self.member.index; + + let state_transition_call = if self.member.is_stateful() { + quote! { + .__private_transition_type_state() + } + } else { + quote! {} + }; + quote! { self.__private_named_members.#index = #member_init; - self.__private_transition_type_state() + self #state_transition_call } } }; @@ -127,10 +136,10 @@ impl<'a> MemberSettersCtx<'a> { quote::format_ident!("Set{}", self.member.norm_ident_pascal.raw_name()); let builder_mod = &self.builder_gen.builder_mod.ident; - let generic_param = if self.builder_gen.named_members().take(2).count() == 1 { + let generic_param = if self.builder_gen.stateful_members().take(2).count() == 1 { quote!() } else { - quote!() + quote!() }; let state_transition = quote! { @@ -140,6 +149,24 @@ impl<'a> MemberSettersCtx<'a> { let builder_ident = &self.builder_gen.builder_type.ident; let generic_args = &self.builder_gen.generics.args; + let return_type = if self.member.is_stateful() { + quote! { + #builder_ident<#(#generic_args,)* #state_transition> + } + } else { + quote! { Self } + }; + + let where_clause = if self.member.is_stateful() && !self.member.params.mutable.is_present() + { + quote! { + where + BuilderState::#member_pascal: ::bon::IsUnset, + } + } else { + quote! {} + }; + quote! { #( #docs )* #[allow( @@ -152,9 +179,8 @@ impl<'a> MemberSettersCtx<'a> { clippy::impl_trait_in_params )] #[inline(always)] - #vis fn #method_name(mut self, #fn_params) -> #builder_ident<#(#generic_args,)* #state_transition> - where - BuilderTypeState::#member_pascal: ::bon::IsUnset, + #vis fn #method_name(mut self, #fn_params) -> #return_type + #where_clause { #body } diff --git a/bon/tests/integration/builder/cfgs.rs b/bon/tests/integration/builder/cfgs.rs index 355d4f65..3376c4a7 100644 --- a/bon/tests/integration/builder/cfgs.rs +++ b/bon/tests/integration/builder/cfgs.rs @@ -66,7 +66,8 @@ fn fn_smoke() { arg1: bool, /// doc comment - #[cfg(not(all()))] arg1: u32, + #[cfg(not(all()))] + arg1: u32, #[cfg(any())] arg1: String, ) -> bool { @@ -84,7 +85,8 @@ fn fn_with_params() { #[cfg(all())] arg1: bool, /// doc comment - #[cfg(not(all()))] arg1: u32, + #[cfg(not(all()))] + arg1: u32, #[cfg_attr(all(), builder(default))] arg2: [u8; 4], @@ -124,7 +126,8 @@ fn impl_block() { arg1: bool, /// doc comment - #[cfg(not(all()))] arg1: u32, + #[cfg(not(all()))] + arg1: u32, #[cfg(any())] arg1: String, ) -> bool { @@ -137,7 +140,8 @@ fn impl_block() { #[cfg(all())] arg1: bool, /// doc comment - #[cfg(not(all()))] arg1: u32, + #[cfg(not(all()))] + arg1: u32, #[cfg_attr(all(), builder(default))] arg2: [u8; 4], diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr index bb48ff07..9319eb7e 100644 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ b/bon/tests/integration/ui/compile_fail/errors.stderr @@ -201,11 +201,11 @@ error[E0277]: the member `Unset` was not set, but this method requires it to | = help: the trait `IsSet` is not implemented for `Unset` = help: the trait `IsSet` is implemented for `Set` -note: required by a bound in `ExampleBuilder::::build` +note: required by a bound in `ExampleBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:24:14 | 24 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `ExampleBuilder::::build` + | ^^^^^^^ required by this bound in `ExampleBuilder::::build` 25 | struct Example { | ------- required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -218,11 +218,11 @@ error[E0277]: the member `Unset` was not set, but this method requires | = help: the trait `IsSet` is not implemented for `Unset` = help: the trait `IsSet` is implemented for `Set` -note: required by a bound in `ExampleBuilder::::build` +note: required by a bound in `ExampleBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:24:14 | 24 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `ExampleBuilder::::build` + | ^^^^^^^ required by this bound in `ExampleBuilder::::build` 25 | struct Example { | ------- required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -235,11 +235,11 @@ error[E0277]: the member `Set` was already set, but this method requires it t | = help: the trait `IsUnset` is not implemented for `Set` = help: the trait `IsUnset` is implemented for `Unset` -note: required by a bound in `ExampleBuilder::::y` +note: required by a bound in `ExampleBuilder::::y` --> tests/integration/ui/compile_fail/errors.rs:24:14 | 24 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `ExampleBuilder::::y` + | ^^^^^^^ required by this bound in `ExampleBuilder::::y` ... 27 | y: u32, | - required by a bound in this associated function @@ -253,11 +253,11 @@ error[E0277]: the member `Unset` was not set, but this method requires it | = help: the trait `IsSet` is not implemented for `Unset` = help: the trait `IsSet` is implemented for `Set` -note: required by a bound in `SutBuilder::::build` +note: required by a bound in `SutBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:42:18 | 42 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `SutBuilder::::build` + | ^^^^^^^ required by this bound in `SutBuilder::::build` 43 | struct Sut { | --- required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/bon/tests/integration/ui/compile_fail/warnings.stderr b/bon/tests/integration/ui/compile_fail/warnings.stderr index 83dcc440..a64ee93e 100644 --- a/bon/tests/integration/ui/compile_fail/warnings.stderr +++ b/bon/tests/integration/ui/compile_fail/warnings.stderr @@ -52,7 +52,7 @@ help: use `let _ = ...` to ignore the resulting value 33 | let _ = Example::builder().x(1); | +++++++ -error: unused return value of `ExampleBuilder::::build` that must be used +error: unused return value of `ExampleBuilder::::build` that must be used --> tests/integration/ui/compile_fail/warnings.rs:34:9 | 34 | Example::builder().x(1).y(2).build(); @@ -64,7 +64,7 @@ help: use `let _ = ...` to ignore the resulting value 34 | let _ = Example::builder().x(1).y(2).build(); | +++++++ -error: unused return value of `ExampleMustUseBuilder::::call` that must be used +error: unused return value of `ExampleMustUseBuilder::::call` that must be used --> tests/integration/ui/compile_fail/warnings.rs:36:9 | 36 | Example::must_use().call(); @@ -75,7 +75,7 @@ help: use `let _ = ...` to ignore the resulting value 36 | let _ = Example::must_use().call(); | +++++++ -error: unused return value of `MustUseBuilder::::call` that must be used +error: unused return value of `MustUseBuilder::::call` that must be used --> tests/integration/ui/compile_fail/warnings.rs:38:9 | 38 | must_use().call(); @@ -97,7 +97,7 @@ help: use `let _ = ...` to ignore the resulting value 39 | let _ = __orig_must_use(); | +++++++ -error: unused return value of `MustUseUnderCfgBuilder::::call` that must be used +error: unused return value of `MustUseUnderCfgBuilder::::call` that must be used --> tests/integration/ui/compile_fail/warnings.rs:47:9 | 47 | must_use_under_cfg().call(); From 71bb4a9ba18fa783d18aa2ea72c618991d6d0c1c Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 25 Sep 2024 12:17:09 +0000 Subject: [PATCH 019/119] Update the tag line in crate description --- bon/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bon/Cargo.toml b/bon/Cargo.toml index 822cadac..a66057cb 100644 --- a/bon/Cargo.toml +++ b/bon/Cargo.toml @@ -2,7 +2,7 @@ name = "bon" version = "2.3.0" -description = "Generate builders for everything!" +description = "Next gen compile-time-checked builder generator, named function arguments, and more!" categories = [ "rust-patterns", From 1ee0c3c73ea783a41ea981d5cc5a487ad89d6ade Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 25 Sep 2024 19:50:20 +0000 Subject: [PATCH 020/119] Fix grammar --- bon/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bon/Cargo.toml b/bon/Cargo.toml index a66057cb..d34be9c7 100644 --- a/bon/Cargo.toml +++ b/bon/Cargo.toml @@ -2,7 +2,7 @@ name = "bon" version = "2.3.0" -description = "Next gen compile-time-checked builder generator, named function arguments, and more!" +description = "Next-gen compile-time-checked builder generator, named function's arguments, and more!" categories = [ "rust-patterns", From a2e2f007d41d12b6e4c3039c113fe70c973370a1 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 25 Sep 2024 19:53:41 +0000 Subject: [PATCH 021/119] Undo visibility syntax change --- .../src/builder/builder_gen/builder_params.rs | 2 - bon-macros/src/util/mod.rs | 1 - bon-macros/src/util/parse.rs | 39 ------------------- website/reference/builder.md | 12 +++--- 4 files changed, 6 insertions(+), 48 deletions(-) delete mode 100644 bon-macros/src/util/parse.rs diff --git a/bon-macros/src/builder/builder_gen/builder_params.rs b/bon-macros/src/builder/builder_gen/builder_params.rs index d0b290c3..3e3e6451 100644 --- a/bon-macros/src/builder/builder_gen/builder_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params.rs @@ -195,8 +195,6 @@ impl ItemParamsParsing<'_> { #[derive(Debug, FromMeta)] struct Full { name: Option, - - #[darling(with = crate::util::parse::visibility, map = Some)] vis: Option, docs: Option, } diff --git a/bon-macros/src/util/mod.rs b/bon-macros/src/util/mod.rs index 21d0ae00..70a6883f 100644 --- a/bon-macros/src/util/mod.rs +++ b/bon-macros/src/util/mod.rs @@ -12,7 +12,6 @@ mod vec; mod visibility; pub(crate) mod ide; -pub(crate) mod parse; use prelude::*; diff --git a/bon-macros/src/util/parse.rs b/bon-macros/src/util/parse.rs deleted file mode 100644 index ab11222f..00000000 --- a/bon-macros/src/util/parse.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::util::prelude::*; -use std::fmt::Display; - -pub(crate) fn visibility(meta: &syn::Meta) -> Result { - let error = |prefix: &dyn Display, path: &str| { - err!( - meta, - "{prefix}; use the following syntax to \ - specify the visibility instead: `{path}(pub(...))`; if you intended \ - to specify private visibility, then use `{path}(pub(self))`" - ) - }; - - let meta = match meta { - syn::Meta::NameValue(name_val) => { - let path = darling::util::path_to_string(&name_val.path); - return Err(error( - &format_args!("`{path} = ...` syntax is not supported"), - &path, - )); - } - syn::Meta::Path(path) => { - let path = darling::util::path_to_string(path); - return Err(error(&"missing visibility value", &path)); - } - syn::Meta::List(meta) => meta, - }; - - meta.require_paren_delim()?; - - if meta.tokens.is_empty() { - let path = darling::util::path_to_string(&meta.path); - return Err(error(&"missing visibility value in parentheses", &path)); - } - - let visibility = syn::parse2::(meta.tokens.clone())?; - - Ok(visibility) -} diff --git a/website/reference/builder.md b/website/reference/builder.md index d0d8ac9c..bf19bf20 100644 --- a/website/reference/builder.md +++ b/website/reference/builder.md @@ -314,9 +314,9 @@ Usually you'd want the underlying positional function to be hidden to provide on This attribute can take several forms. - Simple: `#[builder(expose_positional_fn = identifier)]`. Sets only the name of the positional function. -- Verbose: `#[builder(expose_positional_fn(name = identifier, vis(visibility)))]`. +- Verbose: `#[builder(expose_positional_fn(name = identifier, vis = "visibility"))]`. Allows setting both the name and the visibility of the positional function. - Each key is optional. The `vis` must be specified in parentheses e.g. `vis(pub)`, `vis(pub(crate))`. For private visibility use the syntax `vis(pub(self))`. + Each key is optional. The `vis` must be specified as a string literal e.g. `"pub(crate)"`, `"pub"` or `""` (empty string means private visibility). If `vis` parameter is not specified, then the visibility of the exposed positional function will be the same as specified on the function that the `#[builder]` was applied to. @@ -477,9 +477,9 @@ The default name for this method is `builder`, and the default visibility is the This attribute can take several forms. - Simple: `#[builder(start_fn = identifier)]`. Overrides only the name of the "start" method. -- Verbose: `#[builder(start_fn(name = identifier, vis(visibility)]`. +- Verbose: `#[builder(start_fn(name = identifier, vis = "visibility"))]`. Allows overriding both the name and the visibility of the "start" method. - Each key is optional. The `vis` must be specified in parentheses e.g. `vis(pub)`, `vis(pub(crate))`. For private visibility use the syntax `vis(pub(self))`. + Each key is optional. The `vis` must be specified as a string literal e.g. `"pub(crate)"`, `"pub"` or `""` (empty string means private visibility). **Example:** @@ -503,9 +503,9 @@ User::init() // [!code highlight] use bon::Builder; // `User::init()` method will have `pub(crate)` visibility // [!code highlight] -// Use `vis(pub(self))` to make it fully private instead // [!code highlight] +// Use `vis = ""` to make it fully private instead // [!code highlight] #[derive(Builder)] -#[builder(start_fn(name = init, vis(pub(crate))))] // [!code highlight] +#[builder(start_fn(name = init, vis = "pub(crate)"))] // [!code highlight] pub struct User { id: u32 } From f8d08b50f6e10b5a2382fdd563a7cf7b9ba22dd1 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 25 Sep 2024 20:16:00 +0000 Subject: [PATCH 022/119] Update builder derives --- .../builder/builder_gen/builder_derives.rs | 28 +--- bon-macros/src/builder/builder_gen/mod.rs | 4 +- bon/src/private/derives.rs | 31 +--- bon/src/private/member.rs | 158 ------------------ bon/src/private/mod.rs | 34 +--- .../integration/builder/builder_derives.rs | 4 +- .../ui/compile_fail/builder_derives.stderr | 108 ++++++------ 7 files changed, 71 insertions(+), 296 deletions(-) delete mode 100644 bon/src/private/member.rs diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index 08ca47b1..7e60b7cc 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -85,16 +85,10 @@ impl BuilderGenCtx { // The type hints here are necessary to get better error messages // that point directly to the types that don't implement `Clone` // in the input code using the span info from the type hints. - let clone_fn = member - .as_optional_norm_ty() - .map(|ty| quote!(clone_optional_member::<#ty>)) - .unwrap_or_else(|| { - let ty = &member.norm_ty; - quote!(clone_required_member::<_, #ty>) - }); + let ty = member.as_optional_norm_ty().unwrap_or(&member.norm_ty); quote! { - ::bon::private::derives::#clone_fn( + ::bon::private::derives::clone_member::<#ty>( &self.__private_named_members.#member_index ) } @@ -138,24 +132,12 @@ impl BuilderGenCtx { Member::Named(member) => { let member_index = &member.index; let member_ident_str = member.public_ident().to_string(); - let member_pascal = &member.norm_ident_pascal; - - let debug_fn = member - .as_optional_norm_ty() - .map(|ty| quote!(debug_optional_member::<#ty>)) - .unwrap_or_else(|| { - let ty = &member.norm_ty; - quote!(debug_required_member::<_, #ty>) - }); - + let member_ty = member.as_optional_norm_ty().unwrap_or(&member.norm_ty); Some(quote! { - // Skip members that are not set to reduce noise - if ::is_set() { + if let ::core::option::Option::Some(value) = &self.__private_named_members.#member_index { output.field( #member_ident_str, - ::bon::private::derives::#debug_fn( - &self.__private_named_members.#member_index - ) + ::bon::private::derives::as_dyn_debug::<#member_ty>(value) ); } }) diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 1b3f3412..d3161c24 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -464,7 +464,7 @@ impl BuilderGenCtx { #vis_child trait State: ::core::marker::Sized { #( #[doc = #assoc_types_docs] - type #stateful_members_pascal: ::bon::private::NamedMemberState< + type #stateful_members_pascal: ::bon::private::MemberState< self::members::#stateful_members_idents >; )* @@ -480,7 +480,7 @@ impl BuilderGenCtx { // members named `state` which would create a generic param named `State` // that would shadow the trait `State` in the same scope. impl<#( - #stateful_members_pascal: ::bon::private::NamedMemberState< + #stateful_members_pascal: ::bon::private::MemberState< self::members::#stateful_members_idents >, )*> diff --git a/bon/src/private/derives.rs b/bon/src/private/derives.rs index 8ab72872..9320ace4 100644 --- a/bon/src/private/derives.rs +++ b/bon/src/private/derives.rs @@ -5,43 +5,16 @@ //! which improves the compile errors when the member's type `T` doesn't implement //! the target trait. //! -//! E.g. they remove the messages that reference to the internal [`Member`] -//! type completely like: -//! -//! ```not-rust -//! required for `Member<...>` to implement `Clone` -//! ``` -//! -//! They also improve the spans of error messages because compiler knows that it needs to +//! They improve the spans of error messages because compiler knows that it needs to //! point to the origin of the offending type (member's type T) from the turbofish //! syntax to where the type came from (original code written by the user). -use super::{Member, MemberState}; use core::fmt::Debug; #[inline(always)] -pub fn clone_optional_member(member: &Option) -> Option { +pub fn clone_member(member: &Option) -> Option { member.clone() } -#[inline(always)] -pub fn clone_required_member( - member: &Member, -) -> Member { - member.clone() -} - -#[inline(always)] -pub fn debug_optional_member(member: &Option) -> &dyn Debug { - member -} - -#[inline(always)] -pub fn debug_required_member( - member: &Member, -) -> &dyn Debug { - member -} - #[inline(always)] pub fn as_dyn_debug(member: &T) -> &dyn Debug { member diff --git a/bon/src/private/member.rs b/bon/src/private/member.rs deleted file mode 100644 index 965dd2aa..00000000 --- a/bon/src/private/member.rs +++ /dev/null @@ -1,158 +0,0 @@ -use super::MemberState; -use crate::{IsSet, IsUnset}; -use core::fmt; -use core::marker::PhantomData; -use core::mem::MaybeUninit; - -/// This is an optimized version of the [`Option`] type that encodes the state -/// of [`Some`] or [`None`] at compile time, and thus it's a completely zero-cost -/// transparent wrapper over the inner value of type `T`. -/// -/// The `State` generic type determines whether the value was set or not. It can -/// either implement the [`IsSet`] trait or the [`IsUnset`] trait but never both (!). -/// -/// This requirement that [`IsSet`] and [`IsUnset`] are mutually exclusive is -/// enforced by the type system and also the fact that these traits are sealed, -/// so nothing outside of this module can implement them. -/// -/// The [`MemberState`] trait bound is required because it's needed in the [`Drop`] -/// implementation which does a conditional [`drop`] based on the state of the value -/// (i.e., only when it [`IsSet`]). This is basically a workaround for the lack of -/// specialization in Rust, and that the compiler still conservatively assumes that -/// [`IsSet`] and [`IsUnset`] trait implementations can possible overlap even though -/// they are sealed. -#[doc(hidden)] -#[must_use] -pub struct Member { - /// The [`PhantomData`] uses an `fn()` pointer to signify that this type - /// doesn't hold an instance of `State`. - state: PhantomData State>, - - value: MaybeUninit, -} - -impl Drop for Member { - fn drop(&mut self) { - if State::is_set() { - // SAFETY: this is safe. The `value` is guaranteed to be initialized - // because `State::is_set()` returns `true` only when it implements - // the `IsSet` trait. The `IsSet` trait is sealed and implemented - // only for the `Set` type, and there is only one way to create - // a `MaybeCell` for `IsSet` state - via the `MaybeCell::new` - // constructor that initializes the `value` field. - // - // Also the `MaybeCell::into_inner` method runs `mem::forget` on the - // `MaybeCell` instance once it consumed the inner value, so the - // `drop` method won't be invoked in that case. - #[allow(unsafe_code)] - unsafe { - // MSRV: we can't use `MaybeUninit::assume_init_drop` here because - // it is only available since Rust 1.60.0 (or MSRV is 1.59.0) - core::ptr::drop_in_place(self.value.as_mut_ptr()); - } - } - } -} - -impl Member { - /// Creates a new [`Member`] with an uninitialized value. This is only - /// possible when the `State` implements the [`IsUnset`] trait. - #[inline(always)] - pub fn unset() -> Self { - Self { - state: PhantomData, - value: MaybeUninit::uninit(), - } - } -} - -impl Member { - /// Creates a new [`Member`] initialized with the specified value. This is - /// only possible when the `State` implements the [`IsSet`] trait. - #[inline(always)] - pub fn set(value: T) -> Self { - Self { - state: PhantomData, - value: MaybeUninit::new(value), - } - } - - /// Returns a reference to the value if it's set, otherwise `None`. - #[inline(always)] - pub fn into_inner(self) -> T { - // SAFETY: this is safe. The `value` is guaranteed to be initialized - // by the `Member::new` constructor. There is no other way to - // create a `Member` where `State: IsSet`. The trait implementation - // if `IsSet` and `IsUnset` are guaranteed to be mutually exclusive. - // They are sealed and implemented for Set/Unset types respectively. - #[allow(unsafe_code)] - unsafe { - // MSRV: we can't use `MaybeUninit::assume_init_read` here because - // it is only available since Rust 1.60.0 (or MSRV is 1.59.0) - let value = self.value.as_ptr().read(); - - // SAFETY: Make sure `drop` doesn't run to avoid double drop - // now that we have the `value` moved out of the `MaybeUninit`. - core::mem::forget(self); - - value - } - } -} - -impl Member { - #[inline(always)] - fn try_get(&self) -> Option<&T> { - if State::is_set() { - // SAFETY: this is safe. The `value` is guaranteed to be initialized - // by the `Member::new` constructor. There is no other way to - // create a `Member` where `State: IsSet`. The trait implementation - // if `IsSet` and `IsUnset` are guaranteed to be mutually exclusive. - // They are sealed and implemented for Set/Unset types respectively. - #[allow(unsafe_code)] - unsafe { - return Some(self.value.assume_init_ref()); - } - } - - None - } -} - -impl Clone for Member -where - State: MemberState, - T: Clone, -{ - #[inline(always)] - fn clone(&self) -> Self { - // `map_or_else` reads works, and writing a raw `if let` here may make - // it easier for the compiler to optimize this code out. - #[allow(clippy::option_if_let_else)] - if let Some(value) = self.try_get() { - Self { - state: PhantomData, - value: MaybeUninit::new(value.clone()), - } - } else { - Self { - state: PhantomData, - value: MaybeUninit::uninit(), - } - } - } -} - -impl fmt::Debug for Member -where - State: MemberState, - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(value) = self.try_get() { - fmt::Debug::fmt(value, f) - } else { - f.write_str("Unset") - } - } -} diff --git a/bon/src/private/mod.rs b/bon/src/private/mod.rs index dc44d827..6b49fce2 100644 --- a/bon/src/private/mod.rs +++ b/bon/src/private/mod.rs @@ -22,7 +22,6 @@ pub mod ide; pub mod derives; mod cfg_eval; -mod member; pub(crate) mod sealed { // The purpose of the `Sealed` trait **is** to be unnameable from outside the crate. @@ -33,8 +32,6 @@ pub(crate) mod sealed { impl Sealed for super::Set {} } -pub use member::*; - use sealed::Sealed; /// Used to implement the `alloc` feature. @@ -51,33 +48,14 @@ pub struct Set(Name); impl crate::IsSet for Set {} -/// Allows to statically check if a member is set or not. -/// This is basically a utility to do compile-time downcasts. -pub trait MemberState: Sealed { - fn is_set() -> bool; -} - -impl MemberState for Unset { - #[inline(always)] - fn is_set() -> bool { - false - } -} - -impl MemberState for Set { - #[inline(always)] - fn is_set() -> bool { - true - } -} - #[rustversion::attr( since(1.78.0), diagnostic::on_unimplemented( - message = "expected type state for the member `{Name}`, but found `{Self}`", - label = "expected type state for the member `{Name}`, but found `{Self}`", + message = "expected the type state for the member `{Name}`, but found `{Self}`", + label = "expected the type state for the member `{Name}`, but found `{Self}`", ) )] -pub trait NamedMemberState: MemberState {} -impl NamedMemberState for Set {} -impl NamedMemberState for Unset {} +pub trait MemberState: Sealed {} + +impl MemberState for Unset {} +impl MemberState for Set {} diff --git a/bon/tests/integration/builder/builder_derives.rs b/bon/tests/integration/builder/builder_derives.rs index 5ab1cd09..4d1a63de 100644 --- a/bon/tests/integration/builder/builder_derives.rs +++ b/bon/tests/integration/builder/builder_derives.rs @@ -13,7 +13,7 @@ fn smoke_fn() { assert_debug_eq( actual, - expect![[r#"SutBuilder { arg1: true, arg3: Some("value"), arg4: None }"#]], + expect![[r#"SutBuilder { arg1: true, arg3: "value" }"#]], ); } @@ -36,7 +36,7 @@ fn smoke_struct() { assert_debug_eq( actual, - expect![[r#"SutBuilder { arg1: true, arg3: Some("value"), arg4: None }"#]], + expect![[r#"SutBuilder { arg1: true, arg3: "value" }"#]], ); } diff --git a/bon/tests/integration/ui/compile_fail/builder_derives.stderr b/bon/tests/integration/ui/compile_fail/builder_derives.stderr index 7259161d..4c6a7a84 100644 --- a/bon/tests/integration/ui/compile_fail/builder_derives.stderr +++ b/bon/tests/integration/ui/compile_fail/builder_derives.stderr @@ -16,11 +16,11 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied 11 | no_impls_required: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | -note: required by a bound in `clone_required_member` +note: required by a bound in `clone_member` --> src/private/derives.rs | - | pub fn clone_required_member( - | ^^^^^ required by this bound in `clone_required_member` + | pub fn clone_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | 3 + #[derive(Clone)] @@ -33,11 +33,11 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied 13 | no_impl_optional: Option, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | -note: required by a bound in `clone_optional_member` +note: required by a bound in `clone_member` --> src/private/derives.rs | - | pub fn clone_optional_member(member: &Option) -> Option { - | ^^^^^ required by this bound in `clone_optional_member` + | pub fn clone_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | 3 + #[derive(Clone)] @@ -50,11 +50,11 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied 16 | no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | -note: required by a bound in `clone_optional_member` +note: required by a bound in `clone_member` --> src/private/derives.rs | - | pub fn clone_optional_member(member: &Option) -> Option { - | ^^^^^ required by this bound in `clone_optional_member` + | pub fn clone_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | 3 + #[derive(Clone)] @@ -88,11 +88,11 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` -note: required by a bound in `debug_required_member` +note: required by a bound in `as_dyn_debug` --> src/private/derives.rs | - | pub fn debug_required_member( - | ^^^^^ required by this bound in `debug_required_member` + | pub fn as_dyn_debug(member: &T) -> &dyn Debug { + | ^^^^^ required by this bound in `as_dyn_debug` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] @@ -107,11 +107,11 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` -note: required by a bound in `debug_optional_member` +note: required by a bound in `as_dyn_debug` --> src/private/derives.rs | - | pub fn debug_optional_member(member: &Option) -> &dyn Debug { - | ^^^^^ required by this bound in `debug_optional_member` + | pub fn as_dyn_debug(member: &T) -> &dyn Debug { + | ^^^^^ required by this bound in `as_dyn_debug` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] @@ -126,11 +126,11 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` -note: required by a bound in `debug_optional_member` +note: required by a bound in `as_dyn_debug` --> src/private/derives.rs | - | pub fn debug_optional_member(member: &Option) -> &dyn Debug { - | ^^^^^ required by this bound in `debug_optional_member` + | pub fn as_dyn_debug(member: &T) -> &dyn Debug { + | ^^^^^ required by this bound in `as_dyn_debug` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] @@ -155,11 +155,11 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied 26 | _no_impls_required: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | -note: required by a bound in `clone_required_member` +note: required by a bound in `clone_member` --> src/private/derives.rs | - | pub fn clone_required_member( - | ^^^^^ required by this bound in `clone_required_member` + | pub fn clone_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | 3 + #[derive(Clone)] @@ -172,11 +172,11 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied 28 | _no_impl_optional: Option, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | -note: required by a bound in `clone_optional_member` +note: required by a bound in `clone_member` --> src/private/derives.rs | - | pub fn clone_optional_member(member: &Option) -> Option { - | ^^^^^ required by this bound in `clone_optional_member` + | pub fn clone_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | 3 + #[derive(Clone)] @@ -189,11 +189,11 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied 31 | _no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | -note: required by a bound in `clone_optional_member` +note: required by a bound in `clone_member` --> src/private/derives.rs | - | pub fn clone_optional_member(member: &Option) -> Option { - | ^^^^^ required by this bound in `clone_optional_member` + | pub fn clone_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | 3 + #[derive(Clone)] @@ -227,11 +227,11 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` -note: required by a bound in `debug_required_member` +note: required by a bound in `as_dyn_debug` --> src/private/derives.rs | - | pub fn debug_required_member( - | ^^^^^ required by this bound in `debug_required_member` + | pub fn as_dyn_debug(member: &T) -> &dyn Debug { + | ^^^^^ required by this bound in `as_dyn_debug` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] @@ -246,11 +246,11 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` -note: required by a bound in `debug_optional_member` +note: required by a bound in `as_dyn_debug` --> src/private/derives.rs | - | pub fn debug_optional_member(member: &Option) -> &dyn Debug { - | ^^^^^ required by this bound in `debug_optional_member` + | pub fn as_dyn_debug(member: &T) -> &dyn Debug { + | ^^^^^ required by this bound in `as_dyn_debug` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] @@ -265,11 +265,11 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` -note: required by a bound in `debug_optional_member` +note: required by a bound in `as_dyn_debug` --> src/private/derives.rs | - | pub fn debug_optional_member(member: &Option) -> &dyn Debug { - | ^^^^^ required by this bound in `debug_optional_member` + | pub fn as_dyn_debug(member: &T) -> &dyn Debug { + | ^^^^^ required by this bound in `as_dyn_debug` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] @@ -300,11 +300,11 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied 46 | _no_impls_required: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | -note: required by a bound in `clone_required_member` +note: required by a bound in `clone_member` --> src/private/derives.rs | - | pub fn clone_required_member( - | ^^^^^ required by this bound in `clone_required_member` + | pub fn clone_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | 3 + #[derive(Clone)] @@ -317,11 +317,11 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied 48 | _no_impl_optional: Option, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | -note: required by a bound in `clone_optional_member` +note: required by a bound in `clone_member` --> src/private/derives.rs | - | pub fn clone_optional_member(member: &Option) -> Option { - | ^^^^^ required by this bound in `clone_optional_member` + | pub fn clone_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | 3 + #[derive(Clone)] @@ -334,11 +334,11 @@ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied 51 | _no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | -note: required by a bound in `clone_optional_member` +note: required by a bound in `clone_member` --> src/private/derives.rs | - | pub fn clone_optional_member(member: &Option) -> Option { - | ^^^^^ required by this bound in `clone_optional_member` + | pub fn clone_member(member: &Option) -> Option { + | ^^^^^ required by this bound in `clone_member` help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | 3 + #[derive(Clone)] @@ -386,11 +386,11 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` -note: required by a bound in `debug_required_member` +note: required by a bound in `as_dyn_debug` --> src/private/derives.rs | - | pub fn debug_required_member( - | ^^^^^ required by this bound in `debug_required_member` + | pub fn as_dyn_debug(member: &T) -> &dyn Debug { + | ^^^^^ required by this bound in `as_dyn_debug` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] @@ -405,11 +405,11 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` -note: required by a bound in `debug_optional_member` +note: required by a bound in `as_dyn_debug` --> src/private/derives.rs | - | pub fn debug_optional_member(member: &Option) -> &dyn Debug { - | ^^^^^ required by this bound in `debug_optional_member` + | pub fn as_dyn_debug(member: &T) -> &dyn Debug { + | ^^^^^ required by this bound in `as_dyn_debug` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] @@ -424,11 +424,11 @@ error[E0277]: `NoTraitImpls` doesn't implement `Debug` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` -note: required by a bound in `debug_optional_member` +note: required by a bound in `as_dyn_debug` --> src/private/derives.rs | - | pub fn debug_optional_member(member: &Option) -> &dyn Debug { - | ^^^^^ required by this bound in `debug_optional_member` + | pub fn as_dyn_debug(member: &T) -> &dyn Debug { + | ^^^^^ required by this bound in `as_dyn_debug` help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | 3 + #[derive(Debug)] From 3c7a2573756914734231127d650369748ca94521 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 25 Sep 2024 20:26:03 +0000 Subject: [PATCH 023/119] Fix warnings --- .../builder/builder_gen/member/params/mod.rs | 3 +- bon-macros/src/util/meta_list.rs | 29 ------------------- bon-macros/src/util/mod.rs | 2 -- 3 files changed, 1 insertion(+), 33 deletions(-) delete mode 100644 bon-macros/src/util/meta_list.rs diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index 09d5f525..494198ac 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -2,8 +2,7 @@ mod blanket; pub(crate) use blanket::{BlanketParamName, EvalBlanketFlagParam}; -use super::{MemberOrigin, RawMember}; -use crate::builder::builder_gen::builder_params::OnParams; +use super::MemberOrigin; use crate::util::prelude::*; use darling::util::SpannedValue; use std::fmt; diff --git a/bon-macros/src/util/meta_list.rs b/bon-macros/src/util/meta_list.rs deleted file mode 100644 index bb165d81..00000000 --- a/bon-macros/src/util/meta_list.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::util::prelude::*; - -pub(crate) trait MetaListExt { - fn require_paren_delim(&self) -> Result<()>; -} - -impl MetaListExt for syn::MetaList { - fn require_paren_delim(&self) -> Result<()> { - if matches!(self.delimiter, syn::MacroDelimiter::Paren(_)) { - return Ok(()); - } - - let path = darling::util::path_to_string(&self.path); - - bail!( - self, - "wrong delimiter, expected parentheses e.g. `{path}(...)`, but got {}", - delim_example(&path, &self.delimiter), - ); - } -} - -fn delim_example(path: &str, delimiter: &syn::MacroDelimiter) -> String { - match delimiter { - syn::MacroDelimiter::Paren(_) => format!("`{path}(...)`"), - syn::MacroDelimiter::Brace(_) => format!("`{path}{{...}}`"), - syn::MacroDelimiter::Bracket(_) => format!("`{path}[...]`"), - } -} diff --git a/bon-macros/src/util/mod.rs b/bon-macros/src/util/mod.rs index 70a6883f..3b862aad 100644 --- a/bon-macros/src/util/mod.rs +++ b/bon-macros/src/util/mod.rs @@ -4,7 +4,6 @@ mod generic_param; mod ident; mod item; mod iterator; -mod meta_list; mod path; mod punctuated; mod ty; @@ -35,7 +34,6 @@ pub(crate) mod prelude { 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; From c0e51a0acb78bafdbe43de89bdc21370ffbd798a Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 28 Sep 2024 11:31:25 +0000 Subject: [PATCH 024/119] Rename `mutable` -> `overwritable` --- .../src/builder/builder_gen/builder_params.rs | 12 +++--- .../src/builder/builder_gen/member/mod.rs | 10 ++--- .../builder_gen/member/params/blanket.rs | 8 ++-- .../builder/builder_gen/member/params/mod.rs | 10 ++--- bon-macros/src/builder/builder_gen/mod.rs | 42 ++++++++++--------- bon-macros/src/builder/builder_gen/models.rs | 6 +-- .../src/builder/builder_gen/setter_methods.rs | 2 +- bon/src/lib.rs | 9 ++++ 8 files changed, 54 insertions(+), 45 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/builder_params.rs b/bon-macros/src/builder/builder_gen/builder_params.rs index 3e3e6451..baaa6d35 100644 --- a/bon-macros/src/builder/builder_gen/builder_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params.rs @@ -62,7 +62,7 @@ pub(crate) struct BuilderDerives { pub(crate) struct OnParams { pub(crate) type_pattern: syn::Type, pub(crate) into: darling::util::Flag, - pub(crate) mutable: darling::util::Flag, + pub(crate) overwritable: darling::util::Flag, } impl Parse for OnParams { @@ -75,7 +75,7 @@ impl Parse for OnParams { #[derive(FromMeta)] struct Parsed { into: darling::util::Flag, - mutable: darling::util::Flag, + overwritable: darling::util::Flag, } let parsed = Parsed::from_meta(&syn::parse_quote!(on(#rest)))?; @@ -85,9 +85,9 @@ impl Parse for OnParams { // This lives in a separate block to make sure that if a new // field is added to `Parsed` and unused here, then a compiler // warning is emitted. - let Parsed { into, mutable } = &parsed; + let Parsed { into, overwritable } = &parsed; - if !into.is_present() && !mutable.is_present() { + if !into.is_present() && !overwritable.is_present() { return Err(syn::Error::new_spanned( &rest, "this #[builder(on(type_pattern, ...))] contains no options to override \ @@ -129,12 +129,12 @@ impl Parse for OnParams { "BUG: the type pattern does not match itself: {type_pattern:#?}" ); - let Parsed { into, mutable } = parsed; + let Parsed { into, overwritable } = parsed; Ok(Self { type_pattern, into, - mutable, + overwritable, }) } } diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 60dded20..56cabb10 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -161,13 +161,13 @@ impl NamedMember { } pub(crate) fn is_stateful(&self) -> bool { - !self.is_optional() || !self.params.mutable.is_present() + !self.is_optional() || !self.params.overwritable.is_present() } - pub(crate) fn merge_param_mutable(&mut self, on_params: &[OnParams]) -> Result { - self.params.mutable = params::EvalBlanketFlagParam { + pub(crate) fn merge_param_overwritable(&mut self, on_params: &[OnParams]) -> Result { + self.params.overwritable = params::EvalBlanketFlagParam { on_params, - param_name: params::BlanketParamName::Mutable, + param_name: params::BlanketParamName::Overwritable, member_params: &self.params, scrutinee: &self.norm_ty, origin: self.origin, @@ -298,7 +298,7 @@ impl Member { }; member.merge_param_into(on_params)?; - member.merge_param_mutable(on_params)?; + member.merge_param_overwritable(on_params)?; member.validate()?; output.push(Self::Named(member)); diff --git a/bon-macros/src/builder/builder_gen/member/params/blanket.rs b/bon-macros/src/builder/builder_gen/member/params/blanket.rs index 2e8132ea..988427af 100644 --- a/bon-macros/src/builder/builder_gen/member/params/blanket.rs +++ b/bon-macros/src/builder/builder_gen/member/params/blanket.rs @@ -6,14 +6,14 @@ use std::fmt; pub(crate) enum BlanketParamName { Into, - Mutable, + Overwritable, } 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::Mutable => fmt::Display::fmt(&super::ParamName::Mutable, f), + Self::Overwritable => fmt::Display::fmt(&super::ParamName::Overwritable, f), } } } @@ -22,14 +22,14 @@ impl BlanketParamName { fn value_in_on_params(&self, on_params: &OnParams) -> darling::util::Flag { match self { Self::Into => on_params.into, - Self::Mutable => on_params.mutable, + Self::Overwritable => on_params.overwritable, } } fn value_in_member_params(&self, member_params: &MemberParams) -> darling::util::Flag { match self { Self::Into => member_params.into, - Self::Mutable => member_params.mutable, + Self::Overwritable => member_params.overwritable, } } } diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index 494198ac..18736079 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -43,7 +43,7 @@ pub(crate) struct MemberParams { /// 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) mutable: darling::util::Flag, + pub(crate) overwritable: darling::util::Flag, } #[derive(PartialEq, Eq, Clone, Copy)] @@ -54,7 +54,7 @@ enum ParamName { Skip, StartFn, FinishFn, - Mutable, + Overwritable, } impl fmt::Display for ParamName { @@ -66,7 +66,7 @@ impl fmt::Display for ParamName { Self::Skip => "skip", Self::StartFn => "start_fn", Self::FinishFn => "finish_fn", - Self::Mutable => "mutable", + Self::Overwritable => "overwritable", }; f.write_str(str) } @@ -107,7 +107,7 @@ impl MemberParams { name, finish_fn, start_fn, - mutable, + overwritable, } = self; let attrs = [ @@ -117,7 +117,7 @@ impl MemberParams { (skip.is_some(), ParamName::Skip), (start_fn.is_present(), ParamName::StartFn), (finish_fn.is_present(), ParamName::FinishFn), - (mutable.is_present(), ParamName::Mutable), + (overwritable.is_present(), ParamName::Overwritable), ]; attrs diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index d3161c24..bb8c61dc 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -390,26 +390,20 @@ impl BuilderGenCtx { let alias_ident = quote::format_ident!("Set{}", member.norm_ident_pascal.raw_name()); - if is_single_member { - let docs = format!( - "Changes the type state of the builder to set the member \ - `{member_ident}`.", - ); + let docs = format!( + "Returns a [`State`] that has [`IsSet`] implemented for `{member_ident}`\n\ + \n\ + [`State`]: self::State\n\ + [`IsSet`]: ::bon::IsSet", + ); + if is_single_member { return quote! { #[doc = #docs] #vis_child type #alias_ident = ( #(#states,)* ); }; } - let docs = format!( - "Changes the type state of the builder to set the member `{member_ident}`. \ - The `S` parameter is the type state to update. If not specified \ - then [`AllUnset`] state is used, which results in all members being \ - unset except for `{member_ident}`.\n\n\ - [`AllUnset`]: self::AllUnset", - ); - quote! { #[doc = #docs] #vis_child type #alias_ident< @@ -439,8 +433,12 @@ impl BuilderGenCtx { let assoc_types_docs = self.stateful_members().map(|member| { let ident = &member.public_ident(); format!( - "Represents the state of the member `{ident}`.\n\n\ - See the [`State`] trait-level docs for details", + "Type state of the member `{ident}`.\n\ + \n\ + It can implement either [`IsSet`] or [`IsUnset`].\n\ + \n\ + [`IsSet`]: ::bon::IsSet\n\ + [`IsUnset`]: ::bon::IsUnset", ) }); @@ -454,13 +452,14 @@ impl BuilderGenCtx { #vis_mod mod #builder_mod_ident { #state_transition_aliases - /// Represents the builder's type state that specifies which members are set and which are not. + /// Builder's type state specifies if members are set or not (unset). /// /// You can use the associated types of this trait to control the state of individual members - /// with the [`bon::IsSet`] and [`bon::IsUnset`] traits. + /// with the [`IsSet`] and [`IsUnset`] traits. You can change the state of the members with + /// the `Set*` type aliases available in this module. /// - /// [`bon::IsSet`]: ::bon::IsSet - /// [`bon::IsUnset`]: ::bon::IsUnset + /// [`IsSet`]: ::bon::IsSet + /// [`IsUnset`]: ::bon::IsUnset #vis_child trait State: ::core::marker::Sized { #( #[doc = #assoc_types_docs] @@ -469,16 +468,19 @@ impl BuilderGenCtx { >; )* + #[doc(hidden)] fn __sealed(_: self::sealed::Sealed); } mod sealed { + #[allow(unnameable_types)] #vis_child_child enum Sealed {} } // Using `self::State` explicitly to avoid name conflicts with the // members named `state` which would create a generic param named `State` // that would shadow the trait `State` in the same scope. + #[doc(hidden)] impl<#( #stateful_members_pascal: ::bon::private::MemberState< self::members::#stateful_members_idents @@ -491,7 +493,7 @@ impl BuilderGenCtx { fn __sealed(_: self::sealed::Sealed) {} } - /// Initial state of the builder where all named members are unset + /// Initial state of the builder where all members are unset #vis_child type AllUnset = ( #(::bon::private::Unset,)* ); diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 03e45dfa..05e173d5 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -164,8 +164,7 @@ impl BuilderGenCtx { derives: builder_type.derives, docs: builder_type.docs.unwrap_or_else(|| { let doc = format!( - "Use builder syntax to set the required parameters and finish \ - by calling the method [`Self::{}()`].", + "Use builder syntax to set the inputs and finish with [`Self::{}()`].", finish_fn.ident ); @@ -218,8 +217,7 @@ impl BuilderGenCtx { docs: builder_mod.docs.unwrap_or_else(|| { let docs = format!( - "Contains the traits and type aliases for manipulating \ - the type state of the {}", + "Tools for manipulating the type state of the [`{}`].", builder_type.ident ); diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index 134c677d..d99b9df8 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -157,7 +157,7 @@ impl<'a> MemberSettersCtx<'a> { quote! { Self } }; - let where_clause = if self.member.is_stateful() && !self.member.params.mutable.is_present() + let where_clause = if self.member.is_stateful() && !self.member.params.overwritable.is_present() { quote! { where diff --git a/bon/src/lib.rs b/bon/src/lib.rs index 28791e9e..f19fd42d 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -40,6 +40,7 @@ use private::sealed::Sealed; /// // Import the type aliases for transforming the builder's type state /// use example_builder::{SetX, SetY}; /// +/// // Add method to the builder /// impl ExampleBuilder { /// fn x_doubled(self, value: i32) -> ExampleBuilder> /// where @@ -57,6 +58,14 @@ use private::sealed::Sealed; /// self.y(value * 2) /// } /// } +/// +/// let example = Example::builder() +/// .x_doubled(2) +/// .y_doubled(3) +/// .build(); +/// +/// assert_eq!(example.x, 4); +/// assert_eq!(example.y, 6); /// ``` #[rustversion::attr( since(1.78.0), From dc29b69771df7e512abdba44f163ca6617826fa7 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 28 Sep 2024 16:41:14 +0000 Subject: [PATCH 025/119] start_fn visibility inherits builder_type vis by default --- .../src/builder/builder_gen/input_fn.rs | 12 ++-- .../src/builder/builder_gen/input_struct.rs | 8 +-- .../src/builder/builder_gen/member/mod.rs | 2 +- .../builder_gen/member/params/blanket.rs | 4 +- bon-macros/src/builder/builder_gen/mod.rs | 10 ++-- bon-macros/src/builder/builder_gen/models.rs | 58 +++++++++++++------ .../src/builder/builder_gen/setter_methods.rs | 5 +- 7 files changed, 61 insertions(+), 38 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index 44d5e9e5..23cf8cc4 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -1,10 +1,10 @@ use super::builder_params::BuilderParams; use super::{ AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, - Member, MemberOrigin, RawMember, StartFn, + Member, MemberOrigin, RawMember, }; use crate::builder::builder_gen::builder_params::ItemParams; -use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams}; +use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; use crate::normalization::NormalizeSelfTy; use crate::util::prelude::*; use darling::util::SpannedValue; @@ -257,7 +257,7 @@ impl FnInputCtx { } pub(crate) fn into_builder_gen_ctx(self) -> Result { - let receiver = self.assoc_method_ctx(); + let assoc_method_ctx = self.assoc_method_ctx(); if self.impl_ctx.is_none() { let explanation = "\ @@ -376,7 +376,7 @@ impl FnInputCtx { .chain(fn_allows) .collect(); - let start_fn = StartFn { + let start_fn = StartFnParams { ident: start_fn_ident, // No override for visibility for the start fn is provided here. @@ -408,9 +408,9 @@ impl FnInputCtx { on_params: self.params.base.on, - assoc_method_ctx: receiver, + assoc_method_ctx, generics, - vis: self.norm_fn.vis, + orig_item_vis: self.norm_fn.vis, builder_type, builder_mod: self.params.base.builder_mod, diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 0fb0b4dc..b612ad36 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -1,9 +1,9 @@ use super::builder_params::{BuilderParams, ItemParams, ItemParamsParsing}; use super::{ AssocMethodCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, Member, MemberOrigin, - RawMember, StartFn, + RawMember, }; -use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams}; +use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; use crate::util::prelude::*; use darling::FromMeta; use quote::quote; @@ -183,7 +183,7 @@ impl StructInputCtx { vec![syn::parse_quote!(#[doc = #docs])] }); - let start_fn = StartFn { + let start_fn = StartFnParams { ident: start_fn_ident, vis: start_fn_vis, attrs: start_fn_docs, @@ -226,7 +226,7 @@ impl StructInputCtx { assoc_method_ctx, generics, - vis: self.norm_struct.vis, + orig_item_vis: self.norm_struct.vis, builder_type, builder_mod: self.params.base.builder_mod, diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 56cabb10..4b5176e2 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -219,7 +219,7 @@ impl Member { output.push(Self::StartFnArg(StartFnArgMember { base, index: index.into(), - })) + })); } while let Some((member, params)) = diff --git a/bon-macros/src/builder/builder_gen/member/params/blanket.rs b/bon-macros/src/builder/builder_gen/member/params/blanket.rs index 988427af..e0124483 100644 --- a/bon-macros/src/builder/builder_gen/member/params/blanket.rs +++ b/bon-macros/src/builder/builder_gen/member/params/blanket.rs @@ -58,8 +58,8 @@ impl EvalBlanketFlagParam<'_> { .collect::>>()? .into_iter() .filter(|(_, matched)| *matched) - .map(|(params, _)| param_name.value_in_on_params(¶ms)) - .find(|flag| flag.is_present()); + .map(|(params, _)| param_name.value_in_on_params(params)) + .find(darling::util::Flag::is_present); let value_in_member = param_name.value_in_member_params(member_params); let flag = match (verdict_from_on_params, value_in_member.is_present()) { diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index bb8c61dc..98d55415 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -13,7 +13,6 @@ use member::{ }; use models::{ AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, - StartFn, }; use quote::{quote, ToTokens}; use setter_methods::MemberSettersCtx; @@ -205,9 +204,8 @@ impl BuilderGenCtx { fn start_fn(&self) -> syn::ItemFn { let builder_ident = &self.builder_type.ident; - - let docs = &self.start_fn.attrs; - let vis = self.start_fn.vis.as_ref().unwrap_or(&self.vis); + let attrs = &self.start_fn.attrs; + let vis = &self.start_fn.vis; let start_fn_ident = &self.start_fn.ident; @@ -255,7 +253,7 @@ impl BuilderGenCtx { let ide_hints = self.ide_hints(); - //`Default` trait implementation is provided only for tuples up to 12 + // `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 { @@ -269,7 +267,7 @@ impl BuilderGenCtx { }; syn::parse_quote! { - #(#docs)* + #(#attrs)* #[inline(always)] #[allow( // This is intentional. We want the builder syntax to compile away diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 05e173d5..7c579dd1 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -43,15 +43,27 @@ pub(super) struct FinishFn { pub(super) struct StartFn { pub(super) ident: syn::Ident, + pub(super) vis: syn::Visibility, /// Additional attributes to apply to the item pub(super) attrs: Vec, - /// Overrides the common generics + /// Overrides the default generics pub(super) generics: Option, +} + +pub(super) struct StartFnParams { + pub(super) ident: syn::Ident, - /// If present overrides the automatic visibility + /// If present overrides the default visibility taken from the original item + /// the builder is generated for pub(super) vis: Option, + + /// Additional attributes to apply to the item + pub(super) attrs: Vec, + + /// Overrides the default generics + pub(super) generics: Option, } pub(super) struct BuilderType { @@ -64,6 +76,13 @@ pub(super) struct BuilderType { pub(super) docs: Vec, } +pub(super) struct BuilderTypeParams { + pub(super) ident: syn::Ident, + pub(super) vis: Option, + pub(super) derives: BuilderDerives, + pub(super) docs: Option>, +} + pub(super) struct BuilderMod { pub(super) ident: syn::Ident, @@ -109,9 +128,6 @@ pub(crate) struct BuilderGenCtx { pub(super) generics: Generics, - /// Visibility of the generated items - pub(super) vis: syn::Visibility, - pub(super) assoc_method_ctx: Option, pub(super) builder_type: BuilderType, @@ -126,23 +142,25 @@ pub(super) struct BuilderGenCtxParams { pub(super) allow_attrs: Vec, pub(super) on_params: 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) vis: syn::Visibility, + pub(super) assoc_method_ctx: Option, pub(super) builder_type: BuilderTypeParams, pub(super) builder_mod: ItemParams, - pub(super) start_fn: StartFn, + pub(super) start_fn: StartFnParams, pub(super) finish_fn: FinishFn, } -pub(super) struct BuilderTypeParams { - pub(super) ident: syn::Ident, - pub(super) vis: Option, - pub(super) derives: BuilderDerives, - pub(super) docs: Option>, -} - impl BuilderGenCtx { pub(super) fn new(params: BuilderGenCtxParams) -> Result { let BuilderGenCtxParams { @@ -150,7 +168,7 @@ impl BuilderGenCtx { allow_attrs, on_params, generics, - vis, + orig_item_vis, assoc_method_ctx, builder_type, builder_mod, @@ -160,7 +178,7 @@ impl BuilderGenCtx { let builder_type = BuilderType { ident: builder_type.ident, - vis: builder_type.vis.unwrap_or_else(|| vis.clone()), + vis: builder_type.vis.unwrap_or(orig_item_vis), derives: builder_type.derives, docs: builder_type.docs.unwrap_or_else(|| { let doc = format!( @@ -226,12 +244,18 @@ impl BuilderGenCtx { } }; + let start_fn = StartFn { + ident: start_fn.ident, + attrs: start_fn.attrs, + generics: start_fn.generics, + vis: start_fn.vis.unwrap_or_else(|| builder_type.vis.clone()), + }; + Ok(Self { members, allow_attrs, on_params, generics, - vis, assoc_method_ctx, builder_type, builder_mod, diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index d99b9df8..48fc12f6 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -108,8 +108,6 @@ impl<'a> MemberSettersCtx<'a> { None => self.generate_docs_for_setter(), }; - let vis = &self.builder_gen.vis; - let body = match body { SetterBody::Custom(body) => body, SetterBody::Default { member_init } => { @@ -167,6 +165,9 @@ impl<'a> MemberSettersCtx<'a> { quote! {} }; + + let vis = &self.builder_gen.builder_type.vis; + quote! { #( #docs )* #[allow( From b0d3380bd37e29b0d3c251d1d504757c8d073822 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 28 Sep 2024 17:14:08 +0000 Subject: [PATCH 026/119] Try vendor IsSet/UnsetTraits --- bon-macros/src/builder/builder_gen/mod.rs | 40 ++++++++++++++++++- .../src/builder/builder_gen/setter_methods.rs | 2 +- bon/src/private/mod.rs | 2 + 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 98d55415..3b89ce92 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -450,6 +450,42 @@ impl BuilderGenCtx { #vis_mod mod #builder_mod_ident { #state_transition_aliases + /// Marker trait implemented by members that are set. + #[::bon::private::rustversion::attr( + since(1.78.0), + diagnostic::on_unimplemented( + message = "the member `{Self}` was not set, but this method requires it to be set", + label = "the member `{Self}` was not set, but this method requires it to be set", + ) + )] + #vis_child trait IsSet { + #[doc(hidden)] + fn __sealed(_: self::sealed::Sealed); + } + + #[doc(hidden)] + impl IsSet for ::bon::private::Set { + fn __sealed(_: self::sealed::Sealed) {} + } + + /// Marker trait implemented by members that are not set. + #[::bon::private::rustversion::attr( + since(1.78.0), + diagnostic::on_unimplemented( + message = "the member `{Self}` was already set, but this method requires it to be unset", + label = "the member `{Self}` was already set, but this method requires it to be unset", + ) + )] + #vis_child trait IsUnset { + #[doc(hidden)] + fn __sealed(_: self::sealed::Sealed); + } + + #[doc(hidden)] + impl IsUnset for ::bon::private::Unset { + fn __sealed(_: self::sealed::Sealed) {} + } + /// Builder's type state specifies if members are set or not (unset). /// /// You can use the associated types of this trait to control the state of individual members @@ -701,13 +737,15 @@ impl BuilderGenCtx { } }); + let builder_mod = &self.builder_mod.ident; + let where_bounds = self .named_members() .filter(|member| !member.is_optional()) .map(|member| { let member_pascal = &member.norm_ident_pascal; quote! { - BuilderState::#member_pascal: ::bon::IsSet + BuilderState::#member_pascal: #builder_mod::IsSet } }); diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index 48fc12f6..f9c19ea3 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -159,7 +159,7 @@ impl<'a> MemberSettersCtx<'a> { { quote! { where - BuilderState::#member_pascal: ::bon::IsUnset, + BuilderState::#member_pascal: #builder_mod::IsUnset, } } else { quote! {} diff --git a/bon/src/private/mod.rs b/bon/src/private/mod.rs index 6b49fce2..59ef5cc7 100644 --- a/bon/src/private/mod.rs +++ b/bon/src/private/mod.rs @@ -59,3 +59,5 @@ pub trait MemberState: Sealed {} impl MemberState for Unset {} impl MemberState for Set {} + +pub use rustversion; From 9de917df93e4cccb45862fb3145271879fb9cd01 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 28 Sep 2024 19:49:39 +0000 Subject: [PATCH 027/119] Rename `builder_mod -> state_mod`, cleanup --- .../builder/builder_gen/builder_derives.rs | 8 +- .../src/builder/builder_gen/builder_params.rs | 6 +- .../src/builder/builder_gen/input_fn.rs | 2 +- .../src/builder/builder_gen/input_struct.rs | 2 +- bon-macros/src/builder/builder_gen/mod.rs | 66 +++++---- bon-macros/src/builder/builder_gen/models.rs | 29 ++-- .../src/builder/builder_gen/setter_methods.rs | 6 +- bon/src/lib.rs | 128 +++++++++--------- bon/src/private/mod.rs | 4 - bon/tests/integration/builder/raw_idents.rs | 10 +- 10 files changed, 133 insertions(+), 128 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index 7e60b7cc..1731af27 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -77,7 +77,7 @@ impl BuilderGenCtx { }); let where_clause = self.where_clause_for_derive(&clone); - let builder_mod_ident = &self.builder_mod.ident; + let state_mod = &self.state_mod.ident; let clone_named_members = self.named_members().map(|member| { let member_index = &member.index; @@ -98,7 +98,7 @@ impl BuilderGenCtx { #[automatically_derived] impl< #(#generics_decl,)* - BuilderState: #builder_mod_ident::State + BuilderState: #state_mod::State > #clone for #builder_ident< #(#generic_args,)* @@ -177,7 +177,7 @@ impl BuilderGenCtx { let debug = quote!(::core::fmt::Debug); let where_clause = self.where_clause_for_derive(&debug); - let builder_mod_ident = &self.builder_mod.ident; + 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; @@ -187,7 +187,7 @@ impl BuilderGenCtx { #[automatically_derived] impl< #(#generics_decl,)* - BuilderState: #builder_mod_ident::State + BuilderState: #state_mod::State > #debug for #builder_ident< #(#generic_args,)* diff --git a/bon-macros/src/builder/builder_gen/builder_params.rs b/bon-macros/src/builder/builder_gen/builder_params.rs index baaa6d35..be1bb394 100644 --- a/bon-macros/src/builder/builder_gen/builder_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params.rs @@ -22,7 +22,7 @@ fn parse_builder_type(meta: &syn::Meta) -> Result { .parse() } -fn parse_builder_mod(meta: &syn::Meta) -> Result { +fn parse_state_mod(meta: &syn::Meta) -> Result { ItemParamsParsing { meta, reject_self_mentions: Some("builder module"), @@ -38,8 +38,8 @@ pub(crate) struct BuilderParams { #[darling(default, with = parse_builder_type)] pub(crate) builder_type: ItemParams, - #[darling(default, with = parse_builder_mod)] - pub(crate) builder_mod: ItemParams, + #[darling(default, with = parse_state_mod)] + pub(crate) state_mod: ItemParams, #[darling(multiple)] pub(crate) on: Vec, diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index 23cf8cc4..bb0529fd 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -413,7 +413,7 @@ impl FnInputCtx { orig_item_vis: self.norm_fn.vis, builder_type, - builder_mod: self.params.base.builder_mod, + state_mod: self.params.base.state_mod, start_fn, finish_fn, }) diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index b612ad36..b2e33cc6 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -229,7 +229,7 @@ impl StructInputCtx { orig_item_vis: self.norm_struct.vis, builder_type, - builder_mod: self.params.base.builder_mod, + state_mod: self.params.base.state_mod, start_fn, finish_fn, }) diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 3b89ce92..60325857 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -41,7 +41,7 @@ impl BuilderGenCtx { pub(crate) fn output(self) -> Result { let mut start_fn = self.start_fn(); - let builder_mod = self.builder_mod(); + let state_mod = self.state_mod(); let builder_decl = self.builder_decl(); let builder_impl = self.builder_impl(); let builder_derives = self.builder_derives(); @@ -57,7 +57,7 @@ impl BuilderGenCtx { // -- Postprocessing -- // Here we parse all items back and add the `allow` attributes to them. let other_items = quote! { - #builder_mod + #state_mod #builder_decl #builder_derives #builder_impl @@ -106,7 +106,7 @@ impl BuilderGenCtx { let generic_args = &self.generics.args; let where_clause = &self.generics.where_clause; let builder_ident = &self.builder_type.ident; - let builder_mod = &self.builder_mod.ident; + let state_mod = &self.state_mod.ident; let allows = allow_warnings_on_member_types(); @@ -115,7 +115,7 @@ impl BuilderGenCtx { #[automatically_derived] impl< #(#generics_decl,)* - BuilderState: #builder_mod::State + BuilderState: #state_mod::State > #builder_ident< #(#generic_args,)* @@ -167,7 +167,7 @@ impl BuilderGenCtx { fn transition_type_state_fn(&self) -> TokenStream2 { let builder_ident = &self.builder_type.ident; - let builder_mod = &self.builder_mod.ident; + let state_mod = &self.state_mod.ident; let maybe_receiver_field = self .receiver() @@ -189,7 +189,7 @@ impl BuilderGenCtx { (https://discord.gg/QcBYSamw4c)" ] #[inline(always)] - fn __private_transition_type_state<__NewBuilderState: #builder_mod::State>(self) + fn __private_transition_type_state<__NewBuilderState: #state_mod::State>(self) -> #builder_ident<#(#generic_args,)* __NewBuilderState> { #builder_ident { @@ -362,7 +362,7 @@ impl BuilderGenCtx { } fn state_transition_aliases(&self) -> TokenStream2 { - let vis_child = &self.builder_mod.vis_child; + let vis_child = &self.state_mod.vis_child; let stateful_members = self.stateful_members().collect::>(); let is_single_member = stateful_members.len() == 1; @@ -414,13 +414,13 @@ impl BuilderGenCtx { .concat() } - fn builder_mod(&self) -> TokenStream2 { - let vis_mod = &self.builder_mod.vis; - let vis_child = &self.builder_mod.vis_child; - let vis_child_child = &self.builder_mod.vis_child_child; + fn state_mod(&self) -> TokenStream2 { + let vis_mod = &self.state_mod.vis; + let vis_child = &self.state_mod.vis_child; + let vis_child_child = &self.state_mod.vis_child_child; - let builder_mod_docs = &self.builder_mod.docs; - let builder_mod_ident = &self.builder_mod.ident; + let state_mod_docs = &self.state_mod.docs; + let state_mod_ident = &self.state_mod.ident; let state_transition_aliases = self.state_transition_aliases(); let stateful_members_idents = self @@ -446,9 +446,20 @@ impl BuilderGenCtx { .collect::>(); quote! { - #( #builder_mod_docs )* - #vis_mod mod #builder_mod_ident { - #state_transition_aliases + #( #state_mod_docs )* + // This is 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. + #[allow(unnameable_types, unreachable_pub)] + #vis_mod mod #state_mod_ident { /// Marker trait implemented by members that are set. #[::bon::private::rustversion::attr( @@ -459,6 +470,7 @@ impl BuilderGenCtx { ) )] #vis_child trait IsSet { + // Also a method without `self` makes the trait non-object safe #[doc(hidden)] fn __sealed(_: self::sealed::Sealed); } @@ -477,6 +489,7 @@ impl BuilderGenCtx { ) )] #vis_child trait IsUnset { + // Also a method without `self` makes the trait non-object safe #[doc(hidden)] fn __sealed(_: self::sealed::Sealed); } @@ -507,7 +520,6 @@ impl BuilderGenCtx { } mod sealed { - #[allow(unnameable_types)] #vis_child_child enum Sealed {} } @@ -540,18 +552,12 @@ impl BuilderGenCtx { #[doc(hidden)] mod members { #( - #[allow( - non_camel_case_types, - - // This is intentional. We don't want users to touch - // the implementation details of the builder's type - // signature like this one. They should use only the - // exported items of the parent module. - unnameable_types, - )] + #[allow(non_camel_case_types)] #vis_child_child enum #stateful_members_idents {} )* } + + #state_transition_aliases } } } @@ -562,7 +568,7 @@ impl BuilderGenCtx { let generics_decl = &self.generics.decl_with_defaults; let where_clause = &self.generics.where_clause; let phantom_data = self.phantom_data(); - let builder_mod = &self.builder_mod.ident; + let state_mod = &self.state_mod.ident; let private_field_attrs = quote! { // The fields can't be hidden using Rust's privacy syntax. @@ -634,7 +640,7 @@ impl BuilderGenCtx { )] #builder_vis struct #builder_ident< #(#generics_decl,)* - BuilderState: #builder_mod::State = #builder_mod::AllUnset + BuilderState: #state_mod::State = #state_mod::AllUnset > #where_clause { @@ -737,7 +743,7 @@ impl BuilderGenCtx { } }); - let builder_mod = &self.builder_mod.ident; + let state_mod = &self.state_mod.ident; let where_bounds = self .named_members() @@ -745,7 +751,7 @@ impl BuilderGenCtx { .map(|member| { let member_pascal = &member.norm_ident_pascal; quote! { - BuilderState::#member_pascal: #builder_mod::IsSet + BuilderState::#member_pascal: #state_mod::IsSet } }); diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 7c579dd1..a46e95a7 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -83,7 +83,7 @@ pub(super) struct BuilderTypeParams { pub(super) docs: Option>, } -pub(super) struct BuilderMod { +pub(super) struct StateMod { pub(super) ident: syn::Ident, /// Visibility of the builder module itself. @@ -131,7 +131,7 @@ pub(crate) struct BuilderGenCtx { pub(super) assoc_method_ctx: Option, pub(super) builder_type: BuilderType, - pub(super) builder_mod: BuilderMod, + pub(super) state_mod: StateMod, pub(super) start_fn: StartFn, pub(super) finish_fn: FinishFn, } @@ -156,7 +156,7 @@ pub(super) struct BuilderGenCtxParams { pub(super) assoc_method_ctx: Option, pub(super) builder_type: BuilderTypeParams, - pub(super) builder_mod: ItemParams, + pub(super) state_mod: ItemParams, pub(super) start_fn: StartFnParams, pub(super) finish_fn: FinishFn, } @@ -171,7 +171,7 @@ impl BuilderGenCtx { orig_item_vis, assoc_method_ctx, builder_type, - builder_mod, + state_mod, start_fn, finish_fn, } = params; @@ -192,9 +192,9 @@ impl BuilderGenCtx { }), }; - let builder_mod = { - let ident_overridden = builder_mod.name.is_some(); - let ident = builder_mod + let state_mod = { + let ident_overridden = state_mod.name.is_some(); + let ident = state_mod .name .unwrap_or_else(|| builder_type.ident.pascal_to_snake_case()); @@ -214,11 +214,16 @@ impl BuilderGenCtx { 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(builder_mod = ...)]`" + `#[builder(state_mod = ...)]`" ); } - let vis = builder_mod.vis.unwrap_or_else(|| builder_type.vis.clone()); + // 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.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 @@ -226,14 +231,14 @@ impl BuilderGenCtx { let vis_child = builder_type.vis.clone().into_equivalent_in_child_module()?; let vis_child_child = vis_child.clone().into_equivalent_in_child_module()?; - BuilderMod { + StateMod { vis, vis_child, vis_child_child, ident, - docs: builder_mod.docs.unwrap_or_else(|| { + docs: state_mod.docs.unwrap_or_else(|| { let docs = format!( "Tools for manipulating the type state of the [`{}`].", builder_type.ident @@ -258,7 +263,7 @@ impl BuilderGenCtx { generics, assoc_method_ctx, builder_type, - builder_mod, + state_mod, start_fn, finish_fn, }) diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index f9c19ea3..8a8b2738 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -133,7 +133,7 @@ impl<'a> MemberSettersCtx<'a> { let state_transition = quote::format_ident!("Set{}", self.member.norm_ident_pascal.raw_name()); - let builder_mod = &self.builder_gen.builder_mod.ident; + let state_mod = &self.builder_gen.state_mod.ident; let generic_param = if self.builder_gen.stateful_members().take(2).count() == 1 { quote!() } else { @@ -141,7 +141,7 @@ impl<'a> MemberSettersCtx<'a> { }; let state_transition = quote! { - #builder_mod::#state_transition #generic_param + #state_mod::#state_transition #generic_param }; let builder_ident = &self.builder_gen.builder_type.ident; @@ -159,7 +159,7 @@ impl<'a> MemberSettersCtx<'a> { { quote! { where - BuilderState::#member_pascal: #builder_mod::IsUnset, + BuilderState::#member_pascal: #state_mod::IsUnset, } } else { quote! {} diff --git a/bon/src/lib.rs b/bon/src/lib.rs index f19fd42d..119fbc6a 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -21,68 +21,68 @@ mod collections; /// Rexport all macros from the proc-macro crate. pub use bon_macros::*; -use private::sealed::Sealed; +// use private::sealed::Sealed; +// +// /// Marker trait that indicates that the member is not set, i.e. none of its setters were called. +// /// +// /// You should use this trait bound, for example, if you want to extend the builder with custom +// /// setters. +// /// +// /// **Example:** +// /// +// /// ``` +// /// #[derive(bon::Builder)] +// /// struct Example { +// /// x: i32, +// /// y: i32, +// /// } +// /// +// /// // Import the type aliases for transforming the builder's type state +// /// use example_builder::{SetX, SetY}; +// /// +// /// // Add method to the builder +// /// impl ExampleBuilder { +// /// fn x_doubled(self, value: i32) -> ExampleBuilder> +// /// where +// /// // The code won't compile without this bound +// /// State::X: bon::IsUnset, +// /// { +// /// self.x(value * 2) +// /// } +// /// +// /// fn y_doubled(self, value: i32) -> ExampleBuilder> +// /// where +// /// // The code won't compile without this bound +// /// State::Y: bon::IsUnset, +// /// { +// /// self.y(value * 2) +// /// } +// /// } +// /// +// /// let example = Example::builder() +// /// .x_doubled(2) +// /// .y_doubled(3) +// /// .build(); +// /// +// /// assert_eq!(example.x, 4); +// /// assert_eq!(example.y, 6); +// /// ``` +// #[rustversion::attr( +// since(1.78.0), +// diagnostic::on_unimplemented( +// message = "the member `{Self}` was already set, but this method requires it to be unset", +// label = "the member `{Self}` was already set, but this method requires it to be unset", +// ) +// )] +// pub trait IsUnset: Sealed {} -/// Marker trait that indicates that the member is not set, i.e. none of its setters were called. -/// -/// You should use this trait bound, for example, if you want to extend the builder with custom -/// setters. -/// -/// **Example:** -/// -/// ``` -/// #[derive(bon::Builder)] -/// struct Example { -/// x: i32, -/// y: i32, -/// } -/// -/// // Import the type aliases for transforming the builder's type state -/// use example_builder::{SetX, SetY}; -/// -/// // Add method to the builder -/// impl ExampleBuilder { -/// fn x_doubled(self, value: i32) -> ExampleBuilder> -/// where -/// // The code won't compile without this bound -/// State::X: bon::IsUnset, -/// { -/// self.x(value * 2) -/// } -/// -/// fn y_doubled(self, value: i32) -> ExampleBuilder> -/// where -/// // The code won't compile without this bound -/// State::Y: bon::IsUnset, -/// { -/// self.y(value * 2) -/// } -/// } -/// -/// let example = Example::builder() -/// .x_doubled(2) -/// .y_doubled(3) -/// .build(); -/// -/// assert_eq!(example.x, 4); -/// assert_eq!(example.y, 6); -/// ``` -#[rustversion::attr( - since(1.78.0), - diagnostic::on_unimplemented( - message = "the member `{Self}` was already set, but this method requires it to be unset", - label = "the member `{Self}` was already set, but this method requires it to be unset", - ) -)] -pub trait IsUnset: Sealed {} - -/// Marker trait that indicates that the member is set, i.e. at least one of its setters was called. -// TODO: add examples (they would require having custom renames and visibility overrides for default setters) -#[rustversion::attr( - since(1.78.0), - diagnostic::on_unimplemented( - message = "the member `{Self}` was not set, but this method requires it to be set", - label = "the member `{Self}` was not set, but this method requires it to be set", - ) -)] -pub trait IsSet: Sealed {} +// /// Marker trait that indicates that the member is set, i.e. at least one of its setters was called. +// // TODO: add examples (they would require having custom renames and visibility overrides for default setters) +// #[rustversion::attr( +// since(1.78.0), +// diagnostic::on_unimplemented( +// message = "the member `{Self}` was not set, but this method requires it to be set", +// label = "the member `{Self}` was not set, but this method requires it to be set", +// ) +// )] +// pub trait IsSet: Sealed {} diff --git a/bon/src/private/mod.rs b/bon/src/private/mod.rs index 59ef5cc7..83fd9a03 100644 --- a/bon/src/private/mod.rs +++ b/bon/src/private/mod.rs @@ -41,13 +41,9 @@ pub extern crate alloc; #[derive(Debug)] pub struct Unset(Name); -impl crate::IsUnset for Unset {} - #[derive(Debug)] pub struct Set(Name); -impl crate::IsSet for Set {} - #[rustversion::attr( since(1.78.0), diagnostic::on_unimplemented( diff --git a/bon/tests/integration/builder/raw_idents.rs b/bon/tests/integration/builder/raw_idents.rs index b57cfdb6..4cc96c51 100644 --- a/bon/tests/integration/builder/raw_idents.rs +++ b/bon/tests/integration/builder/raw_idents.rs @@ -17,9 +17,8 @@ fn struct_case() { assert_eq!(actual.r#type, 42); assert_eq!(actual.other, 100); - // TODO: add builder_mod overrides #[derive(Builder)] - #[builder(builder_type = r#type, builder_mod = r#mod)] + #[builder(builder_type = r#type, state_mod = r#mod)] #[allow(clippy::items_after_statements, dead_code)] struct Sut { r#while: u32, @@ -39,9 +38,8 @@ fn fn_case() { r#type().r#type(42).r#while(100).call(); - // TODO: add builder_mod overrides - // #[builder(builder_type = r#type)] - // fn sut() {} + #[builder(builder_type = r#type, state_mod = r#mod)] + fn sut() {} - // let _: r#type = sut(); + let _: r#type = sut(); } From 4563abccf1374e892a109c1a4432cd22f74abc2b Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 30 Sep 2024 20:53:52 +0000 Subject: [PATCH 028/119] Add `IsComplete` trait --- .../src/builder/builder_gen/member/mod.rs | 6 +- bon-macros/src/builder/builder_gen/mod.rs | 214 +----------- .../src/builder/builder_gen/state_mod.rs | 317 ++++++++++++++++++ bon/src/lib.rs | 66 ---- .../integration/ui/compile_fail/errors.stderr | 31 +- 5 files changed, 345 insertions(+), 289 deletions(-) create mode 100644 bon-macros/src/builder/builder_gen/state_mod.rs diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 4b5176e2..392c9133 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -149,8 +149,8 @@ impl NamedMember { Self::as_optional_with_ty(self, &self.norm_ty) } - pub(crate) fn is_optional(&self) -> bool { - self.as_optional_norm_ty().is_some() + pub(crate) fn is_required(&self) -> bool { + self.as_optional_norm_ty().is_none() } pub(crate) fn param_default(&self) -> Option> { @@ -161,7 +161,7 @@ impl NamedMember { } pub(crate) fn is_stateful(&self) -> bool { - !self.is_optional() || !self.params.overwritable.is_present() + self.is_required() || !self.params.overwritable.is_present() } pub(crate) fn merge_param_overwritable(&mut self, on_params: &[OnParams]) -> Result { diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 60325857..507236ed 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -3,6 +3,7 @@ mod builder_params; mod member; mod models; mod setter_methods; +mod state_mod; pub(crate) mod input_fn; pub(crate) mod input_struct; @@ -361,207 +362,6 @@ impl BuilderGenCtx { } } - fn state_transition_aliases(&self) -> TokenStream2 { - let vis_child = &self.state_mod.vis_child; - - let stateful_members = self.stateful_members().collect::>(); - let is_single_member = stateful_members.len() == 1; - - stateful_members - .iter() - .map(|member| { - let states = stateful_members.iter().map(|other_member| { - if other_member.orig_ident == member.orig_ident { - let ident = &member.public_ident(); - quote! { - ::bon::private::Set - } - } else { - let member_pascal = &other_member.norm_ident_pascal; - quote! { - S::#member_pascal - } - } - }); - - let member_ident = member.public_ident(); - let alias_ident = - quote::format_ident!("Set{}", member.norm_ident_pascal.raw_name()); - - let docs = format!( - "Returns a [`State`] that has [`IsSet`] implemented for `{member_ident}`\n\ - \n\ - [`State`]: self::State\n\ - [`IsSet`]: ::bon::IsSet", - ); - - if is_single_member { - return quote! { - #[doc = #docs] - #vis_child type #alias_ident = ( #(#states,)* ); - }; - } - - quote! { - #[doc = #docs] - #vis_child type #alias_ident< - S: self::State = self::AllUnset - > = ( - #(#states,)* - ); - } - }) - .concat() - } - - fn state_mod(&self) -> TokenStream2 { - let vis_mod = &self.state_mod.vis; - let vis_child = &self.state_mod.vis_child; - let vis_child_child = &self.state_mod.vis_child_child; - - let state_mod_docs = &self.state_mod.docs; - let state_mod_ident = &self.state_mod.ident; - let state_transition_aliases = self.state_transition_aliases(); - - let stateful_members_idents = self - .stateful_members() - .map(NamedMember::public_ident) - .collect::>(); - - let assoc_types_docs = self.stateful_members().map(|member| { - let ident = &member.public_ident(); - format!( - "Type state of the member `{ident}`.\n\ - \n\ - It can implement either [`IsSet`] or [`IsUnset`].\n\ - \n\ - [`IsSet`]: ::bon::IsSet\n\ - [`IsUnset`]: ::bon::IsUnset", - ) - }); - - let stateful_members_pascal = self - .stateful_members() - .map(|member| &member.norm_ident_pascal) - .collect::>(); - - quote! { - #( #state_mod_docs )* - // This is 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. - #[allow(unnameable_types, unreachable_pub)] - #vis_mod mod #state_mod_ident { - - /// Marker trait implemented by members that are set. - #[::bon::private::rustversion::attr( - since(1.78.0), - diagnostic::on_unimplemented( - message = "the member `{Self}` was not set, but this method requires it to be set", - label = "the member `{Self}` was not set, but this method requires it to be set", - ) - )] - #vis_child trait IsSet { - // Also a method without `self` makes the trait non-object safe - #[doc(hidden)] - fn __sealed(_: self::sealed::Sealed); - } - - #[doc(hidden)] - impl IsSet for ::bon::private::Set { - fn __sealed(_: self::sealed::Sealed) {} - } - - /// Marker trait implemented by members that are not set. - #[::bon::private::rustversion::attr( - since(1.78.0), - diagnostic::on_unimplemented( - message = "the member `{Self}` was already set, but this method requires it to be unset", - label = "the member `{Self}` was already set, but this method requires it to be unset", - ) - )] - #vis_child trait IsUnset { - // Also a method without `self` makes the trait non-object safe - #[doc(hidden)] - fn __sealed(_: self::sealed::Sealed); - } - - #[doc(hidden)] - impl IsUnset for ::bon::private::Unset { - fn __sealed(_: self::sealed::Sealed) {} - } - - /// Builder's type state specifies if members are set or not (unset). - /// - /// 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*` type aliases available in this module. - /// - /// [`IsSet`]: ::bon::IsSet - /// [`IsUnset`]: ::bon::IsUnset - #vis_child trait State: ::core::marker::Sized { - #( - #[doc = #assoc_types_docs] - type #stateful_members_pascal: ::bon::private::MemberState< - self::members::#stateful_members_idents - >; - )* - - #[doc(hidden)] - fn __sealed(_: self::sealed::Sealed); - } - - mod sealed { - #vis_child_child enum Sealed {} - } - - // Using `self::State` explicitly to avoid name conflicts with the - // members named `state` which would create a generic param named `State` - // that would shadow the trait `State` in the same scope. - #[doc(hidden)] - impl<#( - #stateful_members_pascal: ::bon::private::MemberState< - self::members::#stateful_members_idents - >, - )*> - self::State for ( #(#stateful_members_pascal,)* ) - { - #( type #stateful_members_pascal = #stateful_members_pascal; )* - - fn __sealed(_: self::sealed::Sealed) {} - } - - /// Initial state of the builder where all members are unset - #vis_child type AllUnset = ( - #(::bon::private::Unset,)* - ); - - #[deprecated = - "this is an implementation detail and should not be \ - used directly; use the Set* type aliases to control the \ - state of members instead" - ] - #[doc(hidden)] - mod members { - #( - #[allow(non_camel_case_types)] - #vis_child_child enum #stateful_members_idents {} - )* - } - - #state_transition_aliases - } - } - } - fn builder_decl(&self) -> TokenStream2 { let builder_vis = &self.builder_type.vis; let builder_ident = &self.builder_type.ident; @@ -745,16 +545,6 @@ impl BuilderGenCtx { let state_mod = &self.state_mod.ident; - let where_bounds = self - .named_members() - .filter(|member| !member.is_optional()) - .map(|member| { - let member_pascal = &member.norm_ident_pascal; - quote! { - BuilderState::#member_pascal: #state_mod::IsSet - } - }); - let finish_fn_params = self .members .iter() @@ -790,7 +580,7 @@ impl BuilderGenCtx { #must_use #finish_fn_vis #asyncness #unsafety fn #finish_fn_ident(self, #(#finish_fn_params,)*) #output where - #(#where_bounds,)* + BuilderState: #state_mod::IsComplete { #(#members_vars_decls)* #body diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs new file mode 100644 index 00000000..b2afd3cd --- /dev/null +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -0,0 +1,317 @@ +use crate::builder::builder_gen::member::NamedMember; +use crate::util::prelude::*; +use quote::quote; + +impl super::BuilderGenCtx { + fn state_transition_aliases(&self) -> Vec { + let vis_child = &self.state_mod.vis_child; + + let stateful_members = self.stateful_members().collect::>(); + let is_single_member = stateful_members.len() == 1; + + stateful_members + .iter() + .map(|member| { + let states = stateful_members.iter().map(|other_member| { + if other_member.orig_ident == member.orig_ident { + let ident = &member.public_ident(); + quote! { + ::bon::private::Set + } + } else { + let member_pascal = &other_member.norm_ident_pascal; + quote! { + S::#member_pascal + } + } + }); + + let member_ident = member.public_ident(); + let alias_ident = + quote::format_ident!("Set{}", member.norm_ident_pascal.raw_name()); + + let docs = format!( + "Returns a [`State`] that has [`IsSet`] implemented for `{member_ident}`\n\ + \n\ + [`State`]: self::State\n\ + [`IsSet`]: ::bon::IsSet", + ); + + if is_single_member { + return syn::parse_quote! { + #[doc = #docs] + #vis_child type #alias_ident = ( #(#states,)* ); + }; + } + + syn::parse_quote! { + #[doc = #docs] + #vis_child type #alias_ident< + S: self::State = self::AllUnset + > = ( + #(#states,)* + ); + } + }) + .collect() + } + + #[allow(clippy::cognitive_complexity)] + pub(super) fn state_mod(&self) -> TokenStream2 { + let builder_vis = &self.builder_type.vis; + let vis_mod = &self.state_mod.vis; + let vis_child = &self.state_mod.vis_child; + let vis_child_child = &self.state_mod.vis_child_child; + + let state_mod_docs = &self.state_mod.docs; + let state_mod_ident = &self.state_mod.ident; + let state_transition_aliases = self.state_transition_aliases(); + + let stateful_members_idents = self + .stateful_members() + .map(NamedMember::public_ident) + .collect::>(); + + let assoc_types_docs = self.stateful_members().map(|member| { + let ident = &member.public_ident(); + format!( + "Type state of the member `{ident}`.\n\ + \n\ + It can implement either [`IsSet`] or [`IsUnset`].\n\ + \n\ + [`IsSet`]: ::bon::IsSet\n\ + [`IsUnset`]: ::bon::IsUnset", + ) + }); + + let stateful_members_pascal = self + .stateful_members() + .map(|member| &member.norm_ident_pascal) + .collect::>(); + + let required_members_pascal = self + .named_members() + .filter(|member| member.is_required()) + .map(|member| &member.norm_ident_pascal) + .collect::>(); + + let type_aliases_for_rustdoc = ["e::format_ident!("AllUnset")]; + let type_aliases_for_rustdoc = state_transition_aliases + .iter() + .map(|alias| &alias.ident) + .chain(type_aliases_for_rustdoc); + + quote! { + // This is a workaround for `rustdoc`. Without these `use` statements, + // it inlines the type aliases + #( + #[cfg(doc)] + #[doc(hidden)] + #builder_vis use self::#state_mod_ident::#type_aliases_for_rustdoc as _; + )* + + #( #state_mod_docs )* + // This is 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. + #[allow(unnameable_types, unreachable_pub)] + #[doc(hidden)] + #vis_mod mod #state_mod_ident { + /// Marker trait implemented by members that are set. + #[::bon::private::rustversion::attr( + since(1.78.0), + diagnostic::on_unimplemented( + message = "the member `{Self}` was not set, but this method requires it to be set", + label = "the member `{Self}` was not set, but this method requires it to be set", + ) + )] + #vis_child trait IsSet { + // Also a method without `self` makes the trait non-object safe + #[doc(hidden)] + fn __sealed(_: sealed::Sealed); + } + + #[doc(hidden)] + impl IsSet for ::bon::private::Set { + fn __sealed(_: sealed::Sealed) {} + } + + + // use private::sealed::Sealed; + // + // /// Marker trait that indicates that the member is not set, i.e. none of its setters were called. + // /// + // /// You should use this trait bound, for example, if you want to extend the builder with custom + // /// setters. + // /// + // /// **Example:** + // /// + // /// ``` + // /// #[derive(bon::Builder)] + // /// struct Example { + // /// x: i32, + // /// y: i32, + // /// } + // /// + // /// // Import the type aliases for transforming the builder's type state + // /// use example_builder::{SetX, SetY}; + // /// + // /// // Add method to the builder + // /// impl ExampleBuilder { + // /// fn x_doubled(self, value: i32) -> ExampleBuilder> + // /// where + // /// // The code won't compile without this bound + // /// State::X: bon::IsUnset, + // /// { + // /// self.x(value * 2) + // /// } + // /// + // /// fn y_doubled(self, value: i32) -> ExampleBuilder> + // /// where + // /// // The code won't compile without this bound + // /// State::Y: bon::IsUnset, + // /// { + // /// self.y(value * 2) + // /// } + // /// } + // /// + // /// let example = Example::builder() + // /// .x_doubled(2) + // /// .y_doubled(3) + // /// .build(); + // /// + // /// assert_eq!(example.x, 4); + // /// assert_eq!(example.y, 6); + // /// ``` + // #[rustversion::attr( + // since(1.78.0), + // diagnostic::on_unimplemented( + // message = "the member `{Self}` was already set, but this method requires it to be unset", + // label = "the member `{Self}` was already set, but this method requires it to be unset", + // ) + // )] + // pub trait IsUnset: Sealed {} + + // /// Marker trait that indicates that the member is set, i.e. at least one of its setters was called. + // // TODO: add examples (they would require having custom renames and visibility overrides for default setters) + // #[rustversion::attr( + // since(1.78.0), + // diagnostic::on_unimplemented( + // message = "the member `{Self}` was not set, but this method requires it to be set", + // label = "the member `{Self}` was not set, but this method requires it to be set", + // ) + // )] + // pub trait IsSet: Sealed {} + /// Marker trait implemented by members that are not set. + #[::bon::private::rustversion::attr( + since(1.78.0), + diagnostic::on_unimplemented( + message = "the member `{Self}` was already set, but this method requires it to be unset", + label = "the member `{Self}` was already set, but this method requires it to be unset", + ) + )] + #vis_child trait IsUnset { + // Also a method without `self` makes the trait non-object safe + #[doc(hidden)] + fn __sealed(_: sealed::Sealed); + } + + #[doc(hidden)] + impl IsUnset for ::bon::private::Unset { + fn __sealed(_: sealed::Sealed) {} + } + + #[::bon::private::rustversion::attr( + since(1.78.0), + diagnostic::on_unimplemented( + message = "can't finish building yet; not all required members are set", + label = "can't finish building yet; not all required members are set", + ) + )] + #vis_child trait IsComplete: State { + // Also a method without `self` makes the trait non-object safe + #[doc(hidden)] + fn __sealed(_: sealed::Sealed); + } + + #[doc(hidden)] + impl IsComplete for State + where + #( + State::#required_members_pascal: IsSet, + )* + { + fn __sealed(_: sealed::Sealed) {} + } + + /// Builder's type state specifies if members are set or not (unset). + /// + /// 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*` type aliases available in this module. + /// + /// [`IsSet`]: ::bon::IsSet + /// [`IsUnset`]: ::bon::IsUnset + #vis_child trait State: ::core::marker::Sized { + #( + #[doc = #assoc_types_docs] + type #stateful_members_pascal: ::bon::private::MemberState< + self::members::#stateful_members_idents + >; + )* + + #[doc(hidden)] + fn __sealed(_: self::sealed::Sealed); + } + + mod sealed { + #vis_child_child enum Sealed {} + } + + // Using `self::State` explicitly to avoid name conflicts with the + // members named `state` which would create a generic param named `State` + // that would shadow the trait `State` in the same scope. + #[doc(hidden)] + impl<#( + #stateful_members_pascal: ::bon::private::MemberState< + self::members::#stateful_members_idents + >, + )*> + self::State for ( #(#stateful_members_pascal,)* ) + { + #( type #stateful_members_pascal = #stateful_members_pascal; )* + + fn __sealed(_: self::sealed::Sealed) {} + } + + /// Initial state of the builder where all members are unset + #vis_child type AllUnset = ( + #(::bon::private::Unset,)* + ); + + #[deprecated = + "this is an implementation detail and should not be \ + used directly; use the Set* type aliases to control the \ + state of members instead" + ] + #[doc(hidden)] + mod members { + #( + #[allow(non_camel_case_types)] + #vis_child_child enum #stateful_members_idents {} + )* + } + + #( #state_transition_aliases )* + } + } + } +} diff --git a/bon/src/lib.rs b/bon/src/lib.rs index 119fbc6a..81b9132f 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -20,69 +20,3 @@ mod collections; /// Rexport all macros from the proc-macro crate. pub use bon_macros::*; - -// use private::sealed::Sealed; -// -// /// Marker trait that indicates that the member is not set, i.e. none of its setters were called. -// /// -// /// You should use this trait bound, for example, if you want to extend the builder with custom -// /// setters. -// /// -// /// **Example:** -// /// -// /// ``` -// /// #[derive(bon::Builder)] -// /// struct Example { -// /// x: i32, -// /// y: i32, -// /// } -// /// -// /// // Import the type aliases for transforming the builder's type state -// /// use example_builder::{SetX, SetY}; -// /// -// /// // Add method to the builder -// /// impl ExampleBuilder { -// /// fn x_doubled(self, value: i32) -> ExampleBuilder> -// /// where -// /// // The code won't compile without this bound -// /// State::X: bon::IsUnset, -// /// { -// /// self.x(value * 2) -// /// } -// /// -// /// fn y_doubled(self, value: i32) -> ExampleBuilder> -// /// where -// /// // The code won't compile without this bound -// /// State::Y: bon::IsUnset, -// /// { -// /// self.y(value * 2) -// /// } -// /// } -// /// -// /// let example = Example::builder() -// /// .x_doubled(2) -// /// .y_doubled(3) -// /// .build(); -// /// -// /// assert_eq!(example.x, 4); -// /// assert_eq!(example.y, 6); -// /// ``` -// #[rustversion::attr( -// since(1.78.0), -// diagnostic::on_unimplemented( -// message = "the member `{Self}` was already set, but this method requires it to be unset", -// label = "the member `{Self}` was already set, but this method requires it to be unset", -// ) -// )] -// pub trait IsUnset: Sealed {} - -// /// Marker trait that indicates that the member is set, i.e. at least one of its setters was called. -// // TODO: add examples (they would require having custom renames and visibility overrides for default setters) -// #[rustversion::attr( -// since(1.78.0), -// diagnostic::on_unimplemented( -// message = "the member `{Self}` was not set, but this method requires it to be set", -// label = "the member `{Self}` was not set, but this method requires it to be set", -// ) -// )] -// pub trait IsSet: Sealed {} diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr index 9319eb7e..d52fe041 100644 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ b/bon/tests/integration/ui/compile_fail/errors.stderr @@ -199,8 +199,13 @@ error[E0277]: the member `Unset` was not set, but this method requires it to 34 | let _ = Example::builder().x(1).build(); | ^^^^^ the member `Unset` was not set, but this method requires it to be set | - = help: the trait `IsSet` is not implemented for `Unset` - = help: the trait `IsSet` is implemented for `Set` + = help: the trait `example_builder::IsSet` is not implemented for `Unset`, which is required by `(Set, Unset, Unset): example_builder::IsComplete` + = help: the trait `example_builder::IsSet` is implemented for `Set` +note: required for `(Set, Unset, Unset)` to implement `example_builder::IsComplete` + --> tests/integration/ui/compile_fail/errors.rs:24:14 + | +24 | #[derive(Builder)] + | ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro note: required by a bound in `ExampleBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:24:14 | @@ -216,8 +221,13 @@ error[E0277]: the member `Unset` was not set, but this method requires 34 | let _ = Example::builder().x(1).build(); | ^^^^^ the member `Unset` was not set, but this method requires it to be set | - = help: the trait `IsSet` is not implemented for `Unset` - = help: the trait `IsSet` is implemented for `Set` + = help: the trait `example_builder::IsSet` is not implemented for `Unset`, which is required by `(Set, Unset, Unset): example_builder::IsComplete` + = help: the trait `example_builder::IsSet` is implemented for `Set` +note: required for `(Set, Unset, Unset)` to implement `example_builder::IsComplete` + --> tests/integration/ui/compile_fail/errors.rs:24:14 + | +24 | #[derive(Builder)] + | ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro note: required by a bound in `ExampleBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:24:14 | @@ -233,8 +243,8 @@ error[E0277]: the member `Set` was already set, but this method requires it t 37 | let _ = Example::builder().y(1).y(2); | ^ the member `Set` was already set, but this method requires it to be unset | - = help: the trait `IsUnset` is not implemented for `Set` - = help: the trait `IsUnset` is implemented for `Unset` + = help: the trait `example_builder::IsUnset` is not implemented for `Set` + = help: the trait `example_builder::IsUnset` is implemented for `Unset` note: required by a bound in `ExampleBuilder::::y` --> tests/integration/ui/compile_fail/errors.rs:24:14 | @@ -251,8 +261,13 @@ error[E0277]: the member `Unset` was not set, but this method requires it 47 | let _ = Sut::builder().build(); | ^^^^^ the member `Unset` was not set, but this method requires it to be set | - = help: the trait `IsSet` is not implemented for `Unset` - = help: the trait `IsSet` is implemented for `Set` + = help: the trait `sut_builder::IsSet` is not implemented for `Unset`, which is required by `(Unset,): sut_builder::IsComplete` + = help: the trait `sut_builder::IsSet` is implemented for `Set` +note: required for `(Unset,)` to implement `sut_builder::IsComplete` + --> tests/integration/ui/compile_fail/errors.rs:42:18 + | +42 | #[derive(Builder)] + | ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro note: required by a bound in `SutBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:42:18 | From 6141e55867854e8aab5fd965ec19eb1eed590864 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 1 Oct 2024 15:05:44 +0000 Subject: [PATCH 029/119] Add `msrv-1-79-0` feature --- bon-macros/Cargo.toml | 38 ++++++++++++++ .../src/builder/builder_gen/state_mod.rs | 49 +++++++++++-------- bon/Cargo.toml | 37 ++++++++++++++ 3 files changed, 103 insertions(+), 21 deletions(-) diff --git a/bon-macros/Cargo.toml b/bon-macros/Cargo.toml index b1269908..f4689564 100644 --- a/bon-macros/Cargo.toml +++ b/bon-macros/Cargo.toml @@ -53,3 +53,41 @@ quote = "1" # This is the highest version that supports our MSRV syn = { version = "2.0.56", features = ["full", "visit-mut", "visit"] } + +[features] +# Opts in to the higher MSRV 1.79.0. In this version, Rust stabilized the syntax +# for bounds in associated type position. See the release announcement for more: +# https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.htmlbounds-in-associated-type-position +# +# This feature is useful for the trait `IsComplete` generated by the builder macros. +# When this feature is enabled, the builder macros use the new syntax for bounds in +# associated type position, which enables implied `IsSet` bounds for the type state +# of required members. +# +# To understand why this is useful consider the following example: +# +# ```rust +# #[derive(bon::Builder)] +# struct Example { +# a: u32, +# b: Option, +# } +# +# use example_builder::{IsUnset, IsComplete}; +# +# impl for ExampleBuilder { +# fn build_with_default_b(self) -> Example +# where +# State: IsComplete, +# State::B: IsUnset, +# { +# self.b(42).build() +# } +# } +# ``` +# +# This code wouldn't compile without this feature enabled, because `State: IsComplete` +# wouldn't automatically imply `State::A: IsSet`, so the builder type state returned +# after the `self.b()` doesn't imply that the member `a` is set, and thus `build()` +# can't be called. +msrv-1-79-0 = [] diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index b2afd3cd..b796e835 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -101,16 +101,30 @@ impl super::BuilderGenCtx { .map(|alias| &alias.ident) .chain(type_aliases_for_rustdoc); + let is_complete_assoc_type_bounds = if cfg!(feature = "msrv-1-79-0") { + quote! { + < #( #required_members_pascal: IsSet, )* > + } + } else { + quote! {} + }; + + let sealed_method = quote! { + // A method without `self` makes the trait non-object safe, + // which is convenient, because we want that in this case. + #[doc(hidden)] + fn __sealed(_: sealed::Sealed); + }; + quote! { - // This is a workaround for `rustdoc`. Without these `use` statements, + // This is a workaround for `rustdoc`. Without this `use` statement, // it inlines the type aliases - #( - #[cfg(doc)] - #[doc(hidden)] - #builder_vis use self::#state_mod_ident::#type_aliases_for_rustdoc as _; - )* + #[cfg(doc)] + #[doc(hidden)] + #builder_vis use self::#state_mod_ident::{ + #( #type_aliases_for_rustdoc as _, )* + }; - #( #state_mod_docs )* // This is 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 @@ -123,7 +137,7 @@ impl super::BuilderGenCtx { // public, and users instead should only reference the traits // and state transition type aliases from here. #[allow(unnameable_types, unreachable_pub)] - #[doc(hidden)] + #( #state_mod_docs )* #vis_mod mod #state_mod_ident { /// Marker trait implemented by members that are set. #[::bon::private::rustversion::attr( @@ -134,9 +148,7 @@ impl super::BuilderGenCtx { ) )] #vis_child trait IsSet { - // Also a method without `self` makes the trait non-object safe - #[doc(hidden)] - fn __sealed(_: sealed::Sealed); + #sealed_method } #[doc(hidden)] @@ -144,7 +156,6 @@ impl super::BuilderGenCtx { fn __sealed(_: sealed::Sealed) {} } - // use private::sealed::Sealed; // // /// Marker trait that indicates that the member is not set, i.e. none of its setters were called. @@ -210,6 +221,7 @@ impl super::BuilderGenCtx { // ) // )] // pub trait IsSet: Sealed {} + /// Marker trait implemented by members that are not set. #[::bon::private::rustversion::attr( since(1.78.0), @@ -219,9 +231,7 @@ impl super::BuilderGenCtx { ) )] #vis_child trait IsUnset { - // Also a method without `self` makes the trait non-object safe - #[doc(hidden)] - fn __sealed(_: sealed::Sealed); + #sealed_method } #[doc(hidden)] @@ -236,10 +246,8 @@ impl super::BuilderGenCtx { label = "can't finish building yet; not all required members are set", ) )] - #vis_child trait IsComplete: State { - // Also a method without `self` makes the trait non-object safe - #[doc(hidden)] - fn __sealed(_: sealed::Sealed); + #vis_child trait IsComplete: State #is_complete_assoc_type_bounds { + #sealed_method } #[doc(hidden)] @@ -268,8 +276,7 @@ impl super::BuilderGenCtx { >; )* - #[doc(hidden)] - fn __sealed(_: self::sealed::Sealed); + #sealed_method } mod sealed { diff --git a/bon/Cargo.toml b/bon/Cargo.toml index d34be9c7..23354e77 100644 --- a/bon/Cargo.toml +++ b/bon/Cargo.toml @@ -62,3 +62,40 @@ trybuild = "1.0.89" alloc = [] default = ["std"] std = ["alloc"] + +# Opts in to the higher MSRV 1.79.0. In this version, Rust stabilized the syntax +# for bounds in associated type position. See the release announcement for more: +# https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.htmlbounds-in-associated-type-position +# +# This feature is useful for the trait `IsComplete` generated by the builder macros. +# When this feature is enabled, the builder macros use the new syntax for bounds in +# associated type position, which enables implied `IsSet` bounds for the type state +# of required members. +# +# To understand why this is useful consider the following example: +# +# ```rust +# #[derive(bon::Builder)] +# struct Example { +# a: u32, +# b: Option, +# } +# +# use example_builder::{IsUnset, IsComplete}; +# +# impl for ExampleBuilder { +# fn build_with_default_b(self) -> Example +# where +# State: IsComplete, +# State::B: IsUnset, +# { +# self.b(42).build() +# } +# } +# ``` +# +# This code wouldn't compile without this feature enabled, because `State: IsComplete` +# wouldn't automatically imply `State::A: IsSet`, so the builder type state returned +# after the `self.b()` doesn't imply that the member `a` is set, and thus `build()` +# can't be called. +msrv-1-79-0 = ["bon-macros/msrv-1-79-0"] From d872ebf02f4122f23175907530c6f78d5fa4f913 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 1 Oct 2024 15:57:09 +0000 Subject: [PATCH 030/119] Small fix --- .github/workflows/ci.yml | 2 ++ bon-macros/Cargo.toml | 2 +- bon/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d609688..88cc7c24 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,8 @@ jobs: - run: cargo test --locked --all-features --doc - run: cd bon && cargo test --locked --no-default-features --features= - run: cd bon && cargo test --locked --no-default-features --features=alloc + - run: cd bon && cargo test --locked --no-default-features --features=msrv-1-79-0 + - run: cd bon && cargo test --locked --no-default-features --features=alloc,msrv-1-79-0 test-msrv: runs-on: ${{ matrix.os }}-latest diff --git a/bon-macros/Cargo.toml b/bon-macros/Cargo.toml index f4689564..62ab13cd 100644 --- a/bon-macros/Cargo.toml +++ b/bon-macros/Cargo.toml @@ -75,7 +75,7 @@ syn = { version = "2.0.56", features = ["full", "visit-mut", "visit"] } # # use example_builder::{IsUnset, IsComplete}; # -# impl for ExampleBuilder { +# impl ExampleBuilder { # fn build_with_default_b(self) -> Example # where # State: IsComplete, diff --git a/bon/Cargo.toml b/bon/Cargo.toml index 23354e77..02203d5f 100644 --- a/bon/Cargo.toml +++ b/bon/Cargo.toml @@ -83,7 +83,7 @@ std = ["alloc"] # # use example_builder::{IsUnset, IsComplete}; # -# impl for ExampleBuilder { +# impl ExampleBuilder { # fn build_with_default_b(self) -> Example # where # State: IsComplete, From 56ac346c11ee24b39aaae6230f3da99fb156ff87 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 1 Oct 2024 16:47:24 +0000 Subject: [PATCH 031/119] Rename TokenStream2 -> TokenStream --- bon-macros/src/bon.rs | 4 ++-- .../src/builder/builder_gen/builder_derives.rs | 10 +++++----- .../src/builder/builder_gen/builder_params.rs | 2 +- bon-macros/src/builder/builder_gen/input_fn.rs | 2 +- .../src/builder/builder_gen/input_struct.rs | 2 +- .../builder_gen/member/into_conversion.rs | 4 ++-- bon-macros/src/builder/builder_gen/mod.rs | 18 +++++++++--------- bon-macros/src/builder/builder_gen/models.rs | 2 +- .../src/builder/builder_gen/setter_methods.rs | 12 ++++++------ .../src/builder/builder_gen/state_mod.rs | 2 +- bon-macros/src/builder/item_fn.rs | 2 +- bon-macros/src/builder/item_impl.rs | 2 +- bon-macros/src/builder/item_struct.rs | 2 +- bon-macros/src/builder/mod.rs | 10 +++++----- bon-macros/src/collections/map.rs | 2 +- bon-macros/src/collections/set.rs | 2 +- bon-macros/src/error.rs | 12 ++++++------ bon-macros/src/lib.rs | 18 +++++++++++------- bon-macros/src/normalization/cfg/mod.rs | 10 +++++----- bon-macros/src/normalization/cfg/parse.rs | 8 ++++---- bon-macros/src/util/ide.rs | 6 +++--- bon-macros/src/util/mod.rs | 3 +-- 22 files changed, 69 insertions(+), 66 deletions(-) diff --git a/bon-macros/src/bon.rs b/bon-macros/src/bon.rs index cb4c9670..63cfe484 100644 --- a/bon-macros/src/bon.rs +++ b/bon-macros/src/bon.rs @@ -2,12 +2,12 @@ use crate::builder; use crate::normalization::{ExpandCfg, ExpansionOutput}; use crate::util::prelude::*; -pub(crate) fn generate(params: TokenStream2, item: TokenStream2) -> TokenStream2 { +pub(crate) fn generate(params: TokenStream, item: TokenStream) -> TokenStream { try_generate(params, item.clone()) .unwrap_or_else(|err| crate::error::error_into_token_stream(err, item)) } -pub(crate) fn try_generate(params: TokenStream2, item: TokenStream2) -> Result { +pub(crate) fn try_generate(params: TokenStream, item: TokenStream) -> Result { let item: syn::Item = syn::parse2(item)?; let macro_path = syn::parse_quote!(::bon::bon); diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index 1731af27..63f315f6 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -6,10 +6,10 @@ use darling::ast::GenericParamExt; use quote::quote; impl BuilderGenCtx { - pub(crate) fn builder_derives(&self) -> TokenStream2 { + pub(crate) fn builder_derives(&self) -> TokenStream { let BuilderDerives { clone, debug } = &self.builder_type.derives; - let mut tokens = TokenStream2::new(); + let mut tokens = TokenStream::new(); if clone.is_present() { tokens.extend(self.derive_clone()); @@ -26,7 +26,7 @@ impl BuilderGenCtx { /// 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. - fn where_clause_for_derive(&self, target_trait_bounds: &TokenStream2) -> TokenStream2 { + fn where_clause_for_derive(&self, target_trait_bounds: &TokenStream) -> TokenStream { let target_trait_bounds_predicates = self .generics .decl_without_defaults @@ -48,7 +48,7 @@ impl BuilderGenCtx { } } - fn derive_clone(&self) -> TokenStream2 { + fn derive_clone(&self) -> TokenStream { let generics_decl = &self.generics.decl_without_defaults; let generic_args = &self.generics.args; let builder_ident = &self.builder_type.ident; @@ -126,7 +126,7 @@ impl BuilderGenCtx { } } - fn derive_debug(&self) -> TokenStream2 { + fn derive_debug(&self) -> TokenStream { let format_members = self.members.iter().filter_map(|member| { match member { Member::Named(member) => { diff --git a/bon-macros/src/builder/builder_gen/builder_params.rs b/bon-macros/src/builder/builder_gen/builder_params.rs index be1bb394..b1d52fa5 100644 --- a/bon-macros/src/builder/builder_gen/builder_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params.rs @@ -70,7 +70,7 @@ impl Parse for OnParams { let type_pattern = input.parse()?; let _ = input.parse::()?; - let rest: TokenStream2 = input.parse()?; + let rest: TokenStream = input.parse()?; #[derive(FromMeta)] struct Parsed { diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index bb0529fd..1ba3b6e2 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -426,7 +426,7 @@ struct FnCallBody { } impl FinishFnBody for FnCallBody { - fn generate(&self, members: &[Member]) -> TokenStream2 { + fn generate(&self, members: &[Member]) -> TokenStream { let asyncness = &self.sig.asyncness; let maybe_await = asyncness.is_some().then(|| quote!(.await)); diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index b2e33cc6..7b395f5a 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -241,7 +241,7 @@ struct StructLiteralBody { } impl FinishFnBody for StructLiteralBody { - fn generate(&self, member_exprs: &[Member]) -> TokenStream2 { + fn generate(&self, member_exprs: &[Member]) -> TokenStream { let Self { struct_ident } = self; // The variables with values of members are in scope for this expression. diff --git a/bon-macros/src/builder/builder_gen/member/into_conversion.rs b/bon-macros/src/builder/builder_gen/member/into_conversion.rs index 7e472d30..78940d02 100644 --- a/bon-macros/src/builder/builder_gen/member/into_conversion.rs +++ b/bon-macros/src/builder/builder_gen/member/into_conversion.rs @@ -46,7 +46,7 @@ impl PositionalFnArgMember { Ok(()) } - pub(crate) fn fn_input_param(&self) -> TokenStream2 { + pub(crate) fn fn_input_param(&self) -> TokenStream { let has_into = self.params.into.is_present(); let norm_ty = &self.norm_ty; let ident = &self.ident; @@ -58,7 +58,7 @@ impl PositionalFnArgMember { } } - pub(crate) fn maybe_into_ident_expr(&self) -> TokenStream2 { + pub(crate) fn maybe_into_ident_expr(&self) -> TokenStream { let has_into = self.params.into.is_present(); let ident = &self.ident; diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 507236ed..dc96e163 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -20,7 +20,7 @@ use setter_methods::MemberSettersCtx; pub(crate) struct MacroOutput { pub(crate) start_fn: syn::ItemFn, - pub(crate) other_items: TokenStream2, + pub(crate) other_items: TokenStream, } impl BuilderGenCtx { @@ -96,7 +96,7 @@ impl BuilderGenCtx { }) } - fn builder_impl(&self) -> TokenStream2 { + fn builder_impl(&self) -> TokenStream { let finish_fn = self.finish_fn(); let transition_type_state_fn = self.transition_type_state_fn(); let setter_methods = self @@ -134,7 +134,7 @@ impl BuilderGenCtx { /// 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) -> TokenStream2 { + fn ide_hints(&self) -> TokenStream { let type_patterns = self .on_params .iter() @@ -166,7 +166,7 @@ impl BuilderGenCtx { } } - fn transition_type_state_fn(&self) -> TokenStream2 { + fn transition_type_state_fn(&self) -> TokenStream { let builder_ident = &self.builder_type.ident; let state_mod = &self.state_mod.ident; @@ -297,7 +297,7 @@ impl BuilderGenCtx { } } - fn phantom_data(&self) -> TokenStream2 { + 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 in the types @@ -362,7 +362,7 @@ impl BuilderGenCtx { } } - fn builder_decl(&self) -> TokenStream2 { + 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; @@ -456,7 +456,7 @@ impl BuilderGenCtx { } } - fn finish_fn_member_expr(member: &Member) -> TokenStream2 { + fn finish_fn_member_expr(member: &Member) -> TokenStream { let member = match member { Member::Named(member) => member, Member::Skipped(member) => { @@ -523,7 +523,7 @@ impl BuilderGenCtx { } } - fn finish_fn(&self) -> TokenStream2 { + 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(); @@ -589,7 +589,7 @@ impl BuilderGenCtx { } } -fn allow_warnings_on_member_types() -> TokenStream2 { +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: diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index a46e95a7..e01570d0 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -6,7 +6,7 @@ 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, members: &[Member]) -> TokenStream2; + fn generate(&self, members: &[Member]) -> TokenStream; } pub(super) struct AssocMethodReceiverCtx { diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index 8a8b2738..7f929a7a 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -15,7 +15,7 @@ impl<'a> MemberSettersCtx<'a> { } } - pub(crate) fn setter_methods(&self) -> TokenStream2 { + pub(crate) fn setter_methods(&self) -> TokenStream { let member_type = self.member.norm_ty.as_ref(); if let Some(inner_type) = self.member.as_optional_norm_ty() { @@ -40,7 +40,7 @@ impl<'a> MemberSettersCtx<'a> { }) } - fn setters_for_optional_member(&self, inner_type: &syn::Type) -> TokenStream2 { + fn setters_for_optional_member(&self, inner_type: &syn::Type) -> TokenStream { let has_into = self.member.params.into.is_present(); let (inner_type, maybe_map_conv_call) = if has_into { (quote!(impl Into<#inner_type>), quote!(.map(Into::into))) @@ -94,7 +94,7 @@ impl<'a> MemberSettersCtx<'a> { .concat() } - fn setter_method(&self, method: MemberSetterMethod) -> TokenStream2 { + fn setter_method(&self, method: MemberSetterMethod) -> TokenStream { let MemberSetterMethod { method_name, fn_params, @@ -222,13 +222,13 @@ impl<'a> MemberSettersCtx<'a> { } enum SetterBody { - Custom(TokenStream2), - Default { member_init: TokenStream2 }, + Custom(TokenStream), + Default { member_init: TokenStream }, } struct MemberSetterMethod { method_name: syn::Ident, - fn_params: TokenStream2, + fn_params: TokenStream, overwrite_docs: Option, body: SetterBody, } diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index b796e835..acb0badb 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -57,7 +57,7 @@ impl super::BuilderGenCtx { } #[allow(clippy::cognitive_complexity)] - pub(super) fn state_mod(&self) -> TokenStream2 { + pub(super) fn state_mod(&self) -> TokenStream { let builder_vis = &self.builder_type.vis; let vis_mod = &self.state_mod.vis; let vis_child = &self.state_mod.vis_child; diff --git a/bon-macros/src/builder/item_fn.rs b/bon-macros/src/builder/item_fn.rs index f403c9d9..bd181128 100644 --- a/bon-macros/src/builder/item_fn.rs +++ b/bon-macros/src/builder/item_fn.rs @@ -4,7 +4,7 @@ use crate::util::prelude::*; use quote::quote; use syn::visit_mut::VisitMut; -pub(crate) fn generate(params: FnInputParams, orig_fn: syn::ItemFn) -> Result { +pub(crate) fn generate(params: FnInputParams, orig_fn: syn::ItemFn) -> Result { let mut norm_fn = orig_fn.clone(); crate::normalization::NormalizeLifetimes.visit_item_fn_mut(&mut norm_fn); diff --git a/bon-macros/src/builder/item_impl.rs b/bon-macros/src/builder/item_impl.rs index 46351024..a9fee3ab 100644 --- a/bon-macros/src/builder/item_impl.rs +++ b/bon-macros/src/builder/item_impl.rs @@ -6,7 +6,7 @@ use quote::quote; use std::rc::Rc; use syn::visit_mut::VisitMut; -pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result { +pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result { if let Some((_, trait_path, _)) = &orig_impl_block.trait_ { bail!(trait_path, "Impls of traits are not supported yet"); } diff --git a/bon-macros/src/builder/item_struct.rs b/bon-macros/src/builder/item_struct.rs index 4b42e310..a7e4d07d 100644 --- a/bon-macros/src/builder/item_struct.rs +++ b/bon-macros/src/builder/item_struct.rs @@ -3,7 +3,7 @@ use super::builder_gen::MacroOutput; use crate::util::prelude::*; use quote::quote; -pub(crate) fn generate(orig_struct: syn::ItemStruct) -> Result { +pub(crate) fn generate(orig_struct: syn::ItemStruct) -> Result { let struct_ident = orig_struct.ident.clone(); let ctx = StructInputCtx::new(orig_struct)?; diff --git a/bon-macros/src/builder/mod.rs b/bon-macros/src/builder/mod.rs index e0c9cea5..4f523446 100644 --- a/bon-macros/src/builder/mod.rs +++ b/bon-macros/src/builder/mod.rs @@ -12,11 +12,11 @@ use darling::FromMeta; use quote::quote; use syn::parse::Parser; -pub(crate) fn generate_from_derive(item: TokenStream2) -> TokenStream2 { +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: TokenStream2) -> Result { +fn try_generate_from_derive(item: TokenStream) -> Result { match syn::parse2(item)? { syn::Item::Struct(item_struct) => item_struct::generate(item_struct), _ => bail!( @@ -26,7 +26,7 @@ fn try_generate_from_derive(item: TokenStream2) -> Result { } } -pub(crate) fn generate_from_attr(params: TokenStream2, item: TokenStream2) -> TokenStream2 { +pub(crate) fn generate_from_attr(params: TokenStream, item: TokenStream) -> TokenStream { try_generate_from_attr(params.clone(), item.clone()).unwrap_or_else(|err| { [ generate_completion_triggers(params), @@ -36,7 +36,7 @@ pub(crate) fn generate_from_attr(params: TokenStream2, item: TokenStream2) -> To }) } -fn try_generate_from_attr(params: TokenStream2, item: TokenStream2) -> Result { +fn try_generate_from_attr(params: TokenStream, item: TokenStream) -> Result { let item: syn::Item = syn::parse2(item)?; if let syn::Item::Struct(item_struct) = item { @@ -79,7 +79,7 @@ fn try_generate_from_attr(params: TokenStream2, item: TokenStream2) -> Result TokenStream2 { +fn generate_completion_triggers(params: TokenStream) -> TokenStream { let meta = util::ide::parse_comma_separated_meta .parse2(params) .unwrap_or_default(); diff --git a/bon-macros/src/collections/map.rs b/bon-macros/src/collections/map.rs index fbb73764..ec17ef84 100644 --- a/bon-macros/src/collections/map.rs +++ b/bon-macros/src/collections/map.rs @@ -18,7 +18,7 @@ fn parse_map_pair(pair: ParseStream<'_>) -> Result<(Expr, Expr), syn::Error> { Ok((key, value)) } -pub(crate) fn generate(entries: Punctuated<(Expr, Expr), Token![,]>) -> TokenStream2 { +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)); diff --git a/bon-macros/src/collections/set.rs b/bon-macros/src/collections/set.rs index e9711483..e3e3ffef 100644 --- a/bon-macros/src/collections/set.rs +++ b/bon-macros/src/collections/set.rs @@ -3,7 +3,7 @@ use quote::quote; use syn::punctuated::Punctuated; use syn::{Expr, Token}; -pub(crate) fn generate(entries: Punctuated) -> TokenStream2 { +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! { diff --git a/bon-macros/src/error.rs b/bon-macros/src/error.rs index 00b9605f..bc3a3991 100644 --- a/bon-macros/src/error.rs +++ b/bon-macros/src/error.rs @@ -1,12 +1,12 @@ use crate::util::prelude::*; -use proc_macro2::{TokenStream as TokenStream2, TokenTree}; +use proc_macro2::{TokenStream as TokenStream, TokenTree}; use quote::{quote, ToTokens}; use syn::parse::Parse; /// Handle the error returned from the macro logic. This may be either a syntax -/// error or a logic error. In either case, we want to return a [`TokenStream2`] +/// error or a logic error. In either case, we want to return a [`TokenStream`] /// that still provides good IDE experience. See [`Fallback`] for details. -pub(crate) fn error_into_token_stream(err: Error, item: TokenStream2) -> TokenStream2 { +pub(crate) fn error_into_token_stream(err: Error, item: TokenStream) -> TokenStream { let compile_error = err.write_errors(); syn::parse2::(item) @@ -28,12 +28,12 @@ pub(crate) fn error_into_token_stream(err: Error, item: TokenStream2) -> TokenSt /// attributes that need to be processed by this macro to avoid the IDE from /// reporting those as well. struct Fallback { - output: TokenStream2, + output: TokenStream, } impl Parse for Fallback { fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result { - let mut output = TokenStream2::new(); + let mut output = TokenStream::new(); loop { let found_attr = input.step(|cursor| { @@ -75,7 +75,7 @@ impl Parse for Fallback { } impl ToTokens for Fallback { - fn to_tokens(&self, tokens: &mut TokenStream2) { + fn to_tokens(&self, tokens: &mut TokenStream) { self.output.to_tokens(tokens); } } diff --git a/bon-macros/src/lib.rs b/bon-macros/src/lib.rs index 214eb1c9..dc45bdfe 100644 --- a/bon-macros/src/lib.rs +++ b/bon-macros/src/lib.rs @@ -18,8 +18,6 @@ mod error; mod normalization; mod util; -use proc_macro::TokenStream; - /// Generates a builder for the function or method it's placed on. /// /// ## Quick examples @@ -96,7 +94,10 @@ use proc_macro::TokenStream; /// - [Guide](https://elastio.github.io/bon/guide/overview) /// - [Attributes reference](https://elastio.github.io/bon/reference/builder) #[proc_macro_attribute] -pub fn builder(params: TokenStream, item: TokenStream) -> TokenStream { +pub fn builder( + params: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { builder::generate_from_attr(params.into(), item.into()).into() } @@ -136,7 +137,7 @@ pub fn builder(params: TokenStream, item: TokenStream) -> TokenStream { /// - [Guide](https://elastio.github.io/bon/guide/overview) /// - [Attributes reference](https://elastio.github.io/bon/reference/builder) #[proc_macro_derive(Builder, attributes(builder))] -pub fn derive_builder(item: TokenStream) -> TokenStream { +pub fn derive_builder(item: proc_macro::TokenStream) -> proc_macro::TokenStream { builder::generate_from_derive(item.into()).into() } @@ -198,7 +199,10 @@ pub fn derive_builder(item: TokenStream) -> TokenStream { /// /// [`builder`]: macro@builder #[proc_macro_attribute] -pub fn bon(params: TokenStream, item: TokenStream) -> TokenStream { +pub fn bon( + params: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { bon::generate(params.into(), item.into()).into() } @@ -242,7 +246,7 @@ pub fn bon(params: TokenStream, item: TokenStream) -> TokenStream { /// [`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: TokenStream) -> TokenStream { +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() @@ -288,7 +292,7 @@ pub fn map(input: TokenStream) -> TokenStream { /// [`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: TokenStream) -> TokenStream { +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); diff --git a/bon-macros/src/normalization/cfg/mod.rs b/bon-macros/src/normalization/cfg/mod.rs index e7621279..7f7e917f 100644 --- a/bon-macros/src/normalization/cfg/mod.rs +++ b/bon-macros/src/normalization/cfg/mod.rs @@ -8,15 +8,15 @@ use std::collections::BTreeSet; pub(crate) enum ExpansionOutput { Expanded { - params: TokenStream2, + params: TokenStream, item: syn::Item, }, - Recurse(TokenStream2), + Recurse(TokenStream), } pub(crate) struct ExpandCfg { pub(crate) macro_path: syn::Path, - pub(crate) params: TokenStream2, + pub(crate) params: TokenStream, pub(crate) item: syn::Item, } @@ -65,7 +65,7 @@ impl ExpandCfg { /// 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> { + fn collect_predicates(&mut self) -> Result> { let mut predicates = vec![]; let mut visited = BTreeSet::new(); @@ -95,7 +95,7 @@ impl ExpandCfg { fn into_recursion( self, recursion_counter: usize, - predicates: &[TokenStream2], + predicates: &[TokenStream], ) -> Result { let Self { params, diff --git a/bon-macros/src/normalization/cfg/parse.rs b/bon-macros/src/normalization/cfg/parse.rs index 69db5059..34bd26cd 100644 --- a/bon-macros/src/normalization/cfg/parse.rs +++ b/bon-macros/src/normalization/cfg/parse.rs @@ -6,7 +6,7 @@ mod kw { syn::custom_keyword!(__cfgs); } -pub(crate) fn parse_predicate_results(tokens: TokenStream2) -> Result> { +pub(crate) fn parse_predicate_results(tokens: TokenStream) -> Result> { let results: WrapOption = syn::parse2(tokens)?; Ok(results.0) } @@ -22,14 +22,14 @@ struct WrapOption(Option); pub(crate) struct PredicateResults { pub(crate) results: Vec, pub(crate) recursion_counter: usize, - pub(crate) rest: TokenStream2, + 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::()?; + input.parse::()?; return Ok(Self(None)); } @@ -61,7 +61,7 @@ impl Parse for WrapOption { } pub(crate) enum CfgSyntax { - Cfg(TokenStream2), + Cfg(TokenStream), CfgAttr(CfgAttr), } diff --git a/bon-macros/src/util/ide.rs b/bon-macros/src/util/ide.rs index ddb83a06..c50ca9c7 100644 --- a/bon-macros/src/util/ide.rs +++ b/bon-macros/src/util/ide.rs @@ -100,7 +100,7 @@ impl Parse for Meta { #[derive(Clone, Debug)] pub(crate) struct MetaList { pub(crate) path: syn::Path, - pub(crate) tokens: TokenStream2, + pub(crate) tokens: TokenStream, } #[derive(Clone, Debug)] @@ -135,7 +135,7 @@ fn paths_from_meta(meta: Vec) -> Vec { /// 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) -> TokenStream2 { +pub(crate) fn generate_completion_triggers(meta: Vec) -> TokenStream { let completions = CompletionsSchema::with_children( "builder_top_level", vec![ @@ -195,7 +195,7 @@ impl CompletionsSchema { &self, mut meta: Vec, module_prefix: &[&syn::Ident], - ) -> TokenStream2 { + ) -> TokenStream { if let Some(custom_filter) = self.custom_filter { custom_filter(&mut meta); }; diff --git a/bon-macros/src/util/mod.rs b/bon-macros/src/util/mod.rs index 3b862aad..bba49fca 100644 --- a/bon-macros/src/util/mod.rs +++ b/bon-macros/src/util/mod.rs @@ -15,8 +15,7 @@ pub(crate) mod ide; use prelude::*; pub(crate) mod prelude { - /// A handy alias for [`proc_macro2::TokenStream`]. - pub(crate) use proc_macro2::{Span, TokenStream as TokenStream2}; + pub(crate) use proc_macro2::{Span, TokenStream}; /// 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. From 98cb5cca9cb65fa6f00ebf2c5b7e339e8d7cc1f1 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 1 Oct 2024 17:00:48 +0000 Subject: [PATCH 032/119] Add some more things to the prelude --- .../src/builder/builder_gen/builder_derives.rs | 1 - .../src/builder/builder_gen/builder_params.rs | 1 - bon-macros/src/builder/builder_gen/input_fn.rs | 14 ++++++-------- bon-macros/src/builder/builder_gen/input_struct.rs | 3 +-- .../builder/builder_gen/member/into_conversion.rs | 1 - bon-macros/src/builder/builder_gen/mod.rs | 1 - .../src/builder/builder_gen/setter_methods.rs | 3 +-- bon-macros/src/builder/builder_gen/state_mod.rs | 5 ++--- bon-macros/src/builder/item_fn.rs | 1 - bon-macros/src/builder/item_impl.rs | 1 - bon-macros/src/builder/item_struct.rs | 1 - bon-macros/src/builder/mod.rs | 1 - bon-macros/src/collections/map.rs | 1 - bon-macros/src/collections/set.rs | 1 - bon-macros/src/error.rs | 3 +-- bon-macros/src/normalization/cfg/mod.rs | 3 +-- bon-macros/src/normalization/impl_traits.rs | 2 +- bon-macros/src/util/ide.rs | 3 +-- bon-macros/src/util/ident.rs | 2 +- bon-macros/src/util/mod.rs | 1 + 20 files changed, 16 insertions(+), 33 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index 63f315f6..7e2f7adc 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -3,7 +3,6 @@ use super::BuilderGenCtx; use crate::builder::builder_gen::Member; use crate::util::prelude::*; use darling::ast::GenericParamExt; -use quote::quote; impl BuilderGenCtx { pub(crate) fn builder_derives(&self) -> TokenStream { diff --git a/bon-macros/src/builder/builder_gen/builder_params.rs b/bon-macros/src/builder/builder_gen/builder_params.rs index b1d52fa5..abe7b229 100644 --- a/bon-macros/src/builder/builder_gen/builder_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params.rs @@ -1,7 +1,6 @@ use crate::util::prelude::*; use darling::FromMeta; use proc_macro2::Span; -use quote::quote; use syn::parse::Parse; use syn::spanned::Spanned; use syn::visit::Visit; diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index 1ba3b6e2..eb21d63b 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -9,8 +9,6 @@ use crate::normalization::NormalizeSelfTy; use crate::util::prelude::*; use darling::util::SpannedValue; use darling::FromMeta; -use proc_macro2::Span; -use quote::quote; use std::rc::Rc; use syn::punctuated::Punctuated; use syn::visit::Visit; @@ -143,12 +141,12 @@ impl FnInputCtx { } if self.is_method_new() { - return quote::format_ident!("{}Builder", self.self_ty_prefix().unwrap_or_default()); + return format_ident!("{}Builder", self.self_ty_prefix().unwrap_or_default()); } let pascal_case_fn = self.norm_fn.sig.ident.snake_to_pascal_case(); - quote::format_ident!( + format_ident!( "{}{pascal_case_fn}Builder", self.self_ty_prefix().unwrap_or_default(), ) @@ -207,7 +205,7 @@ impl FnInputCtx { }) // By default we don't want to expose the positional function, so we // hide it under a generated name to avoid name conflicts. - .unwrap_or_else(|| quote::format_ident!("__orig_{}", orig_ident.raw_name())); + .unwrap_or_else(|| format_ident!("__orig_{}", orig_ident.raw_name())); strip_known_attrs_from_args(&mut orig.sig); @@ -325,7 +323,7 @@ impl FnInputCtx { // Special case for `new` methods. We rename them to `builder` // since this is the name that is used in the builder pattern let start_fn_ident = if is_method_new { - quote::format_ident!("builder") + format_ident!("builder") } else { self.norm_fn.sig.ident.clone() }; @@ -339,9 +337,9 @@ impl FnInputCtx { let finish_fn_ident = finish_fn_ident.unwrap_or_else(|| { // For `new` methods the `build` finisher is more conventional if is_method_new { - quote::format_ident!("build") + format_ident!("build") } else { - quote::format_ident!("call") + format_ident!("call") } }); diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 7b395f5a..22cdd416 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -6,7 +6,6 @@ use super::{ use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; use crate::util::prelude::*; use darling::FromMeta; -use quote::quote; use syn::visit_mut::VisitMut; #[derive(Debug, FromMeta)] @@ -206,7 +205,7 @@ impl StructInputCtx { let ItemParams { name, vis, docs } = self.params.base.builder_type; let builder_ident = name.unwrap_or_else(|| { - quote::format_ident!("{}Builder", self.norm_struct.ident.raw_name()) + format_ident!("{}Builder", self.norm_struct.ident.raw_name()) }); BuilderTypeParams { diff --git a/bon-macros/src/builder/builder_gen/member/into_conversion.rs b/bon-macros/src/builder/builder_gen/member/into_conversion.rs index 78940d02..be473880 100644 --- a/bon-macros/src/builder/builder_gen/member/into_conversion.rs +++ b/bon-macros/src/builder/builder_gen/member/into_conversion.rs @@ -2,7 +2,6 @@ use super::params::{BlanketParamName, EvalBlanketFlagParam}; use super::{NamedMember, PositionalFnArgMember}; use crate::builder::builder_gen::builder_params::OnParams; use crate::util::prelude::*; -use quote::{quote, ToTokens}; impl NamedMember { pub(super) fn merge_param_into(&mut self, on_params: &[OnParams]) -> Result { diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index dc96e163..89bd12fa 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -15,7 +15,6 @@ use member::{ use models::{ AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, }; -use quote::{quote, ToTokens}; use setter_methods::MemberSettersCtx; pub(crate) struct MacroOutput { diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index 7f929a7a..4c689b1a 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -1,6 +1,5 @@ use super::{BuilderGenCtx, NamedMember}; use crate::util::prelude::*; -use quote::quote; pub(crate) struct MemberSettersCtx<'a> { builder_gen: &'a BuilderGenCtx, @@ -131,7 +130,7 @@ impl<'a> MemberSettersCtx<'a> { let member_pascal = &self.member.norm_ident_pascal; let state_transition = - quote::format_ident!("Set{}", self.member.norm_ident_pascal.raw_name()); + format_ident!("Set{}", self.member.norm_ident_pascal.raw_name()); let state_mod = &self.builder_gen.state_mod.ident; let generic_param = if self.builder_gen.stateful_members().take(2).count() == 1 { diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index acb0badb..78e53248 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -1,6 +1,5 @@ use crate::builder::builder_gen::member::NamedMember; use crate::util::prelude::*; -use quote::quote; impl super::BuilderGenCtx { fn state_transition_aliases(&self) -> Vec { @@ -28,7 +27,7 @@ impl super::BuilderGenCtx { let member_ident = member.public_ident(); let alias_ident = - quote::format_ident!("Set{}", member.norm_ident_pascal.raw_name()); + format_ident!("Set{}", member.norm_ident_pascal.raw_name()); let docs = format!( "Returns a [`State`] that has [`IsSet`] implemented for `{member_ident}`\n\ @@ -95,7 +94,7 @@ impl super::BuilderGenCtx { .map(|member| &member.norm_ident_pascal) .collect::>(); - let type_aliases_for_rustdoc = ["e::format_ident!("AllUnset")]; + let type_aliases_for_rustdoc = [&format_ident!("AllUnset")]; let type_aliases_for_rustdoc = state_transition_aliases .iter() .map(|alias| &alias.ident) diff --git a/bon-macros/src/builder/item_fn.rs b/bon-macros/src/builder/item_fn.rs index bd181128..e6d6b549 100644 --- a/bon-macros/src/builder/item_fn.rs +++ b/bon-macros/src/builder/item_fn.rs @@ -1,7 +1,6 @@ use super::builder_gen::input_fn::{FnInputCtx, FnInputParams}; use super::builder_gen::MacroOutput; use crate::util::prelude::*; -use quote::quote; use syn::visit_mut::VisitMut; pub(crate) fn generate(params: FnInputParams, orig_fn: syn::ItemFn) -> Result { diff --git a/bon-macros/src/builder/item_impl.rs b/bon-macros/src/builder/item_impl.rs index a9fee3ab..f5c197b5 100644 --- a/bon-macros/src/builder/item_impl.rs +++ b/bon-macros/src/builder/item_impl.rs @@ -2,7 +2,6 @@ use super::builder_gen::input_fn::{FnInputCtx, FnInputParams, ImplCtx}; use crate::util::prelude::*; use darling::ast::NestedMeta; use darling::FromMeta; -use quote::quote; use std::rc::Rc; use syn::visit_mut::VisitMut; diff --git a/bon-macros/src/builder/item_struct.rs b/bon-macros/src/builder/item_struct.rs index a7e4d07d..64011623 100644 --- a/bon-macros/src/builder/item_struct.rs +++ b/bon-macros/src/builder/item_struct.rs @@ -1,7 +1,6 @@ use super::builder_gen::input_struct::StructInputCtx; use super::builder_gen::MacroOutput; use crate::util::prelude::*; -use quote::quote; pub(crate) fn generate(orig_struct: syn::ItemStruct) -> Result { let struct_ident = orig_struct.ident.clone(); diff --git a/bon-macros/src/builder/mod.rs b/bon-macros/src/builder/mod.rs index 4f523446..e752dce7 100644 --- a/bon-macros/src/builder/mod.rs +++ b/bon-macros/src/builder/mod.rs @@ -9,7 +9,6 @@ use crate::normalization::{ExpandCfg, ExpansionOutput}; use crate::util; use crate::util::prelude::*; use darling::FromMeta; -use quote::quote; use syn::parse::Parser; pub(crate) fn generate_from_derive(item: TokenStream) -> TokenStream { diff --git a/bon-macros/src/collections/map.rs b/bon-macros/src/collections/map.rs index ec17ef84..eb195a8e 100644 --- a/bon-macros/src/collections/map.rs +++ b/bon-macros/src/collections/map.rs @@ -1,5 +1,4 @@ use crate::util::prelude::*; -use quote::quote; use syn::parse::ParseStream; use syn::punctuated::Punctuated; use syn::{Expr, Token}; diff --git a/bon-macros/src/collections/set.rs b/bon-macros/src/collections/set.rs index e3e3ffef..175d8edc 100644 --- a/bon-macros/src/collections/set.rs +++ b/bon-macros/src/collections/set.rs @@ -1,5 +1,4 @@ use crate::util::prelude::*; -use quote::quote; use syn::punctuated::Punctuated; use syn::{Expr, Token}; diff --git a/bon-macros/src/error.rs b/bon-macros/src/error.rs index bc3a3991..99cfaf24 100644 --- a/bon-macros/src/error.rs +++ b/bon-macros/src/error.rs @@ -1,6 +1,5 @@ use crate::util::prelude::*; -use proc_macro2::{TokenStream as TokenStream, TokenTree}; -use quote::{quote, ToTokens}; +use proc_macro2::TokenTree; use syn::parse::Parse; /// Handle the error returned from the macro logic. This may be either a syntax diff --git a/bon-macros/src/normalization/cfg/mod.rs b/bon-macros/src/normalization/cfg/mod.rs index 7f7e917f..bf0e5a1b 100644 --- a/bon-macros/src/normalization/cfg/mod.rs +++ b/bon-macros/src/normalization/cfg/mod.rs @@ -3,7 +3,6 @@ mod visit; use crate::util::prelude::*; use parse::CfgSyntax; -use quote::{quote, ToTokens}; use std::collections::BTreeSet; pub(crate) enum ExpansionOutput { @@ -108,7 +107,7 @@ impl ExpandCfg { 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 = quote::format_ident!("{invocation_name}_{recursion_counter}_{i}"); + let pred_id = format_ident!("{invocation_name}_{recursion_counter}_{i}"); quote!(#pred_id: #predicate) }); diff --git a/bon-macros/src/normalization/impl_traits.rs b/bon-macros/src/normalization/impl_traits.rs index d40b6026..9d9c27ea 100644 --- a/bon-macros/src/normalization/impl_traits.rs +++ b/bon-macros/src/normalization/impl_traits.rs @@ -54,7 +54,7 @@ impl VisitMut for AssignTypeParams<'_> { let index = self.next_type_param_index; self.next_type_param_index += 1; - let type_param = quote::format_ident!("ImplTrait{index}"); + let type_param = format_ident!("ImplTrait{index}"); let impl_trait = std::mem::replace(ty, syn::Type::Path(syn::parse_quote!(#type_param))); let impl_trait = match impl_trait { diff --git a/bon-macros/src/util/ide.rs b/bon-macros/src/util/ide.rs index c50ca9c7..8465ac51 100644 --- a/bon-macros/src/util/ide.rs +++ b/bon-macros/src/util/ide.rs @@ -10,8 +10,7 @@ )] use crate::util::prelude::*; -use proc_macro2::{Span, TokenTree}; -use quote::quote; +use proc_macro2::TokenTree; use syn::parse::{Parse, ParseStream, Parser}; use syn::{token, Token}; diff --git a/bon-macros/src/util/ident.rs b/bon-macros/src/util/ident.rs index 9b43f4ee..b88348f9 100644 --- a/bon-macros/src/util/ident.rs +++ b/bon-macros/src/util/ident.rs @@ -1,5 +1,5 @@ use ident_case::RenameRule; -use proc_macro2::Span; +use crate::util::prelude::*; pub(crate) trait IdentExt { /// Converts the ident (assumed to be in `snake_case`) to `PascalCase` without diff --git a/bon-macros/src/util/mod.rs b/bon-macros/src/util/mod.rs index bba49fca..95e4d05c 100644 --- a/bon-macros/src/util/mod.rs +++ b/bon-macros/src/util/mod.rs @@ -16,6 +16,7 @@ use prelude::*; pub(crate) mod prelude { pub(crate) use proc_macro2::{Span, TokenStream}; + pub(crate) use quote::{quote, format_ident, 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. From af42d028d8c55148401ce0a640e338fb7af774d3 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 1 Oct 2024 19:09:12 +0000 Subject: [PATCH 033/119] Fix whitespace --- benchmarks/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/run.sh b/benchmarks/run.sh index 865c5814..2ecc8a5e 100755 --- a/benchmarks/run.sh +++ b/benchmarks/run.sh @@ -16,7 +16,7 @@ cargo asm --features "$bench" --no-color "benchmarks::$bench::builder_bench" > b cargo asm --features "$bench" --no-color "benchmarks::$bench::regular_bench" > regular.dbg.s || true # If vscode is present, show diff: - if command -v code; then +if command -v code; then code --diff regular.dbg.s builder.dbg.s fi From 319ca1283606048bbe1f9d7335e22ae84985761e Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 1 Oct 2024 21:35:59 +0000 Subject: [PATCH 034/119] Commit initial v3 design --- .../src/builder/builder_gen/builder_params.rs | 16 +- .../src/builder/builder_gen/member/mod.rs | 7 +- .../builder/builder_gen/member/params/mod.rs | 33 +- .../builder_gen/member/params/setter.rs | 68 ++++ bon-macros/src/builder/builder_gen/mod.rs | 39 -- bon-macros/src/util/mod.rs | 1 + bon-macros/src/util/parsing.rs | 54 +++ e2e-tests/src/lib.rs | 1 + e2e-tests/src/v3_design.rs | 368 ++++++++++++++++++ 9 files changed, 521 insertions(+), 66 deletions(-) create mode 100644 bon-macros/src/builder/builder_gen/member/params/setter.rs create mode 100644 bon-macros/src/util/parsing.rs create mode 100644 e2e-tests/src/v3_design.rs diff --git a/bon-macros/src/builder/builder_gen/builder_params.rs b/bon-macros/src/builder/builder_gen/builder_params.rs index abe7b229..cf07598b 100644 --- a/bon-macros/src/builder/builder_gen/builder_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params.rs @@ -172,7 +172,7 @@ impl ItemParamsParsing<'_> { if let Some(context) = self.reject_self_mentions { if let Some(docs) = ¶ms.docs { - super::reject_self_mentions_in_docs(context, docs)?; + crate::util::parsing::reject_self_mentions_in_docs(context, docs)?; } } @@ -215,18 +215,8 @@ impl ItemParamsParsing<'_> { let docs = full .docs - .map(|docs| { - let docs = docs.require_list()?; - let docs = docs.parse_args_with(syn::Attribute::parse_outer)?; - - for attr in &docs { - if !attr.is_doc() { - bail!(attr, "expected a doc comment"); - } - } - - Ok(docs) - }) + .as_ref() + .map(crate::util::parsing::parse_docs) .transpose()?; let params = ItemParams { diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 392c9133..77d4ff35 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -46,7 +46,7 @@ pub(crate) enum Member { } /// Regular member for which the builder should have setter methods -#[derive(Debug, Clone)] +#[derive(Debug)] pub(crate) struct NamedMember { /// Specifies what syntax the member comes from. pub(crate) origin: MemberOrigin, @@ -121,7 +121,10 @@ pub(crate) struct SkippedMember { impl NamedMember { fn validate(&self) -> Result { - super::reject_self_mentions_in_docs("builder struct's impl block", &self.docs)?; + crate::util::parsing::reject_self_mentions_in_docs( + "builder struct's impl block", + &self.docs, + )?; if let Some(default) = &self.params.default { if self.norm_ty.is_option() { diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index 18736079..9d647e81 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -1,6 +1,8 @@ mod blanket; +mod setter; pub(crate) use blanket::{BlanketParamName, EvalBlanketFlagParam}; +pub(crate) use setter::{SetterParenParams, SetterParams}; use super::MemberOrigin; use crate::util::prelude::*; @@ -8,7 +10,7 @@ use darling::util::SpannedValue; use std::fmt; use syn::spanned::Spanned; -#[derive(Debug, Clone, darling::FromAttributes)] +#[derive(Debug, darling::FromAttributes)] #[darling(attributes(builder))] pub(crate) struct MemberParams { /// Enables an `Into` conversion for the setter method. @@ -31,6 +33,9 @@ pub(crate) struct MemberParams { /// Rename the name exposed in the builder API. pub(crate) name: Option, + /// Configurations for the setter methods. + pub(crate) setter: Option, + /// Where to place the member in the generated builder methods API. /// By default the member is treated like a named parameter that /// gets its own setter methods. @@ -49,24 +54,26 @@ pub(crate) struct MemberParams { #[derive(PartialEq, Eq, Clone, Copy)] enum ParamName { Default, + FinishFn, Into, Name, + Overwritable, + Setter, Skip, StartFn, - FinishFn, - Overwritable, } impl fmt::Display for ParamName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let str = match self { Self::Default => "default", + Self::FinishFn => "finish_fn", Self::Into => "into", Self::Name => "name", + Self::Overwritable => "overwritable", + Self::Setter => "setter", Self::Skip => "skip", Self::StartFn => "start_fn", - Self::FinishFn => "finish_fn", - Self::Overwritable => "overwritable", }; f.write_str(str) } @@ -101,23 +108,25 @@ impl MemberParams { fn specified_param_names(&self) -> impl Iterator { let Self { - into, default, - skip, - name, finish_fn, - start_fn, + into, + name, overwritable, + setter, + skip, + start_fn, } = self; let attrs = [ (default.is_some(), ParamName::Default), - (name.is_some(), ParamName::Name), + (finish_fn.is_present(), ParamName::FinishFn), (into.is_present(), ParamName::Into), + (name.is_some(), ParamName::Name), + (overwritable.is_present(), ParamName::Overwritable), + (setter.is_some(), ParamName::Setter), (skip.is_some(), ParamName::Skip), (start_fn.is_present(), ParamName::StartFn), - (finish_fn.is_present(), ParamName::FinishFn), - (overwritable.is_present(), ParamName::Overwritable), ]; attrs diff --git a/bon-macros/src/builder/builder_gen/member/params/setter.rs b/bon-macros/src/builder/builder_gen/member/params/setter.rs new file mode 100644 index 00000000..ce1be2a7 --- /dev/null +++ b/bon-macros/src/builder/builder_gen/member/params/setter.rs @@ -0,0 +1,68 @@ +use crate::util::prelude::*; +use darling::FromMeta; + +#[derive(Debug)] +pub(crate) struct SetterParams { + pub(crate) name: Option, + pub(crate) vis: Option, + pub(crate) docs: Option>, + pub(crate) with: Option, +} + +impl FromMeta for SetterParams { + fn from_meta(meta: &syn::Meta) -> Result { + let err = || { + err!( + meta, + "expected a #[builder(setter = {{closure}})], or \ + #[buidler(setter({{key}} = {{value}}... ))] syntax" + ) + }; + + let meta_list = match meta { + syn::Meta::List(meta) => meta, + syn::Meta::Path(_) => return Err(err()), + syn::Meta::NameValue(meta) => match &meta.value { + syn::Expr::Closure(closure) => { + return Ok(Self { + name: None, + vis: None, + docs: None, + with: Some(syn::Expr::Closure(closure.clone())), + }) + }, + _ => return Err(err()), + }, + }; + + #[derive(FromMeta)] + struct Parsed { + name: Option, + vis: Option, + + #[darling(default, with = crate::util::parsing::parse_docs, map = Some)] + docs: Option>, + } + + let Parsed { name, vis, docs } = Parsed::from_meta(meta)?; + + if let Some(docs) = &docs { + crate::util::parsing::reject_self_mentions_in_docs( + "builder struct's impl block", + docs, + )?; + } + + Ok(Self { name, vis, docs }) + + match meta_list.delimiter { + syn::MacroDelimiter::Bracket(_) => return Err(err()), + syn::MacroDelimiter::Paren(_) => { + return SetterParenParams::from_meta(meta).map(Self::Paren) + } + syn::MacroDelimiter::Brace(_) => { + + }, + } + } +} diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 89bd12fa..8ace5b99 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -604,42 +604,3 @@ fn allow_warnings_on_member_types() -> TokenStream { #[allow(unused_parens)] } } - -/// 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. -fn reject_self_mentions_in_docs(context: &'static str, attrs: &[syn::Attribute]) -> Result { - for attr in attrs { - let doc = match attr.as_doc() { - 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(()) -} diff --git a/bon-macros/src/util/mod.rs b/bon-macros/src/util/mod.rs index 95e4d05c..d5ba4956 100644 --- a/bon-macros/src/util/mod.rs +++ b/bon-macros/src/util/mod.rs @@ -11,6 +11,7 @@ mod vec; mod visibility; pub(crate) mod ide; +pub(crate) mod parsing; use prelude::*; diff --git a/bon-macros/src/util/parsing.rs b/bon-macros/src/util/parsing.rs new file mode 100644 index 00000000..0f44c33a --- /dev/null +++ b/bon-macros/src/util/parsing.rs @@ -0,0 +1,54 @@ +use crate::util::prelude::*; + +pub(crate) fn parse_docs(meta: &syn::Meta) -> Result> { + let attrs = meta + .require_list()? + .parse_args_with(syn::Attribute::parse_outer)?; + + for attr in &attrs { + if !attr.is_doc() { + bail!(attr, "expected a doc comment"); + } + } + + Ok(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() { + 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(()) +} diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index 5fec2cc7..ad4dadf1 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -4,6 +4,7 @@ pub mod macro_rules_wrapper_test; pub mod missing_docs_test; +pub mod v3_design; use bon::{bon, builder}; diff --git a/e2e-tests/src/v3_design.rs b/e2e-tests/src/v3_design.rs new file mode 100644 index 00000000..11802859 --- /dev/null +++ b/e2e-tests/src/v3_design.rs @@ -0,0 +1,368 @@ +use bon::{builder, bon}; + +struct Point { + x: f64, + y: f64, +} + +#[derive(bon::Builder, Debug)] +#[builder( + builder_type( + name = ExampleBuilder, + docs { + /// Docs for the builder + }, + ), + + state_mod( + vis = "pub", + name = name_override, + docs { + /// Docs for the state module + }, + // Deprecate the state module + deprecated = "..." + ), + + on( + Point, + + // (v3.3) + // + // Overrides the setters generated for the `Point` type. + setter = |x: f64, y: f64| Point { x, y }, + ), + + // (v3.4) + // + // Makes the builder exhaustive (all members must be set). Doesn't change + // the setters that are generated, only adds more bounds on the `finish_fn` + // to require all members to be set. + // + // Maybe there should be an alias for this pattern `#[builder(exhaustive)]`? + // Will there be frequent use cases for this? + on(_, required), + + // More predicates in `on(...)` + on(not(required()), overwritable) +)] +pub struct Example { + /// Docs on member + #[builder( + name = foo, + + // The `visibility` at this level may be confusing, because it doesn't + // influence the the visibility of "state" items. + // + // (v3.0) + // vis = "", + + // (v3.0) + // + // Override the name in the state + state = PointOverride + + // (v3.0) + // + // Advanced state configuration + state( + name = PointOverride, + + deprecated = "This state is no longer available, the field is overwritable", + assoc_type = NameOverride, + assoc_type( + name = NameOverride + docs { + /// Docs for associated type + }, + ), + transition_alias = NameOverride, + transition_alias( + name = NameOverride, + docs { + /// Docs for transition alias + }, + ) + ), + + // (v3.0) + // + // Overrides the name, visibility and docs of the default setters + setter( + name = point_internal, + vis = "", + + // By default copied to all members + docs { + /// Docs for the required setter + } + + // (v3.1) + // + // Overrides specific to the `{member}` setter that wraps the value with `Some` + // + // Other names: `by_value` + arg_some( + name = point_internal, + vis = "", + docs { ... } + ), + + // (v3.1) + // + // Overrides specific to the `maybe_{member}` setter. + // + // Other names: `by_option` + arg_option( + name = maybe_point_internal, + vis = "", + docs { ... } + ) + + arg_absent( + // ... + ) + ), + + // (v3.1) + // + // Shortcut for: + // ``` + // setters { + // {vis} fn {name}(...) -> _ { + // ... + // } + // } + // ``` + // + // For optional members, the `maybe_` setter will accept an `Option<(..args)>` + // if there is more than one argument. + setter = |value: Option| Point { x, y }, + + // (v3.1) + // + // Completely custom overrides for setters. + // The function needs to place `_` in the return type and return the + // type of the member. It can also be async or unsafe or return a + // `Result<_[, Error]>`, in which case the setter will propagate the + // error to the caller. + // + // Access to `BuilderState` must prohibited. The generic params from + // the struct should be in scope. + setter { + /// Docs for `foo` + #[deprecated] + fn foo(x: f64, y: f64) -> _ { expr } + + /// Docs for `bar` + fn bar(val: Option<(f64, f64)>) -> _ { expr } + + /// (v3.2 ??) syntax sugar + #[deprecated] + foo = |...| expr; + + /// (v3.2 ??) syntax sugar + maybe_foo = |...| expr; + } + + // (v3.2) + // + // These must work for regular members and start_fn args (custom fields?, v3.3) + // Consider exposing `start_fn` args and overwritable optional fields as regular + // private (!) fields on the builder additionally. This will allow for more flexibility + // in the builder impl block. + // + // &T, Option<&T> + getter, + + // (v3.2) + // + // &Option + getter(transparent), + + // (v3.2) + // &mut T, Option<&mut T> + getter(mut), + + // (v3.2) + // + // &mut Option + getter(transparent, mut) + + // (v3.2) + // + // Deref to &str, Option<&str> + getter(deref(&str)), + + // (v3.2) + // + // Deref to &mut str, Option<&mut str> + getter(deref(&mut str)), + + // (v3.2) + // + // AsRef to &str + getter(as_ref(&str)), + + // (v3.2) + // + // `Option::as_ref() -> Option<&T>` + getter(as_ref), + + // (v3.2) + // + // `>::as_ref() -> Option<&_>` + getter(as_ref(&str)), + + + // (v3.2) + // + // Getter by `Copy` -> `T` + getter(copy), + + // (v3.2) + // + // Getter by `Clone` -> `T` + getter(clone), + + getter( + name = getter_name, + vis = "", + docs { + /// Docs for getter_name + } + deprecated, + copy, + ) + + // Multiple getters need to have name specified explicitly + // getter(name = getter_name_1, copy), + getter(name = getter_name_2), + + // Custom readonly getter. Accepts a readonly reference and transforms it. + getter = |value: &_| -> Ty { expr } + + // Custom mutable getter. Accepts a mutable reference and transforms it. + getter = |value: &mut _| -> Ty { expr } + + // If there are multiple getters, then names must be assigned explicitly. + getter { + // Long syntax. Full function signature. `_` can be used in place of + // the member's type to avoid repeating it. + + /// Docs for getter_name_1 + fn getter_name_1(value: &_) -> Ty { expr } + + /// Docs for getter_name_2 + fn getter_name_2(value: &mut _) -> Ty { expr } + + // No short syntax. The syntax savings are minimal compared with the closure style. + // getter_name_3 = |value: &mut _| -> Ty { expr }; + } + )] + point: Point, + + // v3.0 + #[builder(overwritable)] + overwritable: u32, + + #[builder( + field = vec![], + field(name = overridden_name, vis = "pub", docs { ... }, init = vec![]), + deprecated(reason = "saasd"), + )] + #[deprecated = "Use `overridden_name` instead"] + pub custom_state: Vec, + + // Generates two setters for booleans: + // - `my_lovely_flag() -> true` + // - `with_my_lovely_flag(bool)` + // + // It also automatically implies that the setters are optional to call. + // The default value is `false` automatically. The `#[builder(default)]` + // attribute is not allowed with the `flag` attribute. + #[builder(flag)] + my_lovely_flag: bool, + + // The same as `#[builder(flag)]` but additionally requires the caller + // to call the setter for this member explicitly. + #[builder(flag, required)] + my_required_flag: bool, + + // Opts out from the special handling for `Option`. Generates only + // a single setter that accepts `Option` as a value. It's required + // to call the setter. + #[builder(transparent)] + required_option: Option, + + // Still generates a pair of setters (arg_value, arg_option), but requires + // calling ant of these setters. + #[builder(required)] + exhaustive_option: Option, + + // Still generates a pair of setters (arg_value, arg_option), but requires + // calling ant of these setters. + #[builder(required, default = 32)] + exhaustive_default: u32, +} + +// Use cases: +#[derive(bon::Builder)] +struct UseCases { + // (v3.0) + // + // Generate private setters with names `[maybe_]point_internal` and + // preserve the public name in the `state` as `Point`. + #[builder( + name = point_internal, + vis = "", + state = Point, + )] + // (v3.0) + #[builder(setters(docs { + /// Docs for the setter that accepts the value itself. + /// + }))] + override_docs_for_default_setters: Option, + + // (v3.1) + #[builder(setter = |iter: impl IntoIterator| Vec::from_iter(iter))] + take_into_iter: Vec, + + // (v3.1) + #[builder(setter = |x: f64, y: f64| Point { x, y })] + take_several_args: Point, + + // (v3.1) + #[builder(setters { + fn point(x: f64, y: f64) -> _ { + Some(Point { x, y }) + } + fn maybe_point(val: Option<(f64, f64)>) -> _ { + let (x, y) = val?; + point(x, y) + } + })] + several_setters: Option, +} + + +impl ExampleBuilder { + pub fn my_point(self, x: f64, y: f64) -> ExampleBuilder> + where + State::Point: example_builder::IsUnset, + { + self.point(Point { x, y }) + } +} + +#[bon] +impl Example { + // Prevent shadowing the `new` function with the builder syntax. + #[builder(separate)] + fn new() {} +} + +// A rename prevents shadowing the `example` function with the builder syntax +#[builder(start_fn = example_builder)] +fn example() {} From 2c43aa3f575239f0165f077a57556bfc72631117 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 2 Oct 2024 16:10:40 +0000 Subject: [PATCH 035/119] Add setters config --- .../src/builder/builder_gen/builder_params.rs | 80 +------- .../src/builder/builder_gen/input_fn.rs | 2 +- .../src/builder/builder_gen/input_struct.rs | 8 +- .../src/builder/builder_gen/member/mod.rs | 2 +- .../builder/builder_gen/member/params/mod.rs | 15 +- .../builder_gen/member/params/setter.rs | 90 +++------ bon-macros/src/builder/builder_gen/models.rs | 3 +- bon-macros/src/lib.rs | 1 + .../src/{util/parsing.rs => parsing/docs.rs} | 22 ++- bon-macros/src/parsing/item_params.rs | 73 +++++++ bon-macros/src/parsing/mod.rs | 16 ++ bon-macros/src/util/meta_list.rs | 66 +++++++ bon-macros/src/util/mod.rs | 5 +- .../ui/compile_fail/wrong_delimiters.rs | 58 ++++++ .../ui/compile_fail/wrong_delimiters.stderr | 107 ++++++++++ e2e-tests/src/v3_design.rs | 182 +++++++----------- 16 files changed, 463 insertions(+), 267 deletions(-) rename bon-macros/src/{util/parsing.rs => parsing/docs.rs} (74%) create mode 100644 bon-macros/src/parsing/item_params.rs create mode 100644 bon-macros/src/parsing/mod.rs create mode 100644 bon-macros/src/util/meta_list.rs create mode 100644 bon/tests/integration/ui/compile_fail/wrong_delimiters.rs create mode 100644 bon/tests/integration/ui/compile_fail/wrong_delimiters.stderr diff --git a/bon-macros/src/builder/builder_gen/builder_params.rs b/bon-macros/src/builder/builder_gen/builder_params.rs index cf07598b..171ccefd 100644 --- a/bon-macros/src/builder/builder_gen/builder_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params.rs @@ -1,3 +1,4 @@ +use crate::parsing::{ItemParams, ItemParamsParsing}; use crate::util::prelude::*; use darling::FromMeta; use proc_macro2::Span; @@ -40,11 +41,11 @@ pub(crate) struct BuilderParams { #[darling(default, with = parse_state_mod)] pub(crate) state_mod: ItemParams, - #[darling(multiple)] + #[darling(multiple, with = crate::parsing::require_paren_delim_for_meta_list)] pub(crate) on: Vec, /// Specifies the derives to apply to the builder. - #[darling(default)] + #[darling(default, with = crate::parsing::require_paren_delim_for_meta_list)] pub(crate) derive: BuilderDerives, } @@ -153,78 +154,3 @@ impl FromMeta for OnParams { Ok(me) } } - -#[derive(Debug, Clone, Default)] -pub(crate) struct ItemParams { - pub(crate) name: Option, - pub(crate) vis: Option, - pub(crate) docs: Option>, -} - -pub(crate) struct ItemParamsParsing<'a> { - pub(crate) meta: &'a syn::Meta, - pub(crate) reject_self_mentions: Option<&'static str>, -} - -impl ItemParamsParsing<'_> { - pub(crate) fn parse(self) -> Result { - let params = Self::params_from_meta(self.meta)?; - - if let Some(context) = self.reject_self_mentions { - if let Some(docs) = ¶ms.docs { - crate::util::parsing::reject_self_mentions_in_docs(context, docs)?; - } - } - - Ok(params) - } - - fn params_from_meta(meta: &syn::Meta) -> Result { - if let syn::Meta::NameValue(meta) = meta { - let val = &meta.value; - let name = syn::parse2(quote!(#val))?; - - return Ok(ItemParams { - name: Some(name), - vis: None, - docs: None, - }); - } - - #[derive(Debug, FromMeta)] - struct Full { - name: Option, - vis: Option, - docs: Option, - } - - let full = Full::from_meta(meta)?; - - let is_empty = matches!( - full, - Full { - name: None, - vis: None, - docs: None, - } - ); - - if is_empty { - bail!(meta, "expected at least one parameter in parentheses"); - } - - let docs = full - .docs - .as_ref() - .map(crate::util::parsing::parse_docs) - .transpose()?; - - let params = ItemParams { - name: full.name, - vis: full.vis, - docs, - }; - - Ok(params) - } -} diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index eb21d63b..d750a046 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -3,9 +3,9 @@ use super::{ AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, Member, MemberOrigin, RawMember, }; -use crate::builder::builder_gen::builder_params::ItemParams; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; use crate::normalization::NormalizeSelfTy; +use crate::parsing::ItemParams; use crate::util::prelude::*; use darling::util::SpannedValue; use darling::FromMeta; diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 22cdd416..845eacc1 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -1,9 +1,10 @@ -use super::builder_params::{BuilderParams, ItemParams, ItemParamsParsing}; +use super::builder_params::BuilderParams; use super::{ AssocMethodCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, Member, MemberOrigin, RawMember, }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; +use crate::parsing::{ItemParams, ItemParamsParsing}; use crate::util::prelude::*; use darling::FromMeta; use syn::visit_mut::VisitMut; @@ -204,9 +205,8 @@ impl StructInputCtx { let builder_type = { let ItemParams { name, vis, docs } = self.params.base.builder_type; - let builder_ident = name.unwrap_or_else(|| { - format_ident!("{}Builder", self.norm_struct.ident.raw_name()) - }); + let builder_ident = name + .unwrap_or_else(|| format_ident!("{}Builder", self.norm_struct.ident.raw_name())); BuilderTypeParams { derives: self.params.base.derive, diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 77d4ff35..e2f7251f 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -121,7 +121,7 @@ pub(crate) struct SkippedMember { impl NamedMember { fn validate(&self) -> Result { - crate::util::parsing::reject_self_mentions_in_docs( + crate::parsing::reject_self_mentions_in_docs( "builder struct's impl block", &self.docs, )?; diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index 9d647e81..ff08b33e 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -2,7 +2,7 @@ mod blanket; mod setter; pub(crate) use blanket::{BlanketParamName, EvalBlanketFlagParam}; -pub(crate) use setter::{SetterParenParams, SetterParams}; +pub(crate) use setter::SettersParams; use super::MemberOrigin; use crate::util::prelude::*; @@ -34,7 +34,8 @@ pub(crate) struct MemberParams { pub(crate) name: Option, /// Configurations for the setter methods. - pub(crate) setter: Option, + #[darling(with = crate::parsing::require_paren_delim_for_meta_list)] + pub(crate) setters: Option, /// Where to place the member in the generated builder methods API. /// By default the member is treated like a named parameter that @@ -58,7 +59,7 @@ enum ParamName { Into, Name, Overwritable, - Setter, + Setters, Skip, StartFn, } @@ -71,7 +72,7 @@ impl fmt::Display for ParamName { Self::Into => "into", Self::Name => "name", Self::Overwritable => "overwritable", - Self::Setter => "setter", + Self::Setters => "setters", Self::Skip => "skip", Self::StartFn => "start_fn", }; @@ -113,7 +114,7 @@ impl MemberParams { into, name, overwritable, - setter, + setters: setter, skip, start_fn, } = self; @@ -124,7 +125,7 @@ impl MemberParams { (into.is_present(), ParamName::Into), (name.is_some(), ParamName::Name), (overwritable.is_present(), ParamName::Overwritable), - (setter.is_some(), ParamName::Setter), + (setter.is_some(), ParamName::Setters), (skip.is_some(), ParamName::Skip), (start_fn.is_present(), ParamName::StartFn), ]; @@ -184,6 +185,6 @@ fn parse_optional_expression(meta: &syn::Meta) -> Result Ok(SpannedValue::new(None, meta.span())), syn::Meta::List(_) => Err(Error::unsupported_format("list").with_span(meta)), - syn::Meta::NameValue(nv) => Ok(SpannedValue::new(Some(nv.value.clone()), nv.span())), + syn::Meta::NameValue(meta) => Ok(SpannedValue::new(Some(meta.value.clone()), meta.span())), } } diff --git a/bon-macros/src/builder/builder_gen/member/params/setter.rs b/bon-macros/src/builder/builder_gen/member/params/setter.rs index ce1be2a7..fd558df8 100644 --- a/bon-macros/src/builder/builder_gen/member/params/setter.rs +++ b/bon-macros/src/builder/builder_gen/member/params/setter.rs @@ -1,68 +1,40 @@ +use crate::parsing::{ItemParams, ItemParamsParsing}; use crate::util::prelude::*; use darling::FromMeta; -#[derive(Debug)] -pub(crate) struct SetterParams { - pub(crate) name: Option, - pub(crate) vis: Option, - pub(crate) docs: Option>, - pub(crate) with: Option, -} - -impl FromMeta for SetterParams { - fn from_meta(meta: &syn::Meta) -> Result { - let err = || { - err!( - meta, - "expected a #[builder(setter = {{closure}})], or \ - #[buidler(setter({{key}} = {{value}}... ))] syntax" - ) - }; - - let meta_list = match meta { - syn::Meta::List(meta) => meta, - syn::Meta::Path(_) => return Err(err()), - syn::Meta::NameValue(meta) => match &meta.value { - syn::Expr::Closure(closure) => { - return Ok(Self { - name: None, - vis: None, - docs: None, - with: Some(syn::Expr::Closure(closure.clone())), - }) - }, - _ => return Err(err()), - }, - }; - - #[derive(FromMeta)] - struct Parsed { - name: Option, - vis: Option, - - #[darling(default, with = crate::util::parsing::parse_docs, map = Some)] - docs: Option>, - } +const DOCS_CONTEXT: &str = "builder struct's impl block"; - let Parsed { name, vis, docs } = Parsed::from_meta(meta)?; +fn parse_setter_fn(meta: &syn::Meta) -> Result { + ItemParamsParsing { + meta, + reject_self_mentions: Some(DOCS_CONTEXT), + } + .parse() +} - if let Some(docs) = &docs { - crate::util::parsing::reject_self_mentions_in_docs( - "builder struct's impl block", - docs, - )?; - } +fn parse_docs(meta: &syn::Meta) -> Result> { + crate::parsing::parse_docs_without_self_mentions(DOCS_CONTEXT, meta) +} - Ok(Self { name, vis, docs }) +#[derive(Debug, FromMeta)] +pub(crate) struct SettersParams { + pub(crate) name: Option, + pub(crate) vis: Option, - match meta_list.delimiter { - syn::MacroDelimiter::Bracket(_) => return Err(err()), - syn::MacroDelimiter::Paren(_) => { - return SetterParenParams::from_meta(meta).map(Self::Paren) - } - syn::MacroDelimiter::Brace(_) => { + #[darling(default, with = parse_docs, map = Some)] + pub(crate) docs: Option>, - }, - } - } + /// 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)] + pub(crate) some_fn: ItemParams, + + /// 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)] + pub(crate) option_fn: ItemParams, } diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index e01570d0..3ad78ca7 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -1,5 +1,6 @@ -use super::builder_params::{BuilderDerives, ItemParams, OnParams}; +use super::builder_params::{BuilderDerives, OnParams}; use super::member::Member; +use crate::parsing::ItemParams; use crate::util::prelude::*; pub(super) trait FinishFnBody { diff --git a/bon-macros/src/lib.rs b/bon-macros/src/lib.rs index dc45bdfe..ef2930c5 100644 --- a/bon-macros/src/lib.rs +++ b/bon-macros/src/lib.rs @@ -16,6 +16,7 @@ mod builder; mod collections; mod error; mod normalization; +mod parsing; mod util; /// Generates a builder for the function or method it's placed on. diff --git a/bon-macros/src/util/parsing.rs b/bon-macros/src/parsing/docs.rs similarity index 74% rename from bon-macros/src/util/parsing.rs rename to bon-macros/src/parsing/docs.rs index 0f44c33a..f967754f 100644 --- a/bon-macros/src/util/parsing.rs +++ b/bon-macros/src/parsing/docs.rs @@ -1,9 +1,20 @@ 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 attrs = meta - .require_list()? - .parse_args_with(syn::Attribute::parse_outer)?; + 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() { @@ -18,7 +29,10 @@ pub(crate) fn parse_docs(meta: &syn::Meta) -> Result> { /// 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 { +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() { Some(doc) => doc, diff --git a/bon-macros/src/parsing/item_params.rs b/bon-macros/src/parsing/item_params.rs new file mode 100644 index 00000000..184d22a4 --- /dev/null +++ b/bon-macros/src/parsing/item_params.rs @@ -0,0 +1,73 @@ +use crate::util::prelude::*; +use darling::FromMeta; + +#[derive(Debug, Clone, Default)] +pub(crate) struct ItemParams { + pub(crate) name: Option, + pub(crate) vis: Option, + pub(crate) docs: Option>, +} + +pub(crate) struct ItemParamsParsing<'a> { + pub(crate) meta: &'a syn::Meta, + pub(crate) reject_self_mentions: Option<&'static str>, +} + +impl ItemParamsParsing<'_> { + pub(crate) fn parse(self) -> Result { + let params = Self::params_from_meta(self.meta)?; + + if let Some(context) = self.reject_self_mentions { + if let Some(docs) = ¶ms.docs { + crate::parsing::reject_self_mentions_in_docs(context, docs)?; + } + } + + Ok(params) + } + + fn params_from_meta(meta: &syn::Meta) -> Result { + if let syn::Meta::NameValue(meta) = meta { + let val = &meta.value; + let name = syn::parse2(quote!(#val))?; + + return Ok(ItemParams { + name: Some(name), + vis: None, + docs: None, + }); + } + + #[derive(Debug, FromMeta)] + struct Full { + name: Option, + vis: Option, + docs: Option, + } + + let full = crate::parsing::require_paren_delim_for_meta_list(meta)?; + + let is_empty = matches!( + full, + Full { + name: None, + vis: None, + docs: None, + } + ); + + if is_empty { + bail!(meta, "expected at least one parameter in parentheses"); + } + + let docs = full.docs.as_ref().map(super::parse_docs).transpose()?; + + let params = ItemParams { + name: full.name, + vis: full.vis, + docs, + }; + + Ok(params) + } +} diff --git a/bon-macros/src/parsing/mod.rs b/bon-macros/src/parsing/mod.rs new file mode 100644 index 00000000..f4095f64 --- /dev/null +++ b/bon-macros/src/parsing/mod.rs @@ -0,0 +1,16 @@ +mod docs; +mod item_params; + +pub(crate) use docs::*; +pub(crate) use item_params::*; + +use crate::util::prelude::*; +use darling::FromMeta; + +pub(crate) fn require_paren_delim_for_meta_list(meta: &syn::Meta) -> Result { + if let syn::Meta::List(meta) = meta { + meta.require_parens_delim()?; + } + + T::from_meta(meta) +} diff --git a/bon-macros/src/util/meta_list.rs b/bon-macros/src/util/meta_list.rs new file mode 100644 index 00000000..766085e8 --- /dev/null +++ b/bon-macros/src/util/meta_list.rs @@ -0,0 +1,66 @@ +use 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 => "[...]", + } + } +} diff --git a/bon-macros/src/util/mod.rs b/bon-macros/src/util/mod.rs index d5ba4956..e80aaec6 100644 --- a/bon-macros/src/util/mod.rs +++ b/bon-macros/src/util/mod.rs @@ -4,6 +4,7 @@ mod generic_param; mod ident; mod item; mod iterator; +mod meta_list; mod path; mod punctuated; mod ty; @@ -11,13 +12,12 @@ mod vec; mod visibility; pub(crate) mod ide; -pub(crate) mod parsing; use prelude::*; pub(crate) mod prelude { pub(crate) use proc_macro2::{Span, TokenStream}; - pub(crate) use quote::{quote, format_ident, ToTokens}; + pub(crate) use quote::{format_ident, quote, 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. @@ -35,6 +35,7 @@ pub(crate) mod prelude { 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; diff --git a/bon/tests/integration/ui/compile_fail/wrong_delimiters.rs b/bon/tests/integration/ui/compile_fail/wrong_delimiters.rs new file mode 100644 index 00000000..8509bcad --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/wrong_delimiters.rs @@ -0,0 +1,58 @@ +#[derive(bon::Builder)] +#[builder( + builder_type{}, + state_mod{}, + start_fn{}, + finish_fn{}, +)] +struct CurlyBraces {} + +#[derive(bon::Builder)] +struct CurlyBracesInField { + #[builder(setters{})] + x: u32, +} + +#[derive(bon::Builder)] +#[builder( + builder_type[docs[]], + state_mod[docs[]], + start_fn[docs[]], + finish_fn[docs[]], +)] +struct SquareBrackets { + #[builder(setters[])] + x: u32, +} + +#[derive(bon::Builder)] +struct SquareBracketsInField { + #[builder(setters[])] + x: u32, +} + +#[derive(bon::Builder)] +#[builder( + builder_type(docs[]), + state_mod(docs[]), + start_fn(docs[]), + finish_fn(docs[]), +)] +struct SquareBracketsDocs { + #[builder(setters(docs[]))] + x: u32, +} + +#[derive(bon::Builder)] +#[builder( + builder_type(docs()), + state_mod(docs()), + start_fn(docs()), + finish_fn(docs()) +)] +struct Parentheses { + #[builder(setters(docs()))] + x: u32, +} + +fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/wrong_delimiters.stderr b/bon/tests/integration/ui/compile_fail/wrong_delimiters.stderr new file mode 100644 index 00000000..7db423ae --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/wrong_delimiters.stderr @@ -0,0 +1,107 @@ +error: wrong delimiter, expected parentheses e.g. `start_fn(...)`, but got curly braces: `start_fn{...}` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:5:5 + | +5 | start_fn{}, + | ^^^^^^^^ + +error: wrong delimiter, expected parentheses e.g. `builder_type(...)`, but got curly braces: `builder_type{...}` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:3:5 + | +3 | builder_type{}, + | ^^^^^^^^^^^^ + +error: wrong delimiter, expected parentheses e.g. `state_mod(...)`, but got curly braces: `state_mod{...}` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:4:5 + | +4 | state_mod{}, + | ^^^^^^^^^ + +error: wrong delimiter, expected parentheses e.g. `finish_fn(...)`, but got curly braces: `finish_fn{...}` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:6:5 + | +6 | finish_fn{}, + | ^^^^^^^^^ + +error: wrong delimiter, expected parentheses e.g. `setters(...)`, but got curly braces: `setters{...}` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:12:15 + | +12 | #[builder(setters{})] + | ^^^^^^^ + +error: wrong delimiter, expected parentheses e.g. `start_fn(...)`, but got square brackets: `start_fn[...]` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:20:5 + | +20 | start_fn[docs[]], + | ^^^^^^^^ + +error: wrong delimiter, expected parentheses e.g. `builder_type(...)`, but got square brackets: `builder_type[...]` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:18:5 + | +18 | builder_type[docs[]], + | ^^^^^^^^^^^^ + +error: wrong delimiter, expected parentheses e.g. `state_mod(...)`, but got square brackets: `state_mod[...]` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:19:5 + | +19 | state_mod[docs[]], + | ^^^^^^^^^ + +error: wrong delimiter, expected parentheses e.g. `finish_fn(...)`, but got square brackets: `finish_fn[...]` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:21:5 + | +21 | finish_fn[docs[]], + | ^^^^^^^^^ + +error: wrong delimiter, expected parentheses e.g. `setters(...)`, but got square brackets: `setters[...]` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:30:15 + | +30 | #[builder(setters[])] + | ^^^^^^^ + +error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got square brackets: `docs[...]` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:38:14 + | +38 | start_fn(docs[]), + | ^^^^ + +error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got square brackets: `docs[...]` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:36:18 + | +36 | builder_type(docs[]), + | ^^^^ + +error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got square brackets: `docs[...]` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:37:15 + | +37 | state_mod(docs[]), + | ^^^^ + +error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got square brackets: `docs[...]` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:39:15 + | +39 | finish_fn(docs[]), + | ^^^^ + +error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got parentheses: `docs(...)` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:50:14 + | +50 | start_fn(docs()), + | ^^^^ + +error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got parentheses: `docs(...)` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:48:18 + | +48 | builder_type(docs()), + | ^^^^ + +error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got parentheses: `docs(...)` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:49:15 + | +49 | state_mod(docs()), + | ^^^^ + +error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got parentheses: `docs(...)` + --> tests/integration/ui/compile_fail/wrong_delimiters.rs:51:15 + | +51 | finish_fn(docs()) + | ^^^^ diff --git a/e2e-tests/src/v3_design.rs b/e2e-tests/src/v3_design.rs index 11802859..2b1f50f7 100644 --- a/e2e-tests/src/v3_design.rs +++ b/e2e-tests/src/v3_design.rs @@ -1,11 +1,8 @@ -use bon::{builder, bon}; - struct Point { x: f64, y: f64, } -#[derive(bon::Builder, Debug)] #[builder( builder_type( name = ExampleBuilder, @@ -30,7 +27,7 @@ struct Point { // (v3.3) // // Overrides the setters generated for the `Point` type. - setter = |x: f64, y: f64| Point { x, y }, + with = |x: f64, y: f64| Point { x, y }, ), // (v3.4) @@ -60,49 +57,58 @@ pub struct Example { // (v3.0) // // Override the name in the state - state = PointOverride + state = PointOverride, - // (v3.0) + // (v3.1+) // // Advanced state configuration - state( - name = PointOverride, - - deprecated = "This state is no longer available, the field is overwritable", - assoc_type = NameOverride, - assoc_type( - name = NameOverride - docs { - /// Docs for associated type - }, - ), - transition_alias = NameOverride, - transition_alias( - name = NameOverride, - docs { - /// Docs for transition alias - }, - ) - ), + // state( + // name = PointOverride, + + // deprecated = "This state is no longer available, the field is overwritable", + // assoc_type = NameOverride, + // assoc_type( + // name = NameOverride + // docs { + // /// Docs for associated type + // }, + // ), + // transition_alias = NameOverride, + // transition_alias( + // name = NameOverride, + // docs { + // /// Docs for transition alias + // }, + // ) + // ), + + // Closure override + // + // For optional members, the `maybe_` setter will accept an `Option<(..args)>` + // if there is more than one argument. + with = |x: f64, y: f64| Point { x, y }, + + // Makes the setter fallible + with = |x: f64, y: f64| -> Result<_> { Ok(Point { x, y }) } // (v3.0) // // Overrides the name, visibility and docs of the default setters - setter( + setters( name = point_internal, vis = "", // By default copied to all members docs { - /// Docs for the required setter + /// Docs for the setters } // (v3.1) // // Overrides specific to the `{member}` setter that wraps the value with `Some` // - // Other names: `by_value` - arg_some( + // Other names: `by_value`, `arg_value`, `plain` + some_fn( name = point_internal, vis = "", docs { ... } @@ -112,59 +118,23 @@ pub struct Example { // // Overrides specific to the `maybe_{member}` setter. // - // Other names: `by_option` - arg_option( + // Other names: `by_option`, `arg_option` + option_fn( name = maybe_point_internal, vis = "", docs { ... } ) - arg_absent( + // Other names: `arg_absent` + true_fn( + // ... + ), + + bool_fn( // ... ) ), - // (v3.1) - // - // Shortcut for: - // ``` - // setters { - // {vis} fn {name}(...) -> _ { - // ... - // } - // } - // ``` - // - // For optional members, the `maybe_` setter will accept an `Option<(..args)>` - // if there is more than one argument. - setter = |value: Option| Point { x, y }, - - // (v3.1) - // - // Completely custom overrides for setters. - // The function needs to place `_` in the return type and return the - // type of the member. It can also be async or unsafe or return a - // `Result<_[, Error]>`, in which case the setter will propagate the - // error to the caller. - // - // Access to `BuilderState` must prohibited. The generic params from - // the struct should be in scope. - setter { - /// Docs for `foo` - #[deprecated] - fn foo(x: f64, y: f64) -> _ { expr } - - /// Docs for `bar` - fn bar(val: Option<(f64, f64)>) -> _ { expr } - - /// (v3.2 ??) syntax sugar - #[deprecated] - foo = |...| expr; - - /// (v3.2 ??) syntax sugar - maybe_foo = |...| expr; - } - // (v3.2) // // These must work for regular members and start_fn args (custom fields?, v3.3) @@ -245,20 +215,8 @@ pub struct Example { // Custom mutable getter. Accepts a mutable reference and transforms it. getter = |value: &mut _| -> Ty { expr } - // If there are multiple getters, then names must be assigned explicitly. - getter { - // Long syntax. Full function signature. `_` can be used in place of - // the member's type to avoid repeating it. - - /// Docs for getter_name_1 - fn getter_name_1(value: &_) -> Ty { expr } - - /// Docs for getter_name_2 - fn getter_name_2(value: &mut _) -> Ty { expr } - - // No short syntax. The syntax savings are minimal compared with the closure style. - // getter_name_3 = |value: &mut _| -> Ty { expr }; - } + // Give a name to the getter if there are several getters + getter(name = foo, with = |value: &mut _| -> Ty { expr }), )] point: Point, @@ -269,7 +227,7 @@ pub struct Example { #[builder( field = vec![], field(name = overridden_name, vis = "pub", docs { ... }, init = vec![]), - deprecated(reason = "saasd"), + deprecated(reason = "saasd"), )] #[deprecated = "Use `overridden_name` instead"] pub custom_state: Vec, @@ -307,46 +265,48 @@ pub struct Example { } // Use cases: -#[derive(bon::Builder)] struct UseCases { // (v3.0) // // Generate private setters with names `[maybe_]point_internal` and // preserve the public name in the `state` as `Point`. - #[builder( - name = point_internal, - vis = "", - state = Point, - )] + #[builder(setters(name = point_internal, vis = ""))] + point: Point, + // (v3.0) #[builder(setters(docs { - /// Docs for the setter that accepts the value itself. - /// + /// Docs for the setters }))] override_docs_for_default_setters: Option, + #[builder(setters( + some_fn(docs { + /// Docs for the some setter + }), + maybe_fn(docs { + /// Docs for the maybe setter + }) + ))] + override_docs_for_maybe_setter: Option, + // (v3.1) - #[builder(setter = |iter: impl IntoIterator| Vec::from_iter(iter))] + #[builder(with = |iter: impl IntoIterator| iter.into_iter().collect())] + #[builder(with = |iter: impl IntoIterator| Vec::from_iter(iter))] take_into_iter: Vec, // (v3.1) - #[builder(setter = |x: f64, y: f64| Point { x, y })] + #[builder( + setters( + name = member_internal, + vis = "", + docs { + /// ... + } + ) + )] take_several_args: Point, - - // (v3.1) - #[builder(setters { - fn point(x: f64, y: f64) -> _ { - Some(Point { x, y }) - } - fn maybe_point(val: Option<(f64, f64)>) -> _ { - let (x, y) = val?; - point(x, y) - } - })] - several_setters: Option, } - impl ExampleBuilder { pub fn my_point(self, x: f64, y: f64) -> ExampleBuilder> where From 64956ebe049a3b46a53dfd456bf1cb68f3653af9 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 2 Oct 2024 16:12:17 +0000 Subject: [PATCH 036/119] cargo fmt --- .../src/builder/builder_gen/member/mod.rs | 5 +---- .../src/builder/builder_gen/setter_methods.rs | 22 +++++++++---------- .../src/builder/builder_gen/state_mod.rs | 3 +-- bon-macros/src/util/ident.rs | 2 +- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index e2f7251f..0b578b07 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -121,10 +121,7 @@ pub(crate) struct SkippedMember { impl NamedMember { fn validate(&self) -> Result { - crate::parsing::reject_self_mentions_in_docs( - "builder struct's impl block", - &self.docs, - )?; + crate::parsing::reject_self_mentions_in_docs("builder struct's impl block", &self.docs)?; if let Some(default) = &self.params.default { if self.norm_ty.is_option() { diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index 4c689b1a..44c240b0 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -129,8 +129,7 @@ impl<'a> MemberSettersCtx<'a> { let member_pascal = &self.member.norm_ident_pascal; - let state_transition = - format_ident!("Set{}", self.member.norm_ident_pascal.raw_name()); + let state_transition = format_ident!("Set{}", self.member.norm_ident_pascal.raw_name()); let state_mod = &self.builder_gen.state_mod.ident; let generic_param = if self.builder_gen.stateful_members().take(2).count() == 1 { @@ -154,16 +153,15 @@ impl<'a> MemberSettersCtx<'a> { quote! { Self } }; - let where_clause = if self.member.is_stateful() && !self.member.params.overwritable.is_present() - { - quote! { - where - BuilderState::#member_pascal: #state_mod::IsUnset, - } - } else { - quote! {} - }; - + let where_clause = + if self.member.is_stateful() && !self.member.params.overwritable.is_present() { + quote! { + where + BuilderState::#member_pascal: #state_mod::IsUnset, + } + } else { + quote! {} + }; let vis = &self.builder_gen.builder_type.vis; diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index 78e53248..a67a16d6 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -26,8 +26,7 @@ impl super::BuilderGenCtx { }); let member_ident = member.public_ident(); - let alias_ident = - format_ident!("Set{}", member.norm_ident_pascal.raw_name()); + let alias_ident = format_ident!("Set{}", member.norm_ident_pascal.raw_name()); let docs = format!( "Returns a [`State`] that has [`IsSet`] implemented for `{member_ident}`\n\ diff --git a/bon-macros/src/util/ident.rs b/bon-macros/src/util/ident.rs index b88348f9..64f876b7 100644 --- a/bon-macros/src/util/ident.rs +++ b/bon-macros/src/util/ident.rs @@ -1,5 +1,5 @@ -use ident_case::RenameRule; use crate::util::prelude::*; +use ident_case::RenameRule; pub(crate) trait IdentExt { /// Converts the ident (assumed to be in `snake_case`) to `PascalCase` without From 71c427c0684e65b18c98ef633da6bfa06ebe0d39 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 2 Oct 2024 16:13:34 +0000 Subject: [PATCH 037/119] Rename setter -> setters --- bon-macros/src/builder/builder_gen/member/params/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index ff08b33e..9dc14122 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -114,7 +114,7 @@ impl MemberParams { into, name, overwritable, - setters: setter, + setters, skip, start_fn, } = self; @@ -125,7 +125,7 @@ impl MemberParams { (into.is_present(), ParamName::Into), (name.is_some(), ParamName::Name), (overwritable.is_present(), ParamName::Overwritable), - (setter.is_some(), ParamName::Setters), + (setters.is_some(), ParamName::Setters), (skip.is_some(), ParamName::Skip), (start_fn.is_present(), ParamName::StartFn), ]; From 851655891ca9dc65de1925017aac0aa6da759240 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 2 Oct 2024 16:30:54 +0000 Subject: [PATCH 038/119] Refactor --- .../builder/builder_gen/member/params/mod.rs | 39 ++++++++++++++++--- .../integration/ui/compile_fail/errors.stderr | 4 +- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index 9dc14122..2430711c 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -20,14 +20,14 @@ pub(crate) struct MemberParams { /// /// 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_expression, map = Some)] + #[darling(with = parse_optional_expr, map = Some)] pub(crate) default: 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_expression, map = Some)] + #[darling(with = parse_optional_expr, map = Some)] pub(crate) skip: Option>>, /// Rename the name exposed in the builder API. @@ -50,6 +50,9 @@ pub(crate) struct MemberParams { /// 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, + + #[darling(default, with = parse_expr_closure, map = Some)] + pub(crate) with: Option, } #[derive(PartialEq, Eq, Clone, Copy)] @@ -62,6 +65,7 @@ enum ParamName { Setters, Skip, StartFn, + With, } impl fmt::Display for ParamName { @@ -75,6 +79,7 @@ impl fmt::Display for ParamName { Self::Setters => "setters", Self::Skip => "skip", Self::StartFn => "start_fn", + Self::With => "with", }; f.write_str(str) } @@ -117,6 +122,7 @@ impl MemberParams { setters, skip, start_fn, + with, } = self; let attrs = [ @@ -128,6 +134,7 @@ impl MemberParams { (setters.is_some(), ParamName::Setters), (skip.is_some(), ParamName::Skip), (start_fn.is_present(), ParamName::StartFn), + (with.is_some(), ParamName::With), ]; attrs @@ -158,8 +165,8 @@ impl MemberParams { MemberOrigin::FnArg => { bail!( &skip.span(), - "`skip` attribute is not supported on function arguments. \ - Use a local variable instead.", + "`skip` attribute is not supported on function arguments; \ + use a local variable instead.", ); } MemberOrigin::StructField => {} @@ -168,7 +175,7 @@ impl MemberParams { if let Some(Some(_expr)) = self.default.as_deref() { bail!( &skip.span(), - "`skip` attribute can't be specified with `default` attribute; \ + "`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)]`", ); @@ -181,10 +188,30 @@ impl MemberParams { } } -fn parse_optional_expression(meta: &syn::Meta) -> Result>> { +fn parse_optional_expr(meta: &syn::Meta) -> Result>> { match meta { syn::Meta::Path(_) => Ok(SpannedValue::new(None, meta.span())), syn::Meta::List(_) => Err(Error::unsupported_format("list").with_span(meta)), syn::Meta::NameValue(meta) => Ok(SpannedValue::new(Some(meta.value.clone()), meta.span())), } } + +fn parse_expr_closure(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()), + }; + + match &meta.value { + syn::Expr::Closure(closure) => Ok(closure.clone()), + _ => Err(err()), + } +} diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr index d52fe041..20d6433e 100644 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ b/bon/tests/integration/ui/compile_fail/errors.stderr @@ -106,13 +106,13 @@ error: `skip` attribute can't be specified together with `name` 96 | #[builder(skip, name = bar)] | ^^^^ -error: `skip` attribute can't be specified with `default` attribute; if you wanted to specify a value for the member, then use the following syntax instead `#[builder(skip = value)]` +error: `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)]` --> tests/integration/ui/compile_fail/errors.rs:102:15 | 102 | #[builder(skip, default = 42)] | ^^^^ -error: `skip` attribute is not supported on function arguments. Use a local variable instead. +error: `skip` attribute is not supported on function arguments; use a local variable instead. --> tests/integration/ui/compile_fail/errors.rs:108:15 | 108 | #[builder(skip)] _x: u32, From 6f689e77ed0a676e09bd5450d6c5bb7d5fd9edc2 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 2 Oct 2024 16:33:49 +0000 Subject: [PATCH 039/119] Test with parsing error --- bon/tests/integration/ui/compile_fail/errors.rs | 6 ++++++ bon/tests/integration/ui/compile_fail/errors.stderr | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/bon/tests/integration/ui/compile_fail/errors.rs b/bon/tests/integration/ui/compile_fail/errors.rs index f15992f3..66f5dc5c 100644 --- a/bon/tests/integration/ui/compile_fail/errors.rs +++ b/bon/tests/integration/ui/compile_fail/errors.rs @@ -130,3 +130,9 @@ fn double_must_use() {} #[builder] struct BuilderProcMacroAttrOnAStruct {} + +#[derive(Builder)] +struct InvalidWithExpr { + #[builder(with = 42)] + x: u32, +} diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr index 20d6433e..1c4c5524 100644 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ b/bon/tests/integration/ui/compile_fail/errors.stderr @@ -152,6 +152,12 @@ error: Found multiple #[must_use], but bon only works with exactly one (or less) 128 | #[must_use] | ^ +error: expected a closure e.g. `with = |param: T| expression` + --> tests/integration/ui/compile_fail/errors.rs:136:15 + | +136 | #[builder(with = 42)] + | ^^^^ + warning: unused attribute --> tests/integration/ui/compile_fail/errors.rs:128:1 | From 38de96b069af0d5c0e8b8d35ec025a04a0ae82cc Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 3 Oct 2024 00:11:17 +0000 Subject: [PATCH 040/119] Refactoring and `transparent` attribute --- .../builder/builder_gen/builder_derives.rs | 4 +- .../builder_gen/member/into_conversion.rs | 10 +- .../src/builder/builder_gen/member/mod.rs | 101 +----------- .../src/builder/builder_gen/member/named.rs | 155 ++++++++++++++++++ .../builder/builder_gen/member/params/mod.rs | 37 ++++- .../builder_gen/member/params/setter.rs | 18 +- bon-macros/src/builder/builder_gen/mod.rs | 21 ++- .../src/builder/builder_gen/setter_methods.rs | 12 +- .../src/builder/builder_gen/state_mod.rs | 2 + bon-macros/src/parsing/mod.rs | 2 + bon-macros/src/parsing/spanned_key.rs | 35 ++++ .../ui/compile_fail/invalid_setters_attr.rs | 15 ++ .../compile_fail/invalid_transparent_attr.rs | 21 +++ 13 files changed, 308 insertions(+), 125 deletions(-) create mode 100644 bon-macros/src/builder/builder_gen/member/named.rs create mode 100644 bon-macros/src/parsing/spanned_key.rs create mode 100644 bon/tests/integration/ui/compile_fail/invalid_setters_attr.rs create mode 100644 bon/tests/integration/ui/compile_fail/invalid_transparent_attr.rs diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index 7e2f7adc..95f8fd46 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -84,7 +84,7 @@ impl BuilderGenCtx { // The type hints here are necessary to get better error messages // that point directly to the types that don't implement `Clone` // in the input code using the span info from the type hints. - let ty = member.as_optional_norm_ty().unwrap_or(&member.norm_ty); + let ty = member.underlying_norm_ty(); quote! { ::bon::private::derives::clone_member::<#ty>( @@ -131,7 +131,7 @@ impl BuilderGenCtx { Member::Named(member) => { let member_index = &member.index; let member_ident_str = member.public_ident().to_string(); - let member_ty = member.as_optional_norm_ty().unwrap_or(&member.norm_ty); + let member_ty = member.underlying_norm_ty(); Some(quote! { if let ::core::option::Option::Some(value) = &self.__private_named_members.#member_index { output.field( diff --git a/bon-macros/src/builder/builder_gen/member/into_conversion.rs b/bon-macros/src/builder/builder_gen/member/into_conversion.rs index be473880..e3ec09ce 100644 --- a/bon-macros/src/builder/builder_gen/member/into_conversion.rs +++ b/bon-macros/src/builder/builder_gen/member/into_conversion.rs @@ -5,13 +5,17 @@ use crate::util::prelude::*; impl NamedMember { pub(super) fn merge_param_into(&mut self, on_params: &[OnParams]) -> Result { + // `with` is mutually exclusive with `into`. So there is nothing to merge here + // if `with` is present. + if self.params.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 - .as_optional_with_ty(&self.orig_ty) - .unwrap_or(&self.orig_ty); + let scrutinee = self.underlying_orig_ty(); self.params.into = EvalBlanketFlagParam { on_params, diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 0b578b07..0a32872d 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -1,6 +1,9 @@ mod into_conversion; +mod named; mod params; +pub(crate) use named::*; + use super::builder_params::OnParams; use crate::util::prelude::*; use darling::util::SpannedValue; @@ -45,42 +48,6 @@ pub(crate) enum Member { Skipped(SkippedMember), } -/// 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 regular members. The index is 0-based. - pub(crate) index: syn::Index, - - /// Original name of the member is used as the name of the builder field and - /// in its setter methods. Struct field/fn arg names conventionally use `snake_case` - /// in Rust, but this isn't enforced, so this member isn't guaranteed to be in - /// snake case, but 99% of the time it will be. - pub(crate) orig_ident: syn::Ident, - - /// Normalized version of `orig_ident`. Here we stripped the leading `_` from the - /// member name. - pub(crate) norm_ident: syn::Ident, - - /// `PascalCase` version of the `norm_ident`. - pub(crate) norm_ident_pascal: syn::Ident, - - /// Doc comments for the setter methods are copied from the doc comments placed - /// on top of the original member - pub(crate) docs: Vec, - - /// Normalized type of the member that the builder should have setters for. - pub(crate) norm_ty: Box, - - /// Original type of the member (not normalized) - pub(crate) orig_ty: Box, - - /// Parameters configured by the user explicitly via attributes - pub(crate) params: MemberParams, -} - /// Member that was marked with `#[builder(pos = start_fn)]` #[derive(Debug)] pub(crate) struct StartFnArgMember { @@ -119,65 +86,6 @@ pub(crate) struct SkippedMember { pub(crate) value: SpannedValue>, } -impl NamedMember { - fn validate(&self) -> Result { - crate::parsing::reject_self_mentions_in_docs("builder struct's impl block", &self.docs)?; - - if let Some(default) = &self.params.default { - if self.norm_ty.is_option() { - bail!( - &default.span(), - "`Option<_>` already implies a default of `None`, \ - so explicit #[builder(default)] is redundant", - ); - } - } - - Ok(()) - } - - pub(crate) fn public_ident(&self) -> &syn::Ident { - self.params.name.as_ref().unwrap_or(&self.norm_ident) - } - - fn as_optional_with_ty<'a>(&'a self, ty: &'a syn::Type) -> Option<&'a syn::Type> { - ty.option_type_param() - .or_else(|| (self.params.default.is_some()).then(|| ty)) - } - - pub(crate) fn as_optional_norm_ty(&self) -> Option<&syn::Type> { - Self::as_optional_with_ty(self, &self.norm_ty) - } - - pub(crate) fn is_required(&self) -> bool { - self.as_optional_norm_ty().is_none() - } - - pub(crate) fn param_default(&self) -> Option> { - self.params - .default - .as_ref() - .map(|default| default.as_ref().as_ref()) - } - - pub(crate) fn is_stateful(&self) -> bool { - self.is_required() || !self.params.overwritable.is_present() - } - - pub(crate) fn merge_param_overwritable(&mut self, on_params: &[OnParams]) -> Result { - self.params.overwritable = params::EvalBlanketFlagParam { - on_params, - param_name: params::BlanketParamName::Overwritable, - member_params: &self.params, - scrutinee: &self.norm_ty, - origin: self.origin, - } - .eval()?; - - Ok(()) - } -} - pub(crate) struct RawMember<'a> { pub(crate) attrs: &'a [syn::Attribute], pub(crate) ident: syn::Ident, @@ -297,8 +205,7 @@ impl Member { docs, }; - member.merge_param_into(on_params)?; - member.merge_param_overwritable(on_params)?; + member.merge_on_params(on_params)?; member.validate()?; output.push(Self::Named(member)); diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs new file mode 100644 index 00000000..4c2cce80 --- /dev/null +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -0,0 +1,155 @@ +use super::params::MemberParams; +use super::{params, MemberOrigin}; +use crate::builder::builder_gen::builder_params::OnParams; +use crate::builder::builder_gen::member::params::SettersParams; +use crate::util::prelude::*; + +/// 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 regular members. The index is 0-based. + pub(crate) index: syn::Index, + + /// Original name of the member is used as the name of the builder field and + /// in its setter methods. Struct field/fn arg names conventionally use `snake_case` + /// in Rust, but this isn't enforced, so this member isn't guaranteed to be in + /// snake case, but 99% of the time it will be. + pub(crate) orig_ident: syn::Ident, + + /// Normalized version of `orig_ident`. Here we stripped the leading `_` from the + /// member name. + pub(crate) norm_ident: syn::Ident, + + /// `PascalCase` version of the `norm_ident`. + pub(crate) norm_ident_pascal: syn::Ident, + + /// Doc comments for the setter methods are copied from the doc comments placed + /// on top of the original member + pub(crate) docs: Vec, + + /// Normalized type of the member that the builder should have setters for. + pub(crate) norm_ty: Box, + + /// Original type of the member (not normalized) + pub(crate) orig_ty: Box, + + /// Parameters configured by the user explicitly via attributes + pub(crate) params: MemberParams, +} + +impl NamedMember { + pub(super) fn validate(&self) -> Result { + crate::parsing::reject_self_mentions_in_docs("builder struct's impl block", &self.docs)?; + + if let Some(default) = &self.params.default { + if self.norm_ty.is_option() { + bail!( + &default.span(), + "`Option<_>` already implies a default of `None`, \ + so explicit #[builder(default)] is redundant", + ); + } + } + + if !self.is_required() { + self.validate_setters_params_for_member_without_default()?; + } + + if self.params.transparent.is_present() && !self.norm_ty.is_option() { + bail!( + &self.params.transparent.span(), + "`#[builder(transparent)]` can only be applied to members of \ + type `Option` to disable their special handling", + ); + } + + Ok(()) + } + + fn validate_setters_params_for_member_without_default(&self) -> Result { + let setters = match &self.params.setters { + Some(setters) => setters, + None => return Ok(()), + }; + + let SettersParams { + name: _, + vis: _, + docs: _, + some_fn, + option_fn, + } = setters; + + let invalid_setter = [option_fn, some_fn].into_iter().flatten().next(); + + let invalid_fn = match invalid_setter { + Some(invalid_fn) => invalid_fn, + None => return Ok(()), + }; + + bail!( + &invalid_fn.key, + "this setter function applies only to members with `#[builder(default)]` \ + or members of `Option` type", + ); + } + + /// Returns the public identifier of the member that should be used in the + /// generated builder API. + pub(crate) fn public_ident(&self) -> &syn::Ident { + self.params.name.as_ref().unwrap_or(&self.norm_ident) + } + + /// Returns `false` if the member has a default value. It means this member + /// is required to be set before the building can be finished. + pub(crate) fn is_required(&self) -> bool { + self.params.default.is_none() + && (self.params.transparent.is_present() || !self.norm_ty.is_option()) + } + + /// 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 the building can be finished. + pub(crate) fn is_stateful(&self) -> bool { + self.is_required() || !self.params.overwritable.is_present() + } + + /// Returns the normalized type of the member stripping the `Option<_>` + /// wrapper if it's present unless `#[builder(transparent)]` is set. + pub(crate) fn underlying_norm_ty(&self) -> &syn::Type { + self.underlying_ty(&self.norm_ty) + } + + /// Returns the original type of the member stripping the `Option<_>` + /// wrapper if it's present unless `#[builder(transparent)]` is set. + pub(crate) fn underlying_orig_ty(&self) -> &syn::Type { + self.underlying_ty(&self.orig_ty) + } + + fn underlying_ty<'m>(&'m self, ty: &'m syn::Type) -> &'m syn::Type { + if self.params.transparent.is_present() || self.params.default.is_some() { + ty + } else { + ty.option_type_param().unwrap_or(ty) + } + } + + pub(crate) fn merge_on_params(&mut self, on_params: &[OnParams]) -> Result { + self.merge_param_into(on_params)?; + + self.params.overwritable = params::EvalBlanketFlagParam { + on_params, + param_name: params::BlanketParamName::Overwritable, + member_params: &self.params, + scrutinee: &self.norm_ty, + origin: self.origin, + } + .eval()?; + + Ok(()) + } +} diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index 2430711c..03517ba8 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -53,6 +53,12 @@ pub(crate) struct MemberParams { #[darling(default, with = parse_expr_closure, map = Some)] pub(crate) with: Option, + + /// Disables the special handling for a member of type `Option`. The + /// member no longer has the default on `None`. It also becomes a required + /// member unless a separate `#[builder(default = ...)]` attribute is + /// also specified. + pub(crate) transparent: darling::util::Flag, } #[derive(PartialEq, Eq, Clone, Copy)] @@ -65,6 +71,7 @@ enum ParamName { Setters, Skip, StartFn, + Transparent, With, } @@ -79,6 +86,7 @@ impl fmt::Display for ParamName { Self::Setters => "setters", Self::Skip => "skip", Self::StartFn => "start_fn", + Self::Transparent => "transparent", Self::With => "with", }; f.write_str(str) @@ -86,15 +94,34 @@ impl fmt::Display for ParamName { } impl MemberParams { + 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, - allowed: &[ParamName], + 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 && !allowed.contains(name)) + .filter(|name| *name != attr_name && patterns.contains(name) == mutually_exclusive) .collect(); if conflicting.is_empty() { @@ -122,6 +149,7 @@ impl MemberParams { setters, skip, start_fn, + transparent, with, } = self; @@ -134,6 +162,7 @@ impl MemberParams { (setters.is_some(), ParamName::Setters), (skip.is_some(), ParamName::Skip), (start_fn.is_present(), ParamName::StartFn), + (transparent.is_present(), ParamName::Transparent), (with.is_some(), ParamName::With), ]; @@ -184,6 +213,10 @@ impl MemberParams { self.validate_mutually_allowed(ParamName::Skip, skip.span(), &[])?; } + if let Some(with) = &self.with { + self.validate_mutually_exclusive(ParamName::With, with.span(), &[ParamName::Into])?; + } + Ok(()) } } diff --git a/bon-macros/src/builder/builder_gen/member/params/setter.rs b/bon-macros/src/builder/builder_gen/member/params/setter.rs index fd558df8..3df7aef6 100644 --- a/bon-macros/src/builder/builder_gen/member/params/setter.rs +++ b/bon-macros/src/builder/builder_gen/member/params/setter.rs @@ -1,15 +1,17 @@ -use crate::parsing::{ItemParams, ItemParamsParsing}; +use crate::parsing::{ItemParams, ItemParamsParsing, SpannedKey}; use crate::util::prelude::*; use darling::FromMeta; const DOCS_CONTEXT: &str = "builder struct's impl block"; -fn parse_setter_fn(meta: &syn::Meta) -> Result { - ItemParamsParsing { +fn parse_setter_fn(meta: &syn::Meta) -> Result> { + let params = ItemParamsParsing { meta, reject_self_mentions: Some(DOCS_CONTEXT), } - .parse() + .parse()?; + + Ok(SpannedKey::from_parsed(meta, params)) } fn parse_docs(meta: &syn::Meta) -> Result> { @@ -28,13 +30,13 @@ pub(crate) struct SettersParams { /// type `Option` or with `#[builder(default)]`. /// /// By default, it's named `{member}` without any prefix or suffix. - #[darling(default, with = parse_setter_fn)] - pub(crate) some_fn: ItemParams, + #[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)] - pub(crate) option_fn: ItemParams, + #[darling(default, with = parse_setter_fn, map = Some)] + pub(crate) option_fn: Option>, } diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 8ace5b99..563c3193 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -414,12 +414,7 @@ impl BuilderGenCtx { } }); - let named_members_types = self.named_members().map(|member| { - let ty = member.as_optional_norm_ty().unwrap_or(&member.norm_ty); - quote! { - ::core::option::Option<#ty> - } - }); + let named_members_types = self.named_members().map(NamedMember::underlying_norm_ty); let docs = &self.builder_type.docs; @@ -450,7 +445,11 @@ impl BuilderGenCtx { #start_fn_args_field #private_field_attrs - __private_named_members: ( #(#named_members_types,)* ) + __private_named_members: ( + #( + ::core::option::Option<#named_members_types>, + )* + ) } } } @@ -487,7 +486,13 @@ impl BuilderGenCtx { return member_field.to_token_stream(); } - match member.param_default() { + let param_default = member + .params + .default + .as_ref() + .map(|default| default.as_ref().as_ref()); + + match param_default { Some(Some(default)) => { let has_into = member.params.into.is_present(); let default = if has_into { diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index 44c240b0..d1150500 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -17,8 +17,8 @@ impl<'a> MemberSettersCtx<'a> { pub(crate) fn setter_methods(&self) -> TokenStream { let member_type = self.member.norm_ty.as_ref(); - if let Some(inner_type) = self.member.as_optional_norm_ty() { - return self.setters_for_optional_member(inner_type); + if !self.member.is_required() { + return self.setters_for_optional_member(); } let has_into = self.member.params.into.is_present(); @@ -39,12 +39,14 @@ impl<'a> MemberSettersCtx<'a> { }) } - fn setters_for_optional_member(&self, inner_type: &syn::Type) -> TokenStream { + fn setters_for_optional_member(&self) -> TokenStream { + let underlying_ty = self.member.underlying_norm_ty(); + let has_into = self.member.params.into.is_present(); let (inner_type, maybe_map_conv_call) = if has_into { - (quote!(impl Into<#inner_type>), quote!(.map(Into::into))) + (quote!(impl Into<#underlying_ty>), quote!(.map(Into::into))) } else { - (quote!(#inner_type), quote!()) + (quote!(#underlying_ty), quote!()) }; let setter_method_name = self.member.public_ident().clone(); diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index a67a16d6..d7d19c72 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -54,6 +54,8 @@ impl super::BuilderGenCtx { .collect() } + // TODO: shame on me, but clippy is right 😳. Ping @Veetaha if he forgot to + // refactor this function before merging this code to master. #[allow(clippy::cognitive_complexity)] pub(super) fn state_mod(&self) -> TokenStream { let builder_vis = &self.builder_type.vis; diff --git a/bon-macros/src/parsing/mod.rs b/bon-macros/src/parsing/mod.rs index f4095f64..0cf5f56f 100644 --- a/bon-macros/src/parsing/mod.rs +++ b/bon-macros/src/parsing/mod.rs @@ -1,8 +1,10 @@ mod docs; mod item_params; +mod spanned_key; pub(crate) use docs::*; pub(crate) use item_params::*; +pub(crate) use spanned_key::*; use crate::util::prelude::*; use darling::FromMeta; diff --git a/bon-macros/src/parsing/spanned_key.rs b/bon-macros/src/parsing/spanned_key.rs new file mode 100644 index 00000000..b9422d87 --- /dev/null +++ b/bon-macros/src/parsing/spanned_key.rs @@ -0,0 +1,35 @@ +use crate::util::prelude::*; +use darling::FromMeta; +use std::fmt; +use syn::spanned::Spanned; + +/// A type that stores the attribute key span 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. +pub(crate) struct SpannedKey { + pub(crate) key: Span, + pub(crate) value: T, +} + +impl SpannedKey { + pub(crate) fn from_parsed(meta: &syn::Meta, value: T) -> Self { + Self { + key: meta.path().span(), + value, + } + } +} + +impl FromMeta for SpannedKey { + fn from_meta(meta: &syn::Meta) -> Result { + let key = meta.path().span(); + let value = T::from_meta(meta)?; + Ok(Self { key, value }) + } +} + +impl fmt::Debug for SpannedKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.value, f) + } +} diff --git a/bon/tests/integration/ui/compile_fail/invalid_setters_attr.rs b/bon/tests/integration/ui/compile_fail/invalid_setters_attr.rs new file mode 100644 index 00000000..d0a0f268 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/invalid_setters_attr.rs @@ -0,0 +1,15 @@ +use bon::Builder; + +#[derive(Builder)] +pub(crate) struct SomeFnSetterRequiredMember { + #[builder(setters(some_fn = foo))] + pub(crate) member: Option, +} + +#[derive(Builder)] +pub(crate) struct OptionFnSetterOnRequiredMember { + #[builder(setters(option_fn = bar))] + pub(crate) member: Option, +} + +fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.rs b/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.rs new file mode 100644 index 00000000..7ec5179b --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.rs @@ -0,0 +1,21 @@ +use bon::Builder; + +#[derive(Builder)] +struct RegularMember { + #[builder(transparent)] + member: i32, +} + +#[derive(Builder)] +struct StartFnMember { + #[builder(transparent)] + member: Option, +} + +#[derive(Builder)] +struct FinishFnMember { + #[builder(transparent)] + member: Option, +} + +fn main() {} From 446ece0e434e8a19f3ea17a25ce008ed3f0b0e22 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 3 Oct 2024 00:36:49 +0000 Subject: [PATCH 041/119] Better error reporting --- .../src/builder/builder_gen/member/named.rs | 45 ++++++++++--------- bon-macros/src/parsing/spanned_key.rs | 12 ++--- .../ui/compile_fail/invalid_setters_attr.rs | 20 +++++++-- .../compile_fail/invalid_setters_attr.stderr | 23 ++++++++++ .../compile_fail/invalid_transparent_attr.rs | 4 +- .../invalid_transparent_attr.stderr | 17 +++++++ 6 files changed, 89 insertions(+), 32 deletions(-) create mode 100644 bon/tests/integration/ui/compile_fail/invalid_setters_attr.stderr create mode 100644 bon/tests/integration/ui/compile_fail/invalid_transparent_attr.stderr diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index 4c2cce80..2c10e193 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -54,9 +54,7 @@ impl NamedMember { } } - if !self.is_required() { - self.validate_setters_params_for_member_without_default()?; - } + self.validate_setters_params()?; if self.params.transparent.is_present() && !self.norm_ty.is_option() { bail!( @@ -69,32 +67,37 @@ impl NamedMember { Ok(()) } - fn validate_setters_params_for_member_without_default(&self) -> Result { + fn validate_setters_params(&self) -> Result { let setters = match &self.params.setters { Some(setters) => setters, None => return Ok(()), }; - let SettersParams { - name: _, - vis: _, - docs: _, - some_fn, - option_fn, - } = setters; + if self.is_required() { + let SettersParams { + name: _, + vis: _, + docs: _, + some_fn, + option_fn, + } = setters; - let invalid_setter = [option_fn, some_fn].into_iter().flatten().next(); + let unexpected_setter = [option_fn, some_fn].into_iter().find_map(Option::as_ref); - let invalid_fn = match invalid_setter { - Some(invalid_fn) => invalid_fn, - None => return Ok(()), - }; + let setter = match unexpected_setter { + Some(setter) => setter, + None => return Ok(()), + }; + + bail!( + &setter.key, + "`{}` setter function applies only to members with `#[builder(default)]` \ + or members of `Option` type (if #[builder(transparent)] is not set)", + setter.key_to_string() + ); + } - bail!( - &invalid_fn.key, - "this setter function applies only to members with `#[builder(default)]` \ - or members of `Option` type", - ); + Ok(()) } /// Returns the public identifier of the member that should be used in the diff --git a/bon-macros/src/parsing/spanned_key.rs b/bon-macros/src/parsing/spanned_key.rs index b9422d87..2adcc1e1 100644 --- a/bon-macros/src/parsing/spanned_key.rs +++ b/bon-macros/src/parsing/spanned_key.rs @@ -1,28 +1,30 @@ use crate::util::prelude::*; use darling::FromMeta; use std::fmt; -use syn::spanned::Spanned; -/// A type that stores the attribute key span information along with the parsed value. +/// 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. pub(crate) struct SpannedKey { - pub(crate) key: Span, + pub(crate) key: syn::Path, pub(crate) value: T, } impl SpannedKey { pub(crate) fn from_parsed(meta: &syn::Meta, value: T) -> Self { Self { - key: meta.path().span(), + key: meta.path().clone(), value, } } + pub(crate) fn key_to_string(&self) -> String { + darling::util::path_to_string(&self.key) + } } impl FromMeta for SpannedKey { fn from_meta(meta: &syn::Meta) -> Result { - let key = meta.path().span(); + let key = meta.path().clone(); let value = T::from_meta(meta)?; Ok(Self { key, value }) } diff --git a/bon/tests/integration/ui/compile_fail/invalid_setters_attr.rs b/bon/tests/integration/ui/compile_fail/invalid_setters_attr.rs index d0a0f268..d0760d42 100644 --- a/bon/tests/integration/ui/compile_fail/invalid_setters_attr.rs +++ b/bon/tests/integration/ui/compile_fail/invalid_setters_attr.rs @@ -1,15 +1,27 @@ use bon::Builder; #[derive(Builder)] -pub(crate) struct SomeFnSetterRequiredMember { +struct SomeFnSetterRequiredMember { #[builder(setters(some_fn = foo))] - pub(crate) member: Option, + member: i32, } #[derive(Builder)] -pub(crate) struct OptionFnSetterOnRequiredMember { +struct OptionFnSetterOnRequiredMember { #[builder(setters(option_fn = bar))] - pub(crate) member: Option, + member: i32, +} + +#[derive(Builder)] +struct SomeFnSetterWithTransparent { + #[builder(transparent, setters(some_fn = foo))] + member: Option, +} + +#[derive(Builder)] +struct OptionFnSetterWithTransparent { + #[builder(transparent, setters(option_fn = bar))] + member: Option, } fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/invalid_setters_attr.stderr b/bon/tests/integration/ui/compile_fail/invalid_setters_attr.stderr new file mode 100644 index 00000000..8b5b92b5 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/invalid_setters_attr.stderr @@ -0,0 +1,23 @@ +error: `some_fn` setter function applies only to members with `#[builder(default)]` or members of `Option` type (if #[builder(transparent)] is not set) + --> tests/integration/ui/compile_fail/invalid_setters_attr.rs:5:23 + | +5 | #[builder(setters(some_fn = foo))] + | ^^^^^^^ + +error: `option_fn` setter function applies only to members with `#[builder(default)]` or members of `Option` type (if #[builder(transparent)] is not set) + --> tests/integration/ui/compile_fail/invalid_setters_attr.rs:11:23 + | +11 | #[builder(setters(option_fn = bar))] + | ^^^^^^^^^ + +error: `some_fn` setter function applies only to members with `#[builder(default)]` or members of `Option` type (if #[builder(transparent)] is not set) + --> tests/integration/ui/compile_fail/invalid_setters_attr.rs:17:36 + | +17 | #[builder(transparent, setters(some_fn = foo))] + | ^^^^^^^ + +error: `option_fn` setter function applies only to members with `#[builder(default)]` or members of `Option` type (if #[builder(transparent)] is not set) + --> tests/integration/ui/compile_fail/invalid_setters_attr.rs:23:36 + | +23 | #[builder(transparent, setters(option_fn = bar))] + | ^^^^^^^^^ diff --git a/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.rs b/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.rs index 7ec5179b..57ef7e97 100644 --- a/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.rs +++ b/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.rs @@ -8,13 +8,13 @@ struct RegularMember { #[derive(Builder)] struct StartFnMember { - #[builder(transparent)] + #[builder(start_fn, transparent)] member: Option, } #[derive(Builder)] struct FinishFnMember { - #[builder(transparent)] + #[builder(finish_fn, transparent)] member: Option, } diff --git a/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.stderr b/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.stderr new file mode 100644 index 00000000..3b971edd --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.stderr @@ -0,0 +1,17 @@ +error: `#[builder(transparent)]` can only be applied to members of type `Option` to disable their special handling + --> tests/integration/ui/compile_fail/invalid_transparent_attr.rs:5:15 + | +5 | #[builder(transparent)] + | ^^^^^^^^^^^ + +error: `start_fn` attribute can't be specified together with `transparent` + --> tests/integration/ui/compile_fail/invalid_transparent_attr.rs:11:15 + | +11 | #[builder(start_fn, transparent)] + | ^^^^^^^^ + +error: `finish_fn` attribute can't be specified together with `transparent` + --> tests/integration/ui/compile_fail/invalid_transparent_attr.rs:17:15 + | +17 | #[builder(finish_fn, transparent)] + | ^^^^^^^^^ From e5d8652c5e3c8cecde6a1358a6179db91558c610 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 3 Oct 2024 01:20:25 +0000 Subject: [PATCH 042/119] Test and fix `#[builder(transparent)]` --- .../src/builder/builder_gen/input_fn.rs | 41 +++++-- bon-macros/src/builder/builder_gen/mod.rs | 21 +++- .../integration/builder/attr_transparent.rs | 106 ++++++++++++++++++ bon/tests/integration/builder/mod.rs | 1 + .../ui/compile_fail/attr_transparent.rs | 36 ++++++ .../ui/compile_fail/attr_transparent.stderr | 37 ++++++ .../compile_fail/invalid_transparent_attr.rs | 21 ---- .../invalid_transparent_attr.stderr | 17 --- 8 files changed, 225 insertions(+), 55 deletions(-) create mode 100644 bon/tests/integration/builder/attr_transparent.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_transparent.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_transparent.stderr delete mode 100644 bon/tests/integration/ui/compile_fail/invalid_transparent_attr.rs delete mode 100644 bon/tests/integration/ui/compile_fail/invalid_transparent_attr.stderr diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index d750a046..53427b60 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -89,25 +89,44 @@ impl FnInputCtx { Some(prefix) } - fn assoc_method_ctx(&self) -> Option { - Some(AssocMethodCtx { - self_ty: self.impl_ctx.as_deref()?.self_ty.clone(), - receiver: self.assoc_method_receiver_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(AssocMethodCtx { + self_ty, + receiver: self.assoc_method_receiver_ctx()?, + })) } - fn assoc_method_receiver_ctx(&self) -> Option { - let receiver = self.norm_fn.sig.receiver()?; - let self_ty = &self.impl_ctx.as_deref()?.self_ty; + fn assoc_method_receiver_ctx(&self) -> Result> { + let receiver = match self.norm_fn.sig.receiver() { + Some(receiver) => receiver, + None => return Ok(None), + }; + + if let [attr, ..] = receiver.attrs.as_slice() { + bail!( + attr, + "attributes on the receiver are not supported in the #[builder] macro." + ); + } + + 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); - Some(AssocMethodReceiverCtx { + Ok(Some(AssocMethodReceiverCtx { with_self_keyword: receiver.clone(), without_self_keyword, - }) + })) } fn generics(&self) -> Generics { @@ -255,7 +274,7 @@ impl FnInputCtx { } pub(crate) fn into_builder_gen_ctx(self) -> Result { - let assoc_method_ctx = self.assoc_method_ctx(); + let assoc_method_ctx = self.assoc_method_ctx()?; if self.impl_ctx.is_none() { let explanation = "\ diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 563c3193..cdb0cb97 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -434,6 +434,15 @@ impl BuilderGenCtx { )] #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. BuilderState: #state_mod::State = #state_mod::AllUnset > #where_clause @@ -480,12 +489,6 @@ impl BuilderGenCtx { self.__private_named_members.#index }; - // 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.norm_ty.is_option() { - return member_field.to_token_stream(); - } - let param_default = member .params .default @@ -511,6 +514,12 @@ impl BuilderGenCtx { } } 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.params.transparent.is_present() && member.norm_ty.is_option() { + return member_field.to_token_stream(); + } + quote! { unsafe { // SAFETY: we know that the member is set because we are in diff --git a/bon/tests/integration/builder/attr_transparent.rs b/bon/tests/integration/builder/attr_transparent.rs new file mode 100644 index 00000000..502ec3de --- /dev/null +++ b/bon/tests/integration/builder/attr_transparent.rs @@ -0,0 +1,106 @@ +mod smoke { + use crate::prelude::*; + + #[test] + fn test_struct() { + #[derive(Debug, Builder)] + #[allow(dead_code)] + struct Sut { + #[builder(transparent)] + regular: Option, + + #[builder(transparent)] + generic: Option, + + #[builder(into, transparent)] + with_into: Option, + } + + assert_debug_eq( + Sut::builder() + .regular(Some(1)) + .generic(Some(false)) + .with_into(2) + .build(), + expect![[r#" + Sut { + regular: Some( + 1, + ), + generic: Some( + false, + ), + with_into: Some( + 2, + ), + }"#]], + ); + } + + #[test] + fn test_free_fn() { + #[builder] + fn sut( + #[builder(transparent)] regular: Option, + #[builder(transparent)] generic: Option, + #[builder(into, transparent)] with_into: Option, + ) -> (Option, Option, Option) { + (regular, generic, with_into) + } + + assert_debug_eq( + sut() + .regular(Some(1)) + .generic(Some(false)) + .with_into(2) + .call(), + expect!["(Some(1), Some(false), Some(2))"], + ); + } + + #[test] + fn test_assoc_method_method() { + struct Sut; + + #[bon] + impl Sut { + #[builder] + fn sut( + #[builder(transparent)] regular: Option, + #[builder(transparent)] generic: Option, + #[builder(into, transparent)] with_into: Option, + ) -> (Option, Option, Option) { + (regular, generic, with_into) + } + + #[builder] + fn with_self( + &self, + #[builder(transparent)] regular: Option, + #[builder(transparent)] generic: Option, + #[builder(into, transparent)] with_into: Option, + ) -> (Option, Option, Option) { + let _ = self; + (regular, generic, with_into) + } + } + + assert_debug_eq( + Sut::sut() + .regular(Some(1)) + .generic(Some(false)) + .with_into(2) + .call(), + expect!["(Some(1), Some(false), Some(2))"], + ); + + assert_debug_eq( + Sut.with_self() + .regular(Some(1)) + .generic(Some(false)) + .with_into(2) + .call(), + expect!["(Some(1), Some(false), Some(2))"], + ); + } +} diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index 54ade867..3a10a223 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -2,6 +2,7 @@ mod attr_default; mod attr_expose_positional_fn; mod attr_into; mod attr_on; +mod attr_transparent; mod attr_skip; mod builder_derives; mod cfgs; diff --git a/bon/tests/integration/ui/compile_fail/attr_transparent.rs b/bon/tests/integration/ui/compile_fail/attr_transparent.rs new file mode 100644 index 00000000..76e199fe --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_transparent.rs @@ -0,0 +1,36 @@ +use bon::Builder; + +#[derive(Builder)] +struct InvalidOnRequiredMember { + #[builder(transparent)] + member: i32, +} + +#[derive(Builder)] +struct InvalidOnStartFnMember { + #[builder(start_fn, transparent)] + member: Option, +} + +#[derive(Builder)] +struct InvalidOnFnMember { + #[builder(finish_fn, transparent)] + member: Option, +} + +#[derive(Builder)] +struct InvalidOnSkippedMember { + #[builder(skip, transparent)] + member: Option, +} + +#[derive(Builder)] +struct Valid { + #[builder(transparent)] + member: Option, +} + +fn main() { + // Make sure there is no `maybe_` setter generated + let _ = Valid::builder().maybe_member(Some(42)); +} diff --git a/bon/tests/integration/ui/compile_fail/attr_transparent.stderr b/bon/tests/integration/ui/compile_fail/attr_transparent.stderr new file mode 100644 index 00000000..4c7e566d --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_transparent.stderr @@ -0,0 +1,37 @@ +error: `#[builder(transparent)]` can only be applied to members of type `Option` to disable their special handling + --> tests/integration/ui/compile_fail/attr_transparent.rs:5:15 + | +5 | #[builder(transparent)] + | ^^^^^^^^^^^ + +error: `start_fn` attribute can't be specified together with `transparent` + --> tests/integration/ui/compile_fail/attr_transparent.rs:11:15 + | +11 | #[builder(start_fn, transparent)] + | ^^^^^^^^ + +error: `finish_fn` attribute can't be specified together with `transparent` + --> tests/integration/ui/compile_fail/attr_transparent.rs:17:15 + | +17 | #[builder(finish_fn, transparent)] + | ^^^^^^^^^ + +error: `skip` attribute can't be specified together with `transparent` + --> tests/integration/ui/compile_fail/attr_transparent.rs:23:15 + | +23 | #[builder(skip, transparent)] + | ^^^^ + +error[E0599]: no method named `maybe_member` found for struct `ValidBuilder` in the current scope + --> tests/integration/ui/compile_fail/attr_transparent.rs:35:30 + | +27 | #[derive(Builder)] + | ------- method `maybe_member` not found for this struct +... +35 | let _ = Valid::builder().maybe_member(Some(42)); + | ^^^^^^^^^^^^ + | +help: there is a method `member` with a similar name + | +35 | let _ = Valid::builder().member(Some(42)); + | ~~~~~~ diff --git a/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.rs b/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.rs deleted file mode 100644 index 57ef7e97..00000000 --- a/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.rs +++ /dev/null @@ -1,21 +0,0 @@ -use bon::Builder; - -#[derive(Builder)] -struct RegularMember { - #[builder(transparent)] - member: i32, -} - -#[derive(Builder)] -struct StartFnMember { - #[builder(start_fn, transparent)] - member: Option, -} - -#[derive(Builder)] -struct FinishFnMember { - #[builder(finish_fn, transparent)] - member: Option, -} - -fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.stderr b/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.stderr deleted file mode 100644 index 3b971edd..00000000 --- a/bon/tests/integration/ui/compile_fail/invalid_transparent_attr.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error: `#[builder(transparent)]` can only be applied to members of type `Option` to disable their special handling - --> tests/integration/ui/compile_fail/invalid_transparent_attr.rs:5:15 - | -5 | #[builder(transparent)] - | ^^^^^^^^^^^ - -error: `start_fn` attribute can't be specified together with `transparent` - --> tests/integration/ui/compile_fail/invalid_transparent_attr.rs:11:15 - | -11 | #[builder(start_fn, transparent)] - | ^^^^^^^^ - -error: `finish_fn` attribute can't be specified together with `transparent` - --> tests/integration/ui/compile_fail/invalid_transparent_attr.rs:17:15 - | -17 | #[builder(finish_fn, transparent)] - | ^^^^^^^^^ From 60f883063e7cf6e2ba182aa91a5f30c377f4340c Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 3 Oct 2024 01:46:37 +0000 Subject: [PATCH 043/119] Improve tests, fix `transparent` + `default` --- .../src/builder/builder_gen/member/mod.rs | 4 +- .../src/builder/builder_gen/member/named.rs | 4 +- .../builder/builder_gen/member/params/mod.rs | 26 +-- .../builder_gen/member/params/setter.rs | 2 +- bon-macros/src/builder/builder_gen/mod.rs | 2 +- bon-macros/src/parsing/spanned_key.rs | 13 +- bon/tests/integration/builder/attr_default.rs | 10 +- .../builder/attr_expose_positional_fn.rs | 3 +- bon/tests/integration/builder/attr_into.rs | 12 +- .../integration/builder/attr_transparent.rs | 201 ++++++++++-------- bon/tests/integration/builder/attr_with.rs | 37 ++++ bon/tests/integration/builder/init_order.rs | 3 +- bon/tests/integration/builder/mod.rs | 1 + .../integration/builder/positional_members.rs | 28 +-- .../integration/ui/compile_fail/attr_with.rs | 9 + .../ui/compile_fail/attr_with.stderr | 5 + 16 files changed, 218 insertions(+), 142 deletions(-) create mode 100644 bon/tests/integration/builder/attr_with.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_with.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_with.stderr diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 0a32872d..d81113b1 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -6,10 +6,10 @@ pub(crate) use named::*; use super::builder_params::OnParams; use crate::util::prelude::*; -use darling::util::SpannedValue; use darling::FromAttributes; use params::MemberParams; use std::fmt; +use crate::parsing::SpannedKey; #[derive(Debug, Clone, Copy)] pub(crate) enum MemberOrigin { @@ -83,7 +83,7 @@ pub(crate) struct SkippedMember { pub(crate) norm_ty: Box, /// Value to assign to the member - pub(crate) value: SpannedValue>, + pub(crate) value: SpannedKey>, } pub(crate) struct RawMember<'a> { diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index 2c10e193..719eb70c 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -45,9 +45,9 @@ impl NamedMember { crate::parsing::reject_self_mentions_in_docs("builder struct's impl block", &self.docs)?; if let Some(default) = &self.params.default { - if self.norm_ty.is_option() { + if !self.params.transparent.is_present() && self.norm_ty.is_option() { bail!( - &default.span(), + &default.key, "`Option<_>` already implies a default of `None`, \ so explicit #[builder(default)] is redundant", ); diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index 03517ba8..31f06cc4 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -5,8 +5,10 @@ pub(crate) use blanket::{BlanketParamName, EvalBlanketFlagParam}; pub(crate) use setter::SettersParams; use super::MemberOrigin; +use crate::parsing::SpannedKey; use crate::util::prelude::*; use darling::util::SpannedValue; +use darling::FromMeta; use std::fmt; use syn::spanned::Spanned; @@ -21,14 +23,14 @@ pub(crate) struct MemberParams { /// 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>>, + pub(crate) default: 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>>, + pub(crate) skip: Option>>, /// Rename the name exposed in the builder API. pub(crate) name: Option, @@ -52,7 +54,7 @@ pub(crate) struct MemberParams { pub(crate) overwritable: darling::util::Flag, #[darling(default, with = parse_expr_closure, map = Some)] - pub(crate) with: Option, + pub(crate) with: Option>, /// Disables the special handling for a member of type `Option`. The /// member no longer has the default on `None`. It also becomes a required @@ -193,7 +195,7 @@ impl MemberParams { match origin { MemberOrigin::FnArg => { bail!( - &skip.span(), + &skip.key.span(), "`skip` attribute is not supported on function arguments; \ use a local variable instead.", ); @@ -203,33 +205,33 @@ impl MemberParams { if let Some(Some(_expr)) = self.default.as_deref() { bail!( - &skip.span(), + &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.span(), &[])?; + self.validate_mutually_allowed(ParamName::Skip, skip.key.span(), &[])?; } if let Some(with) = &self.with { - self.validate_mutually_exclusive(ParamName::With, with.span(), &[ParamName::Into])?; + self.validate_mutually_exclusive(ParamName::With, with.key.span(), &[ParamName::Into])?; } Ok(()) } } -fn parse_optional_expr(meta: &syn::Meta) -> Result>> { +fn parse_optional_expr(meta: &syn::Meta) -> Result>> { match meta { - syn::Meta::Path(_) => Ok(SpannedValue::new(None, meta.span())), + syn::Meta::Path(path) => Ok(SpannedKey::new(path, None)), syn::Meta::List(_) => Err(Error::unsupported_format("list").with_span(meta)), - syn::Meta::NameValue(meta) => Ok(SpannedValue::new(Some(meta.value.clone()), meta.span())), + syn::Meta::NameValue(meta) => Ok(SpannedKey::new(&meta.path, Some(meta.value.clone()))), } } -fn parse_expr_closure(meta: &syn::Meta) -> Result { +fn parse_expr_closure(meta: &syn::Meta) -> Result> { let err = || { let path = darling::util::path_to_string(meta.path()); err!( @@ -244,7 +246,7 @@ fn parse_expr_closure(meta: &syn::Meta) -> Result { }; match &meta.value { - syn::Expr::Closure(closure) => Ok(closure.clone()), + syn::Expr::Closure(closure) => Ok(SpannedKey::new(&meta.path, closure.clone())), _ => Err(err()), } } diff --git a/bon-macros/src/builder/builder_gen/member/params/setter.rs b/bon-macros/src/builder/builder_gen/member/params/setter.rs index 3df7aef6..d8841245 100644 --- a/bon-macros/src/builder/builder_gen/member/params/setter.rs +++ b/bon-macros/src/builder/builder_gen/member/params/setter.rs @@ -11,7 +11,7 @@ fn parse_setter_fn(meta: &syn::Meta) -> Result> { } .parse()?; - Ok(SpannedKey::from_parsed(meta, params)) + Ok(SpannedKey::new(meta.path(), params)) } fn parse_docs(meta: &syn::Meta) -> Result> { diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index cdb0cb97..81b4ca5b 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -493,7 +493,7 @@ impl BuilderGenCtx { .params .default .as_ref() - .map(|default| default.as_ref().as_ref()); + .map(|default| default.value.as_ref()); match param_default { Some(Some(default)) => { diff --git a/bon-macros/src/parsing/spanned_key.rs b/bon-macros/src/parsing/spanned_key.rs index 2adcc1e1..69ee72c0 100644 --- a/bon-macros/src/parsing/spanned_key.rs +++ b/bon-macros/src/parsing/spanned_key.rs @@ -1,6 +1,7 @@ use crate::util::prelude::*; use darling::FromMeta; use std::fmt; +use std::ops::Deref; /// 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 @@ -11,9 +12,9 @@ pub(crate) struct SpannedKey { } impl SpannedKey { - pub(crate) fn from_parsed(meta: &syn::Meta, value: T) -> Self { + pub(crate) fn new(path: &syn::Path, value: T) -> Self { Self { - key: meta.path().clone(), + key: path.clone(), value, } } @@ -35,3 +36,11 @@ impl fmt::Debug for SpannedKey { fmt::Debug::fmt(&self.value, f) } } + +impl Deref for SpannedKey { + type Target = T; + + fn deref(&self) -> &T { + &self.value + } +} diff --git a/bon/tests/integration/builder/attr_default.rs b/bon/tests/integration/builder/attr_default.rs index 2bc72136..c3db2621 100644 --- a/bon/tests/integration/builder/attr_default.rs +++ b/bon/tests/integration/builder/attr_default.rs @@ -1,4 +1,5 @@ use crate::prelude::*; +use core::fmt; #[cfg(feature = "alloc")] #[test] @@ -8,7 +9,7 @@ fn fn_alloc() { #[builder(default = "default".to_owned())] arg1: String, #[builder(default = vec![42])] arg2: Vec, #[builder(default = "foo", into)] arg3: String, - ) -> (String, Vec, String) { + ) -> impl fmt::Debug { (arg1, arg2, arg3) } @@ -138,7 +139,7 @@ fn struct_alloc() { #[test] fn fn_no_std() { #[builder] - fn sut(#[builder(default)] arg1: u32, #[builder(default = 42)] arg2: u32) -> (u32, u32) { + fn sut(#[builder(default)] arg1: u32, #[builder(default = 42)] arg2: u32) -> impl fmt::Debug { (arg1, arg2) } @@ -212,6 +213,7 @@ fn fn_generic_default() { mod interaction_with_positional_members { use crate::prelude::*; + use core::fmt; #[test] fn test_struct() { @@ -302,7 +304,7 @@ mod interaction_with_positional_members { #[builder(default = [starter_1, starter_2, finisher_1, finisher_2])] // named_1: [u32; 4], #[builder(default = (32, named_1))] named_2: (u32, [u32; 4]), - ) -> (u32, u32, u32, u32, [u32; 4], (u32, [u32; 4])) { + ) -> impl fmt::Debug { ( starter_1, starter_2, finisher_1, finisher_2, named_1, named_2, ) @@ -335,7 +337,7 @@ mod interaction_with_positional_members { #[builder(default = [starter_1, starter_2, finisher_1, finisher_2])] // named_1: [u32; 4], #[builder(default = (32, named_1))] named_2: (u32, [u32; 4]), - ) -> (u32, u32, u32, u32, [u32; 4], (u32, [u32; 4])) { + ) -> impl fmt::Debug { ( starter_1, starter_2, finisher_1, finisher_2, named_1, named_2, ) diff --git a/bon/tests/integration/builder/attr_expose_positional_fn.rs b/bon/tests/integration/builder/attr_expose_positional_fn.rs index 12733810..0a1e892e 100644 --- a/bon/tests/integration/builder/attr_expose_positional_fn.rs +++ b/bon/tests/integration/builder/attr_expose_positional_fn.rs @@ -1,4 +1,5 @@ use crate::prelude::*; +use core::fmt; #[test] fn method_new_doesnt_require_a_value_for_name() { @@ -33,7 +34,7 @@ fn method_new_doesnt_require_a_value_for_name() { #[test] fn with_nested_params() { #[builder(expose_positional_fn(name = positional))] - fn sut(arg1: bool, arg2: u32) -> (bool, u32) { + fn sut(arg1: bool, arg2: u32) -> impl fmt::Debug { (arg1, arg2) } diff --git a/bon/tests/integration/builder/attr_into.rs b/bon/tests/integration/builder/attr_into.rs index 9d7b4b98..881571dd 100644 --- a/bon/tests/integration/builder/attr_into.rs +++ b/bon/tests/integration/builder/attr_into.rs @@ -1,14 +1,14 @@ use crate::prelude::*; +use core::fmt; use core::num::NonZeroU32; #[cfg(feature = "alloc")] #[test] fn into_attr_alloc() { + use core::fmt; + #[builder] - fn sut( - #[builder(into)] set: Option>, - no_into: String, - ) -> (Option>, String) { + fn sut(#[builder(into)] set: Option>, no_into: String) -> impl fmt::Debug { (set, no_into) } @@ -27,7 +27,7 @@ fn into_attr_no_std() { /// Some docs #[builder(into)] u32: u32, - ) -> (&str, u32) { + ) -> impl fmt::Debug + '_ { (str_ref, u32) } @@ -52,7 +52,7 @@ fn into_attr_no_std() { #[test] fn into_string() { #[builder(on(String, into))] - fn sut(arg1: String, arg2: Option) -> (String, Option) { + fn sut(arg1: String, arg2: Option) -> impl fmt::Debug { (arg1, arg2) } diff --git a/bon/tests/integration/builder/attr_transparent.rs b/bon/tests/integration/builder/attr_transparent.rs index 502ec3de..0ebc825f 100644 --- a/bon/tests/integration/builder/attr_transparent.rs +++ b/bon/tests/integration/builder/attr_transparent.rs @@ -1,106 +1,127 @@ -mod smoke { - use crate::prelude::*; +use crate::prelude::*; +use core::fmt; - #[test] - fn test_struct() { - #[derive(Debug, Builder)] - #[allow(dead_code)] - struct Sut { - #[builder(transparent)] - regular: Option, +#[test] +fn test_struct() { + #[derive(Debug, Builder)] + #[allow(dead_code)] + struct Sut { + #[builder(transparent)] + regular: Option, - #[builder(transparent)] - generic: Option, + #[builder(transparent)] + generic: Option, - #[builder(into, transparent)] - with_into: Option, - } + #[builder(into, transparent)] + with_into: Option, + + #[builder(transparent, default = Some(99))] + with_default: Option, + + #[builder(transparent, default = Some(10))] + with_default_2: Option, + } + + assert_debug_eq( + Sut::builder() + .regular(Some(1)) + .generic(Some(false)) + .with_into(2) + .maybe_with_default_2(Some(Some(3))) + .build(), + expect![[r#" + Sut { + regular: Some( + 1, + ), + generic: Some( + false, + ), + with_into: Some( + 2, + ), + with_default: Some( + 99, + ), + with_default_2: Some( + 3, + ), + }"#]], + ); +} - assert_debug_eq( - Sut::builder() - .regular(Some(1)) - .generic(Some(false)) - .with_into(2) - .build(), - expect![[r#" - Sut { - regular: Some( - 1, - ), - generic: Some( - false, - ), - with_into: Some( - 2, - ), - }"#]], - ); +#[test] +fn test_free_fn() { + #[builder] + fn sut( + #[builder(transparent)] regular: Option, + #[builder(transparent)] generic: Option, + #[builder(into, transparent)] with_into: Option, + #[builder(transparent, default = Some(99))] with_default: Option, + #[builder(transparent, default = Some(10))] with_default_2: Option, + ) -> impl fmt::Debug { + (regular, generic, with_into, with_default, with_default_2) } - #[test] - fn test_free_fn() { + assert_debug_eq( + sut() + .regular(Some(1)) + .generic(Some(false)) + .with_into(2) + .maybe_with_default_2(Some(Some(3))) + .call(), + expect!["(Some(1), Some(false), Some(2), Some(99), Some(3))"], + ); +} + +#[test] +fn test_assoc_method_method() { + struct Sut; + + #[bon] + impl Sut { #[builder] - fn sut( + fn sut( #[builder(transparent)] regular: Option, #[builder(transparent)] generic: Option, #[builder(into, transparent)] with_into: Option, - ) -> (Option, Option, Option) { - (regular, generic, with_into) + #[builder(transparent, default = Some(99))] with_default: Option, + #[builder(transparent, default = Some(10))] with_default_2: Option, + ) -> impl fmt::Debug { + (regular, generic, with_into, with_default, with_default_2) } - assert_debug_eq( - sut() - .regular(Some(1)) - .generic(Some(false)) - .with_into(2) - .call(), - expect!["(Some(1), Some(false), Some(2))"], - ); - } - - #[test] - fn test_assoc_method_method() { - struct Sut; - - #[bon] - impl Sut { - #[builder] - fn sut( - #[builder(transparent)] regular: Option, - #[builder(transparent)] generic: Option, - #[builder(into, transparent)] with_into: Option, - ) -> (Option, Option, Option) { - (regular, generic, with_into) - } - - #[builder] - fn with_self( - &self, - #[builder(transparent)] regular: Option, - #[builder(transparent)] generic: Option, - #[builder(into, transparent)] with_into: Option, - ) -> (Option, Option, Option) { - let _ = self; - (regular, generic, with_into) - } + #[builder] + fn with_self( + &self, + #[builder(transparent)] regular: Option, + #[builder(transparent)] generic: Option, + #[builder(into, transparent)] with_into: Option, + #[builder(transparent, default = Some(99))] with_default: Option, + #[builder(transparent, default = Some(10))] with_default_2: Option, + ) -> impl fmt::Debug { + let _ = self; + (regular, generic, with_into, with_default, with_default_2) } + } - assert_debug_eq( - Sut::sut() - .regular(Some(1)) - .generic(Some(false)) - .with_into(2) - .call(), - expect!["(Some(1), Some(false), Some(2))"], - ); + assert_debug_eq( + Sut::sut() + .regular(Some(1)) + .generic(Some(false)) + .with_into(2) + .maybe_with_default_2(Some(Some(3))) + .call(), + expect!["(Some(1), Some(false), Some(2), Some(99), Some(3))"], + ); - assert_debug_eq( - Sut.with_self() - .regular(Some(1)) - .generic(Some(false)) - .with_into(2) - .call(), - expect!["(Some(1), Some(false), Some(2))"], - ); - } + assert_debug_eq( + Sut.with_self() + .regular(Some(1)) + .generic(Some(false)) + .with_into(2) + .maybe_with_default_2(Some(Some(3))) + .call(), + expect!["(Some(1), Some(false), Some(2), Some(99), Some(3))"], + ); } diff --git a/bon/tests/integration/builder/attr_with.rs b/bon/tests/integration/builder/attr_with.rs new file mode 100644 index 00000000..31ad7607 --- /dev/null +++ b/bon/tests/integration/builder/attr_with.rs @@ -0,0 +1,37 @@ +mod single_arg { + use crate::prelude::*; + use core::net::IpAddr; + + #[test] + fn test_struct() { + #[derive(Debug, Builder)] + #[allow(dead_code)] + struct Sut { + #[builder(with = |x: u32| x + y)] + required: u32, + + #[builder(with = |x: u32| 2 * x)] + optional: Option, + + #[builder(with = |x: u32| x + y, default)] + default: u32, + + #[builder(with = |value: &T| value.clone())] + generic: T, + + #[builder(with = |value: impl Into| value)] + impl_trait: IpAddr, + } + + // assert_debug_eq( + // Sut::builder() + // .required(1) + // .optional(2) + // .default(3) + // .generic("hello") + // .impl_trait([127, 0, 0, 1]) + // .build(), + // expect![], + // ) + } +} diff --git a/bon/tests/integration/builder/init_order.rs b/bon/tests/integration/builder/init_order.rs index 99e7d5ba..2dd26a6e 100644 --- a/bon/tests/integration/builder/init_order.rs +++ b/bon/tests/integration/builder/init_order.rs @@ -1,4 +1,5 @@ use crate::prelude::*; +use core::fmt; // Functions don't support `skip` attributes #[test] @@ -8,7 +9,7 @@ fn fn_init_order() { #[builder(default = 1)] arg1: u32, #[builder(default = 2)] arg2: u32, #[builder(default = [arg1, arg2, 3])] arg3: [u32; 3], - ) -> (u32, u32, [u32; 3]) { + ) -> impl fmt::Debug { (arg1, arg2, arg3) } diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index 3a10a223..3db56e4e 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -1,6 +1,7 @@ mod attr_default; mod attr_expose_positional_fn; mod attr_into; +mod attr_with; mod attr_on; mod attr_transparent; mod attr_skip; diff --git a/bon/tests/integration/builder/positional_members.rs b/bon/tests/integration/builder/positional_members.rs index 09be178b..028bd721 100644 --- a/bon/tests/integration/builder/positional_members.rs +++ b/bon/tests/integration/builder/positional_members.rs @@ -18,6 +18,7 @@ impl From for char { mod smoke { use super::*; + use core::fmt; #[test] fn test_struct() { @@ -70,14 +71,7 @@ mod smoke { #[builder(finish_fn)] finisher_1: &'static str, #[builder(finish_fn, into)] finisher_2: &'static str, named: u32, - ) -> ( - bool, - char, - Option<&'static str>, - u32, - &'static str, - &'static str, - ) { + ) -> impl fmt::Debug { ( starter_1, starter_2, starter_3, named, finisher_1, finisher_2, ) @@ -107,14 +101,7 @@ mod smoke { #[builder(finish_fn)] finisher_1: &'static str, #[builder(finish_fn, into)] finisher_2: &'static str, named: u32, - ) -> ( - bool, - char, - Option<&'static str>, - u32, - &'static str, - &'static str, - ) { + ) -> impl fmt::Debug { ( starter_1, starter_2, starter_3, named, finisher_1, finisher_2, ) @@ -126,7 +113,7 @@ mod smoke { #[builder(start_fn)] starter_1: bool, #[builder(finish_fn)] finisher_1: &'static str, named: u32, - ) -> (bool, u32, &'static str) { + ) -> impl fmt::Debug { let _ = self; (starter_1, named, finisher_1) } @@ -150,6 +137,7 @@ mod smoke { mod attr_on { use super::*; + use core::fmt; #[test] fn test_struct() { @@ -196,7 +184,7 @@ mod attr_on { #[builder(finish_fn)] finisher_1: &'static str, named: u32, - ) -> (bool, Option<&'static str>, &'static str, u32) { + ) -> impl fmt::Debug { (starter_1, starter_3, finisher_1, named) } @@ -221,7 +209,7 @@ mod attr_on { #[builder(finish_fn)] finisher_1: &'static str, named: u32, - ) -> (bool, Option<&'static str>, &'static str, u32) { + ) -> impl fmt::Debug { (starter_1, starter_3, finisher_1, named) } @@ -231,7 +219,7 @@ mod attr_on { #[builder(start_fn)] starter_1: bool, #[builder(finish_fn)] finisher_1: &'static str, named: u32, - ) -> (bool, &'static str, u32) { + ) -> impl fmt::Debug { let _ = self; (starter_1, finisher_1, named) } diff --git a/bon/tests/integration/ui/compile_fail/attr_with.rs b/bon/tests/integration/ui/compile_fail/attr_with.rs new file mode 100644 index 00000000..17f5f8e7 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_with.rs @@ -0,0 +1,9 @@ +use bon::Builder; + +#[derive(Builder)] +struct ConflictingInto { + #[builder(into, with = |x: u32| x + 1)] + value: u32, +} + +fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_with.stderr b/bon/tests/integration/ui/compile_fail/attr_with.stderr new file mode 100644 index 00000000..f37da432 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_with.stderr @@ -0,0 +1,5 @@ +error: `with` attribute can't be specified together with `into` + --> tests/integration/ui/compile_fail/attr_with.rs:5:21 + | +5 | #[builder(into, with = |x: u32| x + 1)] + | ^^^^ From 73976a8c6a9c6889c97ee1987933226e3002c65d Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 3 Oct 2024 13:13:34 +0000 Subject: [PATCH 044/119] More validations --- .../src/builder/builder_gen/member/named.rs | 61 ++++++++++++++++--- .../builder/builder_gen/member/params/mod.rs | 6 +- .../builder_gen/member/params/setter.rs | 6 ++ .../src/builder/builder_gen/setter_methods.rs | 10 +++ .../src/builder/builder_gen/state_mod.rs | 2 +- bon/tests/integration/builder/attr_with.rs | 20 +++--- 6 files changed, 82 insertions(+), 23 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index 719eb70c..de0bf894 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -1,8 +1,9 @@ use super::params::MemberParams; use super::{params, MemberOrigin}; use crate::builder::builder_gen::builder_params::OnParams; -use crate::builder::builder_gen::member::params::SettersParams; +use crate::builder::builder_gen::member::params::{SettersFnParams, SettersParams}; use crate::util::prelude::*; +use syn::spanned::Spanned; /// Regular member for which the builder should have setter methods #[derive(Debug)] @@ -74,13 +75,7 @@ impl NamedMember { }; if self.is_required() { - let SettersParams { - name: _, - vis: _, - docs: _, - some_fn, - option_fn, - } = setters; + let SettersFnParams { some_fn, option_fn } = &setters.fns; let unexpected_setter = [option_fn, some_fn].into_iter().find_map(Option::as_ref); @@ -97,9 +92,59 @@ impl NamedMember { ); } + if let SettersFnParams { + some_fn: Some(some_fn), + option_fn: Some(option_fn), + } = &setters.fns + { + Self::validate_unused_config( + "name", + &setters.name, + &[("some_fn", &some_fn.name), ("option_fn", &option_fn.name)], + )?; + + Self::validate_unused_config( + "vis", + &setters.vis, + &[("some_fn", &some_fn.vis), ("option_fn", &option_fn.vis)], + )?; + + // Self::validate_unused_config( + // "docs", + // &setters.docs, + // &[("some_fn", &some_fn.docs), ("option_fn", &option_fn.docs)], + // )?; + } + Ok(()) } + fn validate_unused_config( + name: &'static str, + config: &Option, + overrides: &[(&'static str, &Option)], + ) -> Result { + let config = match config { + Some(config) => config, + None => return Ok(()), + }; + + if !overrides.iter().all(|(_, over)| over.is_some()) { + return Ok(()); + } + + let setters = overrides + .iter() + .map(|(name, _)| format!("`{name}`")) + .join(", "); + + bail!( + config, + "this `{name}` configuration is unused because both all of the \ + {setters} setters contain a `{name}` override" + ); + } + /// Returns the public identifier of the member that should be used in the /// generated builder API. pub(crate) fn public_ident(&self) -> &syn::Ident { diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index 31f06cc4..d25cbfb9 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -1,14 +1,12 @@ mod blanket; mod setter; -pub(crate) use blanket::{BlanketParamName, EvalBlanketFlagParam}; -pub(crate) use setter::SettersParams; +pub(crate) use blanket::*; +pub(crate) use setter::*; use super::MemberOrigin; use crate::parsing::SpannedKey; use crate::util::prelude::*; -use darling::util::SpannedValue; -use darling::FromMeta; use std::fmt; use syn::spanned::Spanned; diff --git a/bon-macros/src/builder/builder_gen/member/params/setter.rs b/bon-macros/src/builder/builder_gen/member/params/setter.rs index d8841245..ed5d30eb 100644 --- a/bon-macros/src/builder/builder_gen/member/params/setter.rs +++ b/bon-macros/src/builder/builder_gen/member/params/setter.rs @@ -26,6 +26,12 @@ pub(crate) struct SettersParams { #[darling(default, with = parse_docs, map = Some)] pub(crate) docs: Option>, + #[darling(flatten)] + pub(crate) fns: SettersFnParams, +} + +#[derive(Debug, FromMeta)] +pub(crate) struct SettersFnParams { /// Config for the setter that accepts the value of type T for a member of /// type `Option` or with `#[builder(default)]`. /// diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index d1150500..d63f6de3 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -29,6 +29,16 @@ impl<'a> MemberSettersCtx<'a> { (quote!(#member_type), quote!()) }; + // let fn_params = self.member.params.with.as_deref().map(|closure| { + // closure.lifetimes + // syn::parse_macro_input + + // quote! { + // value: #fn_param_type, + // with: #closure_params, + // } + // }); + self.setter_method(MemberSetterMethod { method_name: self.member.public_ident().clone(), fn_params: quote!(value: #fn_param_type), diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index d7d19c72..95e05c4f 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -310,9 +310,9 @@ impl super::BuilderGenCtx { state of members instead" ] #[doc(hidden)] + #[allow(non_camel_case_types)] mod members { #( - #[allow(non_camel_case_types)] #vis_child_child enum #stateful_members_idents {} )* } diff --git a/bon/tests/integration/builder/attr_with.rs b/bon/tests/integration/builder/attr_with.rs index 31ad7607..441b6398 100644 --- a/bon/tests/integration/builder/attr_with.rs +++ b/bon/tests/integration/builder/attr_with.rs @@ -23,15 +23,15 @@ mod single_arg { impl_trait: IpAddr, } - // assert_debug_eq( - // Sut::builder() - // .required(1) - // .optional(2) - // .default(3) - // .generic("hello") - // .impl_trait([127, 0, 0, 1]) - // .build(), - // expect![], - // ) + assert_debug_eq( + Sut::builder() + .required(1) + .optional(2) + .default(3) + .generic("hello") + .impl_trait([127, 0, 0, 1]) + .build(), + expect![], + ) } } From 41029d7e9984f4251a09cd2bbe41d79c25c87e17 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 3 Oct 2024 17:20:43 +0000 Subject: [PATCH 045/119] Initial `with` impl --- .../src/builder/builder_gen/input_fn.rs | 58 +++-- .../src/builder/builder_gen/input_struct.rs | 32 +-- .../src/builder/builder_gen/member/mod.rs | 1 + .../src/builder/builder_gen/member/named.rs | 48 ++-- .../builder/builder_gen/member/params/mod.rs | 120 ++++++++-- .../builder_gen/member/params/setter.rs | 10 +- bon-macros/src/builder/builder_gen/models.rs | 27 ++- .../src/builder/builder_gen/setter_methods.rs | 221 ++++++++++++++---- bon-macros/src/parsing/docs.rs | 7 +- bon-macros/src/parsing/item_params.rs | 22 +- bon-macros/src/parsing/mod.rs | 2 + bon-macros/src/parsing/simple_closure.rs | 92 ++++++++ bon-macros/src/parsing/spanned_key.rs | 19 +- bon-macros/src/util/path.rs | 10 + bon-macros/src/util/ty/mod.rs | 87 ++++--- bon/tests/integration/builder/attr_with.rs | 26 +-- .../integration/ui/compile_fail/attr_with.rs | 36 +++ .../ui/compile_fail/attr_with.stderr | 42 ++++ 18 files changed, 630 insertions(+), 230 deletions(-) create mode 100644 bon-macros/src/parsing/simple_closure.rs diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index 53427b60..2698b7fe 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -5,7 +5,7 @@ use super::{ }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; use crate::normalization::NormalizeSelfTy; -use crate::parsing::ItemParams; +use crate::parsing::{ItemParams, SpannedKey}; use crate::util::prelude::*; use darling::util::SpannedValue; use darling::FromMeta; @@ -83,7 +83,11 @@ impl FnInputCtx { .impl_ctx .as_deref()? .self_ty - .last_path_segment_ident()? + .as_path()? + .path + .segments + .last()? + .ident .to_string(); Some(prefix) @@ -110,7 +114,7 @@ impl FnInputCtx { if let [attr, ..] = receiver.attrs.as_slice() { bail!( attr, - "attributes on the receiver are not supported in the #[builder] macro." + "attributes on the receiver are not supported in the #[builder] macro" ); } @@ -153,7 +157,7 @@ impl FnInputCtx { } fn builder_ident(&self) -> syn::Ident { - let user_override = self.params.base.builder_type.name.as_ref(); + let user_override = self.params.base.builder_type.name.as_deref(); if let Some(user_override) = user_override { return user_override.clone(); @@ -353,24 +357,28 @@ impl FnInputCtx { docs: finish_fn_docs, } = self.params.base.finish_fn; - let finish_fn_ident = finish_fn_ident.unwrap_or_else(|| { - // For `new` methods the `build` finisher is more conventional - if is_method_new { - format_ident!("build") - } else { - format_ident!("call") - } - }); + let finish_fn_ident = finish_fn_ident + .map(SpannedKey::into_value) + .unwrap_or_else(|| { + // For `new` methods the `build` finisher is more conventional + if is_method_new { + format_ident!("build") + } else { + format_ident!("call") + } + }); - let finish_fn_docs = finish_fn_docs.unwrap_or_else(|| { - vec![syn::parse_quote! { - /// Finishes building and performs the requested action. - }] - }); + 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 = FinishFn { ident: finish_fn_ident, - vis: finish_fn_vis, + vis: finish_fn_vis.map(SpannedKey::into_value), unsafety: self.norm_fn.sig.unsafety, asyncness: self.norm_fn.sig.asyncness, must_use: get_must_use_attribute(&self.norm_fn.attrs)?, @@ -414,8 +422,18 @@ impl FnInputCtx { let builder_type = BuilderTypeParams { ident: builder_ident, derives: self.params.base.derive, - docs: self.params.base.builder_type.docs, - vis: self.params.base.builder_type.vis, + docs: self + .params + .base + .builder_type + .docs + .map(SpannedKey::into_value), + vis: self + .params + .base + .builder_type + .vis + .map(SpannedKey::into_value), }; BuilderGenCtx::new(BuilderGenCtxParams { diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 845eacc1..a42e379c 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -4,7 +4,7 @@ use super::{ RawMember, }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; -use crate::parsing::{ItemParams, ItemParamsParsing}; +use crate::parsing::{ItemParams, ItemParamsParsing, SpannedKey}; use crate::util::prelude::*; use darling::FromMeta; use syn::visit_mut::VisitMut; @@ -145,6 +145,7 @@ impl StructInputCtx { } = self.params.start_fn; let start_fn_ident = start_fn_ident + .map(SpannedKey::into_value) .unwrap_or_else(|| syn::Ident::new("builder", self.norm_struct.ident.span())); let ItemParams { @@ -153,13 +154,14 @@ impl StructInputCtx { docs: finish_fn_docs, } = self.params.base.finish_fn; - let finish_fn_ident = - finish_fn_ident.unwrap_or_else(|| syn::Ident::new("build", start_fn_ident.span())); + 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 = FinishFn { ident: finish_fn_ident, - vis: finish_fn_vis, + vis: finish_fn_vis.map(SpannedKey::into_value), unsafety: None, asyncness: None, must_use: Some(syn::parse_quote! { @@ -167,14 +169,16 @@ impl StructInputCtx { }), body: Box::new(finish_fn_body), output: syn::parse_quote!(-> #struct_ty), - attrs: finish_fn_docs.unwrap_or_else(|| { - vec![syn::parse_quote! { - /// Finishes building and returns the requested object - }] - }), + attrs: finish_fn_docs + .map(SpannedKey::into_value) + .unwrap_or_else(|| { + vec![syn::parse_quote! { + /// Finishes building and returns the requested object + }] + }), }; - let start_fn_docs = start_fn_docs.unwrap_or_else(|| { + 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.norm_struct.ident @@ -185,7 +189,7 @@ impl StructInputCtx { let start_fn = StartFnParams { ident: start_fn_ident, - vis: start_fn_vis, + vis: start_fn_vis.map(SpannedKey::into_value), attrs: start_fn_docs, generics: None, }; @@ -205,14 +209,14 @@ impl StructInputCtx { let builder_type = { let ItemParams { name, vis, docs } = self.params.base.builder_type; - let builder_ident = name + let builder_ident = name.map(SpannedKey::into_value) .unwrap_or_else(|| format_ident!("{}Builder", self.norm_struct.ident.raw_name())); BuilderTypeParams { derives: self.params.base.derive, ident: builder_ident, - docs, - vis, + docs: docs.map(SpannedKey::into_value), + vis: vis.map(SpannedKey::into_value), } }; diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index d81113b1..4c52536a 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -3,6 +3,7 @@ mod named; mod params; pub(crate) use named::*; +pub(crate) use params::SetterClosure; use super::builder_params::OnParams; use crate::util::prelude::*; diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index de0bf894..cd95c447 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -1,9 +1,9 @@ use super::params::MemberParams; use super::{params, MemberOrigin}; use crate::builder::builder_gen::builder_params::OnParams; -use crate::builder::builder_gen::member::params::{SettersFnParams, SettersParams}; +use crate::builder::builder_gen::member::params::SettersFnParams; +use crate::parsing::SpannedKey; use crate::util::prelude::*; -use syn::spanned::Spanned; /// Regular member for which the builder should have setter methods #[derive(Debug)] @@ -88,7 +88,7 @@ impl NamedMember { &setter.key, "`{}` setter function applies only to members with `#[builder(default)]` \ or members of `Option` type (if #[builder(transparent)] is not set)", - setter.key_to_string() + setter.key ); } @@ -97,51 +97,39 @@ impl NamedMember { option_fn: Some(option_fn), } = &setters.fns { - Self::validate_unused_config( - "name", - &setters.name, - &[("some_fn", &some_fn.name), ("option_fn", &option_fn.name)], - )?; - - Self::validate_unused_config( - "vis", - &setters.vis, - &[("some_fn", &some_fn.vis), ("option_fn", &option_fn.vis)], - )?; - - // Self::validate_unused_config( - // "docs", - // &setters.docs, - // &[("some_fn", &some_fn.docs), ("option_fn", &option_fn.docs)], - // )?; + Self::validate_unused_config(&setters.name, &[&some_fn.name, &option_fn.name])?; + Self::validate_unused_config(&setters.vis, &[&some_fn.vis, &option_fn.vis])?; + Self::validate_unused_config(&setters.docs, &[&some_fn.docs, &option_fn.docs])?; } Ok(()) } - fn validate_unused_config( - name: &'static str, - config: &Option, - overrides: &[(&'static str, &Option)], + fn validate_unused_config( + config: &Option>, + overrides: &[&Option>], ) -> Result { let config = match config { Some(config) => config, None => return Ok(()), }; - if !overrides.iter().all(|(_, over)| over.is_some()) { + let overrides = overrides.iter().copied().map(Option::as_ref); + + if !overrides.clone().all(|over| over.is_some()) { return Ok(()); } let setters = overrides - .iter() - .map(|(name, _)| format!("`{name}`")) + .flatten() + .map(|over| format!("`{}`", over.key)) .join(", "); bail!( - config, - "this `{name}` configuration is unused because both all of the \ - {setters} setters contain a `{name}` override" + &config.key, + "this `{name}` configuration is unused because all of the \ + {setters} setters contain a `{name}` override", + name = config.key, ); } diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index d25cbfb9..ac2a81dc 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -5,10 +5,10 @@ pub(crate) use blanket::*; pub(crate) use setter::*; use super::MemberOrigin; -use crate::parsing::SpannedKey; +use crate::parsing::{SimpleClosure, SpannedKey}; use crate::util::prelude::*; +use darling::FromMeta; use std::fmt; -use syn::spanned::Spanned; #[derive(Debug, darling::FromAttributes)] #[darling(attributes(builder))] @@ -51,8 +51,11 @@ pub(crate) struct MemberParams { /// this option to see if it's worth it. pub(crate) overwritable: darling::util::Flag, - #[darling(default, with = parse_expr_closure, map = Some)] - pub(crate) with: Option>, + /// Customize the setter signature and body with a custom closure. The closure + /// 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>, /// Disables the special handling for a member of type `Option`. The /// member no longer has the default on `None`. It also becomes a required @@ -223,28 +226,99 @@ impl MemberParams { fn parse_optional_expr(meta: &syn::Meta) -> Result>> { match meta { - syn::Meta::Path(path) => Ok(SpannedKey::new(path, None)), + syn::Meta::Path(path) => SpannedKey::new(path, None), syn::Meta::List(_) => Err(Error::unsupported_format("list").with_span(meta)), - syn::Meta::NameValue(meta) => Ok(SpannedKey::new(&meta.path, Some(meta.value.clone()))), + syn::Meta::NameValue(meta) => SpannedKey::new(&meta.path, Some(meta.value.clone())), } } -fn parse_expr_closure(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()), - }; - - match &meta.value { - syn::Expr::Closure(closure) => Ok(SpannedKey::new(&meta.path, closure.clone())), - _ => Err(err()), +#[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, + "expected one of the following syntaxes:\n\ + (1) no return type annotation;\n\ + (2) `-> Result<_, {{ErrorType}}>` or `-> Result<_>` return type annotation;\n\n\ + in the case (1), the closure is expected to return a value \ + of the same type as the member's type;\n\n\ + in the case (2), the closure is expected to return a `Result` \ + where the `Ok` variant is of the same type as the member's type; \ + the `_` placeholder must be spelled literally to mark \ + the type of the member; an optional second generic parameter \ + for the error type is allowed" + ) + }; + + let args = ty.as_generic_angle_bracketed("Result").ok_or_else(err)?; + + if args.len() != 1 && args.len() != 2 { + return Err(err()); + } + + let mut args = args.into_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()?; + + Some(SetterClosureOutput { + result_path: syn::parse_quote!(Result), + err_ty, + }) + } + }; + + Ok(Self { + inputs, + body: closure.body, + output: return_type, + }) } } diff --git a/bon-macros/src/builder/builder_gen/member/params/setter.rs b/bon-macros/src/builder/builder_gen/member/params/setter.rs index ed5d30eb..3ed770d2 100644 --- a/bon-macros/src/builder/builder_gen/member/params/setter.rs +++ b/bon-macros/src/builder/builder_gen/member/params/setter.rs @@ -11,20 +11,20 @@ fn parse_setter_fn(meta: &syn::Meta) -> Result> { } .parse()?; - Ok(SpannedKey::new(meta.path(), params)) + SpannedKey::new(meta.path(), params) } -fn parse_docs(meta: &syn::Meta) -> Result> { +fn parse_docs(meta: &syn::Meta) -> Result>> { crate::parsing::parse_docs_without_self_mentions(DOCS_CONTEXT, meta) } #[derive(Debug, FromMeta)] pub(crate) struct SettersParams { - pub(crate) name: Option, - pub(crate) vis: Option, + pub(crate) name: Option>, + pub(crate) vis: Option>, #[darling(default, with = parse_docs, map = Some)] - pub(crate) docs: Option>, + pub(crate) docs: Option>>, #[darling(flatten)] pub(crate) fns: SettersFnParams, diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 3ad78ca7..221d20ce 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -1,6 +1,6 @@ use super::builder_params::{BuilderDerives, OnParams}; use super::member::Member; -use crate::parsing::ItemParams; +use crate::parsing::{ItemParams, SpannedKey}; use crate::util::prelude::*; pub(super) trait FinishFnBody { @@ -197,6 +197,7 @@ impl BuilderGenCtx { let 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 { @@ -224,7 +225,10 @@ impl BuilderGenCtx { // 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.unwrap_or_else(|| syn::Visibility::Inherited); + 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 @@ -239,14 +243,17 @@ impl BuilderGenCtx { ident, - docs: state_mod.docs.unwrap_or_else(|| { - let docs = format!( - "Tools for manipulating the type state of the [`{}`].", - builder_type.ident - ); - - vec![syn::parse_quote!(#[doc = #docs])] - }), + docs: state_mod + .docs + .map(SpannedKey::into_value) + .unwrap_or_else(|| { + let docs = format!( + "Tools for manipulating the type state of the [`{}`].", + builder_type.ident + ); + + vec![syn::parse_quote!(#[doc = #docs])] + }), } }; diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index d63f6de3..67e2ed4c 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -1,3 +1,4 @@ +use super::member::SetterClosure; use super::{BuilderGenCtx, NamedMember}; use crate::util::prelude::*; @@ -15,74 +16,143 @@ impl<'a> MemberSettersCtx<'a> { } pub(crate) fn setter_methods(&self) -> TokenStream { - let member_type = self.member.norm_ty.as_ref(); - - if !self.member.is_required() { - return self.setters_for_optional_member(); + if self.member.is_required() { + self.setters_for_required_member() + } else { + self.setters_for_optional_member() } + } + + fn setters_for_required_member(&self) -> TokenStream { + let fn_inputs; + let member_init; - let has_into = self.member.params.into.is_present(); + if let Some(closure) = &self.member.params.with { + fn_inputs = Self::inputs_override(closure); - let (fn_param_type, maybe_into_call) = if has_into { - (quote!(impl Into<#member_type>), quote!(.into())) + let member_init_override = self.member_init_override(closure); + member_init = quote!(Some(#member_init_override)); } else { - (quote!(#member_type), quote!()) - }; + let member_type = self.member.norm_ty.as_ref(); + let has_into = self.member.params.into.is_present(); - // let fn_params = self.member.params.with.as_deref().map(|closure| { - // closure.lifetimes - // syn::parse_macro_input + let (fn_param_type, maybe_into_call) = if has_into { + (quote!(impl Into<#member_type>), quote!(.into())) + } else { + (quote!(#member_type), quote!()) + }; - // quote! { - // value: #fn_param_type, - // with: #closure_params, - // } - // }); + fn_inputs = quote!(value: #fn_param_type); + member_init = quote!(Some(value #maybe_into_call)); + } self.setter_method(MemberSetterMethod { method_name: self.member.public_ident().clone(), - fn_params: quote!(value: #fn_param_type), + fn_inputs, overwrite_docs: None, - body: SetterBody::Default { - member_init: quote!(Some(value #maybe_into_call)), - }, + body: SetterBody::Default { member_init }, }) } fn setters_for_optional_member(&self) -> TokenStream { - let underlying_ty = self.member.underlying_norm_ty(); - - let has_into = self.member.params.into.is_present(); - let (inner_type, maybe_map_conv_call) = if has_into { - (quote!(impl Into<#underlying_ty>), quote!(.map(Into::into))) - } else { - (quote!(#underlying_ty), quote!()) - }; - - let setter_method_name = self.member.public_ident().clone(); + let member_name = self.member.public_ident().clone(); // Preserve the original identifier span to make IDE's "go to definition" work correctly - let option_method_name = syn::Ident::new( - &format!("maybe_{}", setter_method_name.raw_name()), - setter_method_name.span(), + let option_fn_name = syn::Ident::new( + &format!("maybe_{}", member_name.raw_name()), + member_name.span(), ); + let some_fn_inputs; // Option-less setter is just a shortcut for wrapping the value in `Some`. - let optionless_setter_body = quote! { - self.#option_method_name(Some(value)) - }; + let some_fn_body; + + let option_fn_inputs; + let option_fn_member_init; + + if let Some(closure) = &self.member.params.with { + some_fn_inputs = Self::inputs_override(closure); + + // If the closure accepts just a single input avoid wrapping it + // in a tuple in the `option_fn` setter. + if let [input] = closure.inputs.as_slice() { + let ident = &input.pat.ident; + some_fn_body = quote! { + self.#option_fn_name(Some(#ident)) + }; + + let ty = &input.ty; + option_fn_inputs = quote!(value: Option<#ty>); + option_fn_member_init = { + let init = self.member_init_override(closure); + let question_mark = closure.output.is_some().then(|| quote!(?)); + + quote! { + match value { + Some(#ident) => #init #question_mark, + None => None, + } + } + }; + } else { + let idents = closure.inputs.iter().map(|input| &input.pat.ident); + some_fn_body = { + let idents = idents.clone(); + quote! { + self.#option_fn_name(Some(( #(#idents,)* ))) + } + }; + + option_fn_inputs = quote!(Option<( #some_fn_inputs )>); + option_fn_member_init = { + let init = self.member_init_override(closure); + let question_mark = closure.output.is_some().then(|| quote!(?)); + + quote! { + match value { + Some(( #(#idents,)* )) => #init #question_mark, + None => None, + } + } + } + }; + } else { + let underlying_ty = self.member.underlying_norm_ty(); + let has_into = self.member.params.into.is_present(); + + let inner_type = if has_into { + quote!(impl Into<#underlying_ty>) + } else { + quote!(#underlying_ty) + }; + + some_fn_inputs = quote!(value: #inner_type); + some_fn_body = quote! { + self.#option_fn_name(Some(value)) + }; + option_fn_inputs = quote!(value: Option<#inner_type>); + + option_fn_member_init = if has_into { + quote!(::core::option::Option::map( + value, + ::core::convert::Into::into + )) + } else { + quote!(value) + }; + } let methods = [ MemberSetterMethod { - method_name: option_method_name, - fn_params: quote!(value: Option<#inner_type>), + method_name: option_fn_name, + fn_inputs: option_fn_inputs, overwrite_docs: Some(format!( - "Same as [`Self::{setter_method_name}`], but accepts \ + "Same as [`Self::{member_name}`], but accepts \ an `Option` as input. See that method's documentation for \ more details.", )), body: SetterBody::Default { - member_init: quote!(value #maybe_map_conv_call), + member_init: option_fn_member_init, }, }, // We intentionally keep the name and signature of the setter method @@ -92,23 +162,59 @@ impl<'a> MemberSettersCtx<'a> { // To be able to explicitly pass an `Option` value to the setter method // users need to use the `maybe_{member_ident}` method. MemberSetterMethod { - method_name: setter_method_name, - fn_params: quote!(value: #inner_type), + method_name: member_name, + fn_inputs: some_fn_inputs, overwrite_docs: None, - body: SetterBody::Custom(optionless_setter_body), + body: SetterBody::Custom(some_fn_body), }, ]; methods .into_iter() .map(|method| self.setter_method(method)) - .concat() + .collect() + } + + fn inputs_override(closure: &SetterClosure) -> TokenStream { + let pats = closure.inputs.iter().map(|input| &input.pat); + let types = closure.inputs.iter().map(|input| &input.ty); + quote! { + #( #pats: #types ),* + } + } + + fn member_init_override(&self, closure: &SetterClosure) -> TokenStream { + let body = &closure.body; + let output = Self::result_output_override(closure, || quote!(_)).into_iter(); + + // let mut ty = self.member.underlying_norm_ty().to_token_stream(); + + // if !self.member.is_required() { + // ty = quote!(Option<#ty>); + // }; + + quote! { + (move || #( -> #output )* #body)() + } + } + + fn result_output_override( + closure: &SetterClosure, + default_ty: impl FnOnce() -> T, + ) -> Option { + let output = closure.output.as_ref()?; + let result_path = &output.result_path; + let err_ty = output.err_ty.iter(); + let default_ty = default_ty(); + Some(quote! { + #result_path< #default_ty #(, #err_ty )* > + }) } fn setter_method(&self, method: MemberSetterMethod) -> TokenStream { let MemberSetterMethod { method_name, - fn_params, + fn_inputs, overwrite_docs, body, } = method; @@ -124,7 +230,7 @@ impl<'a> MemberSettersCtx<'a> { SetterBody::Default { member_init } => { let index = &self.member.index; - let state_transition_call = if self.member.is_stateful() { + let mut state_transition_call = if self.member.is_stateful() { quote! { .__private_transition_type_state() } @@ -132,6 +238,15 @@ impl<'a> MemberSettersCtx<'a> { quote! {} }; + if let Some(closure) = &self.member.params.with { + if let Some(output) = &closure.output { + let result_path = &output.result_path; + state_transition_call = quote! { + #result_path::Ok(#state_transition_call) + }; + } + } + quote! { self.__private_named_members.#index = #member_init; self #state_transition_call @@ -157,7 +272,7 @@ impl<'a> MemberSettersCtx<'a> { let builder_ident = &self.builder_gen.builder_type.ident; let generic_args = &self.builder_gen.generics.args; - let return_type = if self.member.is_stateful() { + let mut return_type = if self.member.is_stateful() { quote! { #builder_ident<#(#generic_args,)* #state_transition> } @@ -165,6 +280,12 @@ impl<'a> MemberSettersCtx<'a> { quote! { Self } }; + if let Some(closure) = &self.member.params.with { + if let Some(overridden) = Self::result_output_override(closure, || &return_type) { + return_type = overridden; + } + } + let where_clause = if self.member.is_stateful() && !self.member.params.overwritable.is_present() { quote! { @@ -189,7 +310,7 @@ impl<'a> MemberSettersCtx<'a> { clippy::impl_trait_in_params )] #[inline(always)] - #vis fn #method_name(mut self, #fn_params) -> #return_type + #vis fn #method_name(mut self, #fn_inputs) -> #return_type #where_clause { #body @@ -237,7 +358,7 @@ enum SetterBody { struct MemberSetterMethod { method_name: syn::Ident, - fn_params: TokenStream, + fn_inputs: TokenStream, overwrite_docs: Option, body: SetterBody, } diff --git a/bon-macros/src/parsing/docs.rs b/bon-macros/src/parsing/docs.rs index f967754f..0b3ac9f0 100644 --- a/bon-macros/src/parsing/docs.rs +++ b/bon-macros/src/parsing/docs.rs @@ -1,15 +1,16 @@ +use super::SpannedKey; use crate::util::prelude::*; pub(crate) fn parse_docs_without_self_mentions( context: &'static str, meta: &syn::Meta, -) -> Result> { +) -> Result>> { let docs = parse_docs(meta)?; reject_self_mentions_in_docs(context, &docs)?; Ok(docs) } -pub(crate) fn parse_docs(meta: &syn::Meta) -> Result> { +pub(crate) fn parse_docs(meta: &syn::Meta) -> Result>> { let meta = meta.require_list()?; meta.require_curly_braces_delim()?; @@ -22,7 +23,7 @@ pub(crate) fn parse_docs(meta: &syn::Meta) -> Result> { } } - Ok(attrs) + SpannedKey::new(&meta.path, attrs) } /// Validates the docs for the presence of `Self` mentions to prevent users from diff --git a/bon-macros/src/parsing/item_params.rs b/bon-macros/src/parsing/item_params.rs index 184d22a4..cafb18e1 100644 --- a/bon-macros/src/parsing/item_params.rs +++ b/bon-macros/src/parsing/item_params.rs @@ -1,11 +1,12 @@ +use super::SpannedKey; use crate::util::prelude::*; use darling::FromMeta; #[derive(Debug, Clone, Default)] pub(crate) struct ItemParams { - pub(crate) name: Option, - pub(crate) vis: Option, - pub(crate) docs: Option>, + pub(crate) name: Option>, + pub(crate) vis: Option>, + pub(crate) docs: Option>>, } pub(crate) struct ItemParamsParsing<'a> { @@ -29,10 +30,10 @@ impl ItemParamsParsing<'_> { fn params_from_meta(meta: &syn::Meta) -> Result { if let syn::Meta::NameValue(meta) = meta { let val = &meta.value; - let name = syn::parse2(quote!(#val))?; + let name = syn::parse2(val.to_token_stream())?; return Ok(ItemParams { - name: Some(name), + name: Some(SpannedKey::new(&meta.path, name)?), vis: None, docs: None, }); @@ -40,9 +41,9 @@ impl ItemParamsParsing<'_> { #[derive(Debug, FromMeta)] struct Full { - name: Option, - vis: Option, - docs: Option, + name: Option>, + vis: Option>, + docs: Option>, } let full = crate::parsing::require_paren_delim_for_meta_list(meta)?; @@ -60,7 +61,10 @@ impl ItemParamsParsing<'_> { bail!(meta, "expected at least one parameter in parentheses"); } - let docs = full.docs.as_ref().map(super::parse_docs).transpose()?; + let docs = full + .docs + .map(|docs| super::parse_docs(&docs.value)) + .transpose()?; let params = ItemParams { name: full.name, diff --git a/bon-macros/src/parsing/mod.rs b/bon-macros/src/parsing/mod.rs index 0cf5f56f..e4a92f4b 100644 --- a/bon-macros/src/parsing/mod.rs +++ b/bon-macros/src/parsing/mod.rs @@ -1,10 +1,12 @@ mod docs; mod item_params; mod spanned_key; +mod simple_closure; pub(crate) use docs::*; pub(crate) use item_params::*; pub(crate) use spanned_key::*; +pub(crate) use simple_closure::*; use crate::util::prelude::*; use darling::FromMeta; diff --git a/bon-macros/src/parsing/simple_closure.rs b/bon-macros/src/parsing/simple_closure.rs new file mode 100644 index 00000000..0b19f1ac --- /dev/null +++ b/bon-macros/src/parsing/simple_closure.rs @@ -0,0 +1,92 @@ +use crate::util::prelude::*; +use darling::FromMeta; +use syn::spanned::Spanned; + +#[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_syntax("attribute", &closure.attrs.first())?; + + 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(), + }) + } +} + +fn reject_syntax(name: &'static str, syntax: &Option) -> Result { + if let Some(syntax) = syntax { + bail!(syntax, "{name} is not allowed here") + } + + Ok(()) +} + +impl SimpleClosureInput { + fn from_pat_ident(pat: syn::PatIdent) -> Result { + reject_syntax("attribute", &pat.attrs.first())?; + reject_syntax("`ref` keyword", &pat.by_ref)?; + Ok(Self { pat, ty: None }) + } + + fn from_pat_type(input: syn::PatType) -> Result { + reject_syntax("attribute", &input.attrs.first())?; + + 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), + }) + } +} diff --git a/bon-macros/src/parsing/spanned_key.rs b/bon-macros/src/parsing/spanned_key.rs index 69ee72c0..f90fc822 100644 --- a/bon-macros/src/parsing/spanned_key.rs +++ b/bon-macros/src/parsing/spanned_key.rs @@ -6,28 +6,29 @@ use std::ops::Deref; /// 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::Path, + pub(crate) key: syn::Ident, pub(crate) value: T, } impl SpannedKey { - pub(crate) fn new(path: &syn::Path, value: T) -> Self { - Self { - key: path.clone(), + pub(crate) fn new(path: &syn::Path, value: T) -> Result { + Ok(Self { + key: path.require_ident()?.clone(), value, - } + }) } - pub(crate) fn key_to_string(&self) -> String { - darling::util::path_to_string(&self.key) + + pub(crate) fn into_value(self) -> T { + self.value } } impl FromMeta for SpannedKey { fn from_meta(meta: &syn::Meta) -> Result { - let key = meta.path().clone(); let value = T::from_meta(meta)?; - Ok(Self { key, value }) + Self::new(meta.path(), value) } } diff --git a/bon-macros/src/util/path.rs b/bon-macros/src/util/path.rs index db4dc282..fc2c75b4 100644 --- a/bon-macros/src/util/path.rs +++ b/bon-macros/src/util/path.rs @@ -1,6 +1,9 @@ pub(crate) trait PathExt { /// Check if the path starts with the given segment. fn starts_with_segment(&self, desired_segment: &str) -> bool; + + /// Check if the path ends with the given segment. + fn ends_with_segment(&self, desired_segment: &str) -> bool; } impl PathExt for syn::Path { @@ -10,4 +13,11 @@ impl PathExt for syn::Path { .map(|first| first.ident == desired_segment) .unwrap_or(false) } + + fn ends_with_segment(&self, desired_segment: &str) -> bool { + self.segments + .last() + .map(|last| last.ident == desired_segment) + .unwrap_or(false) + } } diff --git a/bon-macros/src/util/ty/mod.rs b/bon-macros/src/util/ty/mod.rs index 772b643b..262aa607 100644 --- a/bon-macros/src/util/ty/mod.rs +++ b/bon-macros/src/util/ty/mod.rs @@ -1,24 +1,27 @@ mod 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>; - /// Returns the last identifier of the path if this type is a simple path - fn last_path_segment_ident(&self) -> Option<&syn::Ident>; - - /// Returns `true` if the given type is p [`syn::Type::Path`] and its - /// final segment is equal to `needle` identifier. - fn is_last_segment(&self, needle: &str) -> bool; - - /// Detects if the type is `desired_type` and returns its generic type parameter - fn type_param(&self, desired_type: &str) -> Option<&syn::Type>; + /// 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 `desired_last_segment`, and returns its + /// angle-bracketed arguments + fn as_generic_angle_bracketed( + &self, + desired_last_segment: &str, + ) -> Option<&Punctuated>; + /// Heuristically detects if the type is [`Option`] fn is_option(&self) -> bool; @@ -42,41 +45,21 @@ impl TypeExt for syn::Type { } } - fn last_path_segment_ident(&self) -> Option<&syn::Ident> { - Some(&self.as_path()?.path.segments.last()?.ident) - } - - fn is_last_segment(&self, needle: &str) -> bool { - let path = match self.as_path() { - Some(path) => path, - _ => return false, - }; - - let last_segment = &path - .path - .segments - .last() - .expect("BUG: empty path is not possible") - .ident; - - last_segment == needle - } - - fn type_param(&self, desired_type: &str) -> Option<&syn::Type> { + fn as_path_no_qself(&self) -> Option<&syn::Path> { let path = self.as_path()?; + if path.qself.is_some() { + return None; + } + Some(&path.path) + } - let segment = path - .path - .segments - .iter() - .find(|&segment| segment.ident == desired_type)?; - - let args = match &segment.arguments { - syn::PathArguments::AngleBracketed(args) => args, - _ => return None, - }; + fn option_type_param(&self) -> Option<&syn::Type> { + let args = self.as_generic_angle_bracketed("Option")?; + if args.len() != 1 { + return None; + } - let arg = args.args.first()?; + let arg = args.first()?; let arg = match arg { syn::GenericArgument::Type(arg) => arg, @@ -86,12 +69,28 @@ impl TypeExt for syn::Type { Some(arg) } - fn option_type_param(&self) -> Option<&syn::Type> { - self.type_param("Option") + fn as_generic_angle_bracketed( + &self, + desired_last_segment: &str, + ) -> Option<&Punctuated> { + let path = self.as_path_no_qself()?; + + let last_segment = path.segments.last()?; + + if last_segment.ident != desired_last_segment { + return None; + } + + match &last_segment.arguments { + syn::PathArguments::AngleBracketed(args) => Some(&args.args), + _ => None, + } } fn is_option(&self) -> bool { - self.is_last_segment("Option") + self.as_path_no_qself() + .map(|path| path.ends_with_segment("Option")) + .unwrap_or(false) } fn peel(&self) -> &Self { diff --git a/bon/tests/integration/builder/attr_with.rs b/bon/tests/integration/builder/attr_with.rs index 441b6398..7452afcf 100644 --- a/bon/tests/integration/builder/attr_with.rs +++ b/bon/tests/integration/builder/attr_with.rs @@ -7,13 +7,13 @@ mod single_arg { #[derive(Debug, Builder)] #[allow(dead_code)] struct Sut { - #[builder(with = |x: u32| x + y)] + #[builder(with = |x: u32| x + 1)] required: u32, - #[builder(with = |x: u32| 2 * x)] + #[builder(with = |x: u32| Some(2 * x))] optional: Option, - #[builder(with = |x: u32| x + y, default)] + #[builder(with = |x: u32| Some(x + 1), default)] default: u32, #[builder(with = |value: &T| value.clone())] @@ -23,15 +23,15 @@ mod single_arg { impl_trait: IpAddr, } - assert_debug_eq( - Sut::builder() - .required(1) - .optional(2) - .default(3) - .generic("hello") - .impl_trait([127, 0, 0, 1]) - .build(), - expect![], - ) + // assert_debug_eq( + // Sut::builder() + // .required(1) + // .optional(2) + // .default(3) + // .generic("hello") + // .impl_trait([127, 0, 0, 1]) + // .build(), + // expect![], + // ); } } diff --git a/bon/tests/integration/ui/compile_fail/attr_with.rs b/bon/tests/integration/ui/compile_fail/attr_with.rs index 17f5f8e7..bc22c0b4 100644 --- a/bon/tests/integration/ui/compile_fail/attr_with.rs +++ b/bon/tests/integration/ui/compile_fail/attr_with.rs @@ -6,4 +6,40 @@ struct ConflictingInto { value: u32, } +#[derive(Builder)] +struct RejectForSyntax { + #[builder(with = for<'a> |x: &'a u32| -> u32 { x + 1 })] + value: u32, +} + +#[derive(Builder)] +struct RejectConstSyntax { + #[builder(with = const || 1)] + value: u32, +} + +#[derive(Builder)] +struct RejectStaticSyntax { + #[builder(with = static || 1)] + value: u32, +} + +#[derive(Builder)] +struct RejectAsyncSyntax { + #[builder(with = async || 1)] + value: u32, +} + +#[derive(Builder)] +struct RejectMoveSyntax { + #[builder(with = move || 1)] + value: u32, +} + +#[derive(Builder)] +struct UnexpectedReturnTypeShape { + #[builder(with = |x: u32| -> u32 { x + 1 })] + value: u32, +} + fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_with.stderr b/bon/tests/integration/ui/compile_fail/attr_with.stderr index f37da432..cac7f0fd 100644 --- a/bon/tests/integration/ui/compile_fail/attr_with.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_with.stderr @@ -3,3 +3,45 @@ error: `with` attribute can't be specified together with `into` | 5 | #[builder(into, with = |x: u32| x + 1)] | ^^^^ + +error: `for<...>` syntax is not allowed here + --> tests/integration/ui/compile_fail/attr_with.rs:11:22 + | +11 | #[builder(with = for<'a> |x: &'a u32| -> u32 { x + 1 })] + | ^^^ + +error: `const` keyword is not allowed here + --> tests/integration/ui/compile_fail/attr_with.rs:17:22 + | +17 | #[builder(with = const || 1)] + | ^^^^^ + +error: `static` keyword is not allowed here + --> tests/integration/ui/compile_fail/attr_with.rs:23:22 + | +23 | #[builder(with = static || 1)] + | ^^^^^^ + +error: `async` keyword is not allowed here + --> tests/integration/ui/compile_fail/attr_with.rs:29:22 + | +29 | #[builder(with = async || 1)] + | ^^^^^ + +error: `move` keyword is not allowed here + --> tests/integration/ui/compile_fail/attr_with.rs:35:22 + | +35 | #[builder(with = move || 1)] + | ^^^^ + +error: expected one of the following syntaxes: + (1) no return type annotation; + (2) `-> Result<_, {ErrorType}>` or `-> Result<_>` return type annotation; + + in the case (1), the closure is expected to return a value of the same type as the member's type; + + in the case (2), the closure is expected to return a `Result` where the `Ok` variant is of the same type as the member's type; the `_` placeholder must be spelled literally to mark the type of the member; an optional second generic parameter for the error type is allowed + --> tests/integration/ui/compile_fail/attr_with.rs:41:34 + | +41 | #[builder(with = |x: u32| -> u32 { x + 1 })] + | ^^^ From ce7d01b275bc0605c002db8303d245a54d82cb68 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 3 Oct 2024 18:23:07 +0000 Subject: [PATCH 046/119] More fixes for `with` --- .../src/builder/builder_gen/member/mod.rs | 2 +- .../builder/builder_gen/member/params/mod.rs | 18 ++- bon-macros/src/builder/builder_gen/mod.rs | 4 +- .../src/builder/builder_gen/setter_methods.rs | 135 +++++++++--------- bon-macros/src/util/ty/mod.rs | 29 ++-- bon/tests/integration/builder/attr_with.rs | 11 +- .../integration/ui/compile_fail/attr_with.rs | 14 ++ .../ui/compile_fail/attr_with.stderr | 34 +++++ 8 files changed, 163 insertions(+), 84 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 4c52536a..34048cf0 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -6,11 +6,11 @@ pub(crate) use named::*; pub(crate) use params::SetterClosure; use super::builder_params::OnParams; +use crate::parsing::SpannedKey; use crate::util::prelude::*; use darling::FromAttributes; use params::MemberParams; use std::fmt; -use crate::parsing::SpannedKey; #[derive(Debug, Clone, Copy)] pub(crate) enum MemberOrigin { diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index ac2a81dc..e60c8599 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -287,13 +287,15 @@ impl FromMeta for SetterClosure { ) }; - let args = ty.as_generic_angle_bracketed("Result").ok_or_else(err)?; + let ty = ty + .as_generic_angle_bracketed_path("Result") + .ok_or_else(err)?; - if args.len() != 1 && args.len() != 2 { + if ty.args.len() != 1 && ty.args.len() != 2 { return Err(err()); } - let mut args = args.into_iter(); + 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(_))) { @@ -308,8 +310,16 @@ impl FromMeta for SetterClosure { }) .transpose()?; + let mut result_path = ty.path.clone(); + + result_path + .segments + .last_mut() + .expect("BUG: segments can't be empty") + .arguments = syn::PathArguments::None; + Some(SetterClosureOutput { - result_path: syn::parse_quote!(Result), + result_path, err_ty, }) } diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 81b4ca5b..0b529541 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -15,7 +15,7 @@ use member::{ use models::{ AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, }; -use setter_methods::MemberSettersCtx; +use setter_methods::SettersCtx; pub(crate) struct MacroOutput { pub(crate) start_fn: syn::ItemFn, @@ -100,7 +100,7 @@ impl BuilderGenCtx { let transition_type_state_fn = self.transition_type_state_fn(); let setter_methods = self .named_members() - .map(|member| MemberSettersCtx::new(self, member).setter_methods()); + .map(|member| SettersCtx::new(self, member).setter_methods()); let generics_decl = &self.generics.decl_without_defaults; let generic_args = &self.generics.args; diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index 67e2ed4c..e67a3b9c 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -2,12 +2,12 @@ use super::member::SetterClosure; use super::{BuilderGenCtx, NamedMember}; use crate::util::prelude::*; -pub(crate) struct MemberSettersCtx<'a> { +pub(crate) struct SettersCtx<'a> { builder_gen: &'a BuilderGenCtx, member: &'a NamedMember, } -impl<'a> MemberSettersCtx<'a> { +impl<'a> SettersCtx<'a> { pub(crate) fn new(builder_gen: &'a BuilderGenCtx, member: &'a NamedMember) -> Self { Self { builder_gen, @@ -28,25 +28,25 @@ impl<'a> MemberSettersCtx<'a> { let member_init; if let Some(closure) = &self.member.params.with { - fn_inputs = Self::inputs_override(closure); + fn_inputs = Self::inputs_from_closure(closure); + + let member_init_override = self.member_init_from_closure(closure); - let member_init_override = self.member_init_override(closure); member_init = quote!(Some(#member_init_override)); } else { let member_type = self.member.norm_ty.as_ref(); let has_into = self.member.params.into.is_present(); - let (fn_param_type, maybe_into_call) = if has_into { - (quote!(impl Into<#member_type>), quote!(.into())) + if has_into { + fn_inputs = quote!(value: impl Into<#member_type>); + member_init = quote!(Some(::core::convert::Into::into(value))); } else { - (quote!(#member_type), quote!()) - }; - - fn_inputs = quote!(value: #fn_param_type); - member_init = quote!(Some(value #maybe_into_call)); + fn_inputs = quote!(value: #member_type); + member_init = quote!(Some(value)); + } } - self.setter_method(MemberSetterMethod { + self.setter_method(Setter { method_name: self.member.public_ident().clone(), fn_inputs, overwrite_docs: None, @@ -71,51 +71,42 @@ impl<'a> MemberSettersCtx<'a> { let option_fn_member_init; if let Some(closure) = &self.member.params.with { - some_fn_inputs = Self::inputs_override(closure); + some_fn_inputs = Self::inputs_from_closure(closure); // If the closure accepts just a single input avoid wrapping it // in a tuple in the `option_fn` setter. - if let [input] = closure.inputs.as_slice() { - let ident = &input.pat.ident; - some_fn_body = quote! { - self.#option_fn_name(Some(#ident)) - }; + let maybe_wrap_in_tuple = |val: TokenStream| -> TokenStream { + if closure.inputs.len() == 1 { + val + } else { + quote!((#val)) + } + }; - let ty = &input.ty; - option_fn_inputs = quote!(value: Option<#ty>); - option_fn_member_init = { - let init = self.member_init_override(closure); - let question_mark = closure.output.is_some().then(|| quote!(?)); + let idents = closure.inputs.iter().map(|input| &input.pat.ident); + let idents = maybe_wrap_in_tuple(quote!( #( #idents ),* )); - quote! { - match value { - Some(#ident) => #init #question_mark, - None => None, - } - } - }; - } else { - let idents = closure.inputs.iter().map(|input| &input.pat.ident); - some_fn_body = { - let idents = idents.clone(); - quote! { - self.#option_fn_name(Some(( #(#idents,)* ))) - } - }; + some_fn_body = { + quote! { + self.#option_fn_name(Some(#idents)) + } + }; - option_fn_inputs = quote!(Option<( #some_fn_inputs )>); - option_fn_member_init = { - let init = self.member_init_override(closure); - let question_mark = closure.output.is_some().then(|| quote!(?)); + option_fn_inputs = { + let inputs = closure.inputs.iter().map(|input| &input.ty); + let inputs = maybe_wrap_in_tuple(quote!(#( #inputs, )*)); + quote!(value: Option<#inputs>) + }; - quote! { - match value { - Some(( #(#idents,)* )) => #init #question_mark, - None => None, - } + option_fn_member_init = { + let init = self.member_init_from_closure(closure); + quote! { + match value { + Some(#idents) => #init, + None => None, } } - }; + } } else { let underlying_ty = self.member.underlying_norm_ty(); let has_into = self.member.params.into.is_present(); @@ -143,7 +134,7 @@ impl<'a> MemberSettersCtx<'a> { } let methods = [ - MemberSetterMethod { + Setter { method_name: option_fn_name, fn_inputs: option_fn_inputs, overwrite_docs: Some(format!( @@ -161,7 +152,7 @@ impl<'a> MemberSettersCtx<'a> { // 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. - MemberSetterMethod { + Setter { method_name: member_name, fn_inputs: some_fn_inputs, overwrite_docs: None, @@ -175,7 +166,7 @@ impl<'a> MemberSettersCtx<'a> { .collect() } - fn inputs_override(closure: &SetterClosure) -> TokenStream { + fn inputs_from_closure(closure: &SetterClosure) -> TokenStream { let pats = closure.inputs.iter().map(|input| &input.pat); let types = closure.inputs.iter().map(|input| &input.ty); quote! { @@ -183,22 +174,36 @@ impl<'a> MemberSettersCtx<'a> { } } - fn member_init_override(&self, closure: &SetterClosure) -> TokenStream { + fn member_init_from_closure(&self, closure: &SetterClosure) -> TokenStream { let body = &closure.body; - let output = Self::result_output_override(closure, || quote!(_)).into_iter(); - // let mut ty = self.member.underlying_norm_ty().to_token_stream(); + let mut ty = self.member.underlying_norm_ty().to_token_stream(); + + if !self.member.is_required() { + ty = quote!(Option<#ty>); + }; + + let output = + Self::result_output_from_closure(closure, || &ty).unwrap_or_else(|| ty.to_token_stream()); + + // 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 }) + }; - // if !self.member.is_required() { - // ty = quote!(Option<#ty>); - // }; + let question_mark = closure + .output + .is_some() + .then(|| syn::Token![?](Span::call_site())); quote! { - (move || #( -> #output )* #body)() + (move || -> #output #body)() #question_mark } } - fn result_output_override( + fn result_output_from_closure( closure: &SetterClosure, default_ty: impl FnOnce() -> T, ) -> Option { @@ -211,8 +216,8 @@ impl<'a> MemberSettersCtx<'a> { }) } - fn setter_method(&self, method: MemberSetterMethod) -> TokenStream { - let MemberSetterMethod { + fn setter_method(&self, method: Setter) -> TokenStream { + let Setter { method_name, fn_inputs, overwrite_docs, @@ -232,7 +237,7 @@ impl<'a> MemberSettersCtx<'a> { let mut state_transition_call = if self.member.is_stateful() { quote! { - .__private_transition_type_state() + Self::__private_transition_type_state(self) } } else { quote! {} @@ -249,7 +254,7 @@ impl<'a> MemberSettersCtx<'a> { quote! { self.__private_named_members.#index = #member_init; - self #state_transition_call + #state_transition_call } } }; @@ -281,7 +286,7 @@ impl<'a> MemberSettersCtx<'a> { }; if let Some(closure) = &self.member.params.with { - if let Some(overridden) = Self::result_output_override(closure, || &return_type) { + if let Some(overridden) = Self::result_output_from_closure(closure, || &return_type) { return_type = overridden; } } @@ -356,7 +361,7 @@ enum SetterBody { Default { member_init: TokenStream }, } -struct MemberSetterMethod { +struct Setter { method_name: syn::Ident, fn_inputs: TokenStream, overwrite_docs: Option, diff --git a/bon-macros/src/util/ty/mod.rs b/bon-macros/src/util/ty/mod.rs index 262aa607..bf2ff8f0 100644 --- a/bon-macros/src/util/ty/mod.rs +++ b/bon-macros/src/util/ty/mod.rs @@ -17,10 +17,10 @@ pub(crate) trait TypeExt { /// Validates that this type is a generic type (path without [`syn::QSelf`]) /// which ends with the given `desired_last_segment`, and returns its /// angle-bracketed arguments - fn as_generic_angle_bracketed( + fn as_generic_angle_bracketed_path( &self, desired_last_segment: &str, - ) -> Option<&Punctuated>; + ) -> Option>; /// Heuristically detects if the type is [`Option`] fn is_option(&self) -> bool; @@ -54,12 +54,12 @@ impl TypeExt for syn::Type { } fn option_type_param(&self) -> Option<&syn::Type> { - let args = self.as_generic_angle_bracketed("Option")?; - if args.len() != 1 { + let ty = self.as_generic_angle_bracketed_path("Option")?; + if ty.args.len() != 1 { return None; } - let arg = args.first()?; + let arg = ty.args.first()?; let arg = match arg { syn::GenericArgument::Type(arg) => arg, @@ -69,10 +69,10 @@ impl TypeExt for syn::Type { Some(arg) } - fn as_generic_angle_bracketed( + fn as_generic_angle_bracketed_path( &self, desired_last_segment: &str, - ) -> Option<&Punctuated> { + ) -> Option> { let path = self.as_path_no_qself()?; let last_segment = path.segments.last()?; @@ -81,10 +81,12 @@ impl TypeExt for syn::Type { return None; } - match &last_segment.arguments { - syn::PathArguments::AngleBracketed(args) => Some(&args.args), - _ => None, - } + let args = match &last_segment.arguments { + syn::PathArguments::AngleBracketed(args) => &args.args, + _ => return None, + }; + + Some(GenericAngleBracketedPath { path, args }) } fn is_option(&self) -> bool { @@ -105,3 +107,8 @@ impl TypeExt for syn::Type { match_types::match_types(self, pattern) } } + +pub(crate) struct GenericAngleBracketedPath<'a> { + pub(crate) path: &'a syn::Path, + pub(crate) args: &'a Punctuated, +} diff --git a/bon/tests/integration/builder/attr_with.rs b/bon/tests/integration/builder/attr_with.rs index 7452afcf..806e88a3 100644 --- a/bon/tests/integration/builder/attr_with.rs +++ b/bon/tests/integration/builder/attr_with.rs @@ -19,10 +19,19 @@ mod single_arg { #[builder(with = |value: &T| value.clone())] generic: T, - #[builder(with = |value: impl Into| value)] + #[builder(with = |value: impl Into| value.into())] impl_trait: IpAddr, + + #[builder(with = |value: &str| -> ::core::result::Result<_, core::num::ParseIntError> { + value.parse() + })] + try_required: u32, } + let foo = |value: &str| -> ::core::result::Result<_, core::num::ParseIntError> { + Ok(value.parse::()? + 1) + }; + // assert_debug_eq( // Sut::builder() // .required(1) diff --git a/bon/tests/integration/ui/compile_fail/attr_with.rs b/bon/tests/integration/ui/compile_fail/attr_with.rs index bc22c0b4..ebae733b 100644 --- a/bon/tests/integration/ui/compile_fail/attr_with.rs +++ b/bon/tests/integration/ui/compile_fail/attr_with.rs @@ -42,4 +42,18 @@ struct UnexpectedReturnTypeShape { value: u32, } +#[derive(Builder)] +struct InvalidReturnTypeInResultClosure { + #[builder(with = |value: &str| -> ::core::result::Result<_, core::num::ParseIntError> { + Ok(value) + })] + value: u32, +} + +#[derive(Builder)] +struct InvalidReturnTypeInImplTraitClosure { + #[builder(with = |value: impl Into<::core::net::IpAddr>| value)] + value: u32, +} + fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_with.stderr b/bon/tests/integration/ui/compile_fail/attr_with.stderr index cac7f0fd..a7bc946c 100644 --- a/bon/tests/integration/ui/compile_fail/attr_with.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_with.stderr @@ -45,3 +45,37 @@ error: expected one of the following syntaxes: | 41 | #[builder(with = |x: u32| -> u32 { x + 1 })] | ^^^ + +error[E0308]: mismatched types + --> tests/integration/ui/compile_fail/attr_with.rs:48:12 + | +48 | Ok(value) + | -- ^^^^^ expected `u32`, found `&str` + | | + | arguments to this enum variant are incorrect + | +help: the type constructed contains `&str` due to the type of the argument passed + --> tests/integration/ui/compile_fail/attr_with.rs:48:9 + | +48 | Ok(value) + | ^^^-----^ + | | + | this argument influences the type of `Ok` +note: tuple variant defined here + --> $RUST/core/src/result.rs + | + | Ok(#[stable(feature = "rust1", since = "1.0.0")] T), + | ^^ + +error[E0308]: mismatched types + --> tests/integration/ui/compile_fail/attr_with.rs:55:62 + | +55 | #[builder(with = |value: impl Into<::core::net::IpAddr>| value)] + | ------------------------------ ^^^^^ expected `u32`, found type parameter `impl Into<::core::net::IpAddr>` + | | + | found this type parameter +56 | value: u32, + | --- expected `u32` because of return type + | + = note: expected type `u32` + found type parameter `impl Into<::core::net::IpAddr>` From 768c7b3116a84088259c62f320efa5504892a7e7 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 3 Oct 2024 18:23:15 +0000 Subject: [PATCH 047/119] cargo fmt --- .../src/builder/builder_gen/input_struct.rs | 19 +++++++++++-------- .../src/builder/builder_gen/setter_methods.rs | 4 ++-- bon-macros/src/parsing/mod.rs | 4 ++-- bon/tests/integration/builder/mod.rs | 4 ++-- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index a42e379c..d3060fec 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -178,14 +178,16 @@ impl StructInputCtx { }), }; - 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.norm_struct.ident - ); + 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.norm_struct.ident + ); - vec![syn::parse_quote!(#[doc = #docs])] - }); + vec![syn::parse_quote!(#[doc = #docs])] + }); let start_fn = StartFnParams { ident: start_fn_ident, @@ -209,7 +211,8 @@ impl StructInputCtx { let builder_type = { let ItemParams { name, vis, docs } = self.params.base.builder_type; - let builder_ident = name.map(SpannedKey::into_value) + let builder_ident = name + .map(SpannedKey::into_value) .unwrap_or_else(|| format_ident!("{}Builder", self.norm_struct.ident.raw_name())); BuilderTypeParams { diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index e67a3b9c..c806c553 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -183,8 +183,8 @@ impl<'a> SettersCtx<'a> { ty = quote!(Option<#ty>); }; - let output = - Self::result_output_from_closure(closure, || &ty).unwrap_or_else(|| ty.to_token_stream()); + let output = Self::result_output_from_closure(closure, || &ty) + .unwrap_or_else(|| ty.to_token_stream()); // Avoid wrapping the body in a block if it's already a block. let body = if matches!(body.as_ref(), syn::Expr::Block(_)) { diff --git a/bon-macros/src/parsing/mod.rs b/bon-macros/src/parsing/mod.rs index e4a92f4b..a45dacb7 100644 --- a/bon-macros/src/parsing/mod.rs +++ b/bon-macros/src/parsing/mod.rs @@ -1,12 +1,12 @@ mod docs; mod item_params; -mod spanned_key; mod simple_closure; +mod spanned_key; pub(crate) use docs::*; pub(crate) use item_params::*; -pub(crate) use spanned_key::*; pub(crate) use simple_closure::*; +pub(crate) use spanned_key::*; use crate::util::prelude::*; use darling::FromMeta; diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index 3db56e4e..3adc9a34 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -1,10 +1,10 @@ mod attr_default; mod attr_expose_positional_fn; mod attr_into; -mod attr_with; mod attr_on; -mod attr_transparent; mod attr_skip; +mod attr_transparent; +mod attr_with; mod builder_derives; mod cfgs; mod generics; From 06c2f8b682ff6c57de43965d731f98085af8c600 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 3 Oct 2024 19:54:11 +0000 Subject: [PATCH 048/119] More tests and fixes for `with` --- .../builder/builder_gen/member/params/mod.rs | 110 +---- .../member/params/setter_closure.rs | 116 +++++ .../member/params/{setter.rs => setters.rs} | 0 .../src/builder/builder_gen/setter_methods.rs | 33 +- bon-macros/src/util/ty/mod.rs | 13 +- .../integration/builder/attr_transparent.rs | 2 +- bon/tests/integration/builder/attr_with.rs | 450 +++++++++++++++++- .../ui/compile_fail/attr_with.stderr | 21 +- 8 files changed, 594 insertions(+), 151 deletions(-) create mode 100644 bon-macros/src/builder/builder_gen/member/params/setter_closure.rs rename bon-macros/src/builder/builder_gen/member/params/{setter.rs => setters.rs} (100%) diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index e60c8599..42800948 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -1,13 +1,14 @@ mod blanket; -mod setter; +mod setters; +mod setter_closure; pub(crate) use blanket::*; -pub(crate) use setter::*; +pub(crate) use setters::*; +pub(crate) use setter_closure::*; use super::MemberOrigin; -use crate::parsing::{SimpleClosure, SpannedKey}; +use crate::parsing::SpannedKey; use crate::util::prelude::*; -use darling::FromMeta; use std::fmt; #[derive(Debug, darling::FromAttributes)] @@ -231,104 +232,3 @@ fn parse_optional_expr(meta: &syn::Meta) -> Result> syn::Meta::NameValue(meta) => SpannedKey::new(&meta.path, Some(meta.value.clone())), } } - -#[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, - "expected one of the following syntaxes:\n\ - (1) no return type annotation;\n\ - (2) `-> Result<_, {{ErrorType}}>` or `-> Result<_>` return type annotation;\n\n\ - in the case (1), the closure is expected to return a value \ - of the same type as the member's type;\n\n\ - in the case (2), the closure is expected to return a `Result` \ - where the `Ok` variant is of the same type as the member's type; \ - the `_` placeholder must be spelled literally to mark \ - the type of the member; an optional second generic parameter \ - for the error type is allowed" - ) - }; - - let ty = ty - .as_generic_angle_bracketed_path("Result") - .ok_or_else(err)?; - - if ty.args.len() != 1 && ty.args.len() != 2 { - 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(); - - 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, - }) - } -} diff --git a/bon-macros/src/builder/builder_gen/member/params/setter_closure.rs b/bon-macros/src/builder/builder_gen/member/params/setter_closure.rs new file mode 100644 index 00000000..60d6d0ed --- /dev/null +++ b/bon-macros/src/builder/builder_gen/member/params/setter_closure.rs @@ -0,0 +1,116 @@ +use 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 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 has an `Option` type and no `#[builder(transparent)]` annotation"; + +#[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 + last_segment.to_string().ends_with("Result") + }) + .ok_or_else(err)?; + + if ty.args.len() != 1 && ty.args.len() != 2 { + 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(); + + 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, + }) + } +} diff --git a/bon-macros/src/builder/builder_gen/member/params/setter.rs b/bon-macros/src/builder/builder_gen/member/params/setters.rs similarity index 100% rename from bon-macros/src/builder/builder_gen/member/params/setter.rs rename to bon-macros/src/builder/builder_gen/member/params/setters.rs diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index c806c553..0574a4dd 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -102,7 +102,7 @@ impl<'a> SettersCtx<'a> { let init = self.member_init_from_closure(closure); quote! { match value { - Some(#idents) => #init, + Some(#idents) => Some(#init), None => None, } } @@ -177,11 +177,7 @@ impl<'a> SettersCtx<'a> { fn member_init_from_closure(&self, closure: &SetterClosure) -> TokenStream { let body = &closure.body; - let mut ty = self.member.underlying_norm_ty().to_token_stream(); - - if !self.member.is_required() { - ty = quote!(Option<#ty>); - }; + let ty = self.member.underlying_norm_ty().to_token_stream(); let output = Self::result_output_from_closure(closure, || &ty) .unwrap_or_else(|| ty.to_token_stream()); @@ -235,26 +231,31 @@ impl<'a> SettersCtx<'a> { SetterBody::Default { member_init } => { let index = &self.member.index; - let mut state_transition_call = if self.member.is_stateful() { + let mut output = if self.member.is_stateful() { quote! { Self::__private_transition_type_state(self) } } else { - quote! {} + quote! { + self + } }; - if let Some(closure) = &self.member.params.with { - if let Some(output) = &closure.output { - let result_path = &output.result_path; - state_transition_call = quote! { - #result_path::Ok(#state_transition_call) - }; - } + let result_output = self + .member + .params + .with + .as_ref() + .and_then(|closure| closure.output.as_ref()); + + if let Some(result_output) = result_output { + let result_path = &result_output.result_path; + output = quote!(#result_path::Ok(#output)); } quote! { self.__private_named_members.#index = #member_init; - #state_transition_call + #output } } }; diff --git a/bon-macros/src/util/ty/mod.rs b/bon-macros/src/util/ty/mod.rs index bf2ff8f0..bedcdd9b 100644 --- a/bon-macros/src/util/ty/mod.rs +++ b/bon-macros/src/util/ty/mod.rs @@ -15,11 +15,11 @@ pub(crate) trait TypeExt { 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 `desired_last_segment`, and returns its - /// angle-bracketed arguments + /// which ends with the given last segment that passes the predicate + /// `is_desired_last_segment`. fn as_generic_angle_bracketed_path( &self, - desired_last_segment: &str, + is_desired_last_segment: impl FnOnce(&syn::Ident) -> bool, ) -> Option>; /// Heuristically detects if the type is [`Option`] @@ -54,7 +54,8 @@ impl TypeExt for syn::Type { } fn option_type_param(&self) -> Option<&syn::Type> { - let ty = self.as_generic_angle_bracketed_path("Option")?; + let ty = self.as_generic_angle_bracketed_path(|last_segment| last_segment == "Option")?; + if ty.args.len() != 1 { return None; } @@ -71,13 +72,13 @@ impl TypeExt for syn::Type { fn as_generic_angle_bracketed_path( &self, - desired_last_segment: &str, + is_desired_last_segment: impl FnOnce(&syn::Ident) -> bool, ) -> Option> { let path = self.as_path_no_qself()?; let last_segment = path.segments.last()?; - if last_segment.ident != desired_last_segment { + if !is_desired_last_segment(&last_segment.ident) { return None; } diff --git a/bon/tests/integration/builder/attr_transparent.rs b/bon/tests/integration/builder/attr_transparent.rs index 0ebc825f..6a28f4ca 100644 --- a/bon/tests/integration/builder/attr_transparent.rs +++ b/bon/tests/integration/builder/attr_transparent.rs @@ -75,7 +75,7 @@ fn test_free_fn() { } #[test] -fn test_assoc_method_method() { +fn test_assoc_method() { struct Sut; #[bon] diff --git a/bon/tests/integration/builder/attr_with.rs b/bon/tests/integration/builder/attr_with.rs index 806e88a3..7dc46634 100644 --- a/bon/tests/integration/builder/attr_with.rs +++ b/bon/tests/integration/builder/attr_with.rs @@ -1,46 +1,458 @@ mod single_arg { use crate::prelude::*; + use core::convert::Infallible; use core::net::IpAddr; + use core::{fmt, num}; + type ParseIntResult = Result; #[test] fn test_struct() { #[derive(Debug, Builder)] + #[builder(derive(Clone))] #[allow(dead_code)] - struct Sut { + struct Sut { #[builder(with = |x: u32| x + 1)] required: u32, - #[builder(with = |x: u32| Some(2 * x))] + #[builder(with = |x: u32| x + 1)] optional: Option, - #[builder(with = |x: u32| Some(x + 1), default)] + #[builder(with = |x: u32| x + 1, default)] default: u32, #[builder(with = |value: &T| value.clone())] generic: T, + #[builder(with = |value: &T| value.clone())] + optional_generic: Option, + + #[builder(with = |value: &T| value.clone(), default,)] + default_generic: T, + #[builder(with = |value: impl Into| value.into())] impl_trait: IpAddr, - #[builder(with = |value: &str| -> ::core::result::Result<_, core::num::ParseIntError> { - value.parse() + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_required: u32, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_optional: Option, + + #[builder(with = |value: &str| -> ParseIntResult<_> { value.parse() }, default)] + try_default: u32, + + #[builder(with = |value: &T| -> Result<_, Infallible> { + Ok(value.clone()) })] + try_optional_generic: Option, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] + try_default_generic: T, + + #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] + try_impl_trait: IpAddr, + } + + let builder = Sut::builder() + .required(1) + .optional(2) + .default(3) + .generic(&"hello"); + + let _ignore = builder.clone().optional_generic(&"hello you"); + let builder = builder.maybe_optional_generic(Some(&"littlepip")); + + let _ignore = builder.clone().default_generic(&"blackjack"); + let builder = builder.maybe_default_generic(Some(&"<3")); + + let builder = builder + .impl_trait([127, 0, 0, 1]) + .try_required("4") + .unwrap(); + + let _ignore = builder.clone().try_optional("5").unwrap(); + let builder = builder.maybe_try_optional(Some("6")).unwrap(); + + let _ignore = builder.clone().try_default("7").unwrap(); + let builder = builder.maybe_try_default(Some("8")).unwrap(); + + let _ignore = builder.clone().try_optional_generic(&"9").unwrap(); + let builder = builder.maybe_try_optional_generic(Some(&"10")).unwrap(); + + let _ignore = builder.clone().try_default_generic(&"11").unwrap(); + let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); + + let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); + + assert_debug_eq( + builder.build(), + expect![[r#" + Sut { + required: 2, + optional: Some( + 3, + ), + default: 4, + generic: "hello", + optional_generic: Some( + "littlepip", + ), + default_generic: "<3", + impl_trait: 127.0.0.1, + try_required: 4, + try_optional: Some( + 6, + ), + try_default: 8, + try_optional_generic: Some( + "10", + ), + try_default_generic: "12", + try_impl_trait: 127.0.0.1, + }"#]], + ); + } + + #[test] + fn test_free_fn() { + #[builder(derive(Clone))] + fn sut( + #[builder(with = |x: u32| x + 1)] required: u32, + #[builder(with = |x: u32| x + 1)] optional: Option, + #[builder(with = |x: u32| x + 1, default)] default: u32, + #[builder(with = |value: &T| value.clone())] generic: T, + #[builder(with = |value: &T| value.clone())] optional_generic: Option, + #[builder(with = |value: &T| value.clone(), default)] default_generic: T, + #[builder(with = |value: impl Into| value.into())] impl_trait: IpAddr, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] try_required: u32, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_optional: Option, + + #[builder(with = |value: &str| -> ParseIntResult<_> { value.parse() }, default)] + try_default: u32, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) })] + try_optional_generic: Option, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] + try_default_generic: T, + + #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] + try_impl_trait: IpAddr, + ) -> impl fmt::Debug { + ( + ( + required, + optional, + default, + generic, + optional_generic, + default_generic, + impl_trait, + ), + ( + try_required, + try_optional, + try_default, + try_optional_generic, + try_default_generic, + try_impl_trait, + ), + ) } - let foo = |value: &str| -> ::core::result::Result<_, core::num::ParseIntError> { - Ok(value.parse::()? + 1) - }; - - // assert_debug_eq( - // Sut::builder() - // .required(1) - // .optional(2) - // .default(3) - // .generic("hello") - // .impl_trait([127, 0, 0, 1]) - // .build(), - // expect![], - // ); + let builder = sut().required(1).optional(2).default(3).generic(&"hello"); + + let _ignore = builder.clone().optional_generic(&"hello you"); + let builder = builder.maybe_optional_generic(Some(&"littlepip")); + + let _ignore = builder.clone().default_generic(&"blackjack"); + let builder = builder.maybe_default_generic(Some(&"<3")); + + let builder = builder + .impl_trait([127, 0, 0, 1]) + .try_required("4") + .unwrap(); + + let _ignore = builder.clone().try_optional("5").unwrap(); + let builder = builder.maybe_try_optional(Some("6")).unwrap(); + + let _ignore = builder.clone().try_default("7").unwrap(); + let builder = builder.maybe_try_default(Some("8")).unwrap(); + + let _ignore = builder.clone().try_optional_generic(&"9").unwrap(); + let builder = builder.maybe_try_optional_generic(Some(&"10")).unwrap(); + + let _ignore = builder.clone().try_default_generic(&"11").unwrap(); + let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); + + let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); + + assert_debug_eq( + builder.call(), + expect![[r#" + ( + ( + 2, + Some( + 3, + ), + 4, + "hello", + Some( + "littlepip", + ), + "<3", + 127.0.0.1, + ), + ( + 4, + Some( + 6, + ), + 8, + Some( + "10", + ), + "12", + 127.0.0.1, + ), + )"#]], + ); + } + + #[test] + fn test_assoc_method() { + struct Sut; + + #[bon] + impl Sut { + #[builder(derive(Clone))] + fn sut( + #[builder(with = |x: u32| x + 1)] required: u32, + #[builder(with = |x: u32| x + 1)] optional: Option, + #[builder(with = |x: u32| x + 1, default)] default: u32, + #[builder(with = |value: &T| value.clone())] generic: T, + #[builder(with = |value: &T| value.clone())] optional_generic: Option, + #[builder(with = |value: &T| value.clone(), default)] default_generic: T, + #[builder(with = |value: impl Into| value.into())] impl_trait: IpAddr, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_required: u32, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_optional: Option, + + #[builder(with = |value: &str| -> ParseIntResult<_> { value.parse() }, default)] + try_default: u32, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) })] + try_optional_generic: Option, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] + try_default_generic: T, + + #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] + try_impl_trait: IpAddr, + ) -> impl fmt::Debug { + ( + ( + required, + optional, + default, + generic, + optional_generic, + default_generic, + impl_trait, + ), + ( + try_required, + try_optional, + try_default, + try_optional_generic, + try_default_generic, + try_impl_trait, + ), + ) + } + + #[builder(derive(Clone))] + fn with_self( + &self, + #[builder(with = |x: u32| x + 1)] required: u32, + #[builder(with = |x: u32| x + 1)] optional: Option, + #[builder(with = |x: u32| x + 1, default)] default: u32, + #[builder(with = |value: &T| value.clone())] generic: T, + #[builder(with = |value: &T| value.clone())] optional_generic: Option, + #[builder(with = |value: &T| value.clone(), default)] default_generic: T, + #[builder(with = |value: impl Into| value.into())] impl_trait: IpAddr, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_required: u32, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_optional: Option, + + #[builder(with = |value: &str| -> ParseIntResult<_> { value.parse() }, default)] + try_default: u32, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) })] + try_optional_generic: Option, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] + try_default_generic: T, + + #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] + try_impl_trait: IpAddr, + ) -> impl fmt::Debug { + let _ = self; + ( + ( + required, + optional, + default, + generic, + optional_generic, + default_generic, + impl_trait, + ), + ( + try_required, + try_optional, + try_default, + try_optional_generic, + try_default_generic, + try_impl_trait, + ), + ) + } + } + + let builder = Sut::sut() + .required(1) + .optional(2) + .default(3) + .generic(&"hello"); + + let _ignore = builder.clone().optional_generic(&"hello you"); + let builder = builder.maybe_optional_generic(Some(&"littlepip")); + + let _ignore = builder.clone().default_generic(&"blackjack"); + let builder = builder.maybe_default_generic(Some(&"<3")); + + let builder = builder + .impl_trait([127, 0, 0, 1]) + .try_required("4") + .unwrap(); + + let _ignore = builder.clone().try_optional("5").unwrap(); + let builder = builder.maybe_try_optional(Some("6")).unwrap(); + + let _ignore = builder.clone().try_default("7").unwrap(); + let builder = builder.maybe_try_default(Some("8")).unwrap(); + + let _ignore = builder.clone().try_optional_generic(&"9").unwrap(); + let builder = builder.maybe_try_optional_generic(Some(&"10")).unwrap(); + + let _ignore = builder.clone().try_default_generic(&"11").unwrap(); + let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); + + let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); + + assert_debug_eq( + builder.call(), + expect![[r#" + ( + ( + 2, + Some( + 3, + ), + 4, + "hello", + Some( + "littlepip", + ), + "<3", + 127.0.0.1, + ), + ( + 4, + Some( + 6, + ), + 8, + Some( + "10", + ), + "12", + 127.0.0.1, + ), + )"#]], + ); + + let builder = Sut + .with_self() + .required(1) + .optional(2) + .default(3) + .generic(&"hello"); + + let _ignore = builder.clone().optional_generic(&"hello you"); + let builder = builder.maybe_optional_generic(Some(&"littlepip")); + + let _ignore = builder.clone().default_generic(&"blackjack"); + let builder = builder.maybe_default_generic(Some(&"<3")); + + let builder = builder + .impl_trait([127, 0, 0, 1]) + .try_required("4") + .unwrap(); + + let _ignore = builder.clone().try_optional("5").unwrap(); + let builder = builder.maybe_try_optional(Some("6")).unwrap(); + + let _ignore = builder.clone().try_default("7").unwrap(); + let builder = builder.maybe_try_default(Some("8")).unwrap(); + + let _ignore = builder.clone().try_optional_generic(&"9").unwrap(); + let builder = builder.maybe_try_optional_generic(Some(&"10")).unwrap(); + + let _ignore = builder.clone().try_default_generic(&"11").unwrap(); + let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); + + let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); + + assert_debug_eq(builder.call(), expect![[r#" + ( + ( + 2, + Some( + 3, + ), + 4, + "hello", + Some( + "littlepip", + ), + "<3", + 127.0.0.1, + ), + ( + 4, + Some( + 6, + ), + 8, + Some( + "10", + ), + "12", + 127.0.0.1, + ), + )"#]]); } } diff --git a/bon/tests/integration/ui/compile_fail/attr_with.stderr b/bon/tests/integration/ui/compile_fail/attr_with.stderr index a7bc946c..d82123af 100644 --- a/bon/tests/integration/ui/compile_fail/attr_with.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_with.stderr @@ -34,13 +34,26 @@ error: `move` keyword is not allowed here 35 | #[builder(with = move || 1)] | ^^^^ -error: expected one of the following syntaxes: +error: expected one of the following: + (1) no return type annotation; - (2) `-> Result<_, {ErrorType}>` or `-> Result<_>` 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; - in the case (1), the closure is expected to return a value of the same type as the member's type; + the 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; - in the case (2), the closure is expected to return a `Result` where the `Ok` variant is of the same type as the member's type; the `_` placeholder must be spelled literally to mark the type of the member; an optional second generic parameter for the error type is allowed + *underlying type is the type of the member stripped from the `Option` wrapper + if this member has an `Option` type and no `#[builder(transparent)]` annotation --> tests/integration/ui/compile_fail/attr_with.rs:41:34 | 41 | #[builder(with = |x: u32| -> u32 { x + 1 })] From 197c6747a3cb655004ec2c97c1a760476a5945be Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 3 Oct 2024 20:15:20 +0000 Subject: [PATCH 049/119] More tests --- bon/tests/integration/builder/attr_with.rs | 458 ------------------ .../integration/builder/attr_with/mod.rs | 3 + .../builder/attr_with/multi_arg.rs | 112 +++++ .../builder/attr_with/overwritable.rs | 119 +++++ .../builder/attr_with/single_arg.rs | 454 +++++++++++++++++ 5 files changed, 688 insertions(+), 458 deletions(-) delete mode 100644 bon/tests/integration/builder/attr_with.rs create mode 100644 bon/tests/integration/builder/attr_with/mod.rs create mode 100644 bon/tests/integration/builder/attr_with/multi_arg.rs create mode 100644 bon/tests/integration/builder/attr_with/overwritable.rs create mode 100644 bon/tests/integration/builder/attr_with/single_arg.rs diff --git a/bon/tests/integration/builder/attr_with.rs b/bon/tests/integration/builder/attr_with.rs deleted file mode 100644 index 7dc46634..00000000 --- a/bon/tests/integration/builder/attr_with.rs +++ /dev/null @@ -1,458 +0,0 @@ -mod single_arg { - use crate::prelude::*; - use core::convert::Infallible; - use core::net::IpAddr; - use core::{fmt, num}; - type ParseIntResult = Result; - - #[test] - fn test_struct() { - #[derive(Debug, Builder)] - #[builder(derive(Clone))] - #[allow(dead_code)] - struct Sut { - #[builder(with = |x: u32| x + 1)] - required: u32, - - #[builder(with = |x: u32| x + 1)] - optional: Option, - - #[builder(with = |x: u32| x + 1, default)] - default: u32, - - #[builder(with = |value: &T| value.clone())] - generic: T, - - #[builder(with = |value: &T| value.clone())] - optional_generic: Option, - - #[builder(with = |value: &T| value.clone(), default,)] - default_generic: T, - - #[builder(with = |value: impl Into| value.into())] - impl_trait: IpAddr, - - #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] - try_required: u32, - - #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] - try_optional: Option, - - #[builder(with = |value: &str| -> ParseIntResult<_> { value.parse() }, default)] - try_default: u32, - - #[builder(with = |value: &T| -> Result<_, Infallible> { - Ok(value.clone()) - })] - try_optional_generic: Option, - - #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] - try_default_generic: T, - - #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] - try_impl_trait: IpAddr, - } - - let builder = Sut::builder() - .required(1) - .optional(2) - .default(3) - .generic(&"hello"); - - let _ignore = builder.clone().optional_generic(&"hello you"); - let builder = builder.maybe_optional_generic(Some(&"littlepip")); - - let _ignore = builder.clone().default_generic(&"blackjack"); - let builder = builder.maybe_default_generic(Some(&"<3")); - - let builder = builder - .impl_trait([127, 0, 0, 1]) - .try_required("4") - .unwrap(); - - let _ignore = builder.clone().try_optional("5").unwrap(); - let builder = builder.maybe_try_optional(Some("6")).unwrap(); - - let _ignore = builder.clone().try_default("7").unwrap(); - let builder = builder.maybe_try_default(Some("8")).unwrap(); - - let _ignore = builder.clone().try_optional_generic(&"9").unwrap(); - let builder = builder.maybe_try_optional_generic(Some(&"10")).unwrap(); - - let _ignore = builder.clone().try_default_generic(&"11").unwrap(); - let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); - - let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); - - assert_debug_eq( - builder.build(), - expect![[r#" - Sut { - required: 2, - optional: Some( - 3, - ), - default: 4, - generic: "hello", - optional_generic: Some( - "littlepip", - ), - default_generic: "<3", - impl_trait: 127.0.0.1, - try_required: 4, - try_optional: Some( - 6, - ), - try_default: 8, - try_optional_generic: Some( - "10", - ), - try_default_generic: "12", - try_impl_trait: 127.0.0.1, - }"#]], - ); - } - - #[test] - fn test_free_fn() { - #[builder(derive(Clone))] - fn sut( - #[builder(with = |x: u32| x + 1)] required: u32, - #[builder(with = |x: u32| x + 1)] optional: Option, - #[builder(with = |x: u32| x + 1, default)] default: u32, - #[builder(with = |value: &T| value.clone())] generic: T, - #[builder(with = |value: &T| value.clone())] optional_generic: Option, - #[builder(with = |value: &T| value.clone(), default)] default_generic: T, - #[builder(with = |value: impl Into| value.into())] impl_trait: IpAddr, - - #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] - try_required: u32, - - #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] - try_optional: Option, - - #[builder(with = |value: &str| -> ParseIntResult<_> { value.parse() }, default)] - try_default: u32, - - #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) })] - try_optional_generic: Option, - - #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] - try_default_generic: T, - - #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] - try_impl_trait: IpAddr, - ) -> impl fmt::Debug { - ( - ( - required, - optional, - default, - generic, - optional_generic, - default_generic, - impl_trait, - ), - ( - try_required, - try_optional, - try_default, - try_optional_generic, - try_default_generic, - try_impl_trait, - ), - ) - } - - let builder = sut().required(1).optional(2).default(3).generic(&"hello"); - - let _ignore = builder.clone().optional_generic(&"hello you"); - let builder = builder.maybe_optional_generic(Some(&"littlepip")); - - let _ignore = builder.clone().default_generic(&"blackjack"); - let builder = builder.maybe_default_generic(Some(&"<3")); - - let builder = builder - .impl_trait([127, 0, 0, 1]) - .try_required("4") - .unwrap(); - - let _ignore = builder.clone().try_optional("5").unwrap(); - let builder = builder.maybe_try_optional(Some("6")).unwrap(); - - let _ignore = builder.clone().try_default("7").unwrap(); - let builder = builder.maybe_try_default(Some("8")).unwrap(); - - let _ignore = builder.clone().try_optional_generic(&"9").unwrap(); - let builder = builder.maybe_try_optional_generic(Some(&"10")).unwrap(); - - let _ignore = builder.clone().try_default_generic(&"11").unwrap(); - let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); - - let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); - - assert_debug_eq( - builder.call(), - expect![[r#" - ( - ( - 2, - Some( - 3, - ), - 4, - "hello", - Some( - "littlepip", - ), - "<3", - 127.0.0.1, - ), - ( - 4, - Some( - 6, - ), - 8, - Some( - "10", - ), - "12", - 127.0.0.1, - ), - )"#]], - ); - } - - #[test] - fn test_assoc_method() { - struct Sut; - - #[bon] - impl Sut { - #[builder(derive(Clone))] - fn sut( - #[builder(with = |x: u32| x + 1)] required: u32, - #[builder(with = |x: u32| x + 1)] optional: Option, - #[builder(with = |x: u32| x + 1, default)] default: u32, - #[builder(with = |value: &T| value.clone())] generic: T, - #[builder(with = |value: &T| value.clone())] optional_generic: Option, - #[builder(with = |value: &T| value.clone(), default)] default_generic: T, - #[builder(with = |value: impl Into| value.into())] impl_trait: IpAddr, - - #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] - try_required: u32, - - #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] - try_optional: Option, - - #[builder(with = |value: &str| -> ParseIntResult<_> { value.parse() }, default)] - try_default: u32, - - #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) })] - try_optional_generic: Option, - - #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] - try_default_generic: T, - - #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] - try_impl_trait: IpAddr, - ) -> impl fmt::Debug { - ( - ( - required, - optional, - default, - generic, - optional_generic, - default_generic, - impl_trait, - ), - ( - try_required, - try_optional, - try_default, - try_optional_generic, - try_default_generic, - try_impl_trait, - ), - ) - } - - #[builder(derive(Clone))] - fn with_self( - &self, - #[builder(with = |x: u32| x + 1)] required: u32, - #[builder(with = |x: u32| x + 1)] optional: Option, - #[builder(with = |x: u32| x + 1, default)] default: u32, - #[builder(with = |value: &T| value.clone())] generic: T, - #[builder(with = |value: &T| value.clone())] optional_generic: Option, - #[builder(with = |value: &T| value.clone(), default)] default_generic: T, - #[builder(with = |value: impl Into| value.into())] impl_trait: IpAddr, - - #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] - try_required: u32, - - #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] - try_optional: Option, - - #[builder(with = |value: &str| -> ParseIntResult<_> { value.parse() }, default)] - try_default: u32, - - #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) })] - try_optional_generic: Option, - - #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] - try_default_generic: T, - - #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] - try_impl_trait: IpAddr, - ) -> impl fmt::Debug { - let _ = self; - ( - ( - required, - optional, - default, - generic, - optional_generic, - default_generic, - impl_trait, - ), - ( - try_required, - try_optional, - try_default, - try_optional_generic, - try_default_generic, - try_impl_trait, - ), - ) - } - } - - let builder = Sut::sut() - .required(1) - .optional(2) - .default(3) - .generic(&"hello"); - - let _ignore = builder.clone().optional_generic(&"hello you"); - let builder = builder.maybe_optional_generic(Some(&"littlepip")); - - let _ignore = builder.clone().default_generic(&"blackjack"); - let builder = builder.maybe_default_generic(Some(&"<3")); - - let builder = builder - .impl_trait([127, 0, 0, 1]) - .try_required("4") - .unwrap(); - - let _ignore = builder.clone().try_optional("5").unwrap(); - let builder = builder.maybe_try_optional(Some("6")).unwrap(); - - let _ignore = builder.clone().try_default("7").unwrap(); - let builder = builder.maybe_try_default(Some("8")).unwrap(); - - let _ignore = builder.clone().try_optional_generic(&"9").unwrap(); - let builder = builder.maybe_try_optional_generic(Some(&"10")).unwrap(); - - let _ignore = builder.clone().try_default_generic(&"11").unwrap(); - let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); - - let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); - - assert_debug_eq( - builder.call(), - expect![[r#" - ( - ( - 2, - Some( - 3, - ), - 4, - "hello", - Some( - "littlepip", - ), - "<3", - 127.0.0.1, - ), - ( - 4, - Some( - 6, - ), - 8, - Some( - "10", - ), - "12", - 127.0.0.1, - ), - )"#]], - ); - - let builder = Sut - .with_self() - .required(1) - .optional(2) - .default(3) - .generic(&"hello"); - - let _ignore = builder.clone().optional_generic(&"hello you"); - let builder = builder.maybe_optional_generic(Some(&"littlepip")); - - let _ignore = builder.clone().default_generic(&"blackjack"); - let builder = builder.maybe_default_generic(Some(&"<3")); - - let builder = builder - .impl_trait([127, 0, 0, 1]) - .try_required("4") - .unwrap(); - - let _ignore = builder.clone().try_optional("5").unwrap(); - let builder = builder.maybe_try_optional(Some("6")).unwrap(); - - let _ignore = builder.clone().try_default("7").unwrap(); - let builder = builder.maybe_try_default(Some("8")).unwrap(); - - let _ignore = builder.clone().try_optional_generic(&"9").unwrap(); - let builder = builder.maybe_try_optional_generic(Some(&"10")).unwrap(); - - let _ignore = builder.clone().try_default_generic(&"11").unwrap(); - let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); - - let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); - - assert_debug_eq(builder.call(), expect![[r#" - ( - ( - 2, - Some( - 3, - ), - 4, - "hello", - Some( - "littlepip", - ), - "<3", - 127.0.0.1, - ), - ( - 4, - Some( - 6, - ), - 8, - Some( - "10", - ), - "12", - 127.0.0.1, - ), - )"#]]); - } -} diff --git a/bon/tests/integration/builder/attr_with/mod.rs b/bon/tests/integration/builder/attr_with/mod.rs new file mode 100644 index 00000000..73bf2d07 --- /dev/null +++ b/bon/tests/integration/builder/attr_with/mod.rs @@ -0,0 +1,3 @@ +mod multi_arg; +mod single_arg; +mod overwritable; diff --git a/bon/tests/integration/builder/attr_with/multi_arg.rs b/bon/tests/integration/builder/attr_with/multi_arg.rs new file mode 100644 index 00000000..4d1c07c8 --- /dev/null +++ b/bon/tests/integration/builder/attr_with/multi_arg.rs @@ -0,0 +1,112 @@ +use crate::prelude::*; +use core::convert::Infallible; +use core::net::IpAddr; +use core::{fmt, num}; +type ParseIntResult = Result; + +#[test] +fn test_struct() { + #[derive(Debug, Builder)] + #[builder(derive(Clone))] + #[allow(dead_code)] + struct Sut { + #[builder(with = |x: u32, y: u32| x + y)] + required: u32, + + #[builder(with = |x: u32, y: u32| x + y)] + optional: Option, + + #[builder(with = |x: u32, y: u32| x + y, default)] + default: u32, + + #[builder(with = |value: &T, _value2: &T| value.clone())] + generic: T, + + #[builder(with = |value: &T, _value2: &T| value.clone())] + optional_generic: Option, + + #[builder(with = |value: &T, _value2: &T| value.clone(), default)] + default_generic: T, + + #[builder(with = |value: impl Into| value.into())] + impl_trait: IpAddr, + + #[builder(with = |value: &str, _value2: u32| -> Result<_, num::ParseIntError> { value.parse() })] + try_required: u32, + + #[builder(with = |value: &str, _value2: u32| -> Result<_, num::ParseIntError> { value.parse() })] + try_optional: Option, + + #[builder(with = |value: &str, _value2: u32| -> ParseIntResult<_> { value.parse() }, default)] + try_default: u32, + + #[builder(with = |value: &T, _value2: &T| -> Result<_, Infallible> { Ok(value.clone()) })] + try_optional_generic: Option, + + #[builder(with = |value: &T, _value2: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] + try_default_generic: T, + + #[builder(with = |value: impl Into, _value2: impl fmt::Debug| -> Result<_, Infallible> { Ok(value.into()) })] + try_impl_trait: IpAddr, + } + + let builder = Sut::builder() + .required(1, 2) + .optional(3, 4) + .default(5, 6) + .generic(&"hello", &"world"); + + let _ignore = builder.clone().optional_generic(&"hello", &"you"); + let builder = builder.maybe_optional_generic(Some((&"littlepip", &"blackjack"))); + + let _ignore = builder.clone().default_generic(&"p21", &"rampage"); + let builder = builder.maybe_default_generic(Some((&"<3", &"glory"))); + + let builder = builder + .impl_trait([127, 0, 0, 1]) + .try_required("4", 99) + .unwrap(); + + let _ignore = builder.clone().try_optional("5", 99).unwrap(); + let builder = builder.maybe_try_optional(Some(("6", 99))).unwrap(); + + let _ignore = builder.clone().try_default("7", 99).unwrap(); + let builder = builder.maybe_try_default(Some(("8", 99))).unwrap(); + + let _ignore = builder.clone().try_optional_generic(&"9", &"99").unwrap(); + let builder = builder + .maybe_try_optional_generic(Some((&"10", &"99"))) + .unwrap(); + + let _ignore = builder.clone().try_default_generic(&"11", &"99").unwrap(); + let builder = builder + .maybe_try_default_generic(Some((&"12", &"99"))) + .unwrap(); + + let builder = builder.try_impl_trait([127, 0, 0, 1], true).unwrap(); + + assert_debug_eq(builder.build(), expect![[r#" + Sut { + required: 3, + optional: Some( + 7, + ), + default: 11, + generic: "hello", + optional_generic: Some( + "littlepip", + ), + default_generic: "<3", + impl_trait: 127.0.0.1, + try_required: 4, + try_optional: Some( + 6, + ), + try_default: 8, + try_optional_generic: Some( + "10", + ), + try_default_generic: "12", + try_impl_trait: 127.0.0.1, + }"#]]); +} diff --git a/bon/tests/integration/builder/attr_with/overwritable.rs b/bon/tests/integration/builder/attr_with/overwritable.rs new file mode 100644 index 00000000..79a5fedc --- /dev/null +++ b/bon/tests/integration/builder/attr_with/overwritable.rs @@ -0,0 +1,119 @@ +use crate::prelude::*; +use core::convert::Infallible; +use core::net::IpAddr; +use core::num; +type ParseIntResult = Result; + +#[test] +fn test_struct() { + #[derive(Debug, Builder)] + #[allow(dead_code)] + struct Sut { + #[builder(with = |x: u32| x + 1, overwritable)] + required: u32, + + #[builder(with = |x: u32| x + 1, overwritable)] + optional: Option, + + #[builder(with = |x: u32| x + 1, default, overwritable)] + default: u32, + + #[builder(with = |value: &T| value.clone(), overwritable)] + generic: T, + + #[builder(with = |value: &T| value.clone(), overwritable)] + optional_generic: Option, + + #[builder(with = |value: &T| value.clone(), default, overwritable)] + default_generic: T, + + #[builder(with = |value: impl Into| value.into(), overwritable)] + impl_trait: IpAddr, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() }, overwritable)] + try_required: u32, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() }, overwritable)] + try_optional: Option, + + #[builder(with = |value: &str| -> ParseIntResult<_> { value.parse() }, default, overwritable)] + try_default: u32, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, overwritable)] + try_optional_generic: Option, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default, overwritable)] + try_default_generic: T, + + #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) }, overwritable)] + try_impl_trait: IpAddr, + } + + let builder = Sut::builder() + .required(1) + .required(2) + .optional(3) + .optional(4) + .default(5) + .default(6) + .generic(&"hello") + .generic(&"hello2") + .optional_generic(&"hello you") + .maybe_optional_generic(Some(&"littlepip")) + .default_generic(&"blackjack") + .maybe_default_generic(Some(&"<3")) + .impl_trait([127, 0, 0, 1]) + .impl_trait([127, 0, 0, 2]) + .try_required("7") + .unwrap() + .try_required("8") + .unwrap() + .try_optional("9") + .unwrap() + .maybe_try_optional(Some("10")) + .unwrap() + .try_default("11") + .unwrap() + .maybe_try_default(Some("12")) + .unwrap() + .try_optional_generic(&"13") + .unwrap() + .maybe_try_optional_generic(Some(&"14")) + .unwrap() + .try_default_generic(&"15") + .unwrap() + .maybe_try_default_generic(Some(&"16")) + .unwrap() + .try_impl_trait([127, 0, 0, 3]) + .unwrap() + .try_impl_trait([127, 0, 0, 4]) + .unwrap(); + + assert_debug_eq( + builder.build(), + expect![[r#" + Sut { + required: 3, + optional: Some( + 5, + ), + default: 7, + generic: "hello2", + optional_generic: Some( + "littlepip", + ), + default_generic: "<3", + impl_trait: 127.0.0.2, + try_required: 8, + try_optional: Some( + 10, + ), + try_default: 12, + try_optional_generic: Some( + "14", + ), + try_default_generic: "16", + try_impl_trait: 127.0.0.4, + }"#]], + ); +} diff --git a/bon/tests/integration/builder/attr_with/single_arg.rs b/bon/tests/integration/builder/attr_with/single_arg.rs new file mode 100644 index 00000000..308703eb --- /dev/null +++ b/bon/tests/integration/builder/attr_with/single_arg.rs @@ -0,0 +1,454 @@ +use crate::prelude::*; +use core::convert::Infallible; +use core::net::IpAddr; +use core::{fmt, num}; +type ParseIntResult = Result; + +#[test] +fn test_struct() { + #[derive(Debug, Builder)] + #[builder(derive(Clone))] + #[allow(dead_code)] + struct Sut { + #[builder(with = |x: u32| x + 1)] + required: u32, + + #[builder(with = |x: u32| x + 1)] + optional: Option, + + #[builder(with = |x: u32| x + 1, default)] + default: u32, + + #[builder(with = |value: &T| value.clone())] + generic: T, + + #[builder(with = |value: &T| value.clone())] + optional_generic: Option, + + #[builder(with = |value: &T| value.clone(), default)] + default_generic: T, + + #[builder(with = |value: impl Into| value.into())] + impl_trait: IpAddr, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_required: u32, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_optional: Option, + + #[builder(with = |value: &str| -> ParseIntResult<_> { value.parse() }, default)] + try_default: u32, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) })] + try_optional_generic: Option, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] + try_default_generic: T, + + #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] + try_impl_trait: IpAddr, + } + + let builder = Sut::builder() + .required(1) + .optional(2) + .default(3) + .generic(&"hello"); + + let _ignore = builder.clone().optional_generic(&"hello you"); + let builder = builder.maybe_optional_generic(Some(&"littlepip")); + + let _ignore = builder.clone().default_generic(&"blackjack"); + let builder = builder.maybe_default_generic(Some(&"<3")); + + let builder = builder + .impl_trait([127, 0, 0, 1]) + .try_required("4") + .unwrap(); + + let _ignore = builder.clone().try_optional("5").unwrap(); + let builder = builder.maybe_try_optional(Some("6")).unwrap(); + + let _ignore = builder.clone().try_default("7").unwrap(); + let builder = builder.maybe_try_default(Some("8")).unwrap(); + + let _ignore = builder.clone().try_optional_generic(&"9").unwrap(); + let builder = builder.maybe_try_optional_generic(Some(&"10")).unwrap(); + + let _ignore = builder.clone().try_default_generic(&"11").unwrap(); + let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); + + let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); + + assert_debug_eq( + builder.build(), + expect![[r#" + Sut { + required: 2, + optional: Some( + 3, + ), + default: 4, + generic: "hello", + optional_generic: Some( + "littlepip", + ), + default_generic: "<3", + impl_trait: 127.0.0.1, + try_required: 4, + try_optional: Some( + 6, + ), + try_default: 8, + try_optional_generic: Some( + "10", + ), + try_default_generic: "12", + try_impl_trait: 127.0.0.1, + }"#]], + ); +} + +#[test] +fn test_free_fn() { + #[builder(derive(Clone))] + fn sut( + #[builder(with = |x: u32| x + 1)] required: u32, + #[builder(with = |x: u32| x + 1)] optional: Option, + #[builder(with = |x: u32| x + 1, default)] default: u32, + #[builder(with = |value: &T| value.clone())] generic: T, + #[builder(with = |value: &T| value.clone())] optional_generic: Option, + #[builder(with = |value: &T| value.clone(), default)] default_generic: T, + #[builder(with = |value: impl Into| value.into())] impl_trait: IpAddr, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_required: u32, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_optional: Option, + + #[builder(with = |value: &str| -> ParseIntResult<_> { value.parse() }, default)] + try_default: u32, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) })] + try_optional_generic: Option, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] + try_default_generic: T, + + #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] + try_impl_trait: IpAddr, + ) -> impl fmt::Debug { + ( + ( + required, + optional, + default, + generic, + optional_generic, + default_generic, + impl_trait, + ), + ( + try_required, + try_optional, + try_default, + try_optional_generic, + try_default_generic, + try_impl_trait, + ), + ) + } + + let builder = sut().required(1).optional(2).default(3).generic(&"hello"); + + let _ignore = builder.clone().optional_generic(&"hello you"); + let builder = builder.maybe_optional_generic(Some(&"littlepip")); + + let _ignore = builder.clone().default_generic(&"blackjack"); + let builder = builder.maybe_default_generic(Some(&"<3")); + + let builder = builder + .impl_trait([127, 0, 0, 1]) + .try_required("4") + .unwrap(); + + let _ignore = builder.clone().try_optional("5").unwrap(); + let builder = builder.maybe_try_optional(Some("6")).unwrap(); + + let _ignore = builder.clone().try_default("7").unwrap(); + let builder = builder.maybe_try_default(Some("8")).unwrap(); + + let _ignore = builder.clone().try_optional_generic(&"9").unwrap(); + let builder = builder.maybe_try_optional_generic(Some(&"10")).unwrap(); + + let _ignore = builder.clone().try_default_generic(&"11").unwrap(); + let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); + + let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); + + assert_debug_eq( + builder.call(), + expect![[r#" + ( + ( + 2, + Some( + 3, + ), + 4, + "hello", + Some( + "littlepip", + ), + "<3", + 127.0.0.1, + ), + ( + 4, + Some( + 6, + ), + 8, + Some( + "10", + ), + "12", + 127.0.0.1, + ), + )"#]], + ); +} + +#[test] +fn test_assoc_method() { + struct Sut; + + #[bon] + impl Sut { + #[builder(derive(Clone))] + fn sut( + #[builder(with = |x: u32| x + 1)] required: u32, + #[builder(with = |x: u32| x + 1)] optional: Option, + #[builder(with = |x: u32| x + 1, default)] default: u32, + #[builder(with = |value: &T| value.clone())] generic: T, + #[builder(with = |value: &T| value.clone())] optional_generic: Option, + #[builder(with = |value: &T| value.clone(), default)] default_generic: T, + #[builder(with = |value: impl Into| value.into())] impl_trait: IpAddr, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_required: u32, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_optional: Option, + + #[builder(with = |value: &str| -> ParseIntResult<_> { value.parse() }, default)] + try_default: u32, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) })] + try_optional_generic: Option, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] + try_default_generic: T, + + #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] + try_impl_trait: IpAddr, + ) -> impl fmt::Debug { + ( + ( + required, + optional, + default, + generic, + optional_generic, + default_generic, + impl_trait, + ), + ( + try_required, + try_optional, + try_default, + try_optional_generic, + try_default_generic, + try_impl_trait, + ), + ) + } + + #[builder(derive(Clone))] + fn with_self( + &self, + #[builder(with = |x: u32| x + 1)] required: u32, + #[builder(with = |x: u32| x + 1)] optional: Option, + #[builder(with = |x: u32| x + 1, default)] default: u32, + #[builder(with = |value: &T| value.clone())] generic: T, + #[builder(with = |value: &T| value.clone())] optional_generic: Option, + #[builder(with = |value: &T| value.clone(), default)] default_generic: T, + #[builder(with = |value: impl Into| value.into())] impl_trait: IpAddr, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_required: u32, + + #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] + try_optional: Option, + + #[builder(with = |value: &str| -> ParseIntResult<_> { value.parse() }, default)] + try_default: u32, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) })] + try_optional_generic: Option, + + #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] + try_default_generic: T, + + #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] + try_impl_trait: IpAddr, + ) -> impl fmt::Debug { + let _ = self; + ( + ( + required, + optional, + default, + generic, + optional_generic, + default_generic, + impl_trait, + ), + ( + try_required, + try_optional, + try_default, + try_optional_generic, + try_default_generic, + try_impl_trait, + ), + ) + } + } + + let builder = Sut::sut() + .required(1) + .optional(2) + .default(3) + .generic(&"hello"); + + let _ignore = builder.clone().optional_generic(&"hello you"); + let builder = builder.maybe_optional_generic(Some(&"littlepip")); + + let _ignore = builder.clone().default_generic(&"blackjack"); + let builder = builder.maybe_default_generic(Some(&"<3")); + + let builder = builder + .impl_trait([127, 0, 0, 1]) + .try_required("4") + .unwrap(); + + let _ignore = builder.clone().try_optional("5").unwrap(); + let builder = builder.maybe_try_optional(Some("6")).unwrap(); + + let _ignore = builder.clone().try_default("7").unwrap(); + let builder = builder.maybe_try_default(Some("8")).unwrap(); + + let _ignore = builder.clone().try_optional_generic(&"9").unwrap(); + let builder = builder.maybe_try_optional_generic(Some(&"10")).unwrap(); + + let _ignore = builder.clone().try_default_generic(&"11").unwrap(); + let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); + + let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); + + assert_debug_eq( + builder.call(), + expect![[r#" + ( + ( + 2, + Some( + 3, + ), + 4, + "hello", + Some( + "littlepip", + ), + "<3", + 127.0.0.1, + ), + ( + 4, + Some( + 6, + ), + 8, + Some( + "10", + ), + "12", + 127.0.0.1, + ), + )"#]], + ); + + let builder = Sut + .with_self() + .required(1) + .optional(2) + .default(3) + .generic(&"hello"); + + let _ignore = builder.clone().optional_generic(&"hello you"); + let builder = builder.maybe_optional_generic(Some(&"littlepip")); + + let _ignore = builder.clone().default_generic(&"blackjack"); + let builder = builder.maybe_default_generic(Some(&"<3")); + + let builder = builder + .impl_trait([127, 0, 0, 1]) + .try_required("4") + .unwrap(); + + let _ignore = builder.clone().try_optional("5").unwrap(); + let builder = builder.maybe_try_optional(Some("6")).unwrap(); + + let _ignore = builder.clone().try_default("7").unwrap(); + let builder = builder.maybe_try_default(Some("8")).unwrap(); + + let _ignore = builder.clone().try_optional_generic(&"9").unwrap(); + let builder = builder.maybe_try_optional_generic(Some(&"10")).unwrap(); + + let _ignore = builder.clone().try_default_generic(&"11").unwrap(); + let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); + + let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); + + assert_debug_eq(builder.call(), expect![[r#" + ( + ( + 2, + Some( + 3, + ), + 4, + "hello", + Some( + "littlepip", + ), + "<3", + 127.0.0.1, + ), + ( + 4, + Some( + 6, + ), + 8, + Some( + "10", + ), + "12", + 127.0.0.1, + ), + )"#]]); +} From e1265c8551afd1b705163c78d29669b77a0457a4 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 3 Oct 2024 20:28:16 +0000 Subject: [PATCH 050/119] Fixes for Visibility::into_equivalent_in_child_module --- bon-macros/src/util/visibility.rs | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/bon-macros/src/util/visibility.rs b/bon-macros/src/util/visibility.rs index 74567327..c5da7998 100644 --- a/bon-macros/src/util/visibility.rs +++ b/bon-macros/src/util/visibility.rs @@ -10,7 +10,10 @@ pub(crate) trait VisibilityExt { /// - `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)` (unchange) + /// - `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]: @@ -68,6 +71,18 @@ impl VisibilityExt for syn::Visibility { 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) @@ -89,7 +104,12 @@ mod tests { #[allow(clippy::needless_pass_by_value)] fn test(vis: syn::Visibility, expected: syn::Visibility) { let actual = vis.into_equivalent_in_child_module().unwrap(); - assert_eq!(actual, expected); + assert!( + actual == expected, + "got:\nactual: {}\nexpected: {}", + actual.to_token_stream(), + expected.to_token_stream() + ); } test(pq!(pub), pq!(pub)); @@ -97,10 +117,15 @@ mod tests { 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))); } } From 28d4fd6aa59c6683939a78970c8c05a08a25483e Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 4 Oct 2024 02:44:41 +0000 Subject: [PATCH 051/119] More tests for setters attr --- .../builder/builder_gen/builder_params/mod.rs | 59 ++++++++++++++++ .../on_params.rs} | 56 +-------------- .../src/builder/builder_gen/member/named.rs | 47 ++++++++----- .../src/builder/builder_gen/setter_methods.rs | 4 +- .../src/builder/builder_gen/state_mod.rs | 6 +- .../ui/compile_fail/attr_setters.rs | 69 +++++++++++++++++++ .../ui/compile_fail/attr_setters.stderr | 47 +++++++++++++ .../ui/compile_fail/invalid_setters_attr.rs | 27 -------- .../compile_fail/invalid_setters_attr.stderr | 23 ------- e2e-tests/src/lib.rs | 17 ++++- e2e-tests/src/reexports.rs | 13 ++++ 11 files changed, 238 insertions(+), 130 deletions(-) create mode 100644 bon-macros/src/builder/builder_gen/builder_params/mod.rs rename bon-macros/src/builder/builder_gen/{builder_params.rs => builder_params/on_params.rs} (67%) create mode 100644 bon/tests/integration/ui/compile_fail/attr_setters.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_setters.stderr delete mode 100644 bon/tests/integration/ui/compile_fail/invalid_setters_attr.rs delete mode 100644 bon/tests/integration/ui/compile_fail/invalid_setters_attr.stderr create mode 100644 e2e-tests/src/reexports.rs diff --git a/bon-macros/src/builder/builder_gen/builder_params/mod.rs b/bon-macros/src/builder/builder_gen/builder_params/mod.rs new file mode 100644 index 00000000..f72431f4 --- /dev/null +++ b/bon-macros/src/builder/builder_gen/builder_params/mod.rs @@ -0,0 +1,59 @@ +mod on_params; + +pub(crate) use on_params::OnParams; + +use crate::parsing::{ItemParams, ItemParamsParsing}; +use crate::util::prelude::*; +use darling::FromMeta; + +fn parse_finish_fn(meta: &syn::Meta) -> Result { + ItemParamsParsing { + meta, + reject_self_mentions: Some("builder struct's impl block"), + } + .parse() +} + +fn parse_builder_type(meta: &syn::Meta) -> Result { + ItemParamsParsing { + meta, + reject_self_mentions: Some("builder struct"), + } + .parse() +} + +fn parse_state_mod(meta: &syn::Meta) -> Result { + ItemParamsParsing { + meta, + reject_self_mentions: Some("builder module"), + } + .parse() +} + +#[derive(Debug, FromMeta)] +pub(crate) struct BuilderParams { + #[darling(default, with = parse_finish_fn)] + pub(crate) finish_fn: ItemParams, + + #[darling(default, with = parse_builder_type)] + pub(crate) builder_type: ItemParams, + + #[darling(default, with = parse_state_mod)] + pub(crate) state_mod: ItemParams, + + #[darling(multiple, with = crate::parsing::require_paren_delim_for_meta_list)] + pub(crate) on: Vec, + + /// Specifies the derives to apply to the builder. + #[darling(default, with = crate::parsing::require_paren_delim_for_meta_list)] + pub(crate) derive: BuilderDerives, +} + +#[derive(Debug, Clone, Default, FromMeta)] +pub(crate) struct BuilderDerives { + #[darling(rename = "Clone")] + pub(crate) clone: darling::util::Flag, + + #[darling(rename = "Debug")] + pub(crate) debug: darling::util::Flag, +} diff --git a/bon-macros/src/builder/builder_gen/builder_params.rs b/bon-macros/src/builder/builder_gen/builder_params/on_params.rs similarity index 67% rename from bon-macros/src/builder/builder_gen/builder_params.rs rename to bon-macros/src/builder/builder_gen/builder_params/on_params.rs index 171ccefd..43e7eeb9 100644 --- a/bon-macros/src/builder/builder_gen/builder_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params/on_params.rs @@ -1,62 +1,8 @@ -use crate::parsing::{ItemParams, ItemParamsParsing}; use crate::util::prelude::*; -use darling::FromMeta; -use proc_macro2::Span; use syn::parse::Parse; use syn::spanned::Spanned; use syn::visit::Visit; - -fn parse_finish_fn(meta: &syn::Meta) -> Result { - ItemParamsParsing { - meta, - reject_self_mentions: Some("builder struct's impl block"), - } - .parse() -} - -fn parse_builder_type(meta: &syn::Meta) -> Result { - ItemParamsParsing { - meta, - reject_self_mentions: Some("builder struct"), - } - .parse() -} - -fn parse_state_mod(meta: &syn::Meta) -> Result { - ItemParamsParsing { - meta, - reject_self_mentions: Some("builder module"), - } - .parse() -} - -#[derive(Debug, FromMeta)] -pub(crate) struct BuilderParams { - #[darling(default, with = parse_finish_fn)] - pub(crate) finish_fn: ItemParams, - - #[darling(default, with = parse_builder_type)] - pub(crate) builder_type: ItemParams, - - #[darling(default, with = parse_state_mod)] - pub(crate) state_mod: ItemParams, - - #[darling(multiple, with = crate::parsing::require_paren_delim_for_meta_list)] - pub(crate) on: Vec, - - /// Specifies the derives to apply to the builder. - #[darling(default, with = crate::parsing::require_paren_delim_for_meta_list)] - pub(crate) derive: BuilderDerives, -} - -#[derive(Debug, Clone, Default, FromMeta)] -pub(crate) struct BuilderDerives { - #[darling(rename = "Clone")] - pub(crate) clone: darling::util::Flag, - - #[darling(rename = "Debug")] - pub(crate) debug: darling::util::Flag, -} +use darling::FromMeta; #[derive(Debug)] pub(crate) struct OnParams { diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index cd95c447..6057b063 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -2,7 +2,7 @@ use super::params::MemberParams; use super::{params, MemberOrigin}; use crate::builder::builder_gen::builder_params::OnParams; use crate::builder::builder_gen::member::params::SettersFnParams; -use crate::parsing::SpannedKey; +use crate::parsing::{ItemParams, SpannedKey}; use crate::util::prelude::*; /// Regular member for which the builder should have setter methods @@ -14,21 +14,19 @@ pub(crate) struct NamedMember { /// Index of the member relative to other regular members. The index is 0-based. pub(crate) index: syn::Index, - /// Original name of the member is used as the name of the builder field and - /// in its setter methods. Struct field/fn arg names conventionally use `snake_case` - /// in Rust, but this isn't enforced, so this member isn't guaranteed to be in - /// snake case, but 99% of the time it will be. + /// 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_ident: syn::Ident, /// Normalized version of `orig_ident`. Here we stripped the leading `_` from the - /// member name. + /// member name. This name is in the builder's API unless a `name` override is present. pub(crate) norm_ident: syn::Ident, /// `PascalCase` version of the `norm_ident`. pub(crate) norm_ident_pascal: syn::Ident, - /// Doc comments for the setter methods are copied from the doc comments placed - /// on top of the original member + /// Doc comments on top of the original syntax. These are copied to the setters + /// unless there are overrides for them. pub(crate) docs: Vec, /// Normalized type of the member that the builder should have setters for. @@ -46,7 +44,7 @@ impl NamedMember { crate::parsing::reject_self_mentions_in_docs("builder struct's impl block", &self.docs)?; if let Some(default) = &self.params.default { - if !self.params.transparent.is_present() && self.norm_ty.is_option() { + if self.is_special_option_ty() { bail!( &default.key, "`Option<_>` already implies a default of `None`, \ @@ -97,24 +95,30 @@ impl NamedMember { option_fn: Some(option_fn), } = &setters.fns { - Self::validate_unused_config(&setters.name, &[&some_fn.name, &option_fn.name])?; - Self::validate_unused_config(&setters.vis, &[&some_fn.vis, &option_fn.vis])?; - Self::validate_unused_config(&setters.docs, &[&some_fn.docs, &option_fn.docs])?; + let setter_fns = &[some_fn, option_fn]; + + Self::validate_unused_setters_cfg(setter_fns, &setters.name, |params| ¶ms.name)?; + Self::validate_unused_setters_cfg(setter_fns, &setters.vis, |params| ¶ms.vis)?; + Self::validate_unused_setters_cfg(setter_fns, &setters.docs, |params| ¶ms.docs)?; } Ok(()) } - fn validate_unused_config( + fn validate_unused_setters_cfg( + overrides: &[&SpannedKey], config: &Option>, - overrides: &[&Option>], + get_val: impl Fn(&ItemParams) -> &Option>, ) -> Result { let config = match config { Some(config) => config, None => return Ok(()), }; - let overrides = overrides.iter().copied().map(Option::as_ref); + let overrides = overrides + .iter() + .copied() + .map(|over| get_val(&over.value).as_ref()); if !overrides.clone().all(|over| over.is_some()) { return Ok(()); @@ -139,11 +143,16 @@ impl NamedMember { self.params.name.as_ref().unwrap_or(&self.norm_ident) } + /// Returns `true` if this member is of `Option<_>` type, but returns `false` + /// if `#[builder(transparent)]` is set. + fn is_special_option_ty(&self) -> bool { + !self.params.transparent.is_present() && self.norm_ty.is_option() + } + /// Returns `false` if the member has a default value. It means this member /// is required to be set before the building can be finished. pub(crate) fn is_required(&self) -> bool { - self.params.default.is_none() - && (self.params.transparent.is_present() || !self.norm_ty.is_option()) + self.params.default.is_none() && !self.is_special_option_ty() } /// A stateful member is the one that has a corresponding associated type in @@ -174,6 +183,10 @@ impl NamedMember { } } + pub(crate) fn is(&self, other: &Self) -> bool { + self.index == other.index + } + pub(crate) fn merge_on_params(&mut self, on_params: &[OnParams]) -> Result { self.merge_param_into(on_params)?; diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index 0574a4dd..ecc66024 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -325,7 +325,7 @@ impl<'a> SettersCtx<'a> { } fn generate_docs_for_setter(&self) -> Vec { - let setter_core_name = self.member.public_ident(); + let member_ident = self.member.public_ident(); let start_fn_ident = &self.builder_gen.start_fn.ident; let more = |start_fn_path: &std::fmt::Arguments<'_>| { @@ -351,7 +351,7 @@ impl<'a> SettersCtx<'a> { }) .unwrap_or_else(|| more(&format_args!("[`{start_fn_ident}()`]"))); - let docs = format!("Sets the value of `{setter_core_name}`.{suffix}"); + let docs = format!("Sets the value of `{member_ident}`.{suffix}"); vec![syn::parse_quote!(#[doc = #docs])] } diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index 95e05c4f..10456d22 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -12,8 +12,8 @@ impl super::BuilderGenCtx { .iter() .map(|member| { let states = stateful_members.iter().map(|other_member| { - if other_member.orig_ident == member.orig_ident { - let ident = &member.public_ident(); + if other_member.is(member) { + let ident = member.public_ident(); quote! { ::bon::private::Set } @@ -136,7 +136,7 @@ impl super::BuilderGenCtx { // 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. - #[allow(unnameable_types, unreachable_pub)] + #[allow(unnameable_types, unreachable_pub, clippy::redundant_pub_crate)] #( #state_mod_docs )* #vis_mod mod #state_mod_ident { /// Marker trait implemented by members that are set. diff --git a/bon/tests/integration/ui/compile_fail/attr_setters.rs b/bon/tests/integration/ui/compile_fail/attr_setters.rs new file mode 100644 index 00000000..32da8a0c --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_setters.rs @@ -0,0 +1,69 @@ +use bon::Builder; + +#[derive(Builder)] +struct UnusedNameConfig { + #[builder(setters( + name = littlepip, + some_fn = blackjack, + option_fn = roseluck, + ))] + value: Option, +} + +#[derive(Builder)] +struct UnusedNameConfigVerbose { + #[builder(setters( + name = littlepip, + some_fn(name = blackjack), + option_fn(name = roseluck), + ))] + value: Option, +} + +#[derive(Builder)] +struct UnusedVisConfig { + #[builder(setters(vis = "pub(crate)", some_fn(vis = ""), option_fn(vis = ""),))] + value: Option, +} + +#[derive(Builder)] +struct UnusedDocsConfig { + #[builder(setters( + docs { + /// Unused + }, + some_fn(docs { + /// some_fn docs + }), + option_fn(docs { + /// option_fn docs + }), + ))] + value: Option, +} + +#[derive(Builder)] +struct SomeFnSetterRequiredMember { + #[builder(setters(some_fn = foo))] + member: i32, +} + +#[derive(Builder)] +struct OptionFnSetterOnRequiredMember { + #[builder(setters(option_fn = bar))] + member: i32, +} + +#[derive(Builder)] +struct SomeFnSetterWithTransparent { + #[builder(transparent, setters(some_fn = foo))] + member: Option, +} + +#[derive(Builder)] +struct OptionFnSetterWithTransparent { + #[builder(transparent, setters(option_fn = bar))] + member: Option, +} + +fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_setters.stderr b/bon/tests/integration/ui/compile_fail/attr_setters.stderr new file mode 100644 index 00000000..37355b43 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_setters.stderr @@ -0,0 +1,47 @@ +error: this `name` configuration is unused because all of the `some_fn`, `option_fn` setters contain a `name` override + --> tests/integration/ui/compile_fail/attr_setters.rs:6:9 + | +6 | name = littlepip, + | ^^^^ + +error: this `name` configuration is unused because all of the `name`, `name` setters contain a `name` override + --> tests/integration/ui/compile_fail/attr_setters.rs:16:9 + | +16 | name = littlepip, + | ^^^^ + +error: this `vis` configuration is unused because all of the `vis`, `vis` setters contain a `vis` override + --> tests/integration/ui/compile_fail/attr_setters.rs:25:23 + | +25 | #[builder(setters(vis = "pub(crate)", some_fn(vis = ""), option_fn(vis = ""),))] + | ^^^ + +error: this `docs` configuration is unused because all of the `docs`, `docs` setters contain a `docs` override + --> tests/integration/ui/compile_fail/attr_setters.rs:32:9 + | +32 | docs { + | ^^^^ + +error: `some_fn` setter function applies only to members with `#[builder(default)]` or members of `Option` type (if #[builder(transparent)] is not set) + --> tests/integration/ui/compile_fail/attr_setters.rs:47:23 + | +47 | #[builder(setters(some_fn = foo))] + | ^^^^^^^ + +error: `option_fn` setter function applies only to members with `#[builder(default)]` or members of `Option` type (if #[builder(transparent)] is not set) + --> tests/integration/ui/compile_fail/attr_setters.rs:53:23 + | +53 | #[builder(setters(option_fn = bar))] + | ^^^^^^^^^ + +error: `some_fn` setter function applies only to members with `#[builder(default)]` or members of `Option` type (if #[builder(transparent)] is not set) + --> tests/integration/ui/compile_fail/attr_setters.rs:59:36 + | +59 | #[builder(transparent, setters(some_fn = foo))] + | ^^^^^^^ + +error: `option_fn` setter function applies only to members with `#[builder(default)]` or members of `Option` type (if #[builder(transparent)] is not set) + --> tests/integration/ui/compile_fail/attr_setters.rs:65:36 + | +65 | #[builder(transparent, setters(option_fn = bar))] + | ^^^^^^^^^ diff --git a/bon/tests/integration/ui/compile_fail/invalid_setters_attr.rs b/bon/tests/integration/ui/compile_fail/invalid_setters_attr.rs deleted file mode 100644 index d0760d42..00000000 --- a/bon/tests/integration/ui/compile_fail/invalid_setters_attr.rs +++ /dev/null @@ -1,27 +0,0 @@ -use bon::Builder; - -#[derive(Builder)] -struct SomeFnSetterRequiredMember { - #[builder(setters(some_fn = foo))] - member: i32, -} - -#[derive(Builder)] -struct OptionFnSetterOnRequiredMember { - #[builder(setters(option_fn = bar))] - member: i32, -} - -#[derive(Builder)] -struct SomeFnSetterWithTransparent { - #[builder(transparent, setters(some_fn = foo))] - member: Option, -} - -#[derive(Builder)] -struct OptionFnSetterWithTransparent { - #[builder(transparent, setters(option_fn = bar))] - member: Option, -} - -fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/invalid_setters_attr.stderr b/bon/tests/integration/ui/compile_fail/invalid_setters_attr.stderr deleted file mode 100644 index 8b5b92b5..00000000 --- a/bon/tests/integration/ui/compile_fail/invalid_setters_attr.stderr +++ /dev/null @@ -1,23 +0,0 @@ -error: `some_fn` setter function applies only to members with `#[builder(default)]` or members of `Option` type (if #[builder(transparent)] is not set) - --> tests/integration/ui/compile_fail/invalid_setters_attr.rs:5:23 - | -5 | #[builder(setters(some_fn = foo))] - | ^^^^^^^ - -error: `option_fn` setter function applies only to members with `#[builder(default)]` or members of `Option` type (if #[builder(transparent)] is not set) - --> tests/integration/ui/compile_fail/invalid_setters_attr.rs:11:23 - | -11 | #[builder(setters(option_fn = bar))] - | ^^^^^^^^^ - -error: `some_fn` setter function applies only to members with `#[builder(default)]` or members of `Option` type (if #[builder(transparent)] is not set) - --> tests/integration/ui/compile_fail/invalid_setters_attr.rs:17:36 - | -17 | #[builder(transparent, setters(some_fn = foo))] - | ^^^^^^^ - -error: `option_fn` setter function applies only to members with `#[builder(default)]` or members of `Option` type (if #[builder(transparent)] is not set) - --> tests/integration/ui/compile_fail/invalid_setters_attr.rs:23:36 - | -23 | #[builder(transparent, setters(option_fn = bar))] - | ^^^^^^^^^ diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index ad4dadf1..96612f82 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -4,9 +4,13 @@ pub mod macro_rules_wrapper_test; pub mod missing_docs_test; -pub mod v3_design; +// pub mod v3_design; -use bon::{bon, builder}; +mod reexports; + +pub use reexports::{UnexportedBuilder, UnexportedStateMod, UnexportedStateModBuilder}; + +use bon::{bon, builder, Builder}; #[cfg(doctest)] // We use a bunch of Vitepress-specific syntax in the doctests, for example to @@ -17,8 +21,15 @@ mod website_doctests { include!(concat!(env!("OUT_DIR"), "/website_doctests.rs")); } +/// Some docs on the private builder +#[derive(Builder)] +#[builder(builder_type(vis = ""))] +pub struct PrivateBuilder { + _field: String, +} + /// Docs on the [`Self`] struct -#[derive(bon::Builder)] +#[derive(Builder)] #[builder( builder_type( docs { diff --git a/e2e-tests/src/reexports.rs b/e2e-tests/src/reexports.rs new file mode 100644 index 00000000..ec8191a8 --- /dev/null +++ b/e2e-tests/src/reexports.rs @@ -0,0 +1,13 @@ +/// Unexported builder +#[derive(bon::Builder)] +pub struct UnexportedBuilder { + _field1: u32, + _field2: u32, +} + +/// Unexported state mod +#[derive(bon::Builder)] +pub struct UnexportedStateMod { + _field1: u32, + _field2: u32, +} From fc0e71ffef84b2079ffbb8ada15236ecfb565466 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 4 Oct 2024 18:00:21 +0000 Subject: [PATCH 052/119] Add `StateExt` trait indirection and make `AllUnset` a struct to improve rustdoc --- .../builder/builder_gen/builder_derives.rs | 2 +- .../src/builder/builder_gen/member/named.rs | 11 +- bon-macros/src/builder/builder_gen/mod.rs | 2 +- .../src/builder/builder_gen/setter_methods.rs | 13 +- .../src/builder/builder_gen/state_mod.rs | 536 +++++++++--------- 5 files changed, 292 insertions(+), 272 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index 95f8fd46..b7e2f3ed 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -130,7 +130,7 @@ impl BuilderGenCtx { match member { Member::Named(member) => { let member_index = &member.index; - let member_ident_str = member.public_ident().to_string(); + let member_ident_str = member.public_snake().to_string(); let member_ty = member.underlying_norm_ty(); Some(quote! { if let ::core::option::Option::Some(value) = &self.__private_named_members.#member_index { diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index 6057b063..d6dea3a2 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -138,11 +138,18 @@ impl NamedMember { } /// Returns the public identifier of the member that should be used in the - /// generated builder API. - pub(crate) fn public_ident(&self) -> &syn::Ident { + /// generated builder API where snake case is expected. + pub(crate) fn public_snake(&self) -> &syn::Ident { self.params.name.as_ref().unwrap_or(&self.norm_ident) } + /// Returns the public identifier of the member that should be used in the + /// generated builder API where pascal case is expected. + pub(crate) fn public_pascal(&self) -> &syn::Ident { + // FIXME: this should try to use the pascal case version of the `name` parameter + &self.norm_ident_pascal + } + /// Returns `true` if this member is of `Option<_>` type, but returns `false` /// if `#[builder(transparent)]` is set. fn is_special_option_ty(&self) -> bool { diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 0b529541..7bb91960 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -41,7 +41,7 @@ impl BuilderGenCtx { pub(crate) fn output(self) -> Result { let mut start_fn = self.start_fn(); - let state_mod = self.state_mod(); + 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(); diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index ecc66024..7dc66fa2 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -47,7 +47,7 @@ impl<'a> SettersCtx<'a> { } self.setter_method(Setter { - method_name: self.member.public_ident().clone(), + method_name: self.member.public_snake().clone(), fn_inputs, overwrite_docs: None, body: SetterBody::Default { member_init }, @@ -55,7 +55,7 @@ impl<'a> SettersCtx<'a> { } fn setters_for_optional_member(&self) -> TokenStream { - let member_name = self.member.public_ident().clone(); + let member_name = self.member.public_snake().clone(); // Preserve the original identifier span to make IDE's "go to definition" work correctly let option_fn_name = syn::Ident::new( @@ -265,14 +265,9 @@ impl<'a> SettersCtx<'a> { let state_transition = format_ident!("Set{}", self.member.norm_ident_pascal.raw_name()); let state_mod = &self.builder_gen.state_mod.ident; - let generic_param = if self.builder_gen.stateful_members().take(2).count() == 1 { - quote!() - } else { - quote!() - }; let state_transition = quote! { - #state_mod::#state_transition #generic_param + #state_mod::#state_transition }; let builder_ident = &self.builder_gen.builder_type.ident; @@ -325,7 +320,7 @@ impl<'a> SettersCtx<'a> { } fn generate_docs_for_setter(&self) -> Vec { - let member_ident = self.member.public_ident(); + let member_ident = self.member.public_snake(); let start_fn_ident = &self.builder_gen.start_fn.ident; let more = |start_fn_path: &std::fmt::Arguments<'_>| { diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index 10456d22..8de30e78 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -1,81 +1,173 @@ +use super::BuilderGenCtx; use crate::builder::builder_gen::member::NamedMember; use crate::util::prelude::*; -impl super::BuilderGenCtx { - fn state_transition_aliases(&self) -> Vec { - let vis_child = &self.state_mod.vis_child; +pub(super) struct StateModGenCtx<'a> { + builder_gen: &'a BuilderGenCtx, + stateful_members_snake: Vec<&'a syn::Ident>, + stateful_members_pascal: Vec<&'a syn::Ident>, + sealed_method_decl: TokenStream, + sealed_method_impl: TokenStream, +} - let stateful_members = self.stateful_members().collect::>(); - let is_single_member = stateful_members.len() == 1; +impl<'a> StateModGenCtx<'a> { + pub(super) fn new(builder_gen: &'a BuilderGenCtx) -> Self { + Self { + builder_gen, - stateful_members - .iter() - .map(|member| { - let states = stateful_members.iter().map(|other_member| { + stateful_members_snake: builder_gen + .stateful_members() + .map(NamedMember::public_snake) + .collect(), + + stateful_members_pascal: builder_gen + .stateful_members() + .map(NamedMember::public_pascal) + .collect(), + + // A method without `self` makes the trait non-object safe, + // which is convenient, because we want that in this case. + sealed_method_decl: quote! { + #[doc(hidden)] + fn __sealed(_: sealed::Sealed); + }, + + sealed_method_impl: quote! { + fn __sealed(_: sealed::Sealed) {} + }, + } + } + + pub(super) fn state_mod(&self) -> TokenStream { + let vis_mod = &self.builder_gen.state_mod.vis; + let vis_child_child = &self.builder_gen.state_mod.vis_child_child; + + let state_mod_docs = &self.builder_gen.state_mod.docs; + let state_mod_ident = &self.builder_gen.state_mod.ident; + + let state_trait = self.state_trait(); + let is_set_trait = self.is_set_trait(); + let is_unset_trait = self.is_unset_trait(); + let is_complete_trait = self.is_complete_trait(); + let members_names_mod = self.members_names_mod(); + let state_transitions = self.state_transitions(); + + quote! { + // This is 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. + #[allow(unnameable_types, unreachable_pub, clippy::redundant_pub_crate)] + #( #state_mod_docs )* + #vis_mod mod #state_mod_ident { + mod sealed { + #vis_child_child enum Sealed {} + } + + #state_trait + #is_set_trait + #is_unset_trait + #is_complete_trait + #state_transitions + #members_names_mod + } + } + } + + fn state_transitions(&self) -> TokenStream { + let transition_tuples = self + .builder_gen + .stateful_members() + .map(move |member| { + let states = self.builder_gen.stateful_members().map(|other_member| { if other_member.is(member) { - let ident = member.public_ident(); + let ident = member.public_snake(); quote! { ::bon::private::Set } } else { let member_pascal = &other_member.norm_ident_pascal; quote! { - S::#member_pascal + ::#member_pascal } } }); - let member_ident = member.public_ident(); - let alias_ident = format_ident!("Set{}", member.norm_ident_pascal.raw_name()); + quote! { + ( #( #states, )* ) + } + }) + .collect::>(); + + let (set_member_aliases_docs, set_member_aliases): (Vec<_>, Vec<_>) = self + .builder_gen + .stateful_members() + .map(|member| { + let member_snake = member.public_snake(); + let member_pascal = member.public_pascal(); + let alias = format_ident!("Set{}", member_pascal.raw_name()); let docs = format!( - "Returns a [`State`] that has [`IsSet`] implemented for `{member_ident}`\n\ + "Returns a [`State`] that has [`IsSet`] implemented for `{member_snake}`\n\ \n\ [`State`]: self::State\n\ [`IsSet`]: ::bon::IsSet", ); - if is_single_member { - return syn::parse_quote! { - #[doc = #docs] - #vis_child type #alias_ident = ( #(#states,)* ); - }; - } - - syn::parse_quote! { - #[doc = #docs] - #vis_child type #alias_ident< - S: self::State = self::AllUnset - > = ( - #(#states,)* - ); - } + (docs, alias) }) - .collect() - } + .unzip(); - // TODO: shame on me, but clippy is right 😳. Ping @Veetaha if he forgot to - // refactor this function before merging this code to master. - #[allow(clippy::cognitive_complexity)] - pub(super) fn state_mod(&self) -> TokenStream { - let builder_vis = &self.builder_type.vis; - let vis_mod = &self.state_mod.vis; - let vis_child = &self.state_mod.vis_child; - let vis_child_child = &self.state_mod.vis_child_child; + let vis_child = &self.builder_gen.state_mod.vis_child; + let vis_child_child = &self.builder_gen.state_mod.vis_child_child; + let stateful_members_snake = &self.stateful_members_snake; + let stateful_members_pascal = &self.stateful_members_pascal; + let sealed_method_impl = &self.sealed_method_impl; - let state_mod_docs = &self.state_mod.docs; - let state_mod_ident = &self.state_mod.ident; - let state_transition_aliases = self.state_transition_aliases(); + quote! { + /// Initial state of the builder where all members are unset + #vis_child struct AllUnset { + _private: () + } - let stateful_members_idents = self - .stateful_members() - .map(NamedMember::public_ident) - .collect::>(); + impl State for AllUnset { + #( + type #stateful_members_pascal = ::bon::private::Unset; + )* + + #sealed_method_impl + } - let assoc_types_docs = self.stateful_members().map(|member| { - let ident = &member.public_ident(); + #( + #[doc = #set_member_aliases_docs] + #vis_child type #set_member_aliases = + ::#set_member_aliases; + )* + + mod private { + #[doc(hidden)] + #vis_child_child trait StateExt { + #( type #set_member_aliases; )* + } + } + + impl private::StateExt for S { + #(type #set_member_aliases = #transition_tuples; )* + } + } + } + + fn state_trait(&self) -> TokenStream { + let assoc_types_docs = self.stateful_members_snake.iter().map(|member_snake| { format!( - "Type state of the member `{ident}`.\n\ + "Type state of the member `{member_snake}`.\n\ \n\ It can implement either [`IsSet`] or [`IsUnset`].\n\ \n\ @@ -84,241 +176,167 @@ impl super::BuilderGenCtx { ) }); - let stateful_members_pascal = self - .stateful_members() - .map(|member| &member.norm_ident_pascal) - .collect::>(); + let vis_child = &self.builder_gen.state_mod.vis_child; + let sealed_method_decl = &self.sealed_method_decl; + let sealed_method_impl = &self.sealed_method_impl; + let stateful_members_snake = &self.stateful_members_snake; + let stateful_members_pascal = &self.stateful_members_pascal; + quote! { + /// Builder's type state specifies if members are set or not (unset). + /// + /// 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*` type aliases available in this module. + /// + /// [`IsSet`]: ::bon::IsSet + /// [`IsUnset`]: ::bon::IsUnset + #vis_child trait State: ::core::marker::Sized { + #( + #[doc = #assoc_types_docs] + type #stateful_members_pascal: ::bon::private::MemberState< + self::members::#stateful_members_snake + >; + )* + + #sealed_method_decl + } + + // Using `self::State` explicitly to avoid name conflicts with the + // members named `state` which would create a generic param named `State` + // that would shadow the trait `State` in the same scope. + #[doc(hidden)] + impl<#( + #stateful_members_pascal: ::bon::private::MemberState< + self::members::#stateful_members_snake + >, + )*> + self::State for ( #(#stateful_members_pascal,)* ) + { + #( type #stateful_members_pascal = #stateful_members_pascal; )* + + #sealed_method_impl + } + } + } + + fn is_complete_trait(&self) -> TokenStream { let required_members_pascal = self + .builder_gen .named_members() .filter(|member| member.is_required()) .map(|member| &member.norm_ident_pascal) .collect::>(); - let type_aliases_for_rustdoc = [&format_ident!("AllUnset")]; - let type_aliases_for_rustdoc = state_transition_aliases - .iter() - .map(|alias| &alias.ident) - .chain(type_aliases_for_rustdoc); - - let is_complete_assoc_type_bounds = if cfg!(feature = "msrv-1-79-0") { + let maybe_assoc_type_bounds = cfg!(feature = "msrv-1-79-0").then(|| { quote! { < #( #required_members_pascal: IsSet, )* > } - } else { - quote! {} - }; + }); - let sealed_method = quote! { - // A method without `self` makes the trait non-object safe, - // which is convenient, because we want that in this case. - #[doc(hidden)] - fn __sealed(_: sealed::Sealed); - }; + let vis_child = &self.builder_gen.state_mod.vis_child; + let sealed_method_decl = &self.sealed_method_decl; + let sealed_method_impl = &self.sealed_method_impl; - quote! { - // This is a workaround for `rustdoc`. Without this `use` statement, - // it inlines the type aliases - #[cfg(doc)] - #[doc(hidden)] - #builder_vis use self::#state_mod_ident::{ - #( #type_aliases_for_rustdoc as _, )* - }; + let on_unimplemented = + Self::on_unimplemented("can't finish building yet; not all required members are set"); - // This is 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. - #[allow(unnameable_types, unreachable_pub, clippy::redundant_pub_crate)] - #( #state_mod_docs )* - #vis_mod mod #state_mod_ident { - /// Marker trait implemented by members that are set. - #[::bon::private::rustversion::attr( - since(1.78.0), - diagnostic::on_unimplemented( - message = "the member `{Self}` was not set, but this method requires it to be set", - label = "the member `{Self}` was not set, but this method requires it to be set", - ) - )] - #vis_child trait IsSet { - #sealed_method - } + quote! { + /// Marker trait that indicates that all required members are set. + /// + /// In this state, the builder + #on_unimplemented + #vis_child trait IsComplete: State #maybe_assoc_type_bounds { + #sealed_method_decl + } - #[doc(hidden)] - impl IsSet for ::bon::private::Set { - fn __sealed(_: sealed::Sealed) {} - } + #[doc(hidden)] + impl IsComplete for State + where + #( + State::#required_members_pascal: IsSet, + )* + { + #sealed_method_impl + } + } + } - // use private::sealed::Sealed; - // - // /// Marker trait that indicates that the member is not set, i.e. none of its setters were called. - // /// - // /// You should use this trait bound, for example, if you want to extend the builder with custom - // /// setters. - // /// - // /// **Example:** - // /// - // /// ``` - // /// #[derive(bon::Builder)] - // /// struct Example { - // /// x: i32, - // /// y: i32, - // /// } - // /// - // /// // Import the type aliases for transforming the builder's type state - // /// use example_builder::{SetX, SetY}; - // /// - // /// // Add method to the builder - // /// impl ExampleBuilder { - // /// fn x_doubled(self, value: i32) -> ExampleBuilder> - // /// where - // /// // The code won't compile without this bound - // /// State::X: bon::IsUnset, - // /// { - // /// self.x(value * 2) - // /// } - // /// - // /// fn y_doubled(self, value: i32) -> ExampleBuilder> - // /// where - // /// // The code won't compile without this bound - // /// State::Y: bon::IsUnset, - // /// { - // /// self.y(value * 2) - // /// } - // /// } - // /// - // /// let example = Example::builder() - // /// .x_doubled(2) - // /// .y_doubled(3) - // /// .build(); - // /// - // /// assert_eq!(example.x, 4); - // /// assert_eq!(example.y, 6); - // /// ``` - // #[rustversion::attr( - // since(1.78.0), - // diagnostic::on_unimplemented( - // message = "the member `{Self}` was already set, but this method requires it to be unset", - // label = "the member `{Self}` was already set, but this method requires it to be unset", - // ) - // )] - // pub trait IsUnset: Sealed {} - - // /// Marker trait that indicates that the member is set, i.e. at least one of its setters was called. - // // TODO: add examples (they would require having custom renames and visibility overrides for default setters) - // #[rustversion::attr( - // since(1.78.0), - // diagnostic::on_unimplemented( - // message = "the member `{Self}` was not set, but this method requires it to be set", - // label = "the member `{Self}` was not set, but this method requires it to be set", - // ) - // )] - // pub trait IsSet: Sealed {} - - /// Marker trait implemented by members that are not set. - #[::bon::private::rustversion::attr( - since(1.78.0), - diagnostic::on_unimplemented( - message = "the member `{Self}` was already set, but this method requires it to be unset", - label = "the member `{Self}` was already set, but this method requires it to be unset", - ) - )] - #vis_child trait IsUnset { - #sealed_method - } + fn is_set_trait(&self) -> TokenStream { + let vis_child = &self.builder_gen.state_mod.vis_child; + let sealed_method_decl = &self.sealed_method_decl; + let sealed_method_impl = &self.sealed_method_impl; - #[doc(hidden)] - impl IsUnset for ::bon::private::Unset { - fn __sealed(_: sealed::Sealed) {} - } + let on_unimplemented = Self::on_unimplemented( + "the member `{Self}` was not set, but this method requires it to be set", + ); - #[::bon::private::rustversion::attr( - since(1.78.0), - diagnostic::on_unimplemented( - message = "can't finish building yet; not all required members are set", - label = "can't finish building yet; not all required members are set", - ) - )] - #vis_child trait IsComplete: State #is_complete_assoc_type_bounds { - #sealed_method - } + quote! { + /// Marker trait that indicates that the member is set, i.e. at least + /// one of its setters was called. + // TODO: add examples (they would require having custom renames and + // visibility overrides for default setters) + #on_unimplemented + #vis_child trait IsSet { + #sealed_method_decl + } - #[doc(hidden)] - impl IsComplete for State - where - #( - State::#required_members_pascal: IsSet, - )* - { - fn __sealed(_: sealed::Sealed) {} - } + #[doc(hidden)] + impl IsSet for ::bon::private::Set { + #sealed_method_impl + } + } + } - /// Builder's type state specifies if members are set or not (unset). - /// - /// 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*` type aliases available in this module. - /// - /// [`IsSet`]: ::bon::IsSet - /// [`IsUnset`]: ::bon::IsUnset - #vis_child trait State: ::core::marker::Sized { - #( - #[doc = #assoc_types_docs] - type #stateful_members_pascal: ::bon::private::MemberState< - self::members::#stateful_members_idents - >; - )* - - #sealed_method - } + fn is_unset_trait(&self) -> TokenStream { + let vis_child = &self.builder_gen.state_mod.vis_child; + let sealed_method_decl = &self.sealed_method_decl; + let sealed_method_impl = &self.sealed_method_impl; - mod sealed { - #vis_child_child enum Sealed {} - } + let on_unimplemented = Self::on_unimplemented( + "the member `{Self}` was already set, but this method requires it to be unset", + ); - // Using `self::State` explicitly to avoid name conflicts with the - // members named `state` which would create a generic param named `State` - // that would shadow the trait `State` in the same scope. - #[doc(hidden)] - impl<#( - #stateful_members_pascal: ::bon::private::MemberState< - self::members::#stateful_members_idents - >, - )*> - self::State for ( #(#stateful_members_pascal,)* ) - { - #( type #stateful_members_pascal = #stateful_members_pascal; )* - - fn __sealed(_: self::sealed::Sealed) {} - } + quote! { + /// Marker trait implemented by members that are not set. + #on_unimplemented + #vis_child trait IsUnset { + #sealed_method_decl + } - /// Initial state of the builder where all members are unset - #vis_child type AllUnset = ( - #(::bon::private::Unset,)* - ); + #[doc(hidden)] + impl IsUnset for ::bon::private::Unset { + #sealed_method_impl + } + } + } - #[deprecated = - "this is an implementation detail and should not be \ - used directly; use the Set* type aliases to control the \ - state of members instead" - ] - #[doc(hidden)] - #[allow(non_camel_case_types)] - mod members { - #( - #vis_child_child enum #stateful_members_idents {} - )* - } + fn members_names_mod(&self) -> TokenStream { + let vis_child_child = &self.builder_gen.state_mod.vis_child_child; + let stateful_members_snake = &self.stateful_members_snake; - #( #state_transition_aliases )* + quote! { + #[deprecated = + "this is an implementation detail and should not be \ + used directly; use the Set* type aliases to control the \ + state of members instead" + ] + #[doc(hidden)] + #[allow(non_camel_case_types)] + mod members { + #( + #vis_child_child enum #stateful_members_snake {} + )* } } } + + fn on_unimplemented(message: &str) -> TokenStream { + quote! { + #[::bon::private::rustversion::attr( + since(1.78.0), + diagnostic::on_unimplemented(message = #message, label = #message) + )] + } + } } From cc48c18805756793e329d709ae0b0c6a09e50488 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 4 Oct 2024 18:11:46 +0000 Subject: [PATCH 053/119] Add tests for `overwritable` --- .../src/builder/builder_gen/member/named.rs | 2 +- .../integration/builder/attr_overwritable.rs | 103 ++++++++++++++++++ bon/tests/integration/builder/mod.rs | 1 + .../integration/ui/compile_fail/errors.stderr | 4 +- 4 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 bon/tests/integration/builder/attr_overwritable.rs diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index d6dea3a2..fb48af78 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -201,7 +201,7 @@ impl NamedMember { on_params, param_name: params::BlanketParamName::Overwritable, member_params: &self.params, - scrutinee: &self.norm_ty, + scrutinee: self.underlying_norm_ty(), origin: self.origin, } .eval()?; diff --git a/bon/tests/integration/builder/attr_overwritable.rs b/bon/tests/integration/builder/attr_overwritable.rs new file mode 100644 index 00000000..27dbf87f --- /dev/null +++ b/bon/tests/integration/builder/attr_overwritable.rs @@ -0,0 +1,103 @@ +mod smoke { + use crate::prelude::*; + use core::fmt; + + #[test] + fn test_struct() { + #[derive(Debug, Builder)] + #[allow(dead_code)] + struct Sut { + #[builder(overwritable)] + a: u32, + + #[builder(overwritable)] + b: Option, + + #[builder(overwritable, default)] + c: u32, + } + + assert_debug_eq( + Sut::builder().a(1).a(2).b(3).b(4).c(5).c(6).build(), + expect!["Sut { a: 2, b: Some(4), c: 6 }"], + ); + } + + #[test] + fn test_free_fn() { + #[builder] + fn sut( + #[builder(overwritable)] a: u32, + #[builder(overwritable)] b: Option, + #[builder(overwritable, default)] c: u32, + ) -> impl fmt::Debug { + (a, b, c) + } + + assert_debug_eq( + sut().a(1).a(2).b(3).b(4).c(5).c(6).call(), + expect!["(2, Some(4), 6)"], + ); + } + + #[test] + fn test_assoc_method() { + struct Sut; + + #[bon] + impl Sut { + #[builder] + fn sut( + #[builder(overwritable)] a: u32, + #[builder(overwritable)] b: Option, + #[builder(overwritable, default)] c: u32, + ) -> impl fmt::Debug { + (a, b, c) + } + + #[builder] + fn with_self( + &self, + #[builder(overwritable)] a: u32, + #[builder(overwritable)] b: Option, + #[builder(overwritable, default)] c: u32, + ) -> impl fmt::Debug { + let _ = self; + (a, b, c) + } + } + + assert_debug_eq( + Sut::sut().a(1).a(2).b(3).b(4).c(5).c(6).call(), + expect!["(2, Some(4), 6)"], + ); + + assert_debug_eq( + Sut.with_self().a(1).a(2).b(3).b(4).c(5).c(6).call(), + expect!["(2, Some(4), 6)"], + ); + } +} + +mod on { + use crate::prelude::*; + + #[test] + fn test_struct() { + #[derive(Debug, Builder)] + #[allow(dead_code)] + #[builder(on(u32, overwritable))] + struct Sut { + a: u32, + b: Option, + + #[builder(default)] + c: u32, + } + + assert_debug_eq( + Sut::builder().a(1).a(2).b(3).b(4).c(5).c(6).build(), + expect!["Sut { a: 2, b: Some(4), c: 6 }"], + ); + } +} diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index 3adc9a34..a54cc7a7 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -1,4 +1,5 @@ mod attr_default; +mod attr_overwritable; mod attr_expose_positional_fn; mod attr_into; mod attr_on; diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr index 1c4c5524..1395758b 100644 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ b/bon/tests/integration/ui/compile_fail/errors.stderr @@ -267,9 +267,9 @@ error[E0277]: the member `Unset` was not set, but this method requires it 47 | let _ = Sut::builder().build(); | ^^^^^ the member `Unset` was not set, but this method requires it to be set | - = help: the trait `sut_builder::IsSet` is not implemented for `Unset`, which is required by `(Unset,): sut_builder::IsComplete` + = help: the trait `sut_builder::IsSet` is not implemented for `Unset`, which is required by `sut_builder::AllUnset: sut_builder::IsComplete` = help: the trait `sut_builder::IsSet` is implemented for `Set` -note: required for `(Unset,)` to implement `sut_builder::IsComplete` +note: required for `sut_builder::AllUnset` to implement `sut_builder::IsComplete` --> tests/integration/ui/compile_fail/errors.rs:42:18 | 42 | #[derive(Builder)] From c6104905c2c5edc6324b9ea5fa5398c747aa3912 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 4 Oct 2024 18:24:37 +0000 Subject: [PATCH 054/119] Add comment --- bon-macros/src/builder/builder_gen/member/named.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index fb48af78..27f06281 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -197,6 +197,9 @@ impl NamedMember { pub(crate) fn merge_on_params(&mut self, on_params: &[OnParams]) -> Result { self.merge_param_into(on_params)?; + // FIXME: refactor this to make it more consistent with `into` + // and allow for non-boolean flags in `OnParams`. E.g. add support + // for `with = closure` to `on` as well. self.params.overwritable = params::EvalBlanketFlagParam { on_params, param_name: params::BlanketParamName::Overwritable, From 6cbdcde4e9b89aafce0a9e51d56a9808df04e316 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 4 Oct 2024 19:33:35 +0000 Subject: [PATCH 055/119] The great rename and identifier fixes --- .../builder/builder_gen/builder_derives.rs | 6 +- .../src/builder/builder_gen/input_fn.rs | 97 ++++++++-------- .../src/builder/builder_gen/input_struct.rs | 59 ++++++---- .../builder_gen/member/into_conversion.rs | 8 +- .../src/builder/builder_gen/member/mod.rs | 57 +++------ .../src/builder/builder_gen/member/named.rs | 108 ++++++++++++------ bon-macros/src/builder/builder_gen/mod.rs | 9 +- .../src/builder/builder_gen/setter_methods.rs | 28 ++--- .../src/builder/builder_gen/state_mod.rs | 18 ++- bon-macros/src/builder/item_fn.rs | 9 +- bon-macros/src/builder/item_impl.rs | 9 +- bon-macros/src/normalization/mod.rs | 19 +++ bon-macros/src/util/ident.rs | 2 + 13 files changed, 245 insertions(+), 184 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index b7e2f3ed..5f84781c 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -63,7 +63,7 @@ impl BuilderGenCtx { let clone_start_fn_args = self.start_fn_args().next().map(|_| { let clone_start_fn_args = self.start_fn_args().map(|arg| { - let ty = &arg.base.norm_ty; + let ty = &arg.base.ty.norm; let index = &arg.index; quote! { <#ty as #clone>::clone(&self.__private_start_fn_args.#index) @@ -130,7 +130,7 @@ impl BuilderGenCtx { match member { Member::Named(member) => { let member_index = &member.index; - let member_ident_str = member.public_snake().to_string(); + let member_ident_str = &member.name.snake_raw_str; let member_ty = member.underlying_norm_ty(); Some(quote! { if let ::core::option::Option::Some(value) = &self.__private_named_members.#member_index { @@ -144,7 +144,7 @@ impl BuilderGenCtx { Member::StartFnArg(member) => { let member_index = &member.index; let member_ident_str = member.base.ident.to_string(); - let member_ty = &member.base.norm_ty; + let member_ty = &member.base.ty.norm; Some(quote! { output.field( #member_ident_str, diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index 2698b7fe..0a2565c4 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -4,7 +4,7 @@ use super::{ Member, MemberOrigin, RawMember, }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; -use crate::normalization::NormalizeSelfTy; +use crate::normalization::{NormalizeSelfTy, SyntaxVariant}; use crate::parsing::{ItemParams, SpannedKey}; use crate::util::prelude::*; use darling::util::SpannedValue; @@ -61,8 +61,7 @@ impl FromMeta for ExposePositionalFnParams { } pub(crate) struct FnInputCtx { - pub(crate) orig_fn: syn::ItemFn, - pub(crate) norm_fn: syn::ItemFn, + pub(crate) fn_item: SyntaxVariant, pub(crate) impl_ctx: Option>, pub(crate) params: FnInputParams, } @@ -106,7 +105,7 @@ impl FnInputCtx { } fn assoc_method_receiver_ctx(&self) -> Result> { - let receiver = match self.norm_fn.sig.receiver() { + let receiver = match self.fn_item.norm.sig.receiver() { Some(receiver) => receiver, None => return Ok(None), }; @@ -135,13 +134,13 @@ impl FnInputCtx { fn generics(&self) -> Generics { let impl_ctx = self.impl_ctx.as_ref(); - let norm_fn_params = &self.norm_fn.sig.generics.params; + 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.norm_fn.sig.generics.where_clause.clone(), + self.fn_item.norm.sig.generics.where_clause.clone(), impl_ctx.and_then(|impl_ctx| impl_ctx.generics.where_clause.clone()), ]; @@ -167,7 +166,7 @@ impl FnInputCtx { return format_ident!("{}Builder", self.self_ty_prefix().unwrap_or_default()); } - let pascal_case_fn = self.norm_fn.sig.ident.snake_to_pascal_case(); + let pascal_case_fn = self.fn_item.norm.sig.ident.snake_to_pascal_case(); format_ident!( "{}{pascal_case_fn}Builder", @@ -176,7 +175,7 @@ impl FnInputCtx { } pub(crate) fn adapted_fn(&self) -> Result { - let mut orig = self.orig_fn.clone(); + let mut orig = self.fn_item.orig.clone(); let params = self.params.expose_positional_fn.as_ref(); @@ -187,7 +186,7 @@ impl FnInputCtx { .clone() // If exposing of positional fn is enabled without an explicit // visibility, then just use the visibility of the original function. - .unwrap_or_else(|| self.norm_fn.vis.clone()) + .unwrap_or_else(|| self.fn_item.norm.vis.clone()) }) // By default we change the positional function's visibility to private // to avoid exposing it to the surrounding code. The surrounding code is @@ -274,7 +273,7 @@ impl FnInputCtx { } fn is_method_new(&self) -> bool { - self.impl_ctx.is_some() && self.norm_fn.sig.ident == "new" + self.impl_ctx.is_some() && self.fn_item.norm.sig.ident == "new" } pub(crate) fn into_builder_gen_ctx(self) -> Result { @@ -282,37 +281,37 @@ impl FnInputCtx { if self.impl_ctx.is_none() { let explanation = "\ - but #[bon] attribute \ - is absent on top of the impl block. This additional #[bon] \ - attribute on the impl block is required for the macro to see \ - the type of `Self` and properly generate the builder struct \ - definition adjacently to the impl block."; + but #[bon] attribute is absent on top of the impl block; this \ + additional #[bon] attribute on the impl block is required for \ + the macro to see the type of `Self` and properly generate + the builder struct definition adjacently to the impl block."; - if let Some(receiver) = &self.orig_fn.sig.receiver() { + if let Some(receiver) = &self.fn_item.orig.sig.receiver() { bail!( &receiver.self_token, - "Function contains a `self` parameter {explanation}" + "function contains a `self` parameter {explanation}" ); } let mut ctx = FindSelfReference::default(); - ctx.visit_item_fn(&self.orig_fn); + ctx.visit_item_fn(&self.fn_item.orig); if let Some(self_span) = ctx.self_span { bail!( &self_span, - "Function contains a `Self` type reference {explanation}" + "function contains a `Self` type reference {explanation}" ); } } let builder_ident = self.builder_ident(); - fn typed_args(func: &syn::ItemFn) -> impl Iterator { - func.sig.inputs.iter().filter_map(syn::FnArg::as_typed) - } + let typed_args = self + .fn_item + .apply_ref(|fn_item| fn_item.sig.inputs.iter().filter_map(syn::FnArg::as_typed)); - let members = typed_args(&self.norm_fn) - .zip(typed_args(&self.orig_fn)) + let members = typed_args + .norm + .zip(typed_args.orig) .map(|(norm_arg, orig_arg)| { let pat = match norm_arg.pat.as_ref() { syn::Pat::Ident(pat) => pat, @@ -323,11 +322,15 @@ impl FnInputCtx { ), }; + let ty = SyntaxVariant { + norm: norm_arg.ty.clone(), + orig: orig_arg.ty.clone(), + }; + Ok(RawMember { attrs: &norm_arg.attrs, ident: pat.ident.clone(), - norm_ty: norm_arg.ty.clone(), - orig_ty: orig_arg.ty.clone(), + ty, }) }) .collect::>>()?; @@ -348,7 +351,7 @@ impl FnInputCtx { let start_fn_ident = if is_method_new { format_ident!("builder") } else { - self.norm_fn.sig.ident.clone() + self.fn_item.norm.sig.ident.clone() }; let ItemParams { @@ -379,16 +382,17 @@ impl FnInputCtx { let finish_fn = FinishFn { ident: finish_fn_ident, vis: finish_fn_vis.map(SpannedKey::into_value), - unsafety: self.norm_fn.sig.unsafety, - asyncness: self.norm_fn.sig.asyncness, - must_use: get_must_use_attribute(&self.norm_fn.attrs)?, + unsafety: self.fn_item.norm.sig.unsafety, + asyncness: self.fn_item.norm.sig.asyncness, + must_use: get_must_use_attribute(&self.fn_item.norm.attrs)?, body: Box::new(finish_fn_body), - output: self.norm_fn.sig.output, + output: self.fn_item.norm.sig.output, attrs: finish_fn_docs, }; let fn_allows = self - .norm_fn + .fn_item + .norm .attrs .iter() .filter_map(syn::Attribute::to_allow); @@ -408,32 +412,29 @@ impl FnInputCtx { // It's supposed to be the same as the original function's visibility. vis: None, - attrs: self.norm_fn.attrs.into_iter().filter(<_>::is_doc).collect(), + attrs: self + .fn_item + .norm + .attrs + .into_iter() + .filter(<_>::is_doc) + .collect(), // Override on the start fn to use the the generics from the // target function itself. We don't need to duplicate the generics // from the impl block here. generics: Some(Generics::new( - Vec::from_iter(self.norm_fn.sig.generics.params), - self.norm_fn.sig.generics.where_clause, + Vec::from_iter(self.fn_item.norm.sig.generics.params), + self.fn_item.norm.sig.generics.where_clause, )), }; + let builder_type = self.params.base.builder_type; let builder_type = BuilderTypeParams { ident: builder_ident, derives: self.params.base.derive, - docs: self - .params - .base - .builder_type - .docs - .map(SpannedKey::into_value), - vis: self - .params - .base - .builder_type - .vis - .map(SpannedKey::into_value), + docs: builder_type.docs.map(SpannedKey::into_value), + vis: builder_type.vis.map(SpannedKey::into_value), }; BuilderGenCtx::new(BuilderGenCtxParams { @@ -445,7 +446,7 @@ impl FnInputCtx { assoc_method_ctx, generics, - orig_item_vis: self.norm_fn.vis, + orig_item_vis: self.fn_item.norm.vis, builder_type, state_mod: self.params.base.state_mod, diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index d3060fec..4d404872 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -4,6 +4,7 @@ use super::{ RawMember, }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; +use crate::normalization::SyntaxVariant; use crate::parsing::{ItemParams, ItemParamsParsing, SpannedKey}; use crate::util::prelude::*; use darling::FromMeta; @@ -59,8 +60,7 @@ impl StructInputParams { } pub(crate) struct StructInputCtx { - orig_struct: syn::ItemStruct, - norm_struct: syn::ItemStruct, + struct_item: SyntaxVariant, params: StructInputParams, struct_ty: syn::Type, } @@ -88,26 +88,30 @@ impl StructInputCtx { } .visit_item_struct_mut(&mut norm_struct); + let struct_item = SyntaxVariant { + orig: orig_struct, + norm: norm_struct, + }; + Ok(Self { - orig_struct, - norm_struct, + struct_item, params, struct_ty, }) } pub(crate) fn into_builder_gen_ctx(self) -> Result { - fn fields(struct_item: &syn::ItemStruct) -> Result<&syn::FieldsNamed> { - match &struct_item.fields { + 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(&self.norm_struct)?; - let orig_fields = fields(&self.orig_struct)?; + let norm_fields = fields.norm?; + let orig_fields = fields.orig?; let members = norm_fields .named @@ -118,11 +122,15 @@ impl StructInputCtx { 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, - norm_ty: Box::new(norm_field.ty.clone()), - orig_ty: Box::new(orig_field.ty.clone()), + ty, }) }) .collect::>>()?; @@ -130,12 +138,18 @@ impl StructInputCtx { let members = Member::from_raw(&self.params.base.on, MemberOrigin::StructField, members)?; let generics = Generics::new( - self.norm_struct.generics.params.iter().cloned().collect(), - self.norm_struct.generics.where_clause.clone(), + 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.norm_struct.ident.clone(), + struct_ident: self.struct_item.norm.ident.clone(), }; let ItemParams { @@ -146,7 +160,7 @@ impl StructInputCtx { let start_fn_ident = start_fn_ident .map(SpannedKey::into_value) - .unwrap_or_else(|| syn::Ident::new("builder", self.norm_struct.ident.span())); + .unwrap_or_else(|| syn::Ident::new("builder", self.struct_item.norm.ident.span())); let ItemParams { name: finish_fn_ident, @@ -183,7 +197,7 @@ impl StructInputCtx { .unwrap_or_else(|| { let docs = format!( "Create an instance of [`{}`] using the builder syntax", - self.norm_struct.ident + self.struct_item.norm.ident ); vec![syn::parse_quote!(#[doc = #docs])] @@ -202,7 +216,8 @@ impl StructInputCtx { }); let allow_attrs = self - .norm_struct + .struct_item + .norm .attrs .iter() .filter_map(syn::Attribute::to_allow) @@ -211,9 +226,9 @@ impl StructInputCtx { let builder_type = { let ItemParams { name, vis, docs } = self.params.base.builder_type; - let builder_ident = name - .map(SpannedKey::into_value) - .unwrap_or_else(|| format_ident!("{}Builder", self.norm_struct.ident.raw_name())); + let builder_ident = name.map(SpannedKey::into_value).unwrap_or_else(|| { + format_ident!("{}Builder", self.struct_item.norm.ident.raw_name()) + }); BuilderTypeParams { derives: self.params.base.derive, @@ -232,7 +247,7 @@ impl StructInputCtx { assoc_method_ctx, generics, - orig_item_vis: self.norm_struct.vis, + orig_item_vis: self.struct_item.norm.vis, builder_type, state_mod: self.params.base.state_mod, diff --git a/bon-macros/src/builder/builder_gen/member/into_conversion.rs b/bon-macros/src/builder/builder_gen/member/into_conversion.rs index e3ec09ce..2474f5b4 100644 --- a/bon-macros/src/builder/builder_gen/member/into_conversion.rs +++ b/bon-macros/src/builder/builder_gen/member/into_conversion.rs @@ -35,7 +35,7 @@ impl PositionalFnArgMember { // 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.orig_ty.as_ref(); + let scrutinee = self.ty.orig.as_ref(); self.params.into = EvalBlanketFlagParam { on_params, @@ -51,13 +51,13 @@ impl PositionalFnArgMember { pub(crate) fn fn_input_param(&self) -> TokenStream { let has_into = self.params.into.is_present(); - let norm_ty = &self.norm_ty; + let ty = &self.ty.norm; let ident = &self.ident; if has_into { - quote! { #ident: impl Into<#norm_ty> } + quote! { #ident: impl Into<#ty> } } else { - quote! { #ident: #norm_ty } + quote! { #ident: #ty } } } diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 34048cf0..ddecded1 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -6,6 +6,7 @@ pub(crate) use named::*; pub(crate) use params::SetterClosure; use super::builder_params::OnParams; +use crate::normalization::SyntaxVariant; use crate::parsing::SpannedKey; use crate::util::prelude::*; use darling::FromAttributes; @@ -66,11 +67,8 @@ pub(crate) struct PositionalFnArgMember { /// Original identifier of the member pub(crate) ident: syn::Ident, - /// Normalized type of the member - pub(crate) norm_ty: Box, - - /// Original type of the member (not normalized) - pub(crate) orig_ty: Box, + /// Type of the member + pub(crate) ty: SyntaxVariant>, /// Parameters configured by the user explicitly via attributes pub(crate) params: MemberParams, @@ -81,6 +79,7 @@ pub(crate) struct PositionalFnArgMember { pub(crate) struct SkippedMember { pub(crate) ident: syn::Ident, + /// Normalized type of the member pub(crate) norm_ty: Box, /// Value to assign to the member @@ -90,8 +89,7 @@ pub(crate) struct SkippedMember { pub(crate) struct RawMember<'a> { pub(crate) attrs: &'a [syn::Attribute], pub(crate) ident: syn::Ident, - pub(crate) norm_ty: Box, - pub(crate) orig_ty: Box, + pub(crate) ty: SyntaxVariant>, } impl Member { @@ -141,17 +139,12 @@ impl Member { let mut named_count = 0; for (member, params) in members { - let RawMember { - attrs, - ident: orig_ident, - norm_ty, - orig_ty, - } = member; + let RawMember { attrs, ident, ty } = member; if let Some(value) = params.skip { output.push(Self::Skipped(SkippedMember { - ident: orig_ident, - norm_ty, + ident, + norm_ty: ty.norm, value, })); continue; @@ -181,27 +174,11 @@ impl Member { // itself which is also useful for people reading the source code. let docs = attrs.iter().filter(|attr| attr.is_doc()).cloned().collect(); - let orig_ident_str = orig_ident.to_string(); - let norm_ident = orig_ident_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_ident_str); - - // Preserve the original identifier span to make IDE's "go to definition" work correctly - // and make error messages point to the correct place. - let norm_ident = syn::Ident::new_maybe_raw(norm_ident, orig_ident.span()); - let norm_ident_pascal = norm_ident.snake_to_pascal_case(); - let mut member = NamedMember { index: named_count.into(), origin, - norm_ident_pascal, - orig_ident, - norm_ident, - norm_ty, - orig_ty, + name: MemberName::new(ident, ¶ms), + ty, params, docs, }; @@ -220,16 +197,16 @@ impl Member { impl Member { pub(crate) fn norm_ty(&self) -> &syn::Type { match self { - Self::Named(me) => &me.norm_ty, - Self::StartFnArg(me) => &me.base.norm_ty, - Self::FinishFnArg(me) => &me.norm_ty, + Self::Named(me) => &me.ty.norm, + Self::StartFnArg(me) => &me.base.ty.norm, + Self::FinishFnArg(me) => &me.ty.norm, Self::Skipped(me) => &me.norm_ty, } } pub(crate) fn orig_ident(&self) -> &syn::Ident { match self { - Self::Named(me) => &me.orig_ident, + Self::Named(me) => &me.name.orig, Self::StartFnArg(me) => &me.base.ident, Self::FinishFnArg(me) => &me.ident, Self::Skipped(me) => &me.ident, @@ -268,15 +245,13 @@ impl PositionalFnArgMember { let RawMember { attrs: _, ident, - norm_ty, - orig_ty, + ty, } = member; let mut me = Self { origin, ident, - norm_ty, - orig_ty, + ty, params, }; diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index 27f06281..1a9aecb6 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -2,38 +2,91 @@ use super::params::MemberParams; use super::{params, MemberOrigin}; use crate::builder::builder_gen::builder_params::OnParams; use crate::builder::builder_gen::member::params::SettersFnParams; +use crate::normalization::SyntaxVariant; use crate::parsing::{ItemParams, 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. It 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, params: &MemberParams) -> Self { + let snake = params.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 regular members. The index is 0-based. + /// Index of the member relative to other named members. The index is 0-based. pub(crate) index: syn::Index, - /// 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_ident: syn::Ident, - - /// Normalized version of `orig_ident`. Here we stripped the leading `_` from the - /// member name. This name is in the builder's API unless a `name` override is present. - pub(crate) norm_ident: syn::Ident, - - /// `PascalCase` version of the `norm_ident`. - pub(crate) norm_ident_pascal: syn::Ident, + /// 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, - /// Normalized type of the member that the builder should have setters for. - pub(crate) norm_ty: Box, - - /// Original type of the member (not normalized) - pub(crate) orig_ty: Box, + /// 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) params: MemberParams, @@ -55,7 +108,7 @@ impl NamedMember { self.validate_setters_params()?; - if self.params.transparent.is_present() && !self.norm_ty.is_option() { + if self.params.transparent.is_present() && !self.ty.norm.is_option() { bail!( &self.params.transparent.span(), "`#[builder(transparent)]` can only be applied to members of \ @@ -137,23 +190,10 @@ impl NamedMember { ); } - /// Returns the public identifier of the member that should be used in the - /// generated builder API where snake case is expected. - pub(crate) fn public_snake(&self) -> &syn::Ident { - self.params.name.as_ref().unwrap_or(&self.norm_ident) - } - - /// Returns the public identifier of the member that should be used in the - /// generated builder API where pascal case is expected. - pub(crate) fn public_pascal(&self) -> &syn::Ident { - // FIXME: this should try to use the pascal case version of the `name` parameter - &self.norm_ident_pascal - } - /// Returns `true` if this member is of `Option<_>` type, but returns `false` /// if `#[builder(transparent)]` is set. - fn is_special_option_ty(&self) -> bool { - !self.params.transparent.is_present() && self.norm_ty.is_option() + pub(crate) fn is_special_option_ty(&self) -> bool { + !self.params.transparent.is_present() && self.ty.norm.is_option() } /// Returns `false` if the member has a default value. It means this member @@ -173,13 +213,13 @@ impl NamedMember { /// Returns the normalized type of the member stripping the `Option<_>` /// wrapper if it's present unless `#[builder(transparent)]` is set. pub(crate) fn underlying_norm_ty(&self) -> &syn::Type { - self.underlying_ty(&self.norm_ty) + self.underlying_ty(&self.ty.norm) } /// Returns the original type of the member stripping the `Option<_>` /// wrapper if it's present unless `#[builder(transparent)]` is set. pub(crate) fn underlying_orig_ty(&self) -> &syn::Type { - self.underlying_ty(&self.orig_ty) + self.underlying_ty(&self.ty.orig) } fn underlying_ty<'m>(&'m self, ty: &'m syn::Type) -> &'m syn::Type { diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 7bb91960..0b7e9113 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -302,7 +302,7 @@ impl BuilderGenCtx { // The types of these members already appear in the struct in the types // of __private_named_members and __private_start_fn_args fields. Member::Named(_) | Member::StartFnArg(_) => None, - Member::FinishFnArg(member) => Some(member.norm_ty.as_ref()), + Member::FinishFnArg(member) => Some(member.ty.norm.as_ref()), Member::Skipped(member) => Some(member.norm_ty.as_ref()), } }); @@ -404,7 +404,7 @@ impl BuilderGenCtx { let mut start_fn_arg_types = self .start_fn_args() - .map(|member| &member.base.norm_ty) + .map(|member| &member.base.ty.norm) .peekable(); let start_fn_args_field = start_fn_arg_types.peek().is_some().then(|| { @@ -516,8 +516,9 @@ impl BuilderGenCtx { 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.params.transparent.is_present() && member.norm_ty.is_option() { - return member_field.to_token_stream(); + + if member.is_special_option_ty() { + return member_field; } quote! { diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs index 7dc66fa2..19c040ba 100644 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ b/bon-macros/src/builder/builder_gen/setter_methods.rs @@ -34,7 +34,7 @@ impl<'a> SettersCtx<'a> { member_init = quote!(Some(#member_init_override)); } else { - let member_type = self.member.norm_ty.as_ref(); + let member_type = self.member.ty.norm.as_ref(); let has_into = self.member.params.into.is_present(); if has_into { @@ -47,7 +47,7 @@ impl<'a> SettersCtx<'a> { } self.setter_method(Setter { - method_name: self.member.public_snake().clone(), + method_name: self.member.name.snake.clone(), fn_inputs, overwrite_docs: None, body: SetterBody::Default { member_init }, @@ -55,12 +55,12 @@ impl<'a> SettersCtx<'a> { } fn setters_for_optional_member(&self) -> TokenStream { - let member_name = self.member.public_snake().clone(); + let member_snake = self.member.name.snake.clone(); // Preserve the original identifier span to make IDE's "go to definition" work correctly let option_fn_name = syn::Ident::new( - &format!("maybe_{}", member_name.raw_name()), - member_name.span(), + &format!("maybe_{}", self.member.name.snake_raw_str), + member_snake.span(), ); let some_fn_inputs; @@ -138,7 +138,7 @@ impl<'a> SettersCtx<'a> { method_name: option_fn_name, fn_inputs: option_fn_inputs, overwrite_docs: Some(format!( - "Same as [`Self::{member_name}`], but accepts \ + "Same as [`Self::{member_snake}`], but accepts \ an `Option` as input. See that method's documentation for \ more details.", )), @@ -153,7 +153,7 @@ impl<'a> SettersCtx<'a> { // To be able to explicitly pass an `Option` value to the setter method // users need to use the `maybe_{member_ident}` method. Setter { - method_name: member_name, + method_name: member_snake, fn_inputs: some_fn_inputs, overwrite_docs: None, body: SetterBody::Custom(some_fn_body), @@ -260,9 +260,9 @@ impl<'a> SettersCtx<'a> { } }; - let member_pascal = &self.member.norm_ident_pascal; + let member_pascal = &self.member.name.pascal; - let state_transition = format_ident!("Set{}", self.member.norm_ident_pascal.raw_name()); + let state_transition = format_ident!("Set{}", self.member.name.pascal_str); let state_mod = &self.builder_gen.state_mod.ident; @@ -320,8 +320,8 @@ impl<'a> SettersCtx<'a> { } fn generate_docs_for_setter(&self) -> Vec { - let member_ident = self.member.public_snake(); - let start_fn_ident = &self.builder_gen.start_fn.ident; + let member_snake = &self.member.name.snake; + let start_fn = &self.builder_gen.start_fn.ident; let more = |start_fn_path: &std::fmt::Arguments<'_>| { format!(" See {start_fn_path} for more info.") @@ -342,11 +342,11 @@ impl<'a> SettersCtx<'a> { }; let prefix = darling::util::path_to_string(&ty_path.path); - more(&format_args!("[`{prefix}::{start_fn_ident}()`]")) + more(&format_args!("[`{prefix}::{start_fn}()`]")) }) - .unwrap_or_else(|| more(&format_args!("[`{start_fn_ident}()`]"))); + .unwrap_or_else(|| more(&format_args!("[`{start_fn}()`]"))); - let docs = format!("Sets the value of `{member_ident}`.{suffix}"); + let docs = format!("Sets the value of `{member_snake}`.{suffix}"); vec![syn::parse_quote!(#[doc = #docs])] } diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index 8de30e78..25f48702 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -1,5 +1,4 @@ use super::BuilderGenCtx; -use crate::builder::builder_gen::member::NamedMember; use crate::util::prelude::*; pub(super) struct StateModGenCtx<'a> { @@ -17,12 +16,12 @@ impl<'a> StateModGenCtx<'a> { stateful_members_snake: builder_gen .stateful_members() - .map(NamedMember::public_snake) + .map(|member| &member.name.snake) .collect(), stateful_members_pascal: builder_gen .stateful_members() - .map(NamedMember::public_pascal) + .map(|member| &member.name.pascal) .collect(), // A method without `self` makes the trait non-object safe, @@ -88,12 +87,12 @@ impl<'a> StateModGenCtx<'a> { .map(move |member| { let states = self.builder_gen.stateful_members().map(|other_member| { if other_member.is(member) { - let ident = member.public_snake(); + let member_snake = &member.name.snake; quote! { - ::bon::private::Set + ::bon::private::Set } } else { - let member_pascal = &other_member.norm_ident_pascal; + let member_pascal = &other_member.name.pascal; quote! { ::#member_pascal } @@ -110,9 +109,8 @@ impl<'a> StateModGenCtx<'a> { .builder_gen .stateful_members() .map(|member| { - let member_snake = member.public_snake(); - let member_pascal = member.public_pascal(); - let alias = format_ident!("Set{}", member_pascal.raw_name()); + let alias = format_ident!("Set{}", member.name.pascal_str); + let member_snake = &member.name.snake; let docs = format!( "Returns a [`State`] that has [`IsSet`] implemented for `{member_snake}`\n\ @@ -225,7 +223,7 @@ impl<'a> StateModGenCtx<'a> { .builder_gen .named_members() .filter(|member| member.is_required()) - .map(|member| &member.norm_ident_pascal) + .map(|member| &member.name.pascal) .collect::>(); let maybe_assoc_type_bounds = cfg!(feature = "msrv-1-79-0").then(|| { diff --git a/bon-macros/src/builder/item_fn.rs b/bon-macros/src/builder/item_fn.rs index e6d6b549..7b9bcaa5 100644 --- a/bon-macros/src/builder/item_fn.rs +++ b/bon-macros/src/builder/item_fn.rs @@ -1,5 +1,6 @@ use super::builder_gen::input_fn::{FnInputCtx, FnInputParams}; use super::builder_gen::MacroOutput; +use crate::normalization::SyntaxVariant; use crate::util::prelude::*; use syn::visit_mut::VisitMut; @@ -9,9 +10,13 @@ pub(crate) fn generate(params: FnInputParams, orig_fn: syn::ItemFn) -> Result Result { + /// 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 } + } +} diff --git a/bon-macros/src/util/ident.rs b/bon-macros/src/util/ident.rs index 64f876b7..c68d87f7 100644 --- a/bon-macros/src/util/ident.rs +++ b/bon-macros/src/util/ident.rs @@ -18,6 +18,8 @@ pub(crate) trait IdentExt { /// 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 From a342462bcdf7003f36ebd1f585f78029fa9a19f6 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 5 Oct 2024 12:35:02 +0000 Subject: [PATCH 056/119] Fix mentions of the old `#[builder]` syntax in the "Fallible builders" section --- website/guide/patterns/fallible-builders.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/guide/patterns/fallible-builders.md b/website/guide/patterns/fallible-builders.md index 20f6ebb8..bbc2940b 100644 --- a/website/guide/patterns/fallible-builders.md +++ b/website/guide/patterns/fallible-builders.md @@ -2,7 +2,7 @@ With `bon`, you can write a builder that validates its inputs and returns a `Result`. It's possible to do this only via the function or associated method syntax. -If you need to build a struct and do some validation, you won't be able to use the `#[builder]` annotation on the struct for that. You'll *have to* define your logic in the associated constructor method (e.g. `new`). +If you need to build a struct and do some validation, you won't be able to use the `#[derive(Builder)]` annotation on the struct for that. You'll *have to* define your logic in the associated constructor method (e.g. `new`). **Example:** @@ -38,4 +38,4 @@ if let Err(error) = result { } ``` -If you have a use case for some convenience attributes to do automatic validations using the `#[builder]` macro with the struct syntax, then add a 👍 reaction to [this Github issue](https://github.com/elastio/bon/issues/34). +If you have a use case for some convenience attributes to do automatic validations using the `#[derive(Builder)]` macro with the struct syntax, then add a 👍 reaction to [this Github issue](https://github.com/elastio/bon/issues/34). From 1275d48a904c1a2e9d1deaf652dac0fc0716259e Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 5 Oct 2024 18:30:43 +0000 Subject: [PATCH 057/119] Add a workaround for RA/proc_macro2 bugs on invalid punct --- Cargo.lock | 16 ++++- bon-macros/Cargo.toml | 4 ++ bon-macros/src/error.rs | 17 ++++- bon-macros/src/lib.rs | 3 + bon-macros/src/tests.rs | 66 +++++++++++++++++++ .../tests/snapshots/bon_incomplete_if.rs | 1 + 6 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 bon-macros/src/tests.rs create mode 100644 bon-macros/tests/snapshots/bon_incomplete_if.rs diff --git a/Cargo.lock b/Cargo.lock index e803efcc..40b15145 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,7 +110,9 @@ name = "bon-macros" version = "2.3.0" dependencies = [ "darling", + "expect-test", "ident_case", + "prettyplease", "proc-macro2", "quote", "syn", @@ -658,6 +660,16 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "prettyplease" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -912,9 +924,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.72" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", diff --git a/bon-macros/Cargo.toml b/bon-macros/Cargo.toml index 62ab13cd..dfc36858 100644 --- a/bon-macros/Cargo.toml +++ b/bon-macros/Cargo.toml @@ -91,3 +91,7 @@ syn = { version = "2.0.56", features = ["full", "visit-mut", "visit"] } # after the `self.b()` doesn't imply that the member `a` is set, and thus `build()` # can't be called. msrv-1-79-0 = [] + +[dev-dependencies] +prettyplease = "0.2" +expect-test = "1.4.1" diff --git a/bon-macros/src/error.rs b/bon-macros/src/error.rs index 99cfaf24..bb3fd49c 100644 --- a/bon-macros/src/error.rs +++ b/bon-macros/src/error.rs @@ -43,14 +43,27 @@ impl Parse for Fallback { let fallback: Self = syn::parse2(group.stream())?; let new_group = proc_macro2::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]); + // Workaround for the RA bug where it generates an invalid Punct token tree with + // the character `{`, which is amplified by a bug in `proc_macro2` where its `Punct` + // doesn't panic early on invalid `Punct`. + // + // If this `extend` panics it means there are some invalid token trees in the input. + // We can't do anything about it, and we just ignore them. + // + // ## 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) + let _can_panic = + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + output.extend([tt]); + })); } } diff --git a/bon-macros/src/lib.rs b/bon-macros/src/lib.rs index ef2930c5..1c2004a1 100644 --- a/bon-macros/src/lib.rs +++ b/bon-macros/src/lib.rs @@ -19,6 +19,9 @@ mod normalization; mod parsing; mod util; +#[cfg(test)] +mod tests; + /// Generates a builder for the function or method it's placed on. /// /// ## Quick examples diff --git a/bon-macros/src/tests.rs b/bon-macros/src/tests.rs new file mode 100644 index 00000000..857ebf63 --- /dev/null +++ b/bon-macros/src/tests.rs @@ -0,0 +1,66 @@ +use crate::util::prelude::*; +use expect_test::expect_file; + +#[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()); + + let snapshot_path = format!( + "{}/tests/snapshots/{test_name}.rs", + env!("CARGO_MANIFEST_DIR") + ); + let expected = expect_file![snapshot_path]; + + expected.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 + } + } +} diff --git a/bon-macros/tests/snapshots/bon_incomplete_if.rs b/bon-macros/tests/snapshots/bon_incomplete_if.rs new file mode 100644 index 00000000..e06c730a --- /dev/null +++ b/bon-macros/tests/snapshots/bon_incomplete_if.rs @@ -0,0 +1 @@ +:: core :: compile_error ! { "unexpected end of input, expected curly braces" } impl Sut { fn sut () { if 1 } } \ No newline at end of file From 41038ebddeb5dfb6b56dbeedee9ceb126b2dc8a9 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 8 Oct 2024 01:44:54 +0000 Subject: [PATCH 058/119] Setters configs compile :D --- .../src/builder/builder_gen/member/mod.rs | 2 +- bon-macros/src/builder/builder_gen/mod.rs | 4 +- .../src/builder/builder_gen/setter_methods.rs | 365 -------------- .../src/builder/builder_gen/setters/mod.rs | 456 ++++++++++++++++++ bon-macros/src/lib.rs | 3 +- bon-macros/src/parsing/item_params.rs | 14 + 6 files changed, 475 insertions(+), 369 deletions(-) delete mode 100644 bon-macros/src/builder/builder_gen/setter_methods.rs create mode 100644 bon-macros/src/builder/builder_gen/setters/mod.rs diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index ddecded1..1e20a6e8 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -3,7 +3,7 @@ mod named; mod params; pub(crate) use named::*; -pub(crate) use params::SetterClosure; +pub(crate) use params::*; use super::builder_params::OnParams; use crate::normalization::SyntaxVariant; diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 0b7e9113..a169feef 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -2,7 +2,7 @@ mod builder_derives; mod builder_params; mod member; mod models; -mod setter_methods; +mod setters; mod state_mod; pub(crate) mod input_fn; @@ -15,7 +15,7 @@ use member::{ use models::{ AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, }; -use setter_methods::SettersCtx; +use setters::SettersCtx; pub(crate) struct MacroOutput { pub(crate) start_fn: syn::ItemFn, diff --git a/bon-macros/src/builder/builder_gen/setter_methods.rs b/bon-macros/src/builder/builder_gen/setter_methods.rs deleted file mode 100644 index 19c040ba..00000000 --- a/bon-macros/src/builder/builder_gen/setter_methods.rs +++ /dev/null @@ -1,365 +0,0 @@ -use super::member::SetterClosure; -use super::{BuilderGenCtx, NamedMember}; -use crate::util::prelude::*; - -pub(crate) struct SettersCtx<'a> { - builder_gen: &'a BuilderGenCtx, - member: &'a NamedMember, -} - -impl<'a> SettersCtx<'a> { - pub(crate) fn new(builder_gen: &'a BuilderGenCtx, member: &'a NamedMember) -> Self { - Self { - builder_gen, - member, - } - } - - pub(crate) fn setter_methods(&self) -> TokenStream { - if self.member.is_required() { - self.setters_for_required_member() - } else { - self.setters_for_optional_member() - } - } - - fn setters_for_required_member(&self) -> TokenStream { - let fn_inputs; - let member_init; - - if let Some(closure) = &self.member.params.with { - fn_inputs = Self::inputs_from_closure(closure); - - let member_init_override = self.member_init_from_closure(closure); - - member_init = quote!(Some(#member_init_override)); - } else { - let member_type = self.member.ty.norm.as_ref(); - let has_into = self.member.params.into.is_present(); - - if has_into { - fn_inputs = quote!(value: impl Into<#member_type>); - member_init = quote!(Some(::core::convert::Into::into(value))); - } else { - fn_inputs = quote!(value: #member_type); - member_init = quote!(Some(value)); - } - } - - self.setter_method(Setter { - method_name: self.member.name.snake.clone(), - fn_inputs, - overwrite_docs: None, - body: SetterBody::Default { member_init }, - }) - } - - fn setters_for_optional_member(&self) -> TokenStream { - let member_snake = self.member.name.snake.clone(); - - // Preserve the original identifier span to make IDE's "go to definition" work correctly - let option_fn_name = syn::Ident::new( - &format!("maybe_{}", self.member.name.snake_raw_str), - member_snake.span(), - ); - - let some_fn_inputs; - // Option-less setter is just a shortcut for wrapping the value in `Some`. - let some_fn_body; - - let option_fn_inputs; - let option_fn_member_init; - - if let Some(closure) = &self.member.params.with { - some_fn_inputs = Self::inputs_from_closure(closure); - - // If the closure accepts just a single input avoid wrapping it - // in a tuple in the `option_fn` setter. - let maybe_wrap_in_tuple = |val: TokenStream| -> TokenStream { - if closure.inputs.len() == 1 { - val - } else { - quote!((#val)) - } - }; - - let idents = closure.inputs.iter().map(|input| &input.pat.ident); - let idents = maybe_wrap_in_tuple(quote!( #( #idents ),* )); - - some_fn_body = { - quote! { - self.#option_fn_name(Some(#idents)) - } - }; - - option_fn_inputs = { - let inputs = closure.inputs.iter().map(|input| &input.ty); - let inputs = maybe_wrap_in_tuple(quote!(#( #inputs, )*)); - quote!(value: Option<#inputs>) - }; - - option_fn_member_init = { - let init = self.member_init_from_closure(closure); - quote! { - match value { - Some(#idents) => Some(#init), - None => None, - } - } - } - } else { - let underlying_ty = self.member.underlying_norm_ty(); - let has_into = self.member.params.into.is_present(); - - let inner_type = if has_into { - quote!(impl Into<#underlying_ty>) - } else { - quote!(#underlying_ty) - }; - - some_fn_inputs = quote!(value: #inner_type); - some_fn_body = quote! { - self.#option_fn_name(Some(value)) - }; - option_fn_inputs = quote!(value: Option<#inner_type>); - - option_fn_member_init = if has_into { - quote!(::core::option::Option::map( - value, - ::core::convert::Into::into - )) - } else { - quote!(value) - }; - } - - let methods = [ - Setter { - method_name: option_fn_name, - fn_inputs: option_fn_inputs, - overwrite_docs: Some(format!( - "Same as [`Self::{member_snake}`], but accepts \ - an `Option` as input. See that method's documentation for \ - more details.", - )), - body: SetterBody::Default { - member_init: option_fn_member_init, - }, - }, - // 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. - Setter { - method_name: member_snake, - fn_inputs: some_fn_inputs, - overwrite_docs: None, - body: SetterBody::Custom(some_fn_body), - }, - ]; - - methods - .into_iter() - .map(|method| self.setter_method(method)) - .collect() - } - - fn inputs_from_closure(closure: &SetterClosure) -> TokenStream { - let pats = closure.inputs.iter().map(|input| &input.pat); - let types = closure.inputs.iter().map(|input| &input.ty); - quote! { - #( #pats: #types ),* - } - } - - fn member_init_from_closure(&self, closure: &SetterClosure) -> TokenStream { - let body = &closure.body; - - let ty = self.member.underlying_norm_ty().to_token_stream(); - - let output = Self::result_output_from_closure(closure, || &ty) - .unwrap_or_else(|| ty.to_token_stream()); - - // 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 result_output_from_closure( - closure: &SetterClosure, - default_ty: impl FnOnce() -> T, - ) -> Option { - let output = closure.output.as_ref()?; - let result_path = &output.result_path; - let err_ty = output.err_ty.iter(); - let default_ty = default_ty(); - Some(quote! { - #result_path< #default_ty #(, #err_ty )* > - }) - } - - fn setter_method(&self, method: Setter) -> TokenStream { - let Setter { - method_name, - fn_inputs, - overwrite_docs, - body, - } = method; - - let docs = match overwrite_docs { - Some(docs) => vec![syn::parse_quote!(#[doc = #docs])], - None if !self.member.docs.is_empty() => self.member.docs.clone(), - None => self.generate_docs_for_setter(), - }; - - let body = match body { - SetterBody::Custom(body) => body, - SetterBody::Default { member_init } => { - let index = &self.member.index; - - let mut output = if self.member.is_stateful() { - quote! { - Self::__private_transition_type_state(self) - } - } else { - quote! { - self - } - }; - - let result_output = self - .member - .params - .with - .as_ref() - .and_then(|closure| closure.output.as_ref()); - - if let Some(result_output) = result_output { - let result_path = &result_output.result_path; - output = quote!(#result_path::Ok(#output)); - } - - quote! { - self.__private_named_members.#index = #member_init; - #output - } - } - }; - - let member_pascal = &self.member.name.pascal; - - let state_transition = format_ident!("Set{}", self.member.name.pascal_str); - - let state_mod = &self.builder_gen.state_mod.ident; - - let state_transition = quote! { - #state_mod::#state_transition - }; - - let builder_ident = &self.builder_gen.builder_type.ident; - let generic_args = &self.builder_gen.generics.args; - - let mut return_type = if self.member.is_stateful() { - quote! { - #builder_ident<#(#generic_args,)* #state_transition> - } - } else { - quote! { Self } - }; - - if let Some(closure) = &self.member.params.with { - if let Some(overridden) = Self::result_output_from_closure(closure, || &return_type) { - return_type = overridden; - } - } - - let where_clause = - if self.member.is_stateful() && !self.member.params.overwritable.is_present() { - quote! { - where - BuilderState::#member_pascal: #state_mod::IsUnset, - } - } else { - quote! {} - }; - - let vis = &self.builder_gen.builder_type.vis; - - 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 - )] - #[inline(always)] - #vis fn #method_name(mut self, #fn_inputs) -> #return_type - #where_clause - { - #body - } - } - } - - fn generate_docs_for_setter(&self) -> Vec { - let member_snake = &self.member.name.snake; - let start_fn = &self.builder_gen.start_fn.ident; - - let more = |start_fn_path: &std::fmt::Arguments<'_>| { - format!(" See {start_fn_path} for more info.") - }; - - let suffix = self - .builder_gen - .assoc_method_ctx - .as_ref() - .map(|assoc_ctx| { - let ty_path = match assoc_ctx.self_ty.as_path() { - Some(ty_path) => ty_path, - - // The type is quite complex. It's hard to generate a workable - // intra-doc link for it. So in order to avoid the broken - // intra-doc links lint we'll just skip adding more info. - _ => return String::new(), - }; - - let prefix = darling::util::path_to_string(&ty_path.path); - more(&format_args!("[`{prefix}::{start_fn}()`]")) - }) - .unwrap_or_else(|| more(&format_args!("[`{start_fn}()`]"))); - - let docs = format!("Sets the value of `{member_snake}`.{suffix}"); - - vec![syn::parse_quote!(#[doc = #docs])] - } -} - -enum SetterBody { - Custom(TokenStream), - Default { member_init: TokenStream }, -} - -struct Setter { - method_name: syn::Ident, - fn_inputs: TokenStream, - overwrite_docs: Option, - body: SetterBody, -} diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs new file mode 100644 index 00000000..e052cbc6 --- /dev/null +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -0,0 +1,456 @@ +use super::member::SetterClosure; +use super::{BuilderGenCtx, NamedMember}; +use crate::parsing::ItemParams; +use crate::util::prelude::*; +use std::iter; + +pub(crate) struct SettersCtx<'a> { + builder_gen: &'a BuilderGenCtx, + member: &'a NamedMember, +} + +impl<'a> SettersCtx<'a> { + pub(crate) fn new(builder_gen: &'a BuilderGenCtx, member: &'a NamedMember) -> Self { + Self { + builder_gen, + member, + } + } + + pub(crate) fn setter_methods(&self) -> TokenStream { + 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) -> TokenStream { + let input; + let value; + + let member_type = self.member.ty.norm.as_ref(); + + if let Some(closure) = &self.member.params.with { + input = Self::underlying_input_from_closure(closure); + value = self.member_value_from_closure(closure); + } else if self.member.params.into.is_present() { + input = quote!(value: impl Into<#member_type>); + value = quote!(Into::into(value)); + } else { + input = quote!(value: #member_type); + value = quote!(value); + }; + + let body = SetterBody::SetMember { + value: quote!(Some(#value)), + }; + + self.setter_method(Setter { + item, + imp: SetterImpl { input, body }, + }) + } + + fn setters_for_optional_member(&self, items: OptionalSettersItems) -> TokenStream { + if let Some(closure) = &self.member.params.with { + return self.setters_for_optional_member_with_closure(closure, items); + } + + let underlying_ty = self.member.underlying_norm_ty(); + let underlying_ty = if self.member.params.into.is_present() { + quote!(impl Into<#underlying_ty>) + } else { + quote!(#underlying_ty) + }; + + let some_fn = Setter { + item: items.some_fn, + imp: SetterImpl { + input: quote!(value: #underlying_ty), + 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 { + input: quote!(value: Option<#underlying_ty>), + body: SetterBody::SetMember { + value: if self.member.params.into.is_present() { + quote! { + Option::map(value, Into::into) + } + } else { + quote!(value) + }, + }, + }, + }; + + [self.setter_method(some_fn), self.setter_method(option_fn)].concat() + } + + fn setters_for_optional_member_with_closure( + &self, + closure: &SetterClosure, + items: OptionalSettersItems, + ) -> TokenStream { + let idents = closure.inputs.iter().map(|input| &input.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 closure.inputs.len() == 1 { + val + } else { + quote!((#val)) + } + }; + + let idents = tuple_if_many(quote!( #( #idents ),* )); + + let some_fn = Setter { + item: items.some_fn, + imp: SetterImpl { + input: Self::underlying_input_from_closure(closure), + body: SetterBody::Forward { + body: { + let option_fn_name = &items.option_fn.name; + quote! { + self.#option_fn_name(Some(#idents)) + } + }, + }, + }, + }; + + let option_fn_impl = SetterImpl { + input: { + let input_types = closure.inputs.iter().map(|input| &input.ty); + let input_types = tuple_if_many(quote!(#( #input_types, )*)); + quote!(value: Option<#input_types>) + }, + body: SetterBody::SetMember { + value: { + let value = self.member_value_from_closure(closure); + quote! { + match value { + Some(#idents) => Some(#value), + None => None, + } + } + }, + }, + }; + + let option_fn = Setter { + item: items.option_fn, + imp: option_fn_impl, + }; + + [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_input_from_closure(closure: &SetterClosure) -> TokenStream { + let pats = closure.inputs.iter().map(|input| &input.pat); + let types = closure.inputs.iter().map(|input| &input.ty); + quote! { + #( #pats: #types ),* + } + } + + fn member_value_from_closure(&self, closure: &SetterClosure) -> TokenStream { + let body = &closure.body; + + let ty = self.member.underlying_norm_ty().to_token_stream(); + + let output = Self::maybe_wrap_in_result(closure, ty.to_token_stream()); + + // 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(closure: &SetterClosure, ty: TokenStream) -> TokenStream { + 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 { value } => { + let index = &self.member.index; + + let mut output = if self.member.is_stateful() { + quote! { + Self::__private_transition_type_state(self) + } + } else { + quote! { + self + } + }; + + let result_output = self + .member + .params + .with + .as_ref() + .and_then(|closure| closure.output.as_ref()); + + if let Some(result_output) = result_output { + let result_path = &result_output.result_path; + output = quote!(#result_path::Ok(#output)); + } + + quote! { + self.__private_named_members.#index = #value; + #output + } + } + }; + + let state_mod = &self.builder_gen.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.builder_gen.builder_type.ident; + let generic_args = &self.builder_gen.generics.args; + + quote! { + #builder_ident<#(#generic_args,)* #state_mod::#state_transition> + } + }; + + if let Some(closure) = &self.member.params.with { + return_type = Self::maybe_wrap_in_result(closure, return_type); + } + + let where_clause = (!self.member.params.overwritable.is_present()).then(|| { + let member_pascal = &self.member.name.pascal; + quote! { + where + BuilderState::#member_pascal: #state_mod::IsUnset, + } + }); + + let SetterItem { name, vis, docs } = item; + let input = imp.input; + + 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 + )] + #[inline(always)] + #vis fn #name(#maybe_mut self, #input) -> #return_type + #where_clause + { + #body + } + } + } +} + +struct Setter { + item: SetterItem, + imp: SetterImpl, +} + +struct SetterImpl { + input: TokenStream, + 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 { value: 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, + builder_gen, + } = ctx; + let builder_type = &builder_gen.builder_type; + + let params = member.params.setters.as_ref(); + + let common_name = params.and_then(|params| params.name.as_deref()); + let common_vis = params.and_then(|params| params.vis.as_deref()); + let common_docs = params.and_then(|params| params.docs.as_deref()); + + if member.is_required() { + let docs = common_docs.cloned().unwrap_or_else(|| { + if !member.docs.is_empty() { + return member.docs.clone(); + } + + let docs = format!("_**Required**_. Sets the value of `{}`.", member.name.snake); + + vec![syn::parse_quote!(#[doc = #docs])] + }); + + 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 = params.and_then(|params| params.fns.some_fn.as_deref()); + let some_fn_name = some_fn + .and_then(ItemParams::name) + .or(common_name) + .unwrap_or(&member.name.snake) + .clone(); + + let option_fn = params.and_then(|params| params.fns.option_fn.as_deref()); + let option_fn_name = option_fn + .and_then(ItemParams::name) + .cloned() + .unwrap_or_else(|| { + // Preserve the original identifier span to make IDE's + // "go to definition" works correctly + syn::Ident::new( + &format!("maybe_{}", member.name.snake_raw_str), + member.name.snake.span(), + ) + }); + + // FIXME: the docs shouldn't reference the companion setter if that + // setter has a lower visibility. + let some_fn_docs = some_fn + .and_then(ItemParams::docs) + .map(ToOwned::to_owned) + .unwrap_or_else(|| { + let base_docs = common_docs.unwrap_or(&member.docs); + + let header = format!( + "*Optional*. Sets the value of `{}` to `Some(value)`. \ + Use [`{option_fn_name}()`](Self::{option_fn_name}) \ + to pass an [`Option`] directly.\n\n", + member.name.snake, + ); + + iter::once(syn::parse_quote!(#[doc = #header])) + .chain(base_docs.iter().cloned()) + .collect() + }); + + let option_fn_docs = option_fn + .and_then(ItemParams::docs) + .map(ToOwned::to_owned) + .unwrap_or_else(|| { + let base_docs = common_docs.unwrap_or(&member.docs); + + let header = format!( + "*Optional*. Sets the value of `{}`. \ + Use [`{some_fn_name}()`](Self::{some_fn_name}) to avoid \ + wrapping the value in [`Some`].\n\n", + member.name.snake, + ); + + iter::once(syn::parse_quote!(#[doc = #header])) + .chain(base_docs.iter().cloned()) + .collect() + }); + + let some_fn = SetterItem { + name: some_fn_name, + vis: some_fn + .and_then(ItemParams::vis) + .or(common_vis) + .unwrap_or(&builder_type.vis) + .clone(), + + docs: some_fn_docs, + }; + + let option_fn = params.and_then(|params| params.fns.option_fn.as_deref()); + let option_fn = SetterItem { + name: option_fn_name, + + vis: option_fn + .and_then(ItemParams::vis) + .or(common_vis) + .unwrap_or(&builder_type.vis) + .clone(), + + docs: option_fn_docs, + }; + + Self::Optional(OptionalSettersItems { some_fn, option_fn }) + } +} diff --git a/bon-macros/src/lib.rs b/bon-macros/src/lib.rs index 1c2004a1..882be6ee 100644 --- a/bon-macros/src/lib.rs +++ b/bon-macros/src/lib.rs @@ -8,7 +8,8 @@ clippy::option_option, clippy::option_if_let_else, clippy::enum_glob_use, - clippy::too_many_lines + clippy::too_many_lines, + clippy::if_not_else, )] mod bon; diff --git a/bon-macros/src/parsing/item_params.rs b/bon-macros/src/parsing/item_params.rs index cafb18e1..579fd3be 100644 --- a/bon-macros/src/parsing/item_params.rs +++ b/bon-macros/src/parsing/item_params.rs @@ -9,6 +9,20 @@ pub(crate) struct ItemParams { pub(crate) docs: Option>>, } +impl ItemParams { + 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 ItemParamsParsing<'a> { pub(crate) meta: &'a syn::Meta, pub(crate) reject_self_mentions: Option<&'static str>, From e36efb0e2fcfd4a00212958f84ed85087a85986c Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 8 Oct 2024 02:01:50 +0000 Subject: [PATCH 059/119] Use table formatting --- .../src/builder/builder_gen/setters/mod.rs | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index e052cbc6..eb6e8ef2 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -351,15 +351,15 @@ impl SettersItems { let common_vis = params.and_then(|params| params.vis.as_deref()); let common_docs = params.and_then(|params| params.docs.as_deref()); + let doc = |docs: &str| iter::once(syn::parse_quote!(#[doc = #docs])); + if member.is_required() { let docs = common_docs.cloned().unwrap_or_else(|| { - if !member.docs.is_empty() { - return member.docs.clone(); - } - - let docs = format!("_**Required**_. Sets the value of `{}`.", member.name.snake); + let header = "\ + | **Required** |\n\ + | -- |\n\n"; - vec![syn::parse_quote!(#[doc = #docs])] + doc(header).chain(member.docs.iter().cloned()).collect() }); return Self::Required(SetterItem { @@ -398,15 +398,11 @@ impl SettersItems { let base_docs = common_docs.unwrap_or(&member.docs); let header = format!( - "*Optional*. Sets the value of `{}` to `Some(value)`. \ - Use [`{option_fn_name}()`](Self::{option_fn_name}) \ - to pass an [`Option`] directly.\n\n", - member.name.snake, + "| *Optional* | See also [`{option_fn_name}()`](Self::{option_fn_name}) |\n\ + | -- | -- |\n\n", ); - iter::once(syn::parse_quote!(#[doc = #header])) - .chain(base_docs.iter().cloned()) - .collect() + doc(&header).chain(base_docs.iter().cloned()).collect() }); let option_fn_docs = option_fn @@ -416,15 +412,11 @@ impl SettersItems { let base_docs = common_docs.unwrap_or(&member.docs); let header = format!( - "*Optional*. Sets the value of `{}`. \ - Use [`{some_fn_name}()`](Self::{some_fn_name}) to avoid \ - wrapping the value in [`Some`].\n\n", - member.name.snake, + "| *Optional* | See also [`{some_fn_name}()`](Self::{some_fn_name})\n\ + | -- | -- |\n\n", ); - iter::once(syn::parse_quote!(#[doc = #header])) - .chain(base_docs.iter().cloned()) - .collect() + doc(&header).chain(base_docs.iter().cloned()).collect() }); let some_fn = SetterItem { From 62b2b5d93b73cb1141f82ee3bd7d9940ce1f9730 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 8 Oct 2024 02:32:49 +0000 Subject: [PATCH 060/119] Update tests --- bon-macros/Cargo.toml | 4 +- .../src/builder/builder_gen/setters/mod.rs | 16 +- .../integration/builder/attr_with/mod.rs | 8 + .../builder/attr_with/multi_arg.rs | 67 ++++---- .../builder/attr_with/overwritable.rs | 22 +-- .../builder/attr_with/single_arg.rs | 157 +++++++++--------- e2e-tests/src/lib.rs | 4 +- scripts/test-msrv.sh | 3 +- 8 files changed, 153 insertions(+), 128 deletions(-) diff --git a/bon-macros/Cargo.toml b/bon-macros/Cargo.toml index dfc36858..a3358a41 100644 --- a/bon-macros/Cargo.toml +++ b/bon-macros/Cargo.toml @@ -54,7 +54,10 @@ quote = "1" # This is the highest version that supports our MSRV syn = { version = "2.0.56", features = ["full", "visit-mut", "visit"] } +prettyplease = "0.2" + [features] +default = [] # Opts in to the higher MSRV 1.79.0. In this version, Rust stabilized the syntax # for bounds in associated type position. See the release announcement for more: # https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.htmlbounds-in-associated-type-position @@ -93,5 +96,4 @@ syn = { version = "2.0.56", features = ["full", "visit-mut", "visit"] } msrv-1-79-0 = [] [dev-dependencies] -prettyplease = "0.2" expect-test = "1.4.1" diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index eb6e8ef2..98f7e47e 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -389,6 +389,12 @@ impl SettersItems { ) }); + let default = member + .params + .default + .as_deref() + .map(|default| format!("| Default: ``")); + // FIXME: the docs shouldn't reference the companion setter if that // setter has a lower visibility. let some_fn_docs = some_fn @@ -398,8 +404,9 @@ impl SettersItems { let base_docs = common_docs.unwrap_or(&member.docs); let header = format!( - "| *Optional* | See also [`{option_fn_name}()`](Self::{option_fn_name}) |\n\ - | -- | -- |\n\n", + "| *Optional* |\n\ + | -- |\n\n\ + See other setter: [`{option_fn_name}()`](Self::{option_fn_name}).\n\n", ); doc(&header).chain(base_docs.iter().cloned()).collect() @@ -412,8 +419,9 @@ impl SettersItems { let base_docs = common_docs.unwrap_or(&member.docs); let header = format!( - "| *Optional* | See also [`{some_fn_name}()`](Self::{some_fn_name})\n\ - | -- | -- |\n\n", + "| *Optional* |\n\ + | -- |\n\n\ + See other setter: [`{some_fn_name}()`](Self::{some_fn_name}).\n\n", ); doc(&header).chain(base_docs.iter().cloned()).collect() diff --git a/bon/tests/integration/builder/attr_with/mod.rs b/bon/tests/integration/builder/attr_with/mod.rs index 73bf2d07..0ca6656d 100644 --- a/bon/tests/integration/builder/attr_with/mod.rs +++ b/bon/tests/integration/builder/attr_with/mod.rs @@ -1,3 +1,11 @@ mod multi_arg; mod single_arg; mod overwritable; + +struct IntoStrRef<'a>(&'a str); + +impl<'a> From> for &'a str { + fn from(val: IntoStrRef<'a>) -> Self { + val.0 + } +} diff --git a/bon/tests/integration/builder/attr_with/multi_arg.rs b/bon/tests/integration/builder/attr_with/multi_arg.rs index 4d1c07c8..a70cbeea 100644 --- a/bon/tests/integration/builder/attr_with/multi_arg.rs +++ b/bon/tests/integration/builder/attr_with/multi_arg.rs @@ -1,6 +1,6 @@ +use super::IntoStrRef; use crate::prelude::*; use core::convert::Infallible; -use core::net::IpAddr; use core::{fmt, num}; type ParseIntResult = Result; @@ -28,8 +28,8 @@ fn test_struct() { #[builder(with = |value: &T, _value2: &T| value.clone(), default)] default_generic: T, - #[builder(with = |value: impl Into| value.into())] - impl_trait: IpAddr, + #[builder(with = |value: impl Into<&'static str>| value.into())] + impl_trait: &'static str, #[builder(with = |value: &str, _value2: u32| -> Result<_, num::ParseIntError> { value.parse() })] try_required: u32, @@ -46,8 +46,8 @@ fn test_struct() { #[builder(with = |value: &T, _value2: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] try_default_generic: T, - #[builder(with = |value: impl Into, _value2: impl fmt::Debug| -> Result<_, Infallible> { Ok(value.into()) })] - try_impl_trait: IpAddr, + #[builder(with = |value: impl Into<&'static str>, _value2: impl fmt::Debug| -> Result<_, Infallible> { Ok(value.into()) })] + try_impl_trait: &'static str, } let builder = Sut::builder() @@ -63,7 +63,7 @@ fn test_struct() { let builder = builder.maybe_default_generic(Some((&"<3", &"glory"))); let builder = builder - .impl_trait([127, 0, 0, 1]) + .impl_trait(IntoStrRef("p21")) .try_required("4", 99) .unwrap(); @@ -83,30 +83,33 @@ fn test_struct() { .maybe_try_default_generic(Some((&"12", &"99"))) .unwrap(); - let builder = builder.try_impl_trait([127, 0, 0, 1], true).unwrap(); - - assert_debug_eq(builder.build(), expect![[r#" - Sut { - required: 3, - optional: Some( - 7, - ), - default: 11, - generic: "hello", - optional_generic: Some( - "littlepip", - ), - default_generic: "<3", - impl_trait: 127.0.0.1, - try_required: 4, - try_optional: Some( - 6, - ), - try_default: 8, - try_optional_generic: Some( - "10", - ), - try_default_generic: "12", - try_impl_trait: 127.0.0.1, - }"#]]); + let builder = builder.try_impl_trait(IntoStrRef("p21"), true).unwrap(); + + assert_debug_eq( + builder.build(), + expect![[r#" + Sut { + required: 3, + optional: Some( + 7, + ), + default: 11, + generic: "hello", + optional_generic: Some( + "littlepip", + ), + default_generic: "<3", + impl_trait: "p21", + try_required: 4, + try_optional: Some( + 6, + ), + try_default: 8, + try_optional_generic: Some( + "10", + ), + try_default_generic: "12", + try_impl_trait: "p21", + }"#]], + ); } diff --git a/bon/tests/integration/builder/attr_with/overwritable.rs b/bon/tests/integration/builder/attr_with/overwritable.rs index 79a5fedc..7ac7145b 100644 --- a/bon/tests/integration/builder/attr_with/overwritable.rs +++ b/bon/tests/integration/builder/attr_with/overwritable.rs @@ -1,6 +1,6 @@ +use super::IntoStrRef; use crate::prelude::*; use core::convert::Infallible; -use core::net::IpAddr; use core::num; type ParseIntResult = Result; @@ -27,8 +27,8 @@ fn test_struct() { #[builder(with = |value: &T| value.clone(), default, overwritable)] default_generic: T, - #[builder(with = |value: impl Into| value.into(), overwritable)] - impl_trait: IpAddr, + #[builder(with = |value: impl Into<&'static str>| value.into(), overwritable)] + impl_trait: &'static str, #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() }, overwritable)] try_required: u32, @@ -45,8 +45,8 @@ fn test_struct() { #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default, overwritable)] try_default_generic: T, - #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) }, overwritable)] - try_impl_trait: IpAddr, + #[builder(with = |value: impl Into<&'static str>| -> Result<_, Infallible> { Ok(value.into()) }, overwritable)] + try_impl_trait: &'static str, } let builder = Sut::builder() @@ -62,8 +62,8 @@ fn test_struct() { .maybe_optional_generic(Some(&"littlepip")) .default_generic(&"blackjack") .maybe_default_generic(Some(&"<3")) - .impl_trait([127, 0, 0, 1]) - .impl_trait([127, 0, 0, 2]) + .impl_trait(IntoStrRef("p21")) + .impl_trait(IntoStrRef("rampage")) .try_required("7") .unwrap() .try_required("8") @@ -84,9 +84,9 @@ fn test_struct() { .unwrap() .maybe_try_default_generic(Some(&"16")) .unwrap() - .try_impl_trait([127, 0, 0, 3]) + .try_impl_trait(IntoStrRef("daisy")) .unwrap() - .try_impl_trait([127, 0, 0, 4]) + .try_impl_trait(IntoStrRef("roseluck")) .unwrap(); assert_debug_eq( @@ -103,7 +103,7 @@ fn test_struct() { "littlepip", ), default_generic: "<3", - impl_trait: 127.0.0.2, + impl_trait: "rampage", try_required: 8, try_optional: Some( 10, @@ -113,7 +113,7 @@ fn test_struct() { "14", ), try_default_generic: "16", - try_impl_trait: 127.0.0.4, + try_impl_trait: "roseluck", }"#]], ); } diff --git a/bon/tests/integration/builder/attr_with/single_arg.rs b/bon/tests/integration/builder/attr_with/single_arg.rs index 308703eb..0381ce2e 100644 --- a/bon/tests/integration/builder/attr_with/single_arg.rs +++ b/bon/tests/integration/builder/attr_with/single_arg.rs @@ -1,8 +1,8 @@ use crate::prelude::*; use core::convert::Infallible; -use core::net::IpAddr; use core::{fmt, num}; type ParseIntResult = Result; +use super::IntoStrRef; #[test] fn test_struct() { @@ -28,8 +28,8 @@ fn test_struct() { #[builder(with = |value: &T| value.clone(), default)] default_generic: T, - #[builder(with = |value: impl Into| value.into())] - impl_trait: IpAddr, + #[builder(with = |value: impl Into<&'static str>| value.into())] + impl_trait: &'static str, #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] try_required: u32, @@ -46,8 +46,8 @@ fn test_struct() { #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] try_default_generic: T, - #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] - try_impl_trait: IpAddr, + #[builder(with = |value: impl Into<&'static str>| -> Result<_, Infallible> { Ok(value.into()) })] + try_impl_trait: &'static str, } let builder = Sut::builder() @@ -63,7 +63,7 @@ fn test_struct() { let builder = builder.maybe_default_generic(Some(&"<3")); let builder = builder - .impl_trait([127, 0, 0, 1]) + .impl_trait(IntoStrRef("morning glory")) .try_required("4") .unwrap(); @@ -79,7 +79,7 @@ fn test_struct() { let _ignore = builder.clone().try_default_generic(&"11").unwrap(); let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); - let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); + let builder = builder.try_impl_trait(IntoStrRef("morning glory")).unwrap(); assert_debug_eq( builder.build(), @@ -95,7 +95,7 @@ fn test_struct() { "littlepip", ), default_generic: "<3", - impl_trait: 127.0.0.1, + impl_trait: "morning glory", try_required: 4, try_optional: Some( 6, @@ -105,7 +105,7 @@ fn test_struct() { "10", ), try_default_generic: "12", - try_impl_trait: 127.0.0.1, + try_impl_trait: "morning glory", }"#]], ); } @@ -120,7 +120,7 @@ fn test_free_fn() { #[builder(with = |value: &T| value.clone())] generic: T, #[builder(with = |value: &T| value.clone())] optional_generic: Option, #[builder(with = |value: &T| value.clone(), default)] default_generic: T, - #[builder(with = |value: impl Into| value.into())] impl_trait: IpAddr, + #[builder(with = |value: impl Into<&'static str>| value.into())] impl_trait: &'static str, #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] try_required: u32, @@ -137,8 +137,8 @@ fn test_free_fn() { #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] try_default_generic: T, - #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] - try_impl_trait: IpAddr, + #[builder(with = |value: impl Into<&'static str>| -> Result<_, Infallible> { Ok(value.into()) })] + try_impl_trait: &'static str, ) -> impl fmt::Debug { ( ( @@ -170,7 +170,7 @@ fn test_free_fn() { let builder = builder.maybe_default_generic(Some(&"<3")); let builder = builder - .impl_trait([127, 0, 0, 1]) + .impl_trait(IntoStrRef("morning glory")) .try_required("4") .unwrap(); @@ -186,38 +186,38 @@ fn test_free_fn() { let _ignore = builder.clone().try_default_generic(&"11").unwrap(); let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); - let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); + let builder = builder.try_impl_trait(IntoStrRef("morning glory")).unwrap(); assert_debug_eq( builder.call(), expect![[r#" + ( ( - ( - 2, - Some( - 3, - ), - 4, - "hello", - Some( - "littlepip", - ), - "<3", - 127.0.0.1, + 2, + Some( + 3, ), - ( - 4, - Some( - 6, - ), - 8, - Some( - "10", - ), - "12", - 127.0.0.1, + 4, + "hello", + Some( + "littlepip", ), - )"#]], + "<3", + "morning glory", + ), + ( + 4, + Some( + 6, + ), + 8, + Some( + "10", + ), + "12", + "morning glory", + ), + )"#]], ); } @@ -235,7 +235,8 @@ fn test_assoc_method() { #[builder(with = |value: &T| value.clone())] generic: T, #[builder(with = |value: &T| value.clone())] optional_generic: Option, #[builder(with = |value: &T| value.clone(), default)] default_generic: T, - #[builder(with = |value: impl Into| value.into())] impl_trait: IpAddr, + #[builder(with = |value: impl Into<&'static str>| value.into())] + impl_trait: &'static str, #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] try_required: u32, @@ -252,8 +253,8 @@ fn test_assoc_method() { #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] try_default_generic: T, - #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] - try_impl_trait: IpAddr, + #[builder(with = |value: impl Into<&'static str>| -> Result<_, Infallible> { Ok(value.into()) })] + try_impl_trait: &'static str, ) -> impl fmt::Debug { ( ( @@ -285,7 +286,8 @@ fn test_assoc_method() { #[builder(with = |value: &T| value.clone())] generic: T, #[builder(with = |value: &T| value.clone())] optional_generic: Option, #[builder(with = |value: &T| value.clone(), default)] default_generic: T, - #[builder(with = |value: impl Into| value.into())] impl_trait: IpAddr, + #[builder(with = |value: impl Into<&'static str>| value.into())] + impl_trait: &'static str, #[builder(with = |value: &str| -> Result<_, num::ParseIntError> { value.parse() })] try_required: u32, @@ -302,8 +304,8 @@ fn test_assoc_method() { #[builder(with = |value: &T| -> Result<_, Infallible> { Ok(value.clone()) }, default)] try_default_generic: T, - #[builder(with = |value: impl Into| -> Result<_, Infallible> { Ok(value.into()) })] - try_impl_trait: IpAddr, + #[builder(with = |value: impl Into<&'static str>| -> Result<_, Infallible> { Ok(value.into()) })] + try_impl_trait: &'static str, ) -> impl fmt::Debug { let _ = self; ( @@ -341,7 +343,7 @@ fn test_assoc_method() { let builder = builder.maybe_default_generic(Some(&"<3")); let builder = builder - .impl_trait([127, 0, 0, 1]) + .impl_trait(IntoStrRef("morning glory")) .try_required("4") .unwrap(); @@ -357,38 +359,38 @@ fn test_assoc_method() { let _ignore = builder.clone().try_default_generic(&"11").unwrap(); let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); - let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); + let builder = builder.try_impl_trait(IntoStrRef("morning glory")).unwrap(); assert_debug_eq( builder.call(), expect![[r#" + ( + ( + 2, + Some( + 3, + ), + 4, + "hello", + Some( + "littlepip", + ), + "<3", + "morning glory", + ), ( - ( - 2, - Some( - 3, - ), - 4, - "hello", - Some( - "littlepip", - ), - "<3", - 127.0.0.1, + 4, + Some( + 6, ), - ( - 4, - Some( - 6, - ), - 8, - Some( - "10", - ), - "12", - 127.0.0.1, + 8, + Some( + "10", ), - )"#]], + "12", + "morning glory", + ), + )"#]], ); let builder = Sut @@ -405,7 +407,7 @@ fn test_assoc_method() { let builder = builder.maybe_default_generic(Some(&"<3")); let builder = builder - .impl_trait([127, 0, 0, 1]) + .impl_trait(IntoStrRef("morning glory")) .try_required("4") .unwrap(); @@ -421,9 +423,11 @@ fn test_assoc_method() { let _ignore = builder.clone().try_default_generic(&"11").unwrap(); let builder = builder.maybe_try_default_generic(Some(&"12")).unwrap(); - let builder = builder.try_impl_trait([127, 0, 0, 1]).unwrap(); + let builder = builder.try_impl_trait(IntoStrRef("morning glory")).unwrap(); - assert_debug_eq(builder.call(), expect![[r#" + assert_debug_eq( + builder.call(), + expect![[r#" ( ( 2, @@ -436,7 +440,7 @@ fn test_assoc_method() { "littlepip", ), "<3", - 127.0.0.1, + "morning glory", ), ( 4, @@ -448,7 +452,8 @@ fn test_assoc_method() { "10", ), "12", - 127.0.0.1, + "morning glory", ), - )"#]]); + )"#]], + ); } diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index 96612f82..3bd35272 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -69,7 +69,6 @@ pub struct Counter { #[bon] impl Counter { - /// Creates an instance of [`Self`] with an optional provided `initial` value. #[builder] pub fn new( /// Initial value for the counter. @@ -109,7 +108,8 @@ pub fn documented( _arg2: &str, - _arg3: u32, + /// Optional member docs + _arg3: Option, _arg4: Vec, ) { diff --git a/scripts/test-msrv.sh b/scripts/test-msrv.sh index 36555641..04d7297f 100755 --- a/scripts/test-msrv.sh +++ b/scripts/test-msrv.sh @@ -39,10 +39,9 @@ step cargo update -p windows-sys --precise 0.52.0 export RUSTFLAGS="${RUSTFLAGS:-} --allow unknown-lints" -step cargo clippy --all-features --all-targets --locked +step cargo clippy --all-targets --locked test_args=( - --all-features --locked --lib --tests From 7bbed4e1ab249dc13262ea278c553cb32ad0bbdf Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 8 Oct 2024 03:15:37 +0000 Subject: [PATCH 061/119] Render defaults --- .../src/builder/builder_gen/setters/mod.rs | 74 +++++++++++++++---- e2e-tests/src/lib.rs | 13 ++++ 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index 98f7e47e..4080710d 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -389,11 +389,23 @@ impl SettersItems { ) }); - let default = member - .params - .default - .as_deref() - .map(|default| format!("| Default: ``")); + let default = member.params.default.as_deref().and_then(|default| { + let default = default + .clone() + .or_else(|| well_known_default(&member.ty.norm)) + .unwrap_or_else(|| syn::parse_quote!(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. @@ -403,11 +415,7 @@ impl SettersItems { .unwrap_or_else(|| { let base_docs = common_docs.unwrap_or(&member.docs); - let header = format!( - "| *Optional* |\n\ - | -- |\n\n\ - See other setter: [`{option_fn_name}()`](Self::{option_fn_name}).\n\n", - ); + let header = optional_setter_docs(default, &option_fn_name); doc(&header).chain(base_docs.iter().cloned()).collect() }); @@ -418,11 +426,7 @@ impl SettersItems { .unwrap_or_else(|| { let base_docs = common_docs.unwrap_or(&member.docs); - let header = format!( - "| *Optional* |\n\ - | -- |\n\n\ - See other setter: [`{some_fn_name}()`](Self::{some_fn_name}).\n\n", - ); + let header = optional_setter_docs(default, &some_fn_name); doc(&header).chain(base_docs.iter().cloned()).collect() }); @@ -454,3 +458,43 @@ impl SettersItems { Self::Optional(OptionalSettersItems { some_fn, option_fn }) } } + +fn optional_setter_docs(default: Option<&str>, other_setter: &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**. **See also:** [`{other_setter}()`](Self::{other_setter}).\ + \n\n{default}", + ) +} + +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) +} diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index 3bd35272..63844a7a 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -104,6 +104,7 @@ pub fn documented( /// // Some doc tests as well /// assert_eq!(2 + 2, 4); /// ``` + #[builder(default)] _arg1: String, _arg2: &str, @@ -112,6 +113,18 @@ pub fn documented( _arg3: Option, _arg4: Vec, + + #[builder(default = + Greeter::start_fn_override() + .name( + "Some intentionally big expression to test the overflow to \ + a code fence in the default value docs" + .to_owned() + ) + .level(42) + .finish_fn_override() + )] + _arg5: Greeter, ) { eprintln!("Non-const"); } From d552ae2fd0e8af53b1e3332a8cf16de87934d31c Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 9 Oct 2024 02:00:48 +0000 Subject: [PATCH 062/119] More docs improvements --- .../src/builder/builder_gen/setters/mod.rs | 2 +- .../src/builder/builder_gen/state_mod.rs | 26 ++++++++++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index 4080710d..bbf1dce6 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -465,7 +465,7 @@ fn optional_setter_docs(default: Option<&str>, other_setter: &syn::Ident) -> Str if default.contains('\n') || default.len() > 80 { format!("**Default:**\n````rust,ignore\n{default}\n````\n\n") } else { - format!("**Default:** ``{default}``\n\n") + format!("**Default:** ```{default}```.\n\n") } }) .unwrap_or_default(); diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index 25f48702..a6f0c364 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -49,9 +49,11 @@ impl<'a> StateModGenCtx<'a> { let is_unset_trait = self.is_unset_trait(); let is_complete_trait = self.is_complete_trait(); let members_names_mod = self.members_names_mod(); - let state_transitions = self.state_transitions(); + let (state_transitions, parent_state_transition_items) = self.state_transitions(); quote! { + #parent_state_transition_items + // This is 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 @@ -80,7 +82,7 @@ impl<'a> StateModGenCtx<'a> { } } - fn state_transitions(&self) -> TokenStream { + fn state_transitions(&self) -> (TokenStream, TokenStream) { let transition_tuples = self .builder_gen .stateful_members() @@ -129,7 +131,7 @@ impl<'a> StateModGenCtx<'a> { let stateful_members_pascal = &self.stateful_members_pascal; let sealed_method_impl = &self.sealed_method_impl; - quote! { + let mod_items = quote! { /// Initial state of the builder where all members are unset #vis_child struct AllUnset { _private: () @@ -159,7 +161,23 @@ impl<'a> StateModGenCtx<'a> { impl private::StateExt for S { #(type #set_member_aliases = #transition_tuples; )* } - } + }; + + let state_mod = &self.builder_gen.state_mod.ident; + let builder_vis = &self.builder_gen.builder_type.vis; + + // This is a workaround for `rustdoc`. Without this `use` statement, + // it inlines the type aliases. Although for this workaround to work, + // all items from the current module need to be reexported via a `*` + // reexport, or the items need to be defined in the root lib.rs file. + let parent_items = quote! { + #[doc(hidden)] + #[cfg(doc)] + #[allow(unused_import_braces)] + #builder_vis use #state_mod::{ #( #set_member_aliases as _ ,)* }; + }; + + (mod_items, parent_items) } fn state_trait(&self) -> TokenStream { From 48f6b29a2a11a5640813d0554635c5b9cf484fa3 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 10 Oct 2024 00:06:08 +0000 Subject: [PATCH 063/119] Remove unnecessary `move` --- bon-macros/src/builder/builder_gen/state_mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index a6f0c364..8ce7097c 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -86,7 +86,7 @@ impl<'a> StateModGenCtx<'a> { let transition_tuples = self .builder_gen .stateful_members() - .map(move |member| { + .map(|member| { let states = self.builder_gen.stateful_members().map(|other_member| { if other_member.is(member) { let member_snake = &member.name.snake; From 8441369cd3c8a477fa3cc04e13d273555e25242d Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 11 Oct 2024 02:52:19 +0000 Subject: [PATCH 064/119] Optimize state compile perf --- Cargo.toml | 7 +- bon-macros/Cargo.toml | 5 +- bon-macros/src/builder/builder_gen/mod.rs | 3 + .../src/builder/builder_gen/setters/mod.rs | 8 +- .../src/builder/builder_gen/state_mod.rs | 262 +++++++++++------- bon-macros/src/builder/mod.rs | 2 + bon-macros/src/lib.rs | 9 + bon/Cargo.toml | 4 +- bon/src/lib.rs | 2 +- bon/src/private/mod.rs | 17 +- 10 files changed, 215 insertions(+), 104 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f680af81..1a324a04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,8 +130,7 @@ wildcard_dependencies = "warn" zero_sized_map_values = "warn" # Priorities are used not because we override lints from these lint groups -# but just to order them in order from the less noisy to the more noisy in -# the output +# but just to order them from the less noisy to the more noisy in the output nursery = { level = "warn", priority = -2 } pedantic = { level = "warn", priority = -1 } @@ -176,5 +175,5 @@ rust_2024_compatibility = { level = "warn", priority = 1 } # [workspace.lints.rust] # must_not_suspend = "warn" # rust_2024_incompatible_pat = "warn" -# lossy_provenance_casts = "warn" -# fuzzy_provenance_casts = "warn" +# lossy_provenance_casts = "warn" +# fuzzy_provenance_casts = "warn" diff --git a/bon-macros/Cargo.toml b/bon-macros/Cargo.toml index a3358a41..ee7d2b96 100644 --- a/bon-macros/Cargo.toml +++ b/bon-macros/Cargo.toml @@ -58,6 +58,7 @@ prettyplease = "0.2" [features] default = [] + # Opts in to the higher MSRV 1.79.0. In this version, Rust stabilized the syntax # for bounds in associated type position. See the release announcement for more: # https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.htmlbounds-in-associated-type-position @@ -93,7 +94,9 @@ default = [] # wouldn't automatically imply `State::A: IsSet`, so the builder type state returned # after the `self.b()` doesn't imply that the member `a` is set, and thus `build()` # can't be called. -msrv-1-79-0 = [] +# +# Huge thanks to @harudagondi for suggesting the name of this feature! +implied-bounds = [] [dev-dependencies] expect-test = "1.4.1" diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index a169feef..c429b586 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -8,6 +8,8 @@ mod state_mod; pub(crate) mod input_fn; pub(crate) mod input_struct; +pub(crate) use state_mod::prettier_type_aliases_docs; + use crate::util::prelude::*; use member::{ Member, MemberOrigin, NamedMember, PositionalFnArgMember, RawMember, StartFnArgMember, @@ -594,6 +596,7 @@ impl BuilderGenCtx { #must_use #finish_fn_vis #asyncness #unsafety fn #finish_fn_ident(self, #(#finish_fn_params,)*) #output where + // #( #where_bounds, )* BuilderState: #state_mod::IsComplete { #(#members_vars_decls)* diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index bbf1dce6..bcd31821 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -262,8 +262,14 @@ impl<'a> SettersCtx<'a> { let builder_ident = &self.builder_gen.builder_type.ident; let generic_args = &self.builder_gen.generics.args; + let state_param = (self.builder_gen.stateful_members().take(2).count() > 1).then(|| { + quote! { + + } + }); + quote! { - #builder_ident<#(#generic_args,)* #state_mod::#state_transition> + #builder_ident<#(#generic_args,)* #state_mod::#state_transition #state_param> } }; diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index 8ce7097c..f72c194f 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -39,14 +39,13 @@ impl<'a> StateModGenCtx<'a> { pub(super) fn state_mod(&self) -> TokenStream { let vis_mod = &self.builder_gen.state_mod.vis; + let vis_child = &self.builder_gen.state_mod.vis_child; let vis_child_child = &self.builder_gen.state_mod.vis_child_child; let state_mod_docs = &self.builder_gen.state_mod.docs; let state_mod_ident = &self.builder_gen.state_mod.ident; let state_trait = self.state_trait(); - let is_set_trait = self.is_set_trait(); - let is_unset_trait = self.is_unset_trait(); let is_complete_trait = self.is_complete_trait(); let members_names_mod = self.members_names_mod(); let (state_transitions, parent_state_transition_items) = self.state_transitions(); @@ -54,27 +53,31 @@ impl<'a> StateModGenCtx<'a> { quote! { #parent_state_transition_items - // This is 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. - #[allow(unnameable_types, unreachable_pub, clippy::redundant_pub_crate)] + #[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 mod #state_mod_ident { + #vis_child use ::bon::private::{IsSet, IsUnset}; + #[doc(inline)] + use ::bon::private::{Set, Unset}; + mod sealed { #vis_child_child enum Sealed {} } #state_trait - #is_set_trait - #is_unset_trait #is_complete_trait #state_transitions #members_names_mod @@ -91,12 +94,12 @@ impl<'a> StateModGenCtx<'a> { if other_member.is(member) { let member_snake = &member.name.snake; quote! { - ::bon::private::Set + Set } } else { let member_pascal = &other_member.name.pascal; quote! { - ::#member_pascal + ::#member_pascal } } }); @@ -128,38 +131,53 @@ impl<'a> StateModGenCtx<'a> { let vis_child = &self.builder_gen.state_mod.vis_child; let vis_child_child = &self.builder_gen.state_mod.vis_child_child; let stateful_members_snake = &self.stateful_members_snake; - let stateful_members_pascal = &self.stateful_members_pascal; - let sealed_method_impl = &self.sealed_method_impl; - let mod_items = quote! { - /// Initial state of the builder where all members are unset - #vis_child struct AllUnset { - _private: () + let state_param = (self.stateful_members_snake.len() > 1).then(|| { + quote! { + } + }); - impl State for AllUnset { - #( - type #stateful_members_pascal = ::bon::private::Unset; - )* - - #sealed_method_impl - } + // This code is a bit overcomplicated for a reason. We could avoid + // defining the `mod type_aliases` and a `use type_aliases::*` but + // we do this to have prettier documentation generated by `rustdoc`. + // + // The problem is that `rustdoc` inlines the type aliases if they + // are unreachable from outside of the crate. It means that even if + // the type alias is declared as `pub` but it resides in a private + // module, `rustdoc` will inline it. This is not what we want, because + // the type aliases grow very large and make the documentation noisy. + // + // As a workaround we generate a bit different code for `rustdoc` + // that uses indirection via an associated type of a trait. The + // macro `__prettier_type_aliases_docs` does that if `doc` cfg + // is enabled. That macro modifies the contents of the module + // to add that indirection. + // + // It's implemented this way to optimize the compilation perf. for + // the case when `doc` cfg is disabled. In this case, we pay only + // for a single `#[cfg_attr(doc, ...)]` that expands to nothing. + // + // We could just generate the code that `__prettier_type_aliases_docs` + // generates eagerly and put it under `#[cfg(doc)]` but that increases + // the compile time, because the compiler needs to parse all that code + // even if it's not used and it's a lot of code. + let mod_items = quote! { + #vis_child use type_aliases::*; - #( - #[doc = #set_member_aliases_docs] - #vis_child type #set_member_aliases = - ::#set_member_aliases; - )* + #[cfg_attr(doc, ::bon::private::__prettier_type_aliases_docs)] + mod type_aliases { + use super::{members, State, Unset, Set}; - mod private { - #[doc(hidden)] - #vis_child_child trait StateExt { - #( type #set_member_aliases; )* - } - } + /// Initial state of the builder where all members are unset + #vis_child_child type AllUnset = ( + #( Unset, )* + ); - impl private::StateExt for S { - #(type #set_member_aliases = #transition_tuples; )* + #( + #[doc = #set_member_aliases_docs] + #vis_child_child type #set_member_aliases #state_param = #transition_tuples; + )* } }; @@ -170,11 +188,15 @@ impl<'a> StateModGenCtx<'a> { // it inlines the type aliases. Although for this workaround to work, // all items from the current module need to be reexported via a `*` // reexport, or the items need to be defined in the root lib.rs file. + // + // Therefore we use a `#[prettier_type_aliases_docs]` attribute to + // hide the internals of the type aliases from the documentation + // in all other cases. let parent_items = quote! { #[doc(hidden)] #[cfg(doc)] #[allow(unused_import_braces)] - #builder_vis use #state_mod::{ #( #set_member_aliases as _ ,)* }; + #builder_vis use #state_mod::{ AllUnset as _ #(, #set_member_aliases as _)* }; }; (mod_items, parent_items) @@ -244,7 +266,7 @@ impl<'a> StateModGenCtx<'a> { .map(|member| &member.name.pascal) .collect::>(); - let maybe_assoc_type_bounds = cfg!(feature = "msrv-1-79-0").then(|| { + let maybe_assoc_type_bounds = cfg!(feature = "implied-bounds").then(|| { quote! { < #( #required_members_pascal: IsSet, )* > } @@ -278,55 +300,6 @@ impl<'a> StateModGenCtx<'a> { } } - fn is_set_trait(&self) -> TokenStream { - let vis_child = &self.builder_gen.state_mod.vis_child; - let sealed_method_decl = &self.sealed_method_decl; - let sealed_method_impl = &self.sealed_method_impl; - - let on_unimplemented = Self::on_unimplemented( - "the member `{Self}` was not set, but this method requires it to be set", - ); - - quote! { - /// Marker trait that indicates that the member is set, i.e. at least - /// one of its setters was called. - // TODO: add examples (they would require having custom renames and - // visibility overrides for default setters) - #on_unimplemented - #vis_child trait IsSet { - #sealed_method_decl - } - - #[doc(hidden)] - impl IsSet for ::bon::private::Set { - #sealed_method_impl - } - } - } - - fn is_unset_trait(&self) -> TokenStream { - let vis_child = &self.builder_gen.state_mod.vis_child; - let sealed_method_decl = &self.sealed_method_decl; - let sealed_method_impl = &self.sealed_method_impl; - - let on_unimplemented = Self::on_unimplemented( - "the member `{Self}` was already set, but this method requires it to be unset", - ); - - quote! { - /// Marker trait implemented by members that are not set. - #on_unimplemented - #vis_child trait IsUnset { - #sealed_method_decl - } - - #[doc(hidden)] - impl IsUnset for ::bon::private::Unset { - #sealed_method_impl - } - } - } - fn members_names_mod(&self) -> TokenStream { let vis_child_child = &self.builder_gen.state_mod.vis_child_child; let stateful_members_snake = &self.stateful_members_snake; @@ -356,3 +329,102 @@ impl<'a> StateModGenCtx<'a> { } } } + +pub(crate) fn prettier_type_aliases_docs(module: TokenStream) -> TokenStream { + try_prettier_type_aliases_docs(module.clone()) + .unwrap_or_else(|err| [module, err.write_errors()].concat()) +} + +fn try_prettier_type_aliases_docs(module: TokenStream) -> Result { + let mut module: syn::ItemMod = syn::parse2(module)?; + let (_, module_items) = module.content.as_mut().ok_or_else(|| { + err!( + &Span::call_site(), + "expected an inline module with type aliases inside" + ) + })?; + + let mut aliases = module_items + .iter_mut() + .filter(|item| !matches!(item, syn::Item::Use(_))) + .map(require_type_alias) + .collect::>>()?; + + if aliases.is_empty() { + bail!( + &Span::call_site(), + "expected at least one type alias inside the module (e.g. AllUnset)" + ) + }; + + let all_unset = aliases.remove(0); + let set_members = aliases; + + // This is the case where there is one or zero stateful members. In this + // case type aliases don't have any generic parameters to avoid the error + // that the generic parameter is unused in the type alias definition. + // This case is small and rare enough that we may just avoid doing any + // special handling for it. + if set_members.len() <= 1 { + for alias in set_members { + assert!(alias.generics.params.is_empty()); + } + + return Ok(module.into_token_stream()); + } + + let vis = all_unset.vis.clone().into_equivalent_in_child_module()?; + + let set_members_idents = set_members.iter().map(|alias| &alias.ident); + let set_members_idents2 = set_members_idents.clone(); + let set_members_bodies = set_members.iter().map(|alias| &alias.ty); + + let all_unset_body = &all_unset.ty; + + let trait_module: syn::Item = syn::parse_quote! { + mod private { + use super::*; + + #vis trait OpaqueConst { + type AllUnset; + } + + impl OpaqueConst for () { + type AllUnset = #all_unset_body; + } + + #vis trait Opaque { + #( type #set_members_idents; )* + } + + impl Opaque for S { + #( type #set_members_idents2 = #set_members_bodies; )* + } + } + }; + + all_unset.ty = syn::parse_quote! { + <() as private::OpaqueConst>::AllUnset + }; + + for alias in set_members { + let alias_ident = &alias.ident; + alias.ty = syn::parse_quote! { + ::#alias_ident + }; + } + + module_items.push(trait_module); + + Ok(module.into_token_stream()) +} + +fn require_type_alias(item: &mut syn::Item) -> Result<&mut syn::ItemType> { + match item { + syn::Item::Type(item_type) => Ok(item_type), + _ => bail!( + &item, + "expected a type alias inside the module, but found a different item" + ), + } +} diff --git a/bon-macros/src/builder/mod.rs b/bon-macros/src/builder/mod.rs index e752dce7..a0999623 100644 --- a/bon-macros/src/builder/mod.rs +++ b/bon-macros/src/builder/mod.rs @@ -5,6 +5,8 @@ pub(crate) mod item_impl; mod item_fn; mod item_struct; +pub(crate) use builder_gen::prettier_type_aliases_docs; + use crate::normalization::{ExpandCfg, ExpansionOutput}; use crate::util; use crate::util::prelude::*; diff --git a/bon-macros/src/lib.rs b/bon-macros/src/lib.rs index 882be6ee..f401c475 100644 --- a/bon-macros/src/lib.rs +++ b/bon-macros/src/lib.rs @@ -304,3 +304,12 @@ pub fn set(input: proc_macro::TokenStream) -> proc_macro::TokenStream { collections::set::generate(entries).into() } + +#[doc(hidden)] +#[proc_macro_attribute] +pub fn __prettier_type_aliases_docs( + _: proc_macro::TokenStream, + module: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + builder::prettier_type_aliases_docs(module.into()).into() +} diff --git a/bon/Cargo.toml b/bon/Cargo.toml index 02203d5f..6eb6da37 100644 --- a/bon/Cargo.toml +++ b/bon/Cargo.toml @@ -98,4 +98,6 @@ std = ["alloc"] # wouldn't automatically imply `State::A: IsSet`, so the builder type state returned # after the `self.b()` doesn't imply that the member `a` is set, and thus `build()` # can't be called. -msrv-1-79-0 = ["bon-macros/msrv-1-79-0"] +# +# Huge thanks to @harudagondi for suggesting the name of this feature! +implied-bounds = ["bon-macros/implied-bounds"] diff --git a/bon/src/lib.rs b/bon/src/lib.rs index 81b9132f..431eaf7b 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -19,4 +19,4 @@ pub mod private; mod collections; /// Rexport all macros from the proc-macro crate. -pub use bon_macros::*; +pub use bon_macros::{bon, builder, map, set, Builder}; diff --git a/bon/src/private/mod.rs b/bon/src/private/mod.rs index 83fd9a03..9152124a 100644 --- a/bon/src/private/mod.rs +++ b/bon/src/private/mod.rs @@ -23,6 +23,9 @@ pub mod derives; mod cfg_eval; +pub use bon_macros::__prettier_type_aliases_docs; +pub use rustversion; + pub(crate) mod sealed { // The purpose of the `Sealed` trait **is** to be unnameable from outside the crate. #[allow(unnameable_types)] @@ -56,4 +59,16 @@ pub trait MemberState: Sealed {} impl MemberState for Unset {} impl MemberState for Set {} -pub use rustversion; +#[doc = r" Marker trait that indicates that the member is set, i.e. at least"] +#[doc = r" one of its setters was called."] +#[rustversion::attr(since(1.78.0),diagnostic::on_unimplemented(message = "the member `{Self}` was not set, but this method requires it to be set",label = "the member `{Self}` was not set, but this method requires it to be set"))] +pub trait IsSet: Sealed {} + +#[doc(hidden)] +impl IsSet for Set {} + +#[doc = r" Marker trait implemented by members that are not set."] +#[rustversion::attr(since(1.78.0),diagnostic::on_unimplemented(message = "the member `{Self}` was already set, but this method requires it to be unset",label = "the member `{Self}` was already set, but this method requires it to be unset"))] +pub trait IsUnset: Sealed {} +#[doc(hidden)] +impl IsUnset for Unset {} From b9a1e9e6b140fee1346a32e2ef919a135aac2551 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 11 Oct 2024 03:48:16 +0000 Subject: [PATCH 065/119] Move `IsSet`/`IsUnset` under a `builder_state` module to avoid flagging them as deprecated --- .../builder_gen/builder_params/on_params.rs | 2 +- .../builder/builder_gen/member/params/mod.rs | 4 +- bon-macros/src/builder/builder_gen/models.rs | 2 +- .../src/builder/builder_gen/setters/mod.rs | 11 ++--- bon-macros/src/lib.rs | 2 +- bon/src/builder_state.rs | 40 +++++++++++++++++++ bon/src/lib.rs | 2 + bon/src/private/mod.rs | 21 +++------- .../integration/builder/attr_with/mod.rs | 2 +- bon/tests/integration/builder/mod.rs | 2 +- 10 files changed, 61 insertions(+), 27 deletions(-) create mode 100644 bon/src/builder_state.rs diff --git a/bon-macros/src/builder/builder_gen/builder_params/on_params.rs b/bon-macros/src/builder/builder_gen/builder_params/on_params.rs index 43e7eeb9..e7155156 100644 --- a/bon-macros/src/builder/builder_gen/builder_params/on_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params/on_params.rs @@ -1,8 +1,8 @@ use crate::util::prelude::*; +use darling::FromMeta; use syn::parse::Parse; use syn::spanned::Spanned; use syn::visit::Visit; -use darling::FromMeta; #[derive(Debug)] pub(crate) struct OnParams { diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index 42800948..f4123c87 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -1,10 +1,10 @@ mod blanket; -mod setters; mod setter_closure; +mod setters; pub(crate) use blanket::*; -pub(crate) use setters::*; pub(crate) use setter_closure::*; +pub(crate) use setters::*; use super::MemberOrigin; use crate::parsing::SpannedKey; diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 221d20ce..37791400 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -248,7 +248,7 @@ impl BuilderGenCtx { .map(SpannedKey::into_value) .unwrap_or_else(|| { let docs = format!( - "Tools for manipulating the type state of the [`{}`].", + "Tools for manipulating the type state of [`{}`].", builder_type.ident ); diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index bcd31821..4e16aaba 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -262,11 +262,12 @@ impl<'a> SettersCtx<'a> { let builder_ident = &self.builder_gen.builder_type.ident; let generic_args = &self.builder_gen.generics.args; - let state_param = (self.builder_gen.stateful_members().take(2).count() > 1).then(|| { - quote! { - - } - }); + let state_param = + (self.builder_gen.stateful_members().take(2).count() > 1).then(|| { + quote! { + + } + }); quote! { #builder_ident<#(#generic_args,)* #state_mod::#state_transition #state_param> diff --git a/bon-macros/src/lib.rs b/bon-macros/src/lib.rs index f401c475..37c55eee 100644 --- a/bon-macros/src/lib.rs +++ b/bon-macros/src/lib.rs @@ -9,7 +9,7 @@ clippy::option_if_let_else, clippy::enum_glob_use, clippy::too_many_lines, - clippy::if_not_else, + clippy::if_not_else )] mod bon; diff --git a/bon/src/builder_state.rs b/bon/src/builder_state.rs new file mode 100644 index 00000000..a5137a98 --- /dev/null +++ b/bon/src/builder_state.rs @@ -0,0 +1,40 @@ +//! The items here are intentionally defined in a private module not inside of the +//! [`crate::private`] module. This is because that module is marked with `#[deprecated]` +//! which makes all items defined in that module also deprecated. +//! +//! This is not the desired behavior for the items defined here. They are not deprecated, +//! and they are expected to be exposed to the users. However, the users must not reference +//! them through the `bon` crate. Instead, they should use the re-exports from the state +//! module generated for the builder. + +use crate::private::{Sealed, Set, Unset}; + +/// Marker trait that indicates that the member is set, i.e. at least +/// one of its setters was called. +#[rustversion::attr( + since(1.78.0), + diagnostic::on_unimplemented( + message = "the member `{Self}` was not set, but this method requires it to be set", + label = "the member `{Self}` was not set, but this method requires it to be set" + ) +)] +pub trait IsSet: Sealed {} + + +/// Marker trait that indicates that the member is unset, i.e. none +/// of its setters were called. +#[rustversion::attr( + since(1.78.0), + diagnostic::on_unimplemented( + message = "the member `{Self}` was already set, but this method requires it to be unset", + label = "the member `{Self}` was already set, but this method requires it to be unset" + ) +)] +pub trait IsUnset: Sealed {} + + +#[doc(hidden)] +impl IsSet for Set {} + +#[doc(hidden)] +impl IsUnset for Unset {} diff --git a/bon/src/lib.rs b/bon/src/lib.rs index 431eaf7b..608ab1a3 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -15,6 +15,8 @@ (https://discord.gg/QcBYSamw4c)"] pub mod private; +mod builder_state; + /// Small utility declarative macros for creating colletions with [`Into`] conversions. mod collections; diff --git a/bon/src/private/mod.rs b/bon/src/private/mod.rs index 9152124a..f1680465 100644 --- a/bon/src/private/mod.rs +++ b/bon/src/private/mod.rs @@ -23,6 +23,11 @@ pub mod derives; mod cfg_eval; +// This reexport is a private implementation detail and should not be used +// directly! This reexport may change or be removed at any time between +// patch releases. Use the export from the builder's state module directly +// instead of using this reexport from `bon::private`. +pub use crate::builder_state::{IsSet, IsUnset}; pub use bon_macros::__prettier_type_aliases_docs; pub use rustversion; @@ -35,7 +40,7 @@ pub(crate) mod sealed { impl Sealed for super::Set {} } -use sealed::Sealed; +pub(crate) use sealed::Sealed; /// Used to implement the `alloc` feature. #[cfg(feature = "alloc")] @@ -58,17 +63,3 @@ pub trait MemberState: Sealed {} impl MemberState for Unset {} impl MemberState for Set {} - -#[doc = r" Marker trait that indicates that the member is set, i.e. at least"] -#[doc = r" one of its setters was called."] -#[rustversion::attr(since(1.78.0),diagnostic::on_unimplemented(message = "the member `{Self}` was not set, but this method requires it to be set",label = "the member `{Self}` was not set, but this method requires it to be set"))] -pub trait IsSet: Sealed {} - -#[doc(hidden)] -impl IsSet for Set {} - -#[doc = r" Marker trait implemented by members that are not set."] -#[rustversion::attr(since(1.78.0),diagnostic::on_unimplemented(message = "the member `{Self}` was already set, but this method requires it to be unset",label = "the member `{Self}` was already set, but this method requires it to be unset"))] -pub trait IsUnset: Sealed {} -#[doc(hidden)] -impl IsUnset for Unset {} diff --git a/bon/tests/integration/builder/attr_with/mod.rs b/bon/tests/integration/builder/attr_with/mod.rs index 0ca6656d..c28189c6 100644 --- a/bon/tests/integration/builder/attr_with/mod.rs +++ b/bon/tests/integration/builder/attr_with/mod.rs @@ -1,6 +1,6 @@ mod multi_arg; -mod single_arg; mod overwritable; +mod single_arg; struct IntoStrRef<'a>(&'a str); diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index a54cc7a7..fc4a4bda 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -1,8 +1,8 @@ mod attr_default; -mod attr_overwritable; mod attr_expose_positional_fn; mod attr_into; mod attr_on; +mod attr_overwritable; mod attr_skip; mod attr_transparent; mod attr_with; From 6472fc9d61d55f2673e3f078fc19c6b44312d0c9 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 11 Oct 2024 14:48:55 +0000 Subject: [PATCH 066/119] Add logo and favicon to rustdoc --- bon/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bon/src/lib.rs b/bon/src/lib.rs index 608ab1a3..2977ca9d 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -1,3 +1,7 @@ +#![doc( + html_logo_url = "https://elastio.github.io/bon/bon-logo-thumb.png", + html_favicon_url = "https://elastio.github.io/bon/bon-logo-medium.png" +)] #![doc = include_str!("../README.md")] #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] From 03015914872a5fb86b0b2720d96efb7d24c157c0 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 11 Oct 2024 20:08:05 +0000 Subject: [PATCH 067/119] Render `Optional` in a box for optional member's docs --- .../src/builder/builder_gen/setters/mod.rs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index 4e16aaba..eb44b638 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -422,7 +422,11 @@ impl SettersItems { .unwrap_or_else(|| { let base_docs = common_docs.unwrap_or(&member.docs); - let header = optional_setter_docs(default, &option_fn_name); + let header = optional_setter_docs( + default, + &option_fn_name, + "accepts an `Option`" + ); doc(&header).chain(base_docs.iter().cloned()).collect() }); @@ -433,7 +437,11 @@ impl SettersItems { .unwrap_or_else(|| { let base_docs = common_docs.unwrap_or(&member.docs); - let header = optional_setter_docs(default, &some_fn_name); + let header = optional_setter_docs( + default, + &some_fn_name, + "wraps the value with `Some` internally", + ); doc(&header).chain(base_docs.iter().cloned()).collect() }); @@ -466,7 +474,11 @@ impl SettersItems { } } -fn optional_setter_docs(default: Option<&str>, other_setter: &syn::Ident) -> String { +fn optional_setter_docs( + default: Option<&str>, + other_setter: &syn::Ident, + description: &str, +) -> String { let default = default .map(|default| { if default.contains('\n') || default.len() > 80 { @@ -478,7 +490,10 @@ fn optional_setter_docs(default: Option<&str>, other_setter: &syn::Ident) -> Str .unwrap_or_default(); format!( - "**Optional**. **See also:** [`{other_setter}()`](Self::{other_setter}).\ + "| **Optional** |\n\ + | -- |\n\n\ + **See also** a companion setter that {description}: \ + [`{other_setter}()`](Self::{other_setter}).\ \n\n{default}", ) } From 67bc4cf261cc624ca87cdae9a0a3015bd6904121 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 11 Oct 2024 20:25:20 +0000 Subject: [PATCH 068/119] Some initial tests for `#[builder(setters)]` and reject attributes with empty parens --- .../builder/builder_gen/builder_params/mod.rs | 4 +- .../src/builder/builder_gen/member/named.rs | 2 +- .../builder/builder_gen/member/params/mod.rs | 2 +- .../src/builder/builder_gen/setters/mod.rs | 12 +--- bon-macros/src/parsing/item_params.rs | 2 +- bon-macros/src/parsing/mod.rs | 5 +- bon/tests/integration/builder/attr_setters.rs | 20 +++++++ .../integration/builder/builder_derives.rs | 11 ---- bon/tests/integration/builder/mod.rs | 1 + .../{builder_derives.rs => attr_derive.rs} | 4 ++ ...lder_derives.stderr => attr_derive.stderr} | 58 ++++++++++--------- .../ui/compile_fail/attr_setters.rs | 6 ++ .../ui/compile_fail/attr_setters.stderr | 6 ++ .../integration/ui/compile_fail/errors.stderr | 26 ++++----- 14 files changed, 93 insertions(+), 66 deletions(-) create mode 100644 bon/tests/integration/builder/attr_setters.rs rename bon/tests/integration/ui/compile_fail/{builder_derives.rs => attr_derive.rs} (94%) rename bon/tests/integration/ui/compile_fail/{builder_derives.stderr => attr_derive.stderr} (89%) diff --git a/bon-macros/src/builder/builder_gen/builder_params/mod.rs b/bon-macros/src/builder/builder_gen/builder_params/mod.rs index f72431f4..9cd8d12e 100644 --- a/bon-macros/src/builder/builder_gen/builder_params/mod.rs +++ b/bon-macros/src/builder/builder_gen/builder_params/mod.rs @@ -41,11 +41,11 @@ pub(crate) struct BuilderParams { #[darling(default, with = parse_state_mod)] pub(crate) state_mod: ItemParams, - #[darling(multiple, with = crate::parsing::require_paren_delim_for_meta_list)] + #[darling(multiple, with = crate::parsing::require_non_empty_paren_meta_list)] pub(crate) on: Vec, /// Specifies the derives to apply to the builder. - #[darling(default, with = crate::parsing::require_paren_delim_for_meta_list)] + #[darling(default, with = crate::parsing::require_non_empty_paren_meta_list)] pub(crate) derive: BuilderDerives, } diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index 1a9aecb6..cf94bb30 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -1,5 +1,5 @@ use super::params::MemberParams; -use super::{params, MemberOrigin}; +use super::{params, MemberOrigin, SettersParams}; use crate::builder::builder_gen::builder_params::OnParams; use crate::builder::builder_gen::member::params::SettersFnParams; use crate::normalization::SyntaxVariant; diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index f4123c87..2ebccd76 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -35,7 +35,7 @@ pub(crate) struct MemberParams { pub(crate) name: Option, /// Configurations for the setter methods. - #[darling(with = crate::parsing::require_paren_delim_for_meta_list)] + #[darling(with = crate::parsing::require_non_empty_paren_meta_list)] pub(crate) setters: Option, /// Where to place the member in the generated builder methods API. diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index eb44b638..8bd0e413 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -388,12 +388,10 @@ impl SettersItems { .and_then(ItemParams::name) .cloned() .unwrap_or_else(|| { + let base_name = common_name.unwrap_or(&member.name.snake); // Preserve the original identifier span to make IDE's // "go to definition" works correctly - syn::Ident::new( - &format!("maybe_{}", member.name.snake_raw_str), - member.name.snake.span(), - ) + format_ident!("maybe_{}", base_name) }); let default = member.params.default.as_deref().and_then(|default| { @@ -422,11 +420,7 @@ impl SettersItems { .unwrap_or_else(|| { let base_docs = common_docs.unwrap_or(&member.docs); - let header = optional_setter_docs( - default, - &option_fn_name, - "accepts an `Option`" - ); + let header = optional_setter_docs(default, &option_fn_name, "accepts an `Option`"); doc(&header).chain(base_docs.iter().cloned()).collect() }); diff --git a/bon-macros/src/parsing/item_params.rs b/bon-macros/src/parsing/item_params.rs index 579fd3be..7dd9313a 100644 --- a/bon-macros/src/parsing/item_params.rs +++ b/bon-macros/src/parsing/item_params.rs @@ -60,7 +60,7 @@ impl ItemParamsParsing<'_> { docs: Option>, } - let full = crate::parsing::require_paren_delim_for_meta_list(meta)?; + let full = crate::parsing::require_non_empty_paren_meta_list(meta)?; let is_empty = matches!( full, diff --git a/bon-macros/src/parsing/mod.rs b/bon-macros/src/parsing/mod.rs index a45dacb7..ff30955e 100644 --- a/bon-macros/src/parsing/mod.rs +++ b/bon-macros/src/parsing/mod.rs @@ -11,9 +11,12 @@ pub(crate) use spanned_key::*; use crate::util::prelude::*; use darling::FromMeta; -pub(crate) fn require_paren_delim_for_meta_list(meta: &syn::Meta) -> Result { +pub(crate) fn require_non_empty_paren_meta_list(meta: &syn::Meta) -> Result { if let syn::Meta::List(meta) = meta { meta.require_parens_delim()?; + if meta.tokens.is_empty() { + bail!(meta, "expected at least one parameter in parentheses"); + } } T::from_meta(meta) diff --git a/bon/tests/integration/builder/attr_setters.rs b/bon/tests/integration/builder/attr_setters.rs new file mode 100644 index 00000000..0e62e1b3 --- /dev/null +++ b/bon/tests/integration/builder/attr_setters.rs @@ -0,0 +1,20 @@ +use crate::prelude::*; + +#[test] +fn test_struct() { + #[derive(Builder)] + #[builder(derive(Clone))] + #[allow(dead_code)] + struct Sut { + #[builder(setters(name = arg1_renamed))] + arg1: bool, + + #[builder(setters(name = arg2_renamed))] + arg2: Option<()>, + } + + let builder = Sut::builder().arg1_renamed(true); + + let _ = builder.clone().arg2_renamed(()); + let _ = builder.maybe_arg2_renamed(Some(())); +} diff --git a/bon/tests/integration/builder/builder_derives.rs b/bon/tests/integration/builder/builder_derives.rs index 4d1a63de..76d51a0e 100644 --- a/bon/tests/integration/builder/builder_derives.rs +++ b/bon/tests/integration/builder/builder_derives.rs @@ -79,17 +79,6 @@ fn builder_with_receiver() { ); } -#[test] -fn empty_derives() { - #[derive(Builder)] - #[builder(derive())] - struct Sut { - _arg1: bool, - } - - let _ = Sut::builder().arg1(true).build(); -} - #[test] fn skipped_members() { struct NoDebug; diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index fc4a4bda..7710d41f 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -16,6 +16,7 @@ mod name_conflicts; mod positional_members; mod raw_idents; mod smoke; +mod attr_setters; /// Tests for the deprecated features that we still support, but that we'll /// eventually remove in the future in a new major version release. diff --git a/bon/tests/integration/ui/compile_fail/builder_derives.rs b/bon/tests/integration/ui/compile_fail/attr_derive.rs similarity index 94% rename from bon/tests/integration/ui/compile_fail/builder_derives.rs rename to bon/tests/integration/ui/compile_fail/attr_derive.rs index 331a5124..e98428cf 100644 --- a/bon/tests/integration/ui/compile_fail/builder_derives.rs +++ b/bon/tests/integration/ui/compile_fail/attr_derive.rs @@ -55,4 +55,8 @@ impl StructContainsNonTrait { } } +#[derive(Builder)] +#[builder(derive())] +struct EmptyDerives {} + fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/builder_derives.stderr b/bon/tests/integration/ui/compile_fail/attr_derive.stderr similarity index 89% rename from bon/tests/integration/ui/compile_fail/builder_derives.stderr rename to bon/tests/integration/ui/compile_fail/attr_derive.stderr index 4c6a7a84..75b9da51 100644 --- a/bon/tests/integration/ui/compile_fail/builder_derives.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_derive.stderr @@ -1,5 +1,11 @@ +error: expected at least one parameter in parentheses + --> tests/integration/ui/compile_fail/attr_derive.rs:59:11 + | +59 | #[builder(derive())] + | ^^^^^^ + error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:9:23 + --> tests/integration/ui/compile_fail/attr_derive.rs:9:23 | 9 | no_impl_start_fn: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` @@ -11,7 +17,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:11:24 + --> tests/integration/ui/compile_fail/attr_derive.rs:11:24 | 11 | no_impls_required: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` @@ -28,7 +34,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:13:30 + --> tests/integration/ui/compile_fail/attr_derive.rs:13:30 | 13 | no_impl_optional: Option, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` @@ -45,7 +51,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:16:25 + --> tests/integration/ui/compile_fail/attr_derive.rs:16:25 | 16 | no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` @@ -62,7 +68,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:9:23 + --> tests/integration/ui/compile_fail/attr_derive.rs:9:23 | 9 | no_impl_start_fn: NoTraitImpls, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` @@ -81,7 +87,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:11:24 + --> tests/integration/ui/compile_fail/attr_derive.rs:11:24 | 11 | no_impls_required: NoTraitImpls, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` @@ -100,7 +106,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:13:30 + --> tests/integration/ui/compile_fail/attr_derive.rs:13:30 | 13 | no_impl_optional: Option, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` @@ -119,7 +125,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:16:25 + --> tests/integration/ui/compile_fail/attr_derive.rs:16:25 | 16 | no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` @@ -138,7 +144,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:24:24 + --> tests/integration/ui/compile_fail/attr_derive.rs:24:24 | 24 | _no_impl_start_fn: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` @@ -150,7 +156,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:26:25 + --> tests/integration/ui/compile_fail/attr_derive.rs:26:25 | 26 | _no_impls_required: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` @@ -167,7 +173,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:28:31 + --> tests/integration/ui/compile_fail/attr_derive.rs:28:31 | 28 | _no_impl_optional: Option, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` @@ -184,7 +190,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:31:26 + --> tests/integration/ui/compile_fail/attr_derive.rs:31:26 | 31 | _no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` @@ -201,7 +207,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:24:24 + --> tests/integration/ui/compile_fail/attr_derive.rs:24:24 | 24 | _no_impl_start_fn: NoTraitImpls, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` @@ -220,7 +226,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:26:25 + --> tests/integration/ui/compile_fail/attr_derive.rs:26:25 | 26 | _no_impls_required: NoTraitImpls, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` @@ -239,7 +245,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:28:31 + --> tests/integration/ui/compile_fail/attr_derive.rs:28:31 | 28 | _no_impl_optional: Option, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` @@ -258,7 +264,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:31:26 + --> tests/integration/ui/compile_fail/attr_derive.rs:31:26 | 31 | _no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` @@ -277,13 +283,13 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: the trait bound `StructContainsNonTrait: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:38:6 + --> tests/integration/ui/compile_fail/attr_derive.rs:38:6 | 38 | impl StructContainsNonTrait { | ^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `StructContainsNonTrait` error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:44:28 + --> tests/integration/ui/compile_fail/attr_derive.rs:44:28 | 44 | _no_impl_start_fn: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` @@ -295,7 +301,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:46:29 + --> tests/integration/ui/compile_fail/attr_derive.rs:46:29 | 46 | _no_impls_required: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` @@ -312,7 +318,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:48:35 + --> tests/integration/ui/compile_fail/attr_derive.rs:48:35 | 48 | _no_impl_optional: Option, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` @@ -329,7 +335,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/builder_derives.rs:51:30 + --> tests/integration/ui/compile_fail/attr_derive.rs:51:30 | 51 | _no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` @@ -346,7 +352,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: `StructContainsNonTrait` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:38:6 + --> tests/integration/ui/compile_fail/attr_derive.rs:38:6 | 38 | impl StructContainsNonTrait { | ^^^^^^^^^^^^^^^^^^^^^^ `StructContainsNonTrait` cannot be formatted using `{:?}` @@ -360,7 +366,7 @@ note: required by a bound in `as_dyn_debug` | ^^^^^ required by this bound in `as_dyn_debug` error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:44:28 + --> tests/integration/ui/compile_fail/attr_derive.rs:44:28 | 44 | _no_impl_start_fn: NoTraitImpls, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` @@ -379,7 +385,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:46:29 + --> tests/integration/ui/compile_fail/attr_derive.rs:46:29 | 46 | _no_impls_required: NoTraitImpls, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` @@ -398,7 +404,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:48:35 + --> tests/integration/ui/compile_fail/attr_derive.rs:48:35 | 48 | _no_impl_optional: Option, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` @@ -417,7 +423,7 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/builder_derives.rs:51:30 + --> tests/integration/ui/compile_fail/attr_derive.rs:51:30 | 51 | _no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` diff --git a/bon/tests/integration/ui/compile_fail/attr_setters.rs b/bon/tests/integration/ui/compile_fail/attr_setters.rs index 32da8a0c..da7d971d 100644 --- a/bon/tests/integration/ui/compile_fail/attr_setters.rs +++ b/bon/tests/integration/ui/compile_fail/attr_setters.rs @@ -66,4 +66,10 @@ struct OptionFnSetterWithTransparent { member: Option, } +#[derive(Builder)] +struct EmptySettersConfig { + #[builder(setters())] + member: i32, +} + fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_setters.stderr b/bon/tests/integration/ui/compile_fail/attr_setters.stderr index 37355b43..7afffcad 100644 --- a/bon/tests/integration/ui/compile_fail/attr_setters.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_setters.stderr @@ -45,3 +45,9 @@ error: `option_fn` setter function applies only to members with `#[builder(defau | 65 | #[builder(transparent, setters(option_fn = bar))] | ^^^^^^^^^ + +error: expected at least one parameter in parentheses + --> tests/integration/ui/compile_fail/attr_setters.rs:71:15 + | +71 | #[builder(setters())] + | ^^^^^^^ diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr index 1395758b..c3807863 100644 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ b/bon/tests/integration/ui/compile_fail/errors.stderr @@ -64,13 +64,11 @@ error: Expected an attribute of form `on(type_pattern, ...)` 72 | #[builder(on)] | ^^ -error: unexpected end of input, expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, `dyn`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime - --> tests/integration/ui/compile_fail/errors.rs:75:1 +error: expected at least one parameter in parentheses + --> tests/integration/ui/compile_fail/errors.rs:75:11 | 75 | #[builder(on())] - | ^^^^^^^^^^^^^^^^ - | - = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info) + | ^^ error: expected `,` --> tests/integration/ui/compile_fail/errors.rs:78:1 @@ -205,8 +203,8 @@ error[E0277]: the member `Unset` was not set, but this method requires it to 34 | let _ = Example::builder().x(1).build(); | ^^^^^ the member `Unset` was not set, but this method requires it to be set | - = help: the trait `example_builder::IsSet` is not implemented for `Unset`, which is required by `(Set, Unset, Unset): example_builder::IsComplete` - = help: the trait `example_builder::IsSet` is implemented for `Set` + = help: the trait `IsSet` is not implemented for `Unset`, which is required by `(Set, Unset, Unset): example_builder::IsComplete` + = help: the trait `IsSet` is implemented for `Set` note: required for `(Set, Unset, Unset)` to implement `example_builder::IsComplete` --> tests/integration/ui/compile_fail/errors.rs:24:14 | @@ -227,8 +225,8 @@ error[E0277]: the member `Unset` was not set, but this method requires 34 | let _ = Example::builder().x(1).build(); | ^^^^^ the member `Unset` was not set, but this method requires it to be set | - = help: the trait `example_builder::IsSet` is not implemented for `Unset`, which is required by `(Set, Unset, Unset): example_builder::IsComplete` - = help: the trait `example_builder::IsSet` is implemented for `Set` + = help: the trait `IsSet` is not implemented for `Unset`, which is required by `(Set, Unset, Unset): example_builder::IsComplete` + = help: the trait `IsSet` is implemented for `Set` note: required for `(Set, Unset, Unset)` to implement `example_builder::IsComplete` --> tests/integration/ui/compile_fail/errors.rs:24:14 | @@ -249,8 +247,8 @@ error[E0277]: the member `Set` was already set, but this method requires it t 37 | let _ = Example::builder().y(1).y(2); | ^ the member `Set` was already set, but this method requires it to be unset | - = help: the trait `example_builder::IsUnset` is not implemented for `Set` - = help: the trait `example_builder::IsUnset` is implemented for `Unset` + = help: the trait `IsUnset` is not implemented for `Set` + = help: the trait `IsUnset` is implemented for `Unset` note: required by a bound in `ExampleBuilder::::y` --> tests/integration/ui/compile_fail/errors.rs:24:14 | @@ -267,9 +265,9 @@ error[E0277]: the member `Unset` was not set, but this method requires it 47 | let _ = Sut::builder().build(); | ^^^^^ the member `Unset` was not set, but this method requires it to be set | - = help: the trait `sut_builder::IsSet` is not implemented for `Unset`, which is required by `sut_builder::AllUnset: sut_builder::IsComplete` - = help: the trait `sut_builder::IsSet` is implemented for `Set` -note: required for `sut_builder::AllUnset` to implement `sut_builder::IsComplete` + = help: the trait `IsSet` is not implemented for `Unset`, which is required by `(Unset,): sut_builder::IsComplete` + = help: the trait `IsSet` is implemented for `Set` +note: required for `(Unset,)` to implement `sut_builder::IsComplete` --> tests/integration/ui/compile_fail/errors.rs:42:18 | 42 | #[derive(Builder)] From 7cab13c3fffc73b6a844bea1c83e1c55b779f4f1 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 11 Oct 2024 23:28:04 +0000 Subject: [PATCH 069/119] Some more tests --- .../builder/builder_gen/builder_params/mod.rs | 4 +- .../builder_gen/builder_params/on_params.rs | 2 +- .../src/builder/builder_gen/input_struct.rs | 13 ++++- .../src/builder/builder_gen/member/mod.rs | 6 +++ .../src/builder/builder_gen/member/named.rs | 2 +- .../builder/builder_gen/member/params/mod.rs | 2 +- bon-macros/src/builder/item_impl.rs | 1 + bon-macros/src/builder/mod.rs | 19 +++---- bon-macros/src/parsing/item_params.rs | 2 +- bon-macros/src/parsing/mod.rs | 31 ++++++++--- bon/src/private/deprecations.rs | 6 --- bon/src/private/mod.rs | 3 -- bon/tests/integration/builder/legacy.rs | 40 -------------- bon/tests/integration/builder/mod.rs | 4 -- .../ui/compile_fail/attr_builder.rs | 52 ++++++++++++++++++ .../ui/compile_fail/attr_builder.stderr | 53 +++++++++++++++++++ .../ui/compile_fail/attr_derive.stderr | 4 +- .../ui/compile_fail/attr_setters.stderr | 4 +- .../integration/ui/compile_fail/errors.rs | 3 -- .../integration/ui/compile_fail/errors.stderr | 23 +++----- 20 files changed, 173 insertions(+), 101 deletions(-) delete mode 100644 bon/src/private/deprecations.rs delete mode 100644 bon/tests/integration/builder/legacy.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_builder.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_builder.stderr diff --git a/bon-macros/src/builder/builder_gen/builder_params/mod.rs b/bon-macros/src/builder/builder_gen/builder_params/mod.rs index 9cd8d12e..29288c5f 100644 --- a/bon-macros/src/builder/builder_gen/builder_params/mod.rs +++ b/bon-macros/src/builder/builder_gen/builder_params/mod.rs @@ -41,11 +41,11 @@ pub(crate) struct BuilderParams { #[darling(default, with = parse_state_mod)] pub(crate) state_mod: ItemParams, - #[darling(multiple, with = crate::parsing::require_non_empty_paren_meta_list)] + #[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::require_non_empty_paren_meta_list)] + #[darling(default, with = crate::parsing::parse_non_empty_paren_meta_list)] pub(crate) derive: BuilderDerives, } diff --git a/bon-macros/src/builder/builder_gen/builder_params/on_params.rs b/bon-macros/src/builder/builder_gen/builder_params/on_params.rs index e7155156..6b780b4e 100644 --- a/bon-macros/src/builder/builder_gen/builder_params/on_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params/on_params.rs @@ -91,7 +91,7 @@ impl FromMeta for OnParams { syn::Meta::List(meta) => meta, _ => bail!( meta, - "Expected an attribute of form `on(type_pattern, ...)`" + "expected an attribute of form `on(type_pattern, ...)`" ), }; diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 4d404872..d7a5f923 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -36,7 +36,14 @@ impl StructInputParams { .map(|attr| { let meta = match &attr.meta { syn::Meta::List(meta) => meta, - _ => bail!(attr, "expected `#[builder(...)]` syntax"), + 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" + ), }; if !matches!(meta.delimiter, syn::MacroDelimiter::Paren(_)) { @@ -47,6 +54,10 @@ impl StructInputParams { ); } + if meta.tokens.is_empty() { + bail!(&meta, "this empty `#[builder()]` attribute is redundant; remove it"); + } + let meta = darling::ast::NestedMeta::parse_meta_list(meta.tokens.clone())?; Ok(meta) diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 1e20a6e8..ef026884 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -106,6 +106,12 @@ impl Member { 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_if_meta_list(&attr.meta)?; + } + } + let params = MemberParams::from_attributes(member.attrs)?; params.validate(origin)?; Ok((member, params)) diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index cf94bb30..1a9aecb6 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -1,5 +1,5 @@ use super::params::MemberParams; -use super::{params, MemberOrigin, SettersParams}; +use super::{params, MemberOrigin}; use crate::builder::builder_gen::builder_params::OnParams; use crate::builder::builder_gen::member::params::SettersFnParams; use crate::normalization::SyntaxVariant; diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index 2ebccd76..d8bad33c 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -35,7 +35,7 @@ pub(crate) struct MemberParams { pub(crate) name: Option, /// Configurations for the setter methods. - #[darling(with = crate::parsing::require_non_empty_paren_meta_list)] + #[darling(with = crate::parsing::parse_non_empty_paren_meta_list)] pub(crate) setters: Option, /// Where to place the member in the generated builder methods API. diff --git a/bon-macros/src/builder/item_impl.rs b/bon-macros/src/builder/item_impl.rs index 19a643cd..2fcdddb1 100644 --- a/bon-macros/src/builder/item_impl.rs +++ b/bon-macros/src/builder/item_impl.rs @@ -94,6 +94,7 @@ pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result Toke fn try_generate_from_attr(params: TokenStream, item: TokenStream) -> Result { let item: syn::Item = syn::parse2(item)?; - if let syn::Item::Struct(item_struct) = item { - return Ok(quote! { - // Triggers a deprecation warning if the user is using the old attribute - // syntax on the structs instead of the derive syntax. - use ::bon::private::deprecations::builder_attribute_on_a_struct as _; - - #[derive(::bon::Builder)] - #[builder(#params)] - #item_struct - }); - } - let macro_path = syn::parse_quote!(::bon::builder); let ctx = ExpandCfg { @@ -69,6 +57,13 @@ fn try_generate_from_attr(params: TokenStream, item: TokenStream) -> Result item_fn::generate(FromMeta::from_list(nested_meta)?, item_fn)?, + 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" + ) + } _ => bail!( &Span::call_site(), "only `fn` items are supported by the `#[bon::builder]` attribute" diff --git a/bon-macros/src/parsing/item_params.rs b/bon-macros/src/parsing/item_params.rs index 7dd9313a..37c8094c 100644 --- a/bon-macros/src/parsing/item_params.rs +++ b/bon-macros/src/parsing/item_params.rs @@ -60,7 +60,7 @@ impl ItemParamsParsing<'_> { docs: Option>, } - let full = crate::parsing::require_non_empty_paren_meta_list(meta)?; + let full = crate::parsing::parse_non_empty_paren_meta_list(meta)?; let is_empty = matches!( full, diff --git a/bon-macros/src/parsing/mod.rs b/bon-macros/src/parsing/mod.rs index ff30955e..2b7c58cf 100644 --- a/bon-macros/src/parsing/mod.rs +++ b/bon-macros/src/parsing/mod.rs @@ -11,13 +11,32 @@ pub(crate) use spanned_key::*; use crate::util::prelude::*; use darling::FromMeta; -pub(crate) fn require_non_empty_paren_meta_list(meta: &syn::Meta) -> Result { - if let syn::Meta::List(meta) = meta { - meta.require_parens_delim()?; - if meta.tokens.is_empty() { - bail!(meta, "expected at least one parameter in parentheses"); +pub(crate) fn parse_non_empty_paren_meta_list(meta: &syn::Meta) -> Result { + require_non_empty_paren_if_meta_list(meta)?; + T::from_meta(meta) +} + +pub(crate) fn require_non_empty_paren_if_meta_list(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 at least one parameter in parentheses" + ); + } } + syn::Meta::Path(path) => bail!( + &meta, + "this empty `#[{0}]` attribute is unexpected; \ + remove it or pass some parameters in parentheses: \ + `#[{0}(...)]`", + darling::util::path_to_string(path) + ), + _ => {} } - T::from_meta(meta) + Ok(()) } diff --git a/bon/src/private/deprecations.rs b/bon/src/private/deprecations.rs deleted file mode 100644 index ad9e27a4..00000000 --- a/bon/src/private/deprecations.rs +++ /dev/null @@ -1,6 +0,0 @@ -#[doc(hidden)] -#[deprecated(note = "\ - #[bon::builder] on top of a struct is deprecated; \ - use `#[derive(bon::Builder)]` instead; \ - see more details at https://elastio.github.io/bon/blog/bon-builder-v2-2-release#derive-builder-syntax-for-structs")] -pub mod builder_attribute_on_a_struct {} diff --git a/bon/src/private/mod.rs b/bon/src/private/mod.rs index f1680465..a2117a2d 100644 --- a/bon/src/private/mod.rs +++ b/bon/src/private/mod.rs @@ -13,9 +13,6 @@ deprecated, )] -/// Used to trigger deprecation warnings from the macros. -pub mod deprecations; - /// Used for providing better IDE hints (completions and syntax highlighting). pub mod ide; diff --git a/bon/tests/integration/builder/legacy.rs b/bon/tests/integration/builder/legacy.rs deleted file mode 100644 index f4376d7b..00000000 --- a/bon/tests/integration/builder/legacy.rs +++ /dev/null @@ -1,40 +0,0 @@ -#[rustversion::since(1.77.0)] -#[test] -#[expect(deprecated)] -fn builder_on_struct() { - use crate::prelude::*; - use core::net::IpAddr; - - #[builder] - #[derive(Debug)] - #[allow(dead_code)] - struct Sut { - a: u32, - - #[builder(into)] - b: IpAddr, - - #[builder(default)] - c: u32, - - d: Option, - - #[builder(name = renamed)] - e: u32, - - #[builder(skip = e + 99)] - f: u32, - } - - let actual = Sut::builder() - .a(42) - .b([127, 0, 0, 1]) - .maybe_d(None) - .renamed(1) - .build(); - - assert_debug_eq( - actual, - expect!["Sut { a: 42, b: 127.0.0.1, c: 0, d: None, e: 1, f: 100 }"], - ); -} diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index 7710d41f..7bdd4aaa 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -18,10 +18,6 @@ mod raw_idents; mod smoke; mod attr_setters; -/// Tests for the deprecated features that we still support, but that we'll -/// eventually remove in the future in a new major version release. -mod legacy; - use crate::prelude::*; #[test] diff --git a/bon/tests/integration/ui/compile_fail/attr_builder.rs b/bon/tests/integration/ui/compile_fail/attr_builder.rs new file mode 100644 index 00000000..bf4c84e2 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_builder.rs @@ -0,0 +1,52 @@ +use bon::{bon, builder, Builder}; + +#[derive(Builder)] +#[builder] +struct EmptyTopLevelBuilderAttr {} + +#[derive(Builder)] +#[builder()] +struct EmptyTopLevelBuilderAttrWithParens {} + +#[derive(Builder)] +struct EmptyMemberLevelBuilderAttr { + #[builder] + x: u32, +} + +#[derive(Builder)] +struct EmptyMemberLevelBuilderAttrWithParens { + #[builder()] + x: u32, +} + +#[builder] +fn fn_empty_member_level_builder_attr(#[builder] _x: u32) {} + +#[builder] +fn fn_empty_member_level_builder_attr_with_parens(#[builder()] _x: u32) {} + +struct EmptyBuilderAttr; + +#[bon] +impl EmptyBuilderAttr { + #[builder] + fn empty_member_level_builder_attr(#[builder] _x: u32) {} +} + +#[bon] +impl EmptyBuilderAttr { + #[builder] + fn empty_member_level_builder_attr_with_parens(#[builder()] _x: u32) {} +} + +#[bon] +impl EmptyBuilderAttr { + #[builder()] + fn empty_top_level_builder_attr_with_parens() {} +} + +#[builder] +struct LegacyBuilderProcMacroAttrOnStruct {} + +fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_builder.stderr b/bon/tests/integration/ui/compile_fail/attr_builder.stderr new file mode 100644 index 00000000..08fe2e37 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_builder.stderr @@ -0,0 +1,53 @@ +error: this empty `#[builder]` attribute is redundant; remove it + --> tests/integration/ui/compile_fail/attr_builder.rs:4:3 + | +4 | #[builder] + | ^^^^^^^ + +error: this empty `#[builder()]` attribute is redundant; remove it + --> tests/integration/ui/compile_fail/attr_builder.rs:8:3 + | +8 | #[builder()] + | ^^^^^^^ + +error: this empty `#[builder]` attribute is unexpected; remove it or pass some parameters in parentheses: `#[builder(...)]` + --> tests/integration/ui/compile_fail/attr_builder.rs:13:7 + | +13 | #[builder] + | ^^^^^^^ + +error: expected at least one parameter in parentheses + --> tests/integration/ui/compile_fail/attr_builder.rs:19:14 + | +19 | #[builder()] + | ^^ + +error: this empty `#[builder]` attribute is unexpected; remove it or pass some parameters in parentheses: `#[builder(...)]` + --> tests/integration/ui/compile_fail/attr_builder.rs:24:41 + | +24 | fn fn_empty_member_level_builder_attr(#[builder] _x: u32) {} + | ^^^^^^^ + +error: expected at least one parameter in parentheses + --> tests/integration/ui/compile_fail/attr_builder.rs:27:60 + | +27 | fn fn_empty_member_level_builder_attr_with_parens(#[builder()] _x: u32) {} + | ^^ + +error: this empty `#[builder]` attribute is unexpected; remove it or pass some parameters in parentheses: `#[builder(...)]` + --> tests/integration/ui/compile_fail/attr_builder.rs:34:42 + | +34 | fn empty_member_level_builder_attr(#[builder] _x: u32) {} + | ^^^^^^^ + +error: expected at least one parameter in parentheses + --> tests/integration/ui/compile_fail/attr_builder.rs:40:61 + | +40 | fn empty_member_level_builder_attr_with_parens(#[builder()] _x: u32) {} + | ^^ + +error: to generate a builder for a struct, use `#[derive(bon::Builder)]` instead; `#[bon::builder]` syntax is supported only for functions + --> tests/integration/ui/compile_fail/attr_builder.rs:50:1 + | +50 | struct LegacyBuilderProcMacroAttrOnStruct {} + | ^^^^^^ diff --git a/bon/tests/integration/ui/compile_fail/attr_derive.stderr b/bon/tests/integration/ui/compile_fail/attr_derive.stderr index 75b9da51..60bd0210 100644 --- a/bon/tests/integration/ui/compile_fail/attr_derive.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_derive.stderr @@ -1,8 +1,8 @@ error: expected at least one parameter in parentheses - --> tests/integration/ui/compile_fail/attr_derive.rs:59:11 + --> tests/integration/ui/compile_fail/attr_derive.rs:59:17 | 59 | #[builder(derive())] - | ^^^^^^ + | ^^ error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied --> tests/integration/ui/compile_fail/attr_derive.rs:9:23 diff --git a/bon/tests/integration/ui/compile_fail/attr_setters.stderr b/bon/tests/integration/ui/compile_fail/attr_setters.stderr index 7afffcad..c9583e37 100644 --- a/bon/tests/integration/ui/compile_fail/attr_setters.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_setters.stderr @@ -47,7 +47,7 @@ error: `option_fn` setter function applies only to members with `#[builder(defau | ^^^^^^^^^ error: expected at least one parameter in parentheses - --> tests/integration/ui/compile_fail/attr_setters.rs:71:15 + --> tests/integration/ui/compile_fail/attr_setters.rs:71:22 | 71 | #[builder(setters())] - | ^^^^^^^ + | ^^ diff --git a/bon/tests/integration/ui/compile_fail/errors.rs b/bon/tests/integration/ui/compile_fail/errors.rs index 66f5dc5c..e020ede3 100644 --- a/bon/tests/integration/ui/compile_fail/errors.rs +++ b/bon/tests/integration/ui/compile_fail/errors.rs @@ -128,9 +128,6 @@ fn destructuring_in_fn_is_unsupported((_, _): (u32, u32)) {} #[must_use] fn double_must_use() {} -#[builder] -struct BuilderProcMacroAttrOnAStruct {} - #[derive(Builder)] struct InvalidWithExpr { #[builder(with = 42)] diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr index c3807863..f6c49e2f 100644 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ b/bon/tests/integration/ui/compile_fail/errors.stderr @@ -58,17 +58,17 @@ error: nested attributes are not allowed in the type pattern of #[builder(on(typ 69 | #[builder(on(fn(#[attr] a: u32), into))] | ^ -error: Expected an attribute of form `on(type_pattern, ...)` +error: this empty `#[on]` attribute is unexpected; remove it or pass some parameters in parentheses: `#[on(...)]` --> tests/integration/ui/compile_fail/errors.rs:72:11 | 72 | #[builder(on)] | ^^ error: expected at least one parameter in parentheses - --> tests/integration/ui/compile_fail/errors.rs:75:11 + --> tests/integration/ui/compile_fail/errors.rs:75:13 | 75 | #[builder(on())] - | ^^ + | ^^ error: expected `,` --> tests/integration/ui/compile_fail/errors.rs:78:1 @@ -87,10 +87,10 @@ error: this #[builder(on(type_pattern, ...))] contains no options to override th = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected at least one parameter in parentheses - --> tests/integration/ui/compile_fail/errors.rs:85:11 + --> tests/integration/ui/compile_fail/errors.rs:85:19 | 85 | #[builder(start_fn())] - | ^^^^^^^^ + | ^^ error: `skip` attribute can't be specified together with `into` --> tests/integration/ui/compile_fail/errors.rs:90:15 @@ -151,9 +151,9 @@ error: Found multiple #[must_use], but bon only works with exactly one (or less) | ^ error: expected a closure e.g. `with = |param: T| expression` - --> tests/integration/ui/compile_fail/errors.rs:136:15 + --> tests/integration/ui/compile_fail/errors.rs:133:15 | -136 | #[builder(with = 42)] +133 | #[builder(with = 42)] | ^^^^ warning: unused attribute @@ -170,15 +170,6 @@ note: attribute also specified here = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: `#[warn(unused_attributes)]` on by default -warning: use of deprecated module `bon::private::deprecations::builder_attribute_on_a_struct`: #[bon::builder] on top of a struct is deprecated; use `#[derive(bon::Builder)]` instead; see more details at https://elastio.github.io/bon/blog/bon-builder-v2-2-release#derive-builder-syntax-for-structs - --> tests/integration/ui/compile_fail/errors.rs:131:1 - | -131 | #[builder] - | ^^^^^^^^^^ - | - = note: `#[warn(deprecated)]` on by default - = note: this warning originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info) - error[E0599]: no method named `x` found for struct `SkipGeneratesNoSetterBuilder` in the current scope --> tests/integration/ui/compile_fail/errors.rs:21:38 | From b5fd4520ad5d8542b06d48c3dc74ef9252d15cf8 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 11 Oct 2024 23:31:16 +0000 Subject: [PATCH 070/119] Fix validation --- bon-macros/src/builder/builder_gen/member/mod.rs | 2 +- bon-macros/src/builder/item_impl.rs | 4 +++- bon-macros/src/parsing/mod.rs | 6 +++--- e2e-tests/src/lib.rs | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index ef026884..38bb0712 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -108,7 +108,7 @@ impl Member { .map(|member| { for attr in member.attrs { if attr.meta.path().is_ident("builder") { - crate::parsing::require_non_empty_paren_if_meta_list(&attr.meta)?; + crate::parsing::require_non_empty_paren_meta_list(&attr.meta)?; } } diff --git a/bon-macros/src/builder/item_impl.rs b/bon-macros/src/builder/item_impl.rs index 2fcdddb1..0e6f80d3 100644 --- a/bon-macros/src/builder/item_impl.rs +++ b/bon-macros/src/builder/item_impl.rs @@ -94,7 +94,9 @@ pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result(meta: &syn::Meta) -> Result { - require_non_empty_paren_if_meta_list(meta)?; + require_non_empty_paren_meta_list(meta)?; T::from_meta(meta) } -pub(crate) fn require_non_empty_paren_if_meta_list(meta: &syn::Meta) -> Result { +pub(crate) fn require_non_empty_paren_meta_list(meta: &syn::Meta) -> Result { match meta { syn::Meta::List(meta) => { meta.require_parens_delim()?; @@ -35,7 +35,7 @@ pub(crate) fn require_non_empty_paren_if_meta_list(meta: &syn::Meta) -> Result { `#[{0}(...)]`", darling::util::path_to_string(path) ), - _ => {} + syn::Meta::NameValue(_) => {} } Ok(()) diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index 63844a7a..b4a69980 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -149,8 +149,8 @@ pub fn greet( #[builder] pub fn fn_with_impl_trait( - #[builder] _arg1: impl std::fmt::Debug + Clone, - #[builder] _arg2: impl std::fmt::Debug, + _arg1: impl std::fmt::Debug + Clone, + _arg2: impl std::fmt::Debug, ) { } From fe288e8d8bd9798d0e21468790a0cd73276f6ffa Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 11 Oct 2024 23:36:26 +0000 Subject: [PATCH 071/119] Update test snapshot --- bon/tests/integration/ui/compile_fail/attr_builder.stderr | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bon/tests/integration/ui/compile_fail/attr_builder.stderr b/bon/tests/integration/ui/compile_fail/attr_builder.stderr index 08fe2e37..84641d3b 100644 --- a/bon/tests/integration/ui/compile_fail/attr_builder.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_builder.stderr @@ -46,6 +46,12 @@ error: expected at least one parameter in parentheses 40 | fn empty_member_level_builder_attr_with_parens(#[builder()] _x: u32) {} | ^^ +error: expected at least one parameter in parentheses + --> tests/integration/ui/compile_fail/attr_builder.rs:45:14 + | +45 | #[builder()] + | ^^ + error: to generate a builder for a struct, use `#[derive(bon::Builder)]` instead; `#[bon::builder]` syntax is supported only for functions --> tests/integration/ui/compile_fail/attr_builder.rs:50:1 | From f53988a265cd05100c6e1a01b8e023c6c03d0874 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 11 Oct 2024 23:53:49 +0000 Subject: [PATCH 072/119] Some fixes --- bon-macros/src/builder/builder_gen/input_struct.rs | 5 ++++- bon-macros/src/builder/builder_gen/state_mod.rs | 2 +- bon/src/builder_state.rs | 2 -- bon/tests/integration/builder/attr_setters.rs | 2 +- bon/tests/integration/builder/mod.rs | 2 +- e2e-tests/src/lib.rs | 6 +----- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index d7a5f923..5a829027 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -55,7 +55,10 @@ impl StructInputParams { } if meta.tokens.is_empty() { - bail!(&meta, "this empty `#[builder()]` attribute is redundant; remove it"); + bail!( + &meta, + "this empty `#[builder()]` attribute is redundant; remove it" + ); } let meta = darling::ast::NestedMeta::parse_meta_list(meta.tokens.clone())?; diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index f72c194f..6f43761e 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -69,8 +69,8 @@ impl<'a> StateModGenCtx<'a> { )] #( #state_mod_docs )* #vis_mod mod #state_mod_ident { - #vis_child use ::bon::private::{IsSet, IsUnset}; #[doc(inline)] + #vis_child use ::bon::private::{IsSet, IsUnset}; use ::bon::private::{Set, Unset}; mod sealed { diff --git a/bon/src/builder_state.rs b/bon/src/builder_state.rs index a5137a98..f68fb818 100644 --- a/bon/src/builder_state.rs +++ b/bon/src/builder_state.rs @@ -20,7 +20,6 @@ use crate::private::{Sealed, Set, Unset}; )] pub trait IsSet: Sealed {} - /// Marker trait that indicates that the member is unset, i.e. none /// of its setters were called. #[rustversion::attr( @@ -32,7 +31,6 @@ pub trait IsSet: Sealed {} )] pub trait IsUnset: Sealed {} - #[doc(hidden)] impl IsSet for Set {} diff --git a/bon/tests/integration/builder/attr_setters.rs b/bon/tests/integration/builder/attr_setters.rs index 0e62e1b3..8f1faf5c 100644 --- a/bon/tests/integration/builder/attr_setters.rs +++ b/bon/tests/integration/builder/attr_setters.rs @@ -1,7 +1,7 @@ use crate::prelude::*; #[test] -fn test_struct() { +fn test_name() { #[derive(Builder)] #[builder(derive(Clone))] #[allow(dead_code)] diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index 7bdd4aaa..e4c32caf 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -3,6 +3,7 @@ mod attr_expose_positional_fn; mod attr_into; mod attr_on; mod attr_overwritable; +mod attr_setters; mod attr_skip; mod attr_transparent; mod attr_with; @@ -16,7 +17,6 @@ mod name_conflicts; mod positional_members; mod raw_idents; mod smoke; -mod attr_setters; use crate::prelude::*; diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index b4a69980..3b3816d5 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -148,11 +148,7 @@ pub fn greet( } #[builder] -pub fn fn_with_impl_trait( - _arg1: impl std::fmt::Debug + Clone, - _arg2: impl std::fmt::Debug, -) { -} +pub fn fn_with_impl_trait(_arg1: impl std::fmt::Debug + Clone, _arg2: impl std::fmt::Debug) {} #[builder] pub fn many_function_parameters( From 2516baba0a8a6e235bb13e4947b1ec6a30b5d6e2 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 12 Oct 2024 00:40:59 +0000 Subject: [PATCH 073/119] Some refactoring and more tests --- .../src/builder/builder_gen/state_mod.rs | 19 ++++++++++----- bon/tests/integration/builder/attr_setters.rs | 24 ++++++++++++++++++- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index 6f43761e..682dbb6c 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -86,7 +86,7 @@ impl<'a> StateModGenCtx<'a> { } fn state_transitions(&self) -> (TokenStream, TokenStream) { - let transition_tuples = self + let transitions_bodies = self .builder_gen .stateful_members() .map(|member| { @@ -121,7 +121,7 @@ impl<'a> StateModGenCtx<'a> { "Returns a [`State`] that has [`IsSet`] implemented for `{member_snake}`\n\ \n\ [`State`]: self::State\n\ - [`IsSet`]: ::bon::IsSet", + [`IsSet`]: self::IsSet", ); (docs, alias) @@ -132,6 +132,13 @@ impl<'a> StateModGenCtx<'a> { let vis_child_child = &self.builder_gen.state_mod.vis_child_child; let stateful_members_snake = &self.stateful_members_snake; + // In a special case of 1 stateful member the generic `S` parameter + // is unused in the alias body, which triggers a compile error, because + // all generic type parameters must be used. This special case is + // unfortunate, but it's not the end of the world because it's rare + // that a builder has only one stateful member and the user wants to + // use the state transition type alias and expect it to have a generic + // type parameter. let state_param = (self.stateful_members_snake.len() > 1).then(|| { quote! { @@ -176,7 +183,7 @@ impl<'a> StateModGenCtx<'a> { #( #[doc = #set_member_aliases_docs] - #vis_child_child type #set_member_aliases #state_param = #transition_tuples; + #vis_child_child type #set_member_aliases #state_param = #transitions_bodies; )* } }; @@ -233,7 +240,7 @@ impl<'a> StateModGenCtx<'a> { #( #[doc = #assoc_types_docs] type #stateful_members_pascal: ::bon::private::MemberState< - self::members::#stateful_members_snake + members::#stateful_members_snake >; )* @@ -289,10 +296,10 @@ impl<'a> StateModGenCtx<'a> { } #[doc(hidden)] - impl IsComplete for State + impl IsComplete for S where #( - State::#required_members_pascal: IsSet, + S::#required_members_pascal: IsSet, )* { #sealed_method_impl diff --git a/bon/tests/integration/builder/attr_setters.rs b/bon/tests/integration/builder/attr_setters.rs index 8f1faf5c..b7e7578a 100644 --- a/bon/tests/integration/builder/attr_setters.rs +++ b/bon/tests/integration/builder/attr_setters.rs @@ -11,10 +11,32 @@ fn test_name() { #[builder(setters(name = arg2_renamed))] arg2: Option<()>, + + #[builder(default, setters(name = arg3_renamed))] + arg3: u32, } + use sut_builder::*; + let builder = Sut::builder().arg1_renamed(true); let _ = builder.clone().arg2_renamed(()); - let _ = builder.maybe_arg2_renamed(Some(())); + let _ = builder.clone().maybe_arg2_renamed(Some(())); + + let _ = builder.clone().arg3_renamed(42); + let _ = builder.maybe_arg3_renamed(Some(42)); + + // The name in the state must remain the same + let _: SutBuilder>> = Sut::builder() + .arg1_renamed(true) + .arg2_renamed(()) + .arg3_renamed(42); + + #[allow(clippy::items_after_statements)] + fn _assert_assoc_type_name(_: T) + where + T::Arg1:, + T::Arg2:, + { + } } From 9a6c6c22be7f52281ec6398931409f071ba99380 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 12 Oct 2024 00:59:41 +0000 Subject: [PATCH 074/119] Reorder items in the `bon` crate --- bon/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bon/src/lib.rs b/bon/src/lib.rs index 2977ca9d..cd7349dd 100644 --- a/bon/src/lib.rs +++ b/bon/src/lib.rs @@ -11,6 +11,12 @@ // suppress this lint for the entire crate. #![allow(deprecated)] +// Rexport all macros from the proc-macro crate. +pub use bon_macros::{bon, builder, map, set, Builder}; + +/// Small utility declarative macros for creating colletions with [`Into`] conversions. +mod collections; + #[doc(hidden)] #[deprecated = "the items from the `bon::private` module are an implementation detail; \ they should not be used directly; if you found a need for this, then you are probably \ @@ -20,9 +26,3 @@ pub mod private; mod builder_state; - -/// Small utility declarative macros for creating colletions with [`Into`] conversions. -mod collections; - -/// Rexport all macros from the proc-macro crate. -pub use bon_macros::{bon, builder, map, set, Builder}; From 21ba789b647ee68650d1b2784f860269fc96e4d3 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 12 Oct 2024 01:34:44 +0000 Subject: [PATCH 075/119] Add more tests. This is when I recognized how much it kills the compile time... --- bon/tests/integration/builder/attr_setters.rs | 80 +++++++++++++++++-- 1 file changed, 74 insertions(+), 6 deletions(-) diff --git a/bon/tests/integration/builder/attr_setters.rs b/bon/tests/integration/builder/attr_setters.rs index b7e7578a..4dfefeb0 100644 --- a/bon/tests/integration/builder/attr_setters.rs +++ b/bon/tests/integration/builder/attr_setters.rs @@ -3,7 +3,6 @@ use crate::prelude::*; #[test] fn test_name() { #[derive(Builder)] - #[builder(derive(Clone))] #[allow(dead_code)] struct Sut { #[builder(setters(name = arg1_renamed))] @@ -18,13 +17,13 @@ fn test_name() { use sut_builder::*; - let builder = Sut::builder().arg1_renamed(true); + let _ = Sut::builder().arg1_renamed(true); - let _ = builder.clone().arg2_renamed(()); - let _ = builder.clone().maybe_arg2_renamed(Some(())); + let _ = Sut::builder().arg2_renamed(()); + let _ = Sut::builder().maybe_arg2_renamed(Some(())); - let _ = builder.clone().arg3_renamed(42); - let _ = builder.maybe_arg3_renamed(Some(42)); + let _ = Sut::builder().arg3_renamed(42); + let _ = Sut::builder().maybe_arg3_renamed(Some(42)); // The name in the state must remain the same let _: SutBuilder>> = Sut::builder() @@ -40,3 +39,72 @@ fn test_name() { { } } + +#[test] +fn test_option_fn_name_and_some_fn_name() { + #[derive(Builder)] + #[builder(derive(Clone))] + #[allow(dead_code)] + struct Sut { + #[builder(setters(some_fn = arg1_some))] + arg1: Option<()>, + + #[builder(setters(option_fn = arg2_option))] + arg2: Option<()>, + + #[builder(setters(some_fn = arg3_some, option_fn = arg3_option))] + arg3: Option<()>, + + #[builder(setters(some_fn(name = arg4_some), option_fn(name = arg4_option)))] + arg4: Option<()>, + + #[builder(default, setters(some_fn = arg5_some))] + arg5: (), + + #[builder(default, setters(option_fn = arg6_option))] + arg6: (), + + #[builder(default, setters(some_fn = arg7_some, option_fn = arg7_option))] + arg7: (), + + #[builder(default, setters(some_fn(name = arg8_some), option_fn(name = arg8_option)))] + arg8: (), + } + + use sut_builder::*; + + let _ = Sut::builder().arg1_some(()); + let _ = Sut::builder().maybe_arg1(Some(())); + + let _ = Sut::builder().arg2(()); + let _ = Sut::builder().arg2_option(Some(())); + + let _ = Sut::builder().arg3_some(()); + let _ = Sut::builder().arg3_option(Some(())); + + let _ = Sut::builder().arg4_some(()); + let _ = Sut::builder().arg4_option(Some(())); + + let _ = Sut::builder().arg5_some(()); + let _ = Sut::builder().maybe_arg5(Some(())); + + let _ = Sut::builder().arg6(()); + let _ = Sut::builder().arg6_option(Some(())); + + let _ = Sut::builder().arg7_some(()); + let _ = Sut::builder().arg7_option(Some(())); + + let _ = Sut::builder().arg8_some(()); + let _ = Sut::builder().arg8_option(Some(())); + + // let builder: SutBuilder>>>>>> = + // Sut::builder() + // .arg1_some(()) + // .arg2(()) + // .arg3_some(()) + // .arg4_some(()) + // .arg5_some(()) + // .arg6(()) + // .arg7_some(()); + // .arg8_some(()); +} From 49df1d59db46e131aa9d79363b2c61d8987788dd Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 12 Oct 2024 14:29:02 +0000 Subject: [PATCH 076/119] Use structs as states --- bon-macros/src/builder/builder_gen/mod.rs | 4 +- .../src/builder/builder_gen/setters/mod.rs | 9 +- .../src/builder/builder_gen/state_mod.rs | 312 ++++-------------- bon-macros/src/builder/mod.rs | 2 - bon-macros/src/lib.rs | 9 - bon/src/private/mod.rs | 1 - bon/tests/integration/builder/attr_setters.rs | 23 +- 7 files changed, 86 insertions(+), 274 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index c429b586..8ab3dd3d 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -8,8 +8,6 @@ mod state_mod; pub(crate) mod input_fn; pub(crate) mod input_struct; -pub(crate) use state_mod::prettier_type_aliases_docs; - use crate::util::prelude::*; use member::{ Member, MemberOrigin, NamedMember, PositionalFnArgMember, RawMember, StartFnArgMember, @@ -445,7 +443,7 @@ impl BuilderGenCtx { // 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. - BuilderState: #state_mod::State = #state_mod::AllUnset + BuilderState: #state_mod::State = #state_mod::Empty > #where_clause { diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index 8bd0e413..23fc7a14 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -262,15 +262,8 @@ impl<'a> SettersCtx<'a> { let builder_ident = &self.builder_gen.builder_type.ident; let generic_args = &self.builder_gen.generics.args; - let state_param = - (self.builder_gen.stateful_members().take(2).count() > 1).then(|| { - quote! { - - } - }); - quote! { - #builder_ident<#(#generic_args,)* #state_mod::#state_transition #state_param> + #builder_ident<#(#generic_args,)* #state_mod::#state_transition> } }; diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index 682dbb6c..7c73534d 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -48,11 +48,9 @@ impl<'a> StateModGenCtx<'a> { 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, parent_state_transition_items) = self.state_transitions(); + let state_transitions = self.state_transitions(); quote! { - #parent_state_transition_items - #[allow( // These are intentional. By default, the builder module is private // and can't be accessed outside of the module where the builder @@ -79,134 +77,88 @@ impl<'a> StateModGenCtx<'a> { #state_trait #is_complete_trait - #state_transitions #members_names_mod + #state_transitions } } } - fn state_transitions(&self) -> (TokenStream, TokenStream) { - let transitions_bodies = self - .builder_gen - .stateful_members() - .map(|member| { - let states = self.builder_gen.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! { - ::#member_pascal - } - } - }); + fn state_transitions(&self) -> TokenStream { + 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()); - quote! { - ( #( #states, )* ) - } - }) - .collect::>(); + let vis_child = &self.builder_gen.state_mod.vis_child; + let sealed_method_impl = &self.sealed_method_impl; - let (set_member_aliases_docs, set_member_aliases): (Vec<_>, Vec<_>) = self - .builder_gen - .stateful_members() - .map(|member| { - let alias = format_ident!("Set{}", member.name.pascal_str); - let member_snake = &member.name.snake; - - let docs = format!( - "Returns a [`State`] that has [`IsSet`] implemented for `{member_snake}`\n\ - \n\ - [`State`]: self::State\n\ - [`IsSet`]: self::IsSet", - ); - - (docs, alias) - }) - .unzip(); + for member in self.builder_gen.stateful_members() { + let member_pascal = &member.name.pascal; - let vis_child = &self.builder_gen.state_mod.vis_child; - let vis_child_child = &self.builder_gen.state_mod.vis_child_child; - let stateful_members_snake = &self.stateful_members_snake; + let docs = format!( + "Returns 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.\n\n\ + [`State`]: self::State\n\ + [`IsSet`]: self::IsSet\n\ + [`State::{member_pascal}`]: self::State::{member_pascal}", + ); - // In a special case of 1 stateful member the generic `S` parameter - // is unused in the alias body, which triggers a compile error, because - // all generic type parameters must be used. This special case is - // unfortunate, but it's not the end of the world because it's rare - // that a builder has only one stateful member and the user wants to - // use the state transition type alias and expect it to have a generic - // type parameter. - let state_param = (self.stateful_members_snake.len() > 1).then(|| { - quote! { - - } - }); - // This code is a bit overcomplicated for a reason. We could avoid - // defining the `mod type_aliases` and a `use type_aliases::*` but - // we do this to have prettier documentation generated by `rustdoc`. - // - // The problem is that `rustdoc` inlines the type aliases if they - // are unreachable from outside of the crate. It means that even if - // the type alias is declared as `pub` but it resides in a private - // module, `rustdoc` will inline it. This is not what we want, because - // the type aliases grow very large and make the documentation noisy. - // - // As a workaround we generate a bit different code for `rustdoc` - // that uses indirection via an associated type of a trait. The - // macro `__prettier_type_aliases_docs` does that if `doc` cfg - // is enabled. That macro modifies the contents of the module - // to add that indirection. - // - // It's implemented this way to optimize the compilation perf. for - // the case when `doc` cfg is disabled. In this case, we pay only - // for a single `#[cfg_attr(doc, ...)]` that expands to nothing. - // - // We could just generate the code that `__prettier_type_aliases_docs` - // generates eagerly and put it under `#[cfg(doc)]` but that increases - // the compile time, because the compiler needs to parse all that code - // even if it's not used and it's a lot of code. - let mod_items = quote! { - #vis_child use type_aliases::*; - - #[cfg_attr(doc, ::bon::private::__prettier_type_aliases_docs)] - mod type_aliases { - use super::{members, State, Unset, Set}; - - /// Initial state of the builder where all members are unset - #vis_child_child type AllUnset = ( - #( Unset, )* - ); + let struct_ident = format_ident!("Set{}", member.name.pascal_str); - #( - #[doc = #set_member_aliases_docs] - #vis_child_child type #set_member_aliases #state_param = #transitions_bodies; - )* - } - }; - - let state_mod = &self.builder_gen.state_mod.ident; - let builder_vis = &self.builder_gen.builder_type.vis; - - // This is a workaround for `rustdoc`. Without this `use` statement, - // it inlines the type aliases. Although for this workaround to work, - // all items from the current module need to be reexported via a `*` - // reexport, or the items need to be defined in the root lib.rs file. - // - // Therefore we use a `#[prettier_type_aliases_docs]` attribute to - // hide the internals of the type aliases from the documentation - // in all other cases. - let parent_items = quote! { - #[doc(hidden)] - #[cfg(doc)] - #[allow(unused_import_braces)] - #builder_vis use #state_mod::{ AllUnset as _ #(, #set_member_aliases as _)* }; - }; + set_members_structs.push(quote! { + #[doc = #docs] + #vis_child struct #struct_ident(S); + }); + + let states = self.builder_gen.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! { + ::#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_method_impl + } + }); + } - (mod_items, parent_items) + let stateful_members_snake = &self.stateful_members_snake; + let stateful_members_pascal = &self.stateful_members_pascal; + + quote! { + /// Initial state of the builder where all members are unset + #vis_child struct Empty(()); + + #( #set_members_structs )* + + // Put it under an anonymous const to make it possible to collapse + // all this boilerplate when viewing the generated code. + #[allow(non_local_definitions)] + const _: () = { + impl State for Empty { + #( + type #stateful_members_pascal = Unset; + )* + #sealed_method_impl + } + + #( #state_impls )* + }; + } } fn state_trait(&self) -> TokenStream { @@ -223,8 +175,6 @@ impl<'a> StateModGenCtx<'a> { let vis_child = &self.builder_gen.state_mod.vis_child; let sealed_method_decl = &self.sealed_method_decl; - let sealed_method_impl = &self.sealed_method_impl; - let stateful_members_snake = &self.stateful_members_snake; let stateful_members_pascal = &self.stateful_members_pascal; quote! { @@ -239,29 +189,10 @@ impl<'a> StateModGenCtx<'a> { #vis_child trait State: ::core::marker::Sized { #( #[doc = #assoc_types_docs] - type #stateful_members_pascal: ::bon::private::MemberState< - members::#stateful_members_snake - >; + type #stateful_members_pascal; )* - #sealed_method_decl } - - // Using `self::State` explicitly to avoid name conflicts with the - // members named `state` which would create a generic param named `State` - // that would shadow the trait `State` in the same scope. - #[doc(hidden)] - impl<#( - #stateful_members_pascal: ::bon::private::MemberState< - self::members::#stateful_members_snake - >, - )*> - self::State for ( #(#stateful_members_pascal,)* ) - { - #( type #stateful_members_pascal = #stateful_members_pascal; )* - - #sealed_method_impl - } } } @@ -336,102 +267,3 @@ impl<'a> StateModGenCtx<'a> { } } } - -pub(crate) fn prettier_type_aliases_docs(module: TokenStream) -> TokenStream { - try_prettier_type_aliases_docs(module.clone()) - .unwrap_or_else(|err| [module, err.write_errors()].concat()) -} - -fn try_prettier_type_aliases_docs(module: TokenStream) -> Result { - let mut module: syn::ItemMod = syn::parse2(module)?; - let (_, module_items) = module.content.as_mut().ok_or_else(|| { - err!( - &Span::call_site(), - "expected an inline module with type aliases inside" - ) - })?; - - let mut aliases = module_items - .iter_mut() - .filter(|item| !matches!(item, syn::Item::Use(_))) - .map(require_type_alias) - .collect::>>()?; - - if aliases.is_empty() { - bail!( - &Span::call_site(), - "expected at least one type alias inside the module (e.g. AllUnset)" - ) - }; - - let all_unset = aliases.remove(0); - let set_members = aliases; - - // This is the case where there is one or zero stateful members. In this - // case type aliases don't have any generic parameters to avoid the error - // that the generic parameter is unused in the type alias definition. - // This case is small and rare enough that we may just avoid doing any - // special handling for it. - if set_members.len() <= 1 { - for alias in set_members { - assert!(alias.generics.params.is_empty()); - } - - return Ok(module.into_token_stream()); - } - - let vis = all_unset.vis.clone().into_equivalent_in_child_module()?; - - let set_members_idents = set_members.iter().map(|alias| &alias.ident); - let set_members_idents2 = set_members_idents.clone(); - let set_members_bodies = set_members.iter().map(|alias| &alias.ty); - - let all_unset_body = &all_unset.ty; - - let trait_module: syn::Item = syn::parse_quote! { - mod private { - use super::*; - - #vis trait OpaqueConst { - type AllUnset; - } - - impl OpaqueConst for () { - type AllUnset = #all_unset_body; - } - - #vis trait Opaque { - #( type #set_members_idents; )* - } - - impl Opaque for S { - #( type #set_members_idents2 = #set_members_bodies; )* - } - } - }; - - all_unset.ty = syn::parse_quote! { - <() as private::OpaqueConst>::AllUnset - }; - - for alias in set_members { - let alias_ident = &alias.ident; - alias.ty = syn::parse_quote! { - ::#alias_ident - }; - } - - module_items.push(trait_module); - - Ok(module.into_token_stream()) -} - -fn require_type_alias(item: &mut syn::Item) -> Result<&mut syn::ItemType> { - match item { - syn::Item::Type(item_type) => Ok(item_type), - _ => bail!( - &item, - "expected a type alias inside the module, but found a different item" - ), - } -} diff --git a/bon-macros/src/builder/mod.rs b/bon-macros/src/builder/mod.rs index 72eb995e..57f59b50 100644 --- a/bon-macros/src/builder/mod.rs +++ b/bon-macros/src/builder/mod.rs @@ -5,8 +5,6 @@ pub(crate) mod item_impl; mod item_fn; mod item_struct; -pub(crate) use builder_gen::prettier_type_aliases_docs; - use crate::normalization::{ExpandCfg, ExpansionOutput}; use crate::util; use crate::util::prelude::*; diff --git a/bon-macros/src/lib.rs b/bon-macros/src/lib.rs index 37c55eee..4d330e0e 100644 --- a/bon-macros/src/lib.rs +++ b/bon-macros/src/lib.rs @@ -304,12 +304,3 @@ pub fn set(input: proc_macro::TokenStream) -> proc_macro::TokenStream { collections::set::generate(entries).into() } - -#[doc(hidden)] -#[proc_macro_attribute] -pub fn __prettier_type_aliases_docs( - _: proc_macro::TokenStream, - module: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - builder::prettier_type_aliases_docs(module.into()).into() -} diff --git a/bon/src/private/mod.rs b/bon/src/private/mod.rs index a2117a2d..872d0842 100644 --- a/bon/src/private/mod.rs +++ b/bon/src/private/mod.rs @@ -25,7 +25,6 @@ mod cfg_eval; // patch releases. Use the export from the builder's state module directly // instead of using this reexport from `bon::private`. pub use crate::builder_state::{IsSet, IsUnset}; -pub use bon_macros::__prettier_type_aliases_docs; pub use rustversion; pub(crate) mod sealed { diff --git a/bon/tests/integration/builder/attr_setters.rs b/bon/tests/integration/builder/attr_setters.rs index 4dfefeb0..df020371 100644 --- a/bon/tests/integration/builder/attr_setters.rs +++ b/bon/tests/integration/builder/attr_setters.rs @@ -26,7 +26,7 @@ fn test_name() { let _ = Sut::builder().maybe_arg3_renamed(Some(42)); // The name in the state must remain the same - let _: SutBuilder>> = Sut::builder() + let _: SutBuilder>> = Sut::builder() .arg1_renamed(true) .arg2_renamed(()) .arg3_renamed(42); @@ -97,14 +97,15 @@ fn test_option_fn_name_and_some_fn_name() { let _ = Sut::builder().arg8_some(()); let _ = Sut::builder().arg8_option(Some(())); - // let builder: SutBuilder>>>>>> = - // Sut::builder() - // .arg1_some(()) - // .arg2(()) - // .arg3_some(()) - // .arg4_some(()) - // .arg5_some(()) - // .arg6(()) - // .arg7_some(()); - // .arg8_some(()); + #[allow(clippy::type_complexity)] + let _: SutBuilder>>>>>>> = + Sut::builder() + .arg1_some(()) + .arg2(()) + .arg3_some(()) + .arg4_some(()) + .arg5_some(()) + .arg6(()) + .arg7_some(()) + .arg8_some(()); } From ae640fe2e2d424cc3ab920e60df15a4d866a9060 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 12 Oct 2024 15:14:17 +0000 Subject: [PATCH 077/119] Simplify --- bon-macros/src/builder/builder_gen/state_mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index 7c73534d..5b95e3f9 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -101,7 +101,6 @@ impl<'a> StateModGenCtx<'a> { [`State::{member_pascal}`]: self::State::{member_pascal}", ); - let struct_ident = format_ident!("Set{}", member.name.pascal_str); set_members_structs.push(quote! { @@ -118,7 +117,7 @@ impl<'a> StateModGenCtx<'a> { } else { let member_pascal = &other_member.name.pascal; quote! { - ::#member_pascal + S::#member_pascal } } }); From 1430ee52f809689770fae8b2681e215a851af552 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 12 Oct 2024 16:33:39 +0000 Subject: [PATCH 078/119] Simplify sealing and remove unused `#[diagnostic::on_unimplemented]` --- .../src/builder/builder_gen/state_mod.rs | 61 +++++++------------ 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index 5b95e3f9..3533bbd0 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -5,8 +5,8 @@ pub(super) struct StateModGenCtx<'a> { builder_gen: &'a BuilderGenCtx, stateful_members_snake: Vec<&'a syn::Ident>, stateful_members_pascal: Vec<&'a syn::Ident>, - sealed_method_decl: TokenStream, - sealed_method_impl: TokenStream, + sealed_item_decl: TokenStream, + sealed_item_impl: TokenStream, } impl<'a> StateModGenCtx<'a> { @@ -26,13 +26,13 @@ impl<'a> StateModGenCtx<'a> { // A method without `self` makes the trait non-object safe, // which is convenient, because we want that in this case. - sealed_method_decl: quote! { + sealed_item_decl: quote! { #[doc(hidden)] - fn __sealed(_: sealed::Sealed); + const SEALED: sealed::Sealed; }, - sealed_method_impl: quote! { - fn __sealed(_: sealed::Sealed) {} + sealed_item_impl: quote! { + const SEALED: sealed::Sealed = sealed::Sealed; }, } } @@ -72,7 +72,7 @@ impl<'a> StateModGenCtx<'a> { use ::bon::private::{Set, Unset}; mod sealed { - #vis_child_child enum Sealed {} + #vis_child_child struct Sealed; } #state_trait @@ -88,7 +88,7 @@ impl<'a> StateModGenCtx<'a> { let mut state_impls = Vec::with_capacity(self.stateful_members_snake.len()); let vis_child = &self.builder_gen.state_mod.vis_child; - let sealed_method_impl = &self.sealed_method_impl; + let sealed_item_impl = &self.sealed_item_impl; for member in self.builder_gen.stateful_members() { let member_pascal = &member.name.pascal; @@ -130,7 +130,7 @@ impl<'a> StateModGenCtx<'a> { #( type #stateful_members_pascal = #states; )* - #sealed_method_impl + #sealed_item_impl } }); } @@ -146,17 +146,15 @@ impl<'a> StateModGenCtx<'a> { // Put it under an anonymous const to make it possible to collapse // all this boilerplate when viewing the generated code. - #[allow(non_local_definitions)] - const _: () = { - impl State for Empty { - #( - type #stateful_members_pascal = Unset; - )* - #sealed_method_impl - } + impl State for Empty { + #( + type #stateful_members_pascal = Unset; + )* + #sealed_item_impl + } + + #( #state_impls )* - #( #state_impls )* - }; } } @@ -173,7 +171,7 @@ impl<'a> StateModGenCtx<'a> { }); let vis_child = &self.builder_gen.state_mod.vis_child; - let sealed_method_decl = &self.sealed_method_decl; + let sealed_item_decl = &self.sealed_item_decl; let stateful_members_pascal = &self.stateful_members_pascal; quote! { @@ -190,7 +188,7 @@ impl<'a> StateModGenCtx<'a> { #[doc = #assoc_types_docs] type #stateful_members_pascal; )* - #sealed_method_decl + #sealed_item_decl } } } @@ -210,19 +208,15 @@ impl<'a> StateModGenCtx<'a> { }); let vis_child = &self.builder_gen.state_mod.vis_child; - let sealed_method_decl = &self.sealed_method_decl; - let sealed_method_impl = &self.sealed_method_impl; - - let on_unimplemented = - Self::on_unimplemented("can't finish building yet; not all required members are set"); + let sealed_item_decl = &self.sealed_item_decl; + let sealed_item_impl = &self.sealed_item_impl; quote! { /// Marker trait that indicates that all required members are set. /// /// In this state, the builder - #on_unimplemented #vis_child trait IsComplete: State #maybe_assoc_type_bounds { - #sealed_method_decl + #sealed_item_decl } #[doc(hidden)] @@ -232,7 +226,7 @@ impl<'a> StateModGenCtx<'a> { S::#required_members_pascal: IsSet, )* { - #sealed_method_impl + #sealed_item_impl } } } @@ -256,13 +250,4 @@ impl<'a> StateModGenCtx<'a> { } } } - - fn on_unimplemented(message: &str) -> TokenStream { - quote! { - #[::bon::private::rustversion::attr( - since(1.78.0), - diagnostic::on_unimplemented(message = #message, label = #message) - )] - } - } } From 6d6453a7bac3798e364755e262ed23cb39b691fe Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 12 Oct 2024 16:52:48 +0000 Subject: [PATCH 079/119] Rename --- bon-macros/src/builder/builder_gen/state_mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index 3533bbd0..057a7afb 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -38,7 +38,7 @@ impl<'a> StateModGenCtx<'a> { } pub(super) fn state_mod(&self) -> TokenStream { - let vis_mod = &self.builder_gen.state_mod.vis; + let vis = &self.builder_gen.state_mod.vis; let vis_child = &self.builder_gen.state_mod.vis_child; let vis_child_child = &self.builder_gen.state_mod.vis_child_child; @@ -66,7 +66,7 @@ impl<'a> StateModGenCtx<'a> { unnameable_types, unreachable_pub, clippy::redundant_pub_crate )] #( #state_mod_docs )* - #vis_mod mod #state_mod_ident { + #vis mod #state_mod_ident { #[doc(inline)] #vis_child use ::bon::private::{IsSet, IsUnset}; use ::bon::private::{Set, Unset}; From a4d3a84db2bab9afc9cc33d94b75e33e00a3d072 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 12 Oct 2024 17:13:20 +0000 Subject: [PATCH 080/119] Move `finish_fn` into a dedicated module --- .../src/builder/builder_gen/finish_fn.rs | 143 +++++++++++++++++ bon-macros/src/builder/builder_gen/mod.rs | 145 +----------------- 2 files changed, 145 insertions(+), 143 deletions(-) create mode 100644 bon-macros/src/builder/builder_gen/finish_fn.rs diff --git a/bon-macros/src/builder/builder_gen/finish_fn.rs b/bon-macros/src/builder/builder_gen/finish_fn.rs new file mode 100644 index 00000000..e64c7e02 --- /dev/null +++ b/bon-macros/src/builder/builder_gen/finish_fn.rs @@ -0,0 +1,143 @@ +use super::member::{Member, PositionalFnArgMember}; +use crate::util::prelude::*; + +impl super::BuilderGenCtx { + fn finish_fn_member_expr(member: &Member) -> TokenStream { + let member = match member { + Member::Named(member) => member, + Member::Skipped(member) => { + return member + .value + .as_ref() + .as_ref() + .map(|value| quote! { (|| #value)() }) + .unwrap_or_else(|| quote! { ::core::default::Default::default() }); + } + Member::StartFnArg(member) => { + let index = &member.index; + return quote! { self.__private_start_fn_args.#index }; + } + Member::FinishFnArg(member) => { + return member.maybe_into_ident_expr(); + } + }; + + let index = &member.index; + + let member_field = quote! { + self.__private_named_members.#index + }; + + let param_default = member + .params + .default + .as_ref() + .map(|default| default.value.as_ref()); + + match param_default { + Some(Some(default)) => { + let has_into = member.params.into.is_present(); + let default = if has_into { + quote! { ::core::convert::Into::into((|| #default)()) } + } else { + quote! { #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; + } + + quote! { + unsafe { + // SAFETY: we know that the member is set because we are in + // the `finish` function because this method uses the trait + // bounds 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). + ::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. + let ty = member.norm_ty(); + + quote! { + let #var_ident: #ty = #expr; + } + }); + + let state_mod = &self.state_mod.ident; + + let finish_fn_params = self + .members + .iter() + .filter_map(Member::as_finish_fn_arg) + .map(PositionalFnArgMember::fn_input_param); + + let body = &self.finish_fn.body.generate(&self.members); + let asyncness = &self.finish_fn.asyncness; + let unsafety = &self.finish_fn.unsafety; + let must_use = &self.finish_fn.must_use; + let attrs = &self.finish_fn.attrs; + let finish_fn_vis = self + .finish_fn + .vis + .as_ref() + .unwrap_or(&self.builder_type.vis); + let finish_fn_ident = &self.finish_fn.ident; + let output = &self.finish_fn.output; + + quote! { + #(#attrs)* + #[inline(always)] + #[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, + )] + #must_use + #finish_fn_vis #asyncness #unsafety fn #finish_fn_ident(self, #(#finish_fn_params,)*) #output + where + BuilderState: #state_mod::IsComplete + { + #(#members_vars_decls)* + #body + } + } + } +} diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 8ab3dd3d..c7de6f56 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -1,5 +1,6 @@ mod builder_derives; mod builder_params; +mod finish_fn; mod member; mod models; mod setters; @@ -9,9 +10,7 @@ pub(crate) mod input_fn; pub(crate) mod input_struct; use crate::util::prelude::*; -use member::{ - Member, MemberOrigin, NamedMember, PositionalFnArgMember, RawMember, StartFnArgMember, -}; +use member::{Member, MemberOrigin, NamedMember, RawMember, StartFnArgMember}; use models::{ AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, }; @@ -462,146 +461,6 @@ impl BuilderGenCtx { } } } - - fn finish_fn_member_expr(member: &Member) -> TokenStream { - let member = match member { - Member::Named(member) => member, - Member::Skipped(member) => { - return member - .value - .as_ref() - .as_ref() - .map(|value| quote! { (|| #value)() }) - .unwrap_or_else(|| quote! { ::core::default::Default::default() }); - } - Member::StartFnArg(member) => { - let index = &member.index; - return quote! { self.__private_start_fn_args.#index }; - } - Member::FinishFnArg(member) => { - return member.maybe_into_ident_expr(); - } - }; - - let index = &member.index; - - let member_field = quote! { - self.__private_named_members.#index - }; - - let param_default = member - .params - .default - .as_ref() - .map(|default| default.value.as_ref()); - - match param_default { - Some(Some(default)) => { - let has_into = member.params.into.is_present(); - let default = if has_into { - quote! { ::core::convert::Into::into((|| #default)()) } - } else { - quote! { #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; - } - - quote! { - unsafe { - // SAFETY: we know that the member is set because we are in - // the `finish` function because this method uses the trait - // bounds 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). - ::core::option::Option::unwrap_unchecked(#member_field) - } - } - } - } - } - - 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. - let ty = member.norm_ty(); - - quote! { - let #var_ident: #ty = #expr; - } - }); - - let state_mod = &self.state_mod.ident; - - let finish_fn_params = self - .members - .iter() - .filter_map(Member::as_finish_fn_arg) - .map(PositionalFnArgMember::fn_input_param); - - let body = &self.finish_fn.body.generate(&self.members); - let asyncness = &self.finish_fn.asyncness; - let unsafety = &self.finish_fn.unsafety; - let must_use = &self.finish_fn.must_use; - let attrs = &self.finish_fn.attrs; - let finish_fn_vis = self - .finish_fn - .vis - .as_ref() - .unwrap_or(&self.builder_type.vis); - let finish_fn_ident = &self.finish_fn.ident; - let output = &self.finish_fn.output; - - quote! { - #(#attrs)* - #[inline(always)] - #[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, - )] - #must_use - #finish_fn_vis #asyncness #unsafety fn #finish_fn_ident(self, #(#finish_fn_params,)*) #output - where - // #( #where_bounds, )* - BuilderState: #state_mod::IsComplete - { - #(#members_vars_decls)* - #body - } - } - } } fn allow_warnings_on_member_types() -> TokenStream { From 763b7f40acc7d54b249264edfc451b4d6f06760b Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 12 Oct 2024 17:32:25 +0000 Subject: [PATCH 081/119] Remove `transition_type_state_fn` --- bon-macros/src/builder/builder_gen/mod.rs | 43 +------------------ .../src/builder/builder_gen/setters/mod.rs | 22 +++++++++- 2 files changed, 23 insertions(+), 42 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index c7de6f56..664ec1f7 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -96,7 +96,6 @@ impl BuilderGenCtx { fn builder_impl(&self) -> TokenStream { let finish_fn = self.finish_fn(); - let transition_type_state_fn = self.transition_type_state_fn(); let setter_methods = self .named_members() .map(|member| SettersCtx::new(self, member).setter_methods()); @@ -124,7 +123,6 @@ impl BuilderGenCtx { { #finish_fn #(#setter_methods)* - #transition_type_state_fn } } } @@ -164,43 +162,6 @@ impl BuilderGenCtx { } } - fn transition_type_state_fn(&self) -> TokenStream { - let builder_ident = &self.builder_type.ident; - let state_mod = &self.state_mod.ident; - - let maybe_receiver_field = self - .receiver() - .map(|_| quote!(__private_receiver: self.__private_receiver,)); - - let maybe_start_fn_args_field = self - .start_fn_args() - .next() - .map(|_| quote!(__private_start_fn_args: self.__private_start_fn_args,)); - - let generic_args = &self.generics.args; - - quote! { - #[deprecated = - "this method is an implementation detail; it should not be used directly; \ - 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://discord.gg/QcBYSamw4c)" - ] - #[inline(always)] - fn __private_transition_type_state<__NewBuilderState: #state_mod::State>(self) - -> #builder_ident<#(#generic_args,)* __NewBuilderState> - { - #builder_ident { - __private_phantom: ::core::marker::PhantomData, - #maybe_receiver_field - #maybe_start_fn_args_field - __private_named_members: self.__private_named_members, - } - } - } - } - fn start_fn(&self) -> syn::ItemFn { let builder_ident = &self.builder_type.ident; let attrs = &self.start_fn.attrs; @@ -255,10 +216,10 @@ impl BuilderGenCtx { // `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 { + let named_members_field_init = if false { quote!(::core::default::Default::default()) } else { - let none = quote!(::core::option::Option::None); + let none = format_ident!("None"); let nones = self.named_members().map(|_| &none); quote! { (#(#nones,)*) diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index 23fc7a14..320f095c 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -225,9 +225,29 @@ impl<'a> SettersCtx<'a> { let index = &self.member.index; let mut output = if self.member.is_stateful() { + let builder_ident = &self.builder_gen.builder_type.ident; + let maybe_receiver_field = self + .builder_gen + .receiver() + .map(|_| quote!(__private_receiver: self.__private_receiver,)); + + let maybe_start_fn_args_field = + self.builder_gen.start_fn_args().next().map( + |_| quote!(__private_start_fn_args: self.__private_start_fn_args,), + ); + quote! { - Self::__private_transition_type_state(self) + #builder_ident { + __private_phantom: ::core::marker::PhantomData, + #maybe_receiver_field + #maybe_start_fn_args_field + __private_named_members: self.__private_named_members, + } } + + // quote! { + // Self::__private_transition_type_state(self) + // } } else { quote! { self From d27cc7cb9bd58b0280cd206caccdadb1a6332fde Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 12 Oct 2024 19:05:56 +0000 Subject: [PATCH 082/119] Randomize private field names --- .../builder/builder_gen/builder_derives.rs | 27 ++++--- .../src/builder/builder_gen/finish_fn.rs | 13 ++-- .../src/builder/builder_gen/input_fn.rs | 9 ++- .../src/builder/builder_gen/input_struct.rs | 4 +- bon-macros/src/builder/builder_gen/mod.rs | 27 ++++--- bon-macros/src/builder/builder_gen/models.rs | 72 ++++++++++++++++++- .../src/builder/builder_gen/setters/mod.rs | 49 ++++++------- .../integration/ui/compile_fail/errors.stderr | 12 ++-- 8 files changed, 154 insertions(+), 59 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index 5f84781c..dcee8498 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -52,12 +52,17 @@ impl BuilderGenCtx { let generic_args = &self.generics.args; let builder_ident = &self.builder_type.ident; + let phantom_field = &self.private_builder_fields.phantom; + let receiver_field = &self.private_builder_fields.receiver; + let start_fn_args_field = &self.private_builder_fields.start_fn_args; + let named_members_field = &self.private_builder_fields.named_members; + let clone = quote!(::core::clone::Clone); let clone_receiver = self.receiver().map(|receiver| { let ty = &receiver.without_self_keyword; quote! { - __private_receiver: <#ty as #clone>::clone(&self.__private_receiver), + #receiver_field: <#ty as #clone>::clone(&self.#receiver_field), } }); @@ -66,12 +71,12 @@ impl BuilderGenCtx { let ty = &arg.base.ty.norm; let index = &arg.index; quote! { - <#ty as #clone>::clone(&self.__private_start_fn_args.#index) + <#ty as #clone>::clone(&self.#start_fn_args_field.#index) } }); quote! { - __private_start_fn_args: ( #(#clone_start_fn_args,)* ), + #start_fn_args_field: ( #(#clone_start_fn_args,)* ), } }); @@ -88,7 +93,7 @@ impl BuilderGenCtx { quote! { ::bon::private::derives::clone_member::<#ty>( - &self.__private_named_members.#member_index + &self.#named_members_field.#member_index ) } }); @@ -107,7 +112,7 @@ impl BuilderGenCtx { { fn clone(&self) -> Self { Self { - __private_phantom: ::core::marker::PhantomData, + #phantom_field: ::core::marker::PhantomData, #clone_receiver #clone_start_fn_args @@ -118,7 +123,7 @@ impl BuilderGenCtx { // ``` // required for `(...huge tuple type...)` to implement `Clone` // ``` - __private_named_members: ( #( #clone_named_members, )* ), + #named_members_field: ( #( #clone_named_members, )* ), } } } @@ -126,6 +131,10 @@ impl BuilderGenCtx { } fn derive_debug(&self) -> TokenStream { + let receiver_field = &self.private_builder_fields.receiver; + let start_fn_args_field = &self.private_builder_fields.start_fn_args; + let named_members_field = &self.private_builder_fields.named_members; + let format_members = self.members.iter().filter_map(|member| { match member { Member::Named(member) => { @@ -133,7 +142,7 @@ impl BuilderGenCtx { let member_ident_str = &member.name.snake_raw_str; let member_ty = member.underlying_norm_ty(); Some(quote! { - if let ::core::option::Option::Some(value) = &self.__private_named_members.#member_index { + if let Some(value) = &self.#named_members_field.#member_index { output.field( #member_ident_str, ::bon::private::derives::as_dyn_debug::<#member_ty>(value) @@ -149,7 +158,7 @@ impl BuilderGenCtx { output.field( #member_ident_str, ::bon::private::derives::as_dyn_debug::<#member_ty>( - &self.__private_start_fn_args.#member_index + &self.#start_fn_args_field.#member_index ) ); }) @@ -168,7 +177,7 @@ impl BuilderGenCtx { output.field( "self", ::bon::private::derives::as_dyn_debug::<#ty>( - &self.__private_receiver + &self.#receiver_field ) ); } diff --git a/bon-macros/src/builder/builder_gen/finish_fn.rs b/bon-macros/src/builder/builder_gen/finish_fn.rs index e64c7e02..b2558c11 100644 --- a/bon-macros/src/builder/builder_gen/finish_fn.rs +++ b/bon-macros/src/builder/builder_gen/finish_fn.rs @@ -2,7 +2,10 @@ use super::member::{Member, PositionalFnArgMember}; use crate::util::prelude::*; impl super::BuilderGenCtx { - fn finish_fn_member_expr(member: &Member) -> TokenStream { + fn finish_fn_member_expr(&self, member: &Member) -> TokenStream { + let start_fn_args_field = &self.private_builder_fields.start_fn_args; + let named_members_field = &self.private_builder_fields.named_members; + let member = match member { Member::Named(member) => member, Member::Skipped(member) => { @@ -15,7 +18,7 @@ impl super::BuilderGenCtx { } Member::StartFnArg(member) => { let index = &member.index; - return quote! { self.__private_start_fn_args.#index }; + return quote! { self.#start_fn_args_field.#index }; } Member::FinishFnArg(member) => { return member.maybe_into_ident_expr(); @@ -25,7 +28,7 @@ impl super::BuilderGenCtx { let index = &member.index; let member_field = quote! { - self.__private_named_members.#index + self.#named_members_field.#index }; let param_default = member @@ -78,7 +81,7 @@ impl super::BuilderGenCtx { 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 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 @@ -104,7 +107,7 @@ impl super::BuilderGenCtx { .filter_map(Member::as_finish_fn_arg) .map(PositionalFnArgMember::fn_input_param); - let body = &self.finish_fn.body.generate(&self.members); + let body = &self.finish_fn.body.generate(self); let asyncness = &self.finish_fn.asyncness; let unsafety = &self.finish_fn.unsafety; let must_use = &self.finish_fn.must_use; diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index 0a2565c4..be5f1b14 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -462,7 +462,7 @@ struct FnCallBody { } impl FinishFnBody for FnCallBody { - fn generate(&self, members: &[Member]) -> TokenStream { + fn generate(&self, ctx: &BuilderGenCtx) -> TokenStream { let asyncness = &self.sig.asyncness; let maybe_await = asyncness.is_some().then(|| quote!(.await)); @@ -482,7 +482,10 @@ impl FinishFnBody for FnCallBody { let prefix = self .sig .receiver() - .map(|_| quote!(self.__private_receiver.)) + .map(|_| { + let receiver_field = &ctx.private_builder_fields.receiver; + quote!(self.#receiver_field.) + }) .or_else(|| { let self_ty = &self.impl_ctx.as_deref()?.self_ty; Some(quote!(<#self_ty>::)) @@ -491,7 +494,7 @@ impl FinishFnBody for FnCallBody { let fn_ident = &self.sig.ident; // The variables with values of members are in scope for this expression. - let member_vars = members.iter().map(Member::orig_ident); + let member_vars = ctx.members.iter().map(Member::orig_ident); quote! { #prefix #fn_ident::<#(#generic_args,)*>( diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 5a829027..c4a10f91 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -276,11 +276,11 @@ struct StructLiteralBody { } impl FinishFnBody for StructLiteralBody { - fn generate(&self, member_exprs: &[Member]) -> TokenStream { + 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 = member_exprs.iter().map(Member::orig_ident); + let member_vars = ctx.members.iter().map(Member::orig_ident); quote! { #struct_ident { diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 664ec1f7..40230113 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -182,12 +182,17 @@ impl BuilderGenCtx { let where_clause = &generics.where_clause; let generic_args = &self.generics.args; + let phantom_field = &self.private_builder_fields.phantom; + let receiver_field = &self.private_builder_fields.receiver; + let start_fn_args_field = &self.private_builder_fields.start_fn_args; + let named_members_field = &self.private_builder_fields.named_members; + let receiver = self.receiver(); let receiver_field_init = receiver.map(|receiver| { let self_token = &receiver.with_self_keyword.self_token; quote! { - __private_receiver: #self_token, + #receiver_field: #self_token, } }); @@ -207,7 +212,7 @@ impl BuilderGenCtx { let start_fn_args_field_init = start_fn_arg_exprs.peek().is_some().then(|| { quote! { - __private_start_fn_args: (#(#start_fn_arg_exprs,)*), + #start_fn_args_field: (#(#start_fn_arg_exprs,)*), } }); @@ -247,10 +252,10 @@ impl BuilderGenCtx { #ide_hints #builder_ident { - __private_phantom: ::core::marker::PhantomData, + #phantom_field: ::core::marker::PhantomData, #receiver_field_init #start_fn_args_field_init - __private_named_members: #named_members_field_init, + #named_members_field: #named_members_field_init, } } } @@ -260,7 +265,7 @@ impl BuilderGenCtx { let member_types = self.members.iter().filter_map(|member| { match member { // The types of these members already appear in the struct in the types - // of __private_named_members and __private_start_fn_args fields. + // of named_members and start_fn_args fields. Member::Named(_) | Member::StartFnArg(_) => None, Member::FinishFnArg(member) => Some(member.ty.norm.as_ref()), Member::Skipped(member) => Some(member.norm_ty.as_ref()), @@ -328,6 +333,10 @@ impl BuilderGenCtx { let where_clause = &self.generics.where_clause; let phantom_data = self.phantom_data(); let state_mod = &self.state_mod.ident; + let phantom_field = &self.private_builder_fields.phantom; + let receiver_field = &self.private_builder_fields.receiver; + let start_fn_args_field = &self.private_builder_fields.start_fn_args; + let named_members_field = &self.private_builder_fields.named_members; let private_field_attrs = quote! { // The fields can't be hidden using Rust's privacy syntax. @@ -351,7 +360,7 @@ impl BuilderGenCtx { let ty = &receiver.without_self_keyword; quote! { #private_field_attrs - __private_receiver: #ty, + #receiver_field: #ty, } }); @@ -370,7 +379,7 @@ impl BuilderGenCtx { let start_fn_args_field = start_fn_arg_types.peek().is_some().then(|| { quote! { #private_field_attrs - __private_start_fn_args: (#(#start_fn_arg_types,)*), + #start_fn_args_field: (#(#start_fn_arg_types,)*), } }); @@ -408,13 +417,13 @@ impl BuilderGenCtx { #where_clause { #private_field_attrs - __private_phantom: #phantom_data, + #phantom_field: #phantom_data, #receiver_field #start_fn_args_field #private_field_attrs - __private_named_members: ( + #named_members_field: ( #( ::core::option::Option<#named_members_types>, )* diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 37791400..06146cee 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -7,7 +7,7 @@ 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, members: &[Member]) -> TokenStream; + fn generate(&self, ctx: &BuilderGenCtx) -> TokenStream; } pub(super) struct AssocMethodReceiverCtx { @@ -119,6 +119,7 @@ pub(super) struct Generics { } pub(crate) struct BuilderGenCtx { + pub(super) private_builder_fields: BuilderPrivateFields, pub(super) members: Vec, /// Lint suppressions from the original item that will be inherited by all items @@ -137,6 +138,13 @@ pub(crate) struct BuilderGenCtx { pub(super) finish_fn: FinishFn, } +pub(super) struct BuilderPrivateFields { + pub(super) phantom: syn::Ident, + pub(super) receiver: syn::Ident, + pub(super) start_fn_args: syn::Ident, + pub(super) named_members: syn::Ident, +} + pub(super) struct BuilderGenCtxParams { pub(super) members: Vec, @@ -265,6 +273,7 @@ impl BuilderGenCtx { }; Ok(Self { + private_builder_fields: BuilderPrivateFields::new(), members, allow_attrs, on_params, @@ -278,6 +287,67 @@ impl BuilderGenCtx { } } +impl BuilderPrivateFields { + fn new() -> Self { + use std::collections::hash_map::RandomState; + use std::hash::{BuildHasher, Hasher}; + + // Thanks @orhun for the article https://blog.orhun.dev/zero-deps-random-in-rust/ + let random = RandomState::new().build_hasher().finish(); + + let random_words = [ + "amethyst", + "applejack", + "blackjack", + "bon", + "cadance", + "celestia", + "cheerilee", + "derpy", + "fleetfoot", + "flitter", + "fluttershy", + "izzy", + "lilly", + "littlepip", + "luna", + "lyra", + "maud", + "minuette", + "octavia", + "pinkie", + "pipp", + "rainbow", + "rarity", + "roseluck", + "scootaloo", + "seaswirl", + "spitfire", + "starlight", + "sunset", + "sweetie", + "trixie", + "twilight", + "twinkleshine", + "twist", + "velvet", + "vinyl", + ]; + + #[allow(clippy::cast_possible_truncation)] + let random_word = random_words[(random % (random_words.len() as u64)) as usize]; + + let ident = |name: &str| format_ident!("__private_{random_word}_{name}"); + + Self { + phantom: ident("phantom"), + receiver: ident("receiver"), + start_fn_args: ident("start_fn_args"), + named_members: ident("named_members"), + } + } +} + impl Generics { pub(super) fn new( decl_with_defaults: Vec, diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index 320f095c..00bdd9cf 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -5,16 +5,13 @@ use crate::util::prelude::*; use std::iter; pub(crate) struct SettersCtx<'a> { - builder_gen: &'a BuilderGenCtx, + base: &'a BuilderGenCtx, member: &'a NamedMember, } impl<'a> SettersCtx<'a> { - pub(crate) fn new(builder_gen: &'a BuilderGenCtx, member: &'a NamedMember) -> Self { - Self { - builder_gen, - member, - } + pub(crate) fn new(base: &'a BuilderGenCtx, member: &'a NamedMember) -> Self { + Self { base, member } } pub(crate) fn setter_methods(&self) -> TokenStream { @@ -224,30 +221,34 @@ impl<'a> SettersCtx<'a> { SetterBody::SetMember { value } => { let index = &self.member.index; + let fields = &self.base.private_builder_fields; + let phantom_field = &fields.phantom; + let receiver_field = &fields.receiver; + let start_fn_args_field = &fields.start_fn_args; + let named_members_field = &fields.named_members; + let mut output = if self.member.is_stateful() { - let builder_ident = &self.builder_gen.builder_type.ident; + let builder_ident = &self.base.builder_type.ident; + let maybe_receiver_field = self - .builder_gen + .base .receiver() - .map(|_| quote!(__private_receiver: self.__private_receiver,)); + .map(|_| quote!(#receiver_field: self.#receiver_field,)); - let maybe_start_fn_args_field = - self.builder_gen.start_fn_args().next().map( - |_| quote!(__private_start_fn_args: self.__private_start_fn_args,), - ); + let maybe_start_fn_args_field = self + .base + .start_fn_args() + .next() + .map(|_| quote!(#start_fn_args_field: self.#start_fn_args_field,)); quote! { #builder_ident { - __private_phantom: ::core::marker::PhantomData, + #phantom_field: ::core::marker::PhantomData, #maybe_receiver_field #maybe_start_fn_args_field - __private_named_members: self.__private_named_members, + #named_members_field: self.#named_members_field, } } - - // quote! { - // Self::__private_transition_type_state(self) - // } } else { quote! { self @@ -267,20 +268,20 @@ impl<'a> SettersCtx<'a> { } quote! { - self.__private_named_members.#index = #value; + self.#named_members_field.#index = #value; #output } } }; - let state_mod = &self.builder_gen.state_mod.ident; + 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.builder_gen.builder_type.ident; - let generic_args = &self.builder_gen.generics.args; + let builder_ident = &self.base.builder_type.ident; + let generic_args = &self.base.generics.args; quote! { #builder_ident<#(#generic_args,)* #state_mod::#state_transition> @@ -361,7 +362,7 @@ impl SettersItems { fn new(ctx: &SettersCtx<'_>) -> Self { let SettersCtx { member, - builder_gen, + base: builder_gen, } = ctx; let builder_type = &builder_gen.builder_type; diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr index f6c49e2f..91154b82 100644 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ b/bon/tests/integration/ui/compile_fail/errors.stderr @@ -194,9 +194,9 @@ error[E0277]: the member `Unset` was not set, but this method requires it to 34 | let _ = Example::builder().x(1).build(); | ^^^^^ the member `Unset` was not set, but this method requires it to be set | - = help: the trait `IsSet` is not implemented for `Unset`, which is required by `(Set, Unset, Unset): example_builder::IsComplete` + = help: the trait `IsSet` is not implemented for `Unset`, which is required by `SetX: example_builder::IsComplete` = help: the trait `IsSet` is implemented for `Set` -note: required for `(Set, Unset, Unset)` to implement `example_builder::IsComplete` +note: required for `SetX` to implement `example_builder::IsComplete` --> tests/integration/ui/compile_fail/errors.rs:24:14 | 24 | #[derive(Builder)] @@ -216,9 +216,9 @@ error[E0277]: the member `Unset` was not set, but this method requires 34 | let _ = Example::builder().x(1).build(); | ^^^^^ the member `Unset` was not set, but this method requires it to be set | - = help: the trait `IsSet` is not implemented for `Unset`, which is required by `(Set, Unset, Unset): example_builder::IsComplete` + = help: the trait `IsSet` is not implemented for `Unset`, which is required by `SetX: example_builder::IsComplete` = help: the trait `IsSet` is implemented for `Set` -note: required for `(Set, Unset, Unset)` to implement `example_builder::IsComplete` +note: required for `SetX` to implement `example_builder::IsComplete` --> tests/integration/ui/compile_fail/errors.rs:24:14 | 24 | #[derive(Builder)] @@ -256,9 +256,9 @@ error[E0277]: the member `Unset` was not set, but this method requires it 47 | let _ = Sut::builder().build(); | ^^^^^ the member `Unset` was not set, but this method requires it to be set | - = help: the trait `IsSet` is not implemented for `Unset`, which is required by `(Unset,): sut_builder::IsComplete` + = help: the trait `IsSet` is not implemented for `Unset`, which is required by `sut_builder::Empty: sut_builder::IsComplete` = help: the trait `IsSet` is implemented for `Set` -note: required for `(Unset,)` to implement `sut_builder::IsComplete` +note: required for `sut_builder::Empty` to implement `sut_builder::IsComplete` --> tests/integration/ui/compile_fail/errors.rs:42:18 | 42 | #[derive(Builder)] From fd0fa8dba80179556bed78680c8fe592a7051f99 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 13 Oct 2024 01:30:48 +0000 Subject: [PATCH 083/119] Small fixes --- bon-macros/src/builder/builder_gen/finish_fn.rs | 2 +- bon-macros/src/builder/builder_gen/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/finish_fn.rs b/bon-macros/src/builder/builder_gen/finish_fn.rs index b2558c11..62a29a5d 100644 --- a/bon-macros/src/builder/builder_gen/finish_fn.rs +++ b/bon-macros/src/builder/builder_gen/finish_fn.rs @@ -41,7 +41,7 @@ impl super::BuilderGenCtx { Some(Some(default)) => { let has_into = member.params.into.is_present(); let default = if has_into { - quote! { ::core::convert::Into::into((|| #default)()) } + quote! { Into::into((|| #default)()) } } else { quote! { #default } }; diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 40230113..af9b7d02 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -221,7 +221,7 @@ impl BuilderGenCtx { // `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 false { + let named_members_field_init = if self.named_members().take(13).count() > 12 { quote!(::core::default::Default::default()) } else { let none = format_ident!("None"); From cf1db157f35590b32332a6dd4e56c38944e757d6 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 13 Oct 2024 16:21:31 +0000 Subject: [PATCH 084/119] More tests for `builder(setters)` --- bon-macros/src/builder/builder_gen/mod.rs | 2 +- .../src/builder/builder_gen/state_mod.rs | 81 ++-- bon/src/builder_state.rs | 2 +- bon/tests/integration/builder/attr_setters.rs | 429 +++++++++++++++--- e2e-tests/src/lib.rs | 1 + e2e-tests/src/state_mod_pub.rs | 25 + 6 files changed, 436 insertions(+), 104 deletions(-) create mode 100644 e2e-tests/src/state_mod_pub.rs diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index af9b7d02..19bbf1bb 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -221,7 +221,7 @@ impl BuilderGenCtx { // `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 { + let named_members_field_init = if self.named_members().take(13).count() <= 12 { quote!(::core::default::Default::default()) } else { let none = format_ident!("None"); diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index 057a7afb..93a16ea1 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -2,7 +2,7 @@ use super::BuilderGenCtx; use crate::util::prelude::*; pub(super) struct StateModGenCtx<'a> { - builder_gen: &'a BuilderGenCtx, + base: &'a BuilderGenCtx, stateful_members_snake: Vec<&'a syn::Ident>, stateful_members_pascal: Vec<&'a syn::Ident>, sealed_item_decl: TokenStream, @@ -12,7 +12,7 @@ pub(super) struct StateModGenCtx<'a> { impl<'a> StateModGenCtx<'a> { pub(super) fn new(builder_gen: &'a BuilderGenCtx) -> Self { Self { - builder_gen, + base: builder_gen, stateful_members_snake: builder_gen .stateful_members() @@ -38,12 +38,12 @@ impl<'a> StateModGenCtx<'a> { } pub(super) fn state_mod(&self) -> TokenStream { - let vis = &self.builder_gen.state_mod.vis; - let vis_child = &self.builder_gen.state_mod.vis_child; - let vis_child_child = &self.builder_gen.state_mod.vis_child_child; + 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.builder_gen.state_mod.docs; - let state_mod_ident = &self.builder_gen.state_mod.ident; + 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(); @@ -87,28 +87,26 @@ impl<'a> StateModGenCtx<'a> { 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.builder_gen.state_mod.vis_child; + let vis_child = &self.base.state_mod.vis_child; let sealed_item_impl = &self.sealed_item_impl; - for member in self.builder_gen.stateful_members() { + for member in self.base.stateful_members() { let member_pascal = &member.name.pascal; let docs = format!( - "Returns a [`State`] that has [`IsSet`] implemented for [`State::{member_pascal}`]\n\n\ + "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.\n\n\ - [`State`]: self::State\n\ - [`IsSet`]: self::IsSet\n\ - [`State::{member_pascal}`]: self::State::{member_pascal}", + [`State::{member_pascal}`]: State::{member_pascal}", ); let struct_ident = format_ident!("Set{}", member.name.pascal_str); set_members_structs.push(quote! { #[doc = #docs] - #vis_child struct #struct_ident(S); + #vis_child struct #struct_ident(::core::marker::PhantomData S>); }); - let states = self.builder_gen.stateful_members().map(|other_member| { + let states = self.base.stateful_members().map(|other_member| { if other_member.is(member) { let member_snake = &member.name.snake; quote! { @@ -139,13 +137,16 @@ impl<'a> StateModGenCtx<'a> { let stateful_members_pascal = &self.stateful_members_pascal; quote! { - /// Initial state of the builder where all members are unset + /// 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 )* // Put it under an anonymous const to make it possible to collapse // all this boilerplate when viewing the generated code. + #[doc(hidden)] impl State for Empty { #( type #stateful_members_pascal = Unset; @@ -170,19 +171,30 @@ impl<'a> StateModGenCtx<'a> { ) }); - let vis_child = &self.builder_gen.state_mod.vis_child; + 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.\n\ + \n\ + [`IsSet`]: ::bon::IsSet + [`IsUnset`]: ::bon::IsUnset" + }; + + let docs = format!( + "Builder's type state specifies if members are set or not (unset).{docs_suffix}" + ); + quote! { - /// Builder's type state specifies if members are set or not (unset). - /// - /// 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*` type aliases available in this module. - /// - /// [`IsSet`]: ::bon::IsSet - /// [`IsUnset`]: ::bon::IsUnset + #[doc = #docs] #vis_child trait State: ::core::marker::Sized { #( #[doc = #assoc_types_docs] @@ -195,7 +207,7 @@ impl<'a> StateModGenCtx<'a> { fn is_complete_trait(&self) -> TokenStream { let required_members_pascal = self - .builder_gen + .base .named_members() .filter(|member| member.is_required()) .map(|member| &member.name.pascal) @@ -207,14 +219,21 @@ impl<'a> StateModGenCtx<'a> { } }); - let vis_child = &self.builder_gen.state_mod.vis_child; + 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 the building by calling the method \ + [`{builder_ident}::{finish_fn}()`]", + ); + quote! { - /// Marker trait that indicates that all required members are set. - /// - /// In this state, the builder + #[doc = #docs] #vis_child trait IsComplete: State #maybe_assoc_type_bounds { #sealed_item_decl } @@ -232,7 +251,7 @@ impl<'a> StateModGenCtx<'a> { } fn members_names_mod(&self) -> TokenStream { - let vis_child_child = &self.builder_gen.state_mod.vis_child_child; + let vis_child_child = &self.base.state_mod.vis_child_child; let stateful_members_snake = &self.stateful_members_snake; quote! { diff --git a/bon/src/builder_state.rs b/bon/src/builder_state.rs index f68fb818..4336eebb 100644 --- a/bon/src/builder_state.rs +++ b/bon/src/builder_state.rs @@ -21,7 +21,7 @@ use crate::private::{Sealed, Set, Unset}; pub trait IsSet: Sealed {} /// Marker trait that indicates that the member is unset, i.e. none -/// of its setters were called. +/// of its setters was called. #[rustversion::attr( since(1.78.0), diagnostic::on_unimplemented( diff --git a/bon/tests/integration/builder/attr_setters.rs b/bon/tests/integration/builder/attr_setters.rs index df020371..4e05a52a 100644 --- a/bon/tests/integration/builder/attr_setters.rs +++ b/bon/tests/integration/builder/attr_setters.rs @@ -1,105 +1,263 @@ -use crate::prelude::*; +mod name { + use crate::prelude::*; -#[test] -fn test_name() { - #[derive(Builder)] - #[allow(dead_code)] - struct Sut { - #[builder(setters(name = arg1_renamed))] - arg1: bool, + #[test] + fn test_struct() { + #[derive(Builder)] + #[allow(dead_code)] + struct Sut { + #[builder(setters(name = arg1_renamed))] + arg1: bool, - #[builder(setters(name = arg2_renamed))] - arg2: Option<()>, + #[builder(setters(name = arg2_renamed))] + arg2: Option<()>, - #[builder(default, setters(name = arg3_renamed))] - arg3: u32, + #[builder(setters(name = arg3_renamed), default)] + arg3: u32, + } + + use sut_builder::*; + + #[allow(type_alias_bounds)] + type _AssocTypes = (T::Arg1, T::Arg2, T::Arg3); + + let _ = Sut::builder().arg1_renamed(true); + + let _ = Sut::builder().arg2_renamed(()); + let _ = Sut::builder().maybe_arg2_renamed(Some(())); + + let _ = Sut::builder().arg3_renamed(42); + let _ = Sut::builder().maybe_arg3_renamed(Some(42)); + + // The name in the state must remain the same + let _: SutBuilder>> = Sut::builder() + .arg1_renamed(true) + .arg2_renamed(()) + .arg3_renamed(42); + } + + #[test] + fn test_free_fn() { + #[builder] + fn sut( + #[builder(setters(name = arg1_renamed))] _arg1: bool, + #[builder(setters(name = arg2_renamed))] _arg2: Option<()>, + #[builder(setters(name = arg3_renamed), default)] _arg3: u32, + ) { + } + + use sut_builder::*; + + #[allow(type_alias_bounds)] + type _AssocTypes = (T::Arg1, T::Arg2, T::Arg3); + + let _ = sut().arg1_renamed(true); + + let _ = sut().arg2_renamed(()); + let _ = sut().maybe_arg2_renamed(Some(())); + + let _ = sut().arg3_renamed(42); + let _ = sut().maybe_arg3_renamed(Some(42)); + + // The name in the state must remain the same + let _: SutBuilder>> = + sut().arg1_renamed(true).arg2_renamed(()).arg3_renamed(42); } - use sut_builder::*; + #[test] + fn test_assoc_method() { + struct Sut; + + #[bon] + impl Sut { + #[builder] + fn sut( + #[builder(setters(name = arg1_renamed))] _arg1: bool, + #[builder(setters(name = arg2_renamed))] _arg2: Option<()>, + #[builder(setters(name = arg3_renamed), default)] _arg3: u32, + ) { + } + + #[builder] + fn with_self( + &self, + #[builder(setters(name = arg1_renamed))] _arg1: bool, + #[builder(setters(name = arg2_renamed))] _arg2: Option<()>, + #[builder(setters(name = arg3_renamed), default)] _arg3: u32, + ) { + let _ = self; + } + } + + { + use sut_sut_builder::*; + + #[allow(type_alias_bounds)] + type _AssocTypes = (T::Arg1, T::Arg2, T::Arg3); - let _ = Sut::builder().arg1_renamed(true); + let _ = Sut::sut().arg1_renamed(true); - let _ = Sut::builder().arg2_renamed(()); - let _ = Sut::builder().maybe_arg2_renamed(Some(())); + let _ = Sut::sut().arg2_renamed(()); + let _ = Sut::sut().maybe_arg2_renamed(Some(())); - let _ = Sut::builder().arg3_renamed(42); - let _ = Sut::builder().maybe_arg3_renamed(Some(42)); + let _ = Sut::sut().arg3_renamed(42); + let _ = Sut::sut().maybe_arg3_renamed(Some(42)); - // The name in the state must remain the same - let _: SutBuilder>> = Sut::builder() - .arg1_renamed(true) - .arg2_renamed(()) - .arg3_renamed(42); + // The name in the state must remain the same + let _: SutSutBuilder>> = Sut::sut() + .arg1_renamed(true) + .arg2_renamed(()) + .arg3_renamed(42); + } - #[allow(clippy::items_after_statements)] - fn _assert_assoc_type_name(_: T) - where - T::Arg1:, - T::Arg2:, - { + { + use sut_with_self_builder::*; + + #[allow(type_alias_bounds)] + type _AssocTypes = (T::Arg1, T::Arg2, T::Arg3); + + let sut = Sut; + + let _ = sut.with_self().arg1_renamed(true); + + let _ = sut.with_self().arg2_renamed(()); + let _ = sut.with_self().maybe_arg2_renamed(Some(())); + + let _ = sut.with_self().arg3_renamed(42); + let _ = sut.with_self().maybe_arg3_renamed(Some(42)); + + // The name in the state must remain the same + let _: SutWithSelfBuilder<'_, SetArg3>> = sut + .with_self() + .arg1_renamed(true) + .arg2_renamed(()) + .arg3_renamed(42); + } } } -#[test] -fn test_option_fn_name_and_some_fn_name() { - #[derive(Builder)] - #[builder(derive(Clone))] - #[allow(dead_code)] - struct Sut { - #[builder(setters(some_fn = arg1_some))] - arg1: Option<()>, +mod option_fn_name_and_some_fn_name { + use crate::prelude::*; + + #[test] + fn test_struct() { + #[derive(Builder)] + #[builder(derive(Clone))] + #[allow(dead_code)] + struct Sut { + #[builder(setters(some_fn = arg1_some))] + arg1: Option<()>, + + #[builder(setters(option_fn = arg2_option))] + arg2: Option<()>, + + #[builder(setters(some_fn = arg3_some, option_fn = arg3_option))] + arg3: Option<()>, - #[builder(setters(option_fn = arg2_option))] - arg2: Option<()>, + #[builder(setters(some_fn(name = arg4_some), option_fn(name = arg4_option)))] + arg4: Option<()>, - #[builder(setters(some_fn = arg3_some, option_fn = arg3_option))] - arg3: Option<()>, + #[builder(default, setters(some_fn = arg5_some))] + arg5: (), - #[builder(setters(some_fn(name = arg4_some), option_fn(name = arg4_option)))] - arg4: Option<()>, + #[builder(default, setters(option_fn = arg6_option))] + arg6: (), - #[builder(default, setters(some_fn = arg5_some))] - arg5: (), + #[builder(default, setters(some_fn = arg7_some, option_fn = arg7_option))] + arg7: (), - #[builder(default, setters(option_fn = arg6_option))] - arg6: (), + #[builder(default, setters(some_fn(name = arg8_some), option_fn(name = arg8_option)))] + arg8: (), + } - #[builder(default, setters(some_fn = arg7_some, option_fn = arg7_option))] - arg7: (), + use sut_builder::*; - #[builder(default, setters(some_fn(name = arg8_some), option_fn(name = arg8_option)))] - arg8: (), + let _ = Sut::builder().arg1_some(()); + let _ = Sut::builder().maybe_arg1(Some(())); + + let _ = Sut::builder().arg2(()); + let _ = Sut::builder().arg2_option(Some(())); + + let _ = Sut::builder().arg3_some(()); + let _ = Sut::builder().arg3_option(Some(())); + + let _ = Sut::builder().arg4_some(()); + let _ = Sut::builder().arg4_option(Some(())); + + let _ = Sut::builder().arg5_some(()); + let _ = Sut::builder().maybe_arg5(Some(())); + + let _ = Sut::builder().arg6(()); + let _ = Sut::builder().arg6_option(Some(())); + + let _ = Sut::builder().arg7_some(()); + let _ = Sut::builder().arg7_option(Some(())); + + let _ = Sut::builder().arg8_some(()); + let _ = Sut::builder().arg8_option(Some(())); + + #[allow(clippy::type_complexity)] + let _: SutBuilder< + SetArg8>>>>>>, + > = Sut::builder() + .arg1_some(()) + .arg2(()) + .arg3_some(()) + .arg4_some(()) + .arg5_some(()) + .arg6(()) + .arg7_some(()) + .arg8_some(()); } - use sut_builder::*; + #[test] + fn test_free_fn() { + #[builder(derive(Clone))] + fn sut( + #[builder(setters(some_fn = arg1_some))] _arg1: Option<()>, + #[builder(setters(option_fn = arg2_option))] _arg2: Option<()>, + #[builder(setters(some_fn = arg3_some, option_fn = arg3_option))] _arg3: Option<()>, + #[builder(setters(some_fn(name = arg4_some), option_fn(name = arg4_option)))] + _arg4: Option<()>, + + #[builder(default, setters(some_fn = arg5_some))] _arg5: (), + #[builder(default, setters(option_fn = arg6_option))] _arg6: (), + #[builder(default, setters(some_fn = arg7_some, option_fn = arg7_option))] _arg7: (), + #[builder(default, setters(some_fn(name = arg8_some), option_fn(name = arg8_option)))] + _arg8: (), + ) { + } + + use sut_builder::*; - let _ = Sut::builder().arg1_some(()); - let _ = Sut::builder().maybe_arg1(Some(())); + let _ = sut().arg1_some(()); + let _ = sut().maybe_arg1(Some(())); - let _ = Sut::builder().arg2(()); - let _ = Sut::builder().arg2_option(Some(())); + let _ = sut().arg2(()); + let _ = sut().arg2_option(Some(())); - let _ = Sut::builder().arg3_some(()); - let _ = Sut::builder().arg3_option(Some(())); + let _ = sut().arg3_some(()); + let _ = sut().arg3_option(Some(())); - let _ = Sut::builder().arg4_some(()); - let _ = Sut::builder().arg4_option(Some(())); + let _ = sut().arg4_some(()); + let _ = sut().arg4_option(Some(())); - let _ = Sut::builder().arg5_some(()); - let _ = Sut::builder().maybe_arg5(Some(())); + let _ = sut().arg5_some(()); + let _ = sut().maybe_arg5(Some(())); - let _ = Sut::builder().arg6(()); - let _ = Sut::builder().arg6_option(Some(())); + let _ = sut().arg6(()); + let _ = sut().arg6_option(Some(())); - let _ = Sut::builder().arg7_some(()); - let _ = Sut::builder().arg7_option(Some(())); + let _ = sut().arg7_some(()); + let _ = sut().arg7_option(Some(())); - let _ = Sut::builder().arg8_some(()); - let _ = Sut::builder().arg8_option(Some(())); + let _ = sut().arg8_some(()); + let _ = sut().arg8_option(Some(())); - #[allow(clippy::type_complexity)] - let _: SutBuilder>>>>>>> = - Sut::builder() + #[allow(clippy::type_complexity)] + let _: SutBuilder< + SetArg8>>>>>>, + > = sut() .arg1_some(()) .arg2(()) .arg3_some(()) @@ -108,4 +266,133 @@ fn test_option_fn_name_and_some_fn_name() { .arg6(()) .arg7_some(()) .arg8_some(()); + } + + #[test] + fn test_assoc_method() { + struct Sut; + + #[bon] + impl Sut { + #[builder(derive(Clone))] + fn sut( + #[builder(setters(some_fn = arg1_some))] _arg1: Option<()>, + #[builder(setters(option_fn = arg2_option))] _arg2: Option<()>, + #[builder(setters(some_fn = arg3_some, option_fn = arg3_option))] _arg3: Option<()>, + #[builder(setters(some_fn(name = arg4_some), option_fn(name = arg4_option)))] + _arg4: Option<()>, + + #[builder(default, setters(some_fn = arg5_some))] _arg5: (), + #[builder(default, setters(option_fn = arg6_option))] _arg6: (), + #[builder(default, setters(some_fn = arg7_some, option_fn = arg7_option))] _arg7: ( + ), + #[builder(default, setters(some_fn(name = arg8_some), option_fn(name = arg8_option)))] + _arg8: (), + ) { + } + + #[builder(derive(Clone))] + fn with_self( + &self, + #[builder(setters(some_fn = arg1_some))] _arg1: Option<()>, + #[builder(setters(option_fn = arg2_option))] _arg2: Option<()>, + #[builder(setters(some_fn = arg3_some, option_fn = arg3_option))] _arg3: Option<()>, + #[builder(setters(some_fn(name = arg4_some), option_fn(name = arg4_option)))] + _arg4: Option<()>, + + #[builder(default, setters(some_fn = arg5_some))] _arg5: (), + #[builder(default, setters(option_fn = arg6_option))] _arg6: (), + #[builder(default, setters(some_fn = arg7_some, option_fn = arg7_option))] _arg7: ( + ), + #[builder(default, setters(some_fn(name = arg8_some), option_fn(name = arg8_option)))] + _arg8: (), + ) { + let _ = self; + } + } + + { + use sut_sut_builder::*; + + let _ = Sut::sut().arg1_some(()); + let _ = Sut::sut().maybe_arg1(Some(())); + + let _ = Sut::sut().arg2(()); + let _ = Sut::sut().arg2_option(Some(())); + + let _ = Sut::sut().arg3_some(()); + let _ = Sut::sut().arg3_option(Some(())); + + let _ = Sut::sut().arg4_some(()); + let _ = Sut::sut().arg4_option(Some(())); + + let _ = Sut::sut().arg5_some(()); + let _ = Sut::sut().maybe_arg5(Some(())); + + let _ = Sut::sut().arg6(()); + let _ = Sut::sut().arg6_option(Some(())); + + let _ = Sut::sut().arg7_some(()); + let _ = Sut::sut().arg7_option(Some(())); + + let _ = Sut::sut().arg8_some(()); + let _ = Sut::sut().arg8_option(Some(())); + + #[allow(clippy::type_complexity)] + let _: SutSutBuilder< + SetArg8>>>>>>, + > = Sut::sut() + .arg1_some(()) + .arg2(()) + .arg3_some(()) + .arg4_some(()) + .arg5_some(()) + .arg6(()) + .arg7_some(()) + .arg8_some(()); + } + + { + use sut_with_self_builder::*; + + let _ = Sut.with_self().arg1_some(()); + let _ = Sut.with_self().maybe_arg1(Some(())); + + let _ = Sut.with_self().arg2(()); + let _ = Sut.with_self().arg2_option(Some(())); + + let _ = Sut.with_self().arg3_some(()); + let _ = Sut.with_self().arg3_option(Some(())); + + let _ = Sut.with_self().arg4_some(()); + let _ = Sut.with_self().arg4_option(Some(())); + + let _ = Sut.with_self().arg5_some(()); + let _ = Sut.with_self().maybe_arg5(Some(())); + + let _ = Sut.with_self().arg6(()); + let _ = Sut.with_self().arg6_option(Some(())); + + let _ = Sut.with_self().arg7_some(()); + let _ = Sut.with_self().arg7_option(Some(())); + + let _ = Sut.with_self().arg8_some(()); + let _ = Sut.with_self().arg8_option(Some(())); + + #[allow(clippy::type_complexity)] + let _: SutWithSelfBuilder< + '_, + SetArg8>>>>>>, + > = Sut + .with_self() + .arg1_some(()) + .arg2(()) + .arg3_some(()) + .arg4_some(()) + .arg5_some(()) + .arg6(()) + .arg7_some(()) + .arg8_some(()); + } + } } diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index 3b3816d5..73233a2e 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -4,6 +4,7 @@ pub mod macro_rules_wrapper_test; pub mod missing_docs_test; +pub mod state_mod_pub; // pub mod v3_design; mod reexports; diff --git a/e2e-tests/src/state_mod_pub.rs b/e2e-tests/src/state_mod_pub.rs new file mode 100644 index 00000000..3b593c12 --- /dev/null +++ b/e2e-tests/src/state_mod_pub.rs @@ -0,0 +1,25 @@ +#[derive(bon::Builder)] +#[builder(state_mod(vis = "pub"))] +#[allow(dead_code)] +pub struct PubStateMod { + required_arg: u32, + optional_arg: Option, + + #[builder(default)] + default_arg: u32, + + #[builder(overwritable)] + overwritable_required_arg: u32, + + #[builder(overwritable)] + overwritable_optional_arg: Option, + + #[builder(overwritable, default = 2 * 2 + 3)] + overwritable_default_arg: u32, + + #[builder(transparent)] + transparent_arg: Option, + + #[builder(with = |x: &str| -> Result<_, std::num::ParseIntError> { x.parse() })] + with_arg: u32, +} From 9d5dbf2cce3962762409d45842a96bd1cf8a8bae Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 13 Oct 2024 17:03:14 +0000 Subject: [PATCH 085/119] Add tests for `#[builder(setters(docs, vis))]` --- .../src/builder/builder_gen/setters/mod.rs | 46 ++-- bon-macros/src/tests/attr_setters.rs | 151 +++++++++++ bon-macros/src/tests/mod.rs | 19 ++ .../src/{tests.rs => tests/syntax_errors.rs} | 10 +- .../tests/snapshots/setters_docs_and_vis.rs | 236 ++++++++++++++++++ 5 files changed, 431 insertions(+), 31 deletions(-) create mode 100644 bon-macros/src/tests/attr_setters.rs create mode 100644 bon-macros/src/tests/mod.rs rename bon-macros/src/{tests.rs => tests/syntax_errors.rs} (84%) create mode 100644 bon-macros/tests/snapshots/setters_docs_and_vis.rs diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index 00bdd9cf..dffc7afe 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -370,18 +370,18 @@ impl SettersItems { let common_name = params.and_then(|params| params.name.as_deref()); let common_vis = params.and_then(|params| params.vis.as_deref()); - let common_docs = params.and_then(|params| params.docs.as_deref()); + let common_docs = params.and_then(|params| params.docs.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.cloned().unwrap_or_else(|| { - let header = "\ - | **Required** |\n\ - | -- |\n\n"; + let docs = common_docs.unwrap_or(&member.docs); - doc(header).chain(member.docs.iter().cloned()).collect() - }); + let header = "\ + | **Required** |\n\ + | -- |\n\n"; + + let docs = doc(header).chain(docs.iter().cloned()).collect(); return Self::Required(SetterItem { name: common_name.unwrap_or(&member.name.snake).clone(), @@ -430,29 +430,29 @@ impl SettersItems { // setter has a lower visibility. let some_fn_docs = some_fn .and_then(ItemParams::docs) - .map(ToOwned::to_owned) - .unwrap_or_else(|| { - let base_docs = common_docs.unwrap_or(&member.docs); + .or(common_docs) + .unwrap_or(&member.docs); - let header = optional_setter_docs(default, &option_fn_name, "accepts an `Option`"); + let some_fn_docs = { + let header = optional_setter_docs(default, &option_fn_name, "accepts an `Option`"); - doc(&header).chain(base_docs.iter().cloned()).collect() - }); + doc(&header).chain(some_fn_docs.iter().cloned()).collect() + }; let option_fn_docs = option_fn .and_then(ItemParams::docs) - .map(ToOwned::to_owned) - .unwrap_or_else(|| { - let base_docs = common_docs.unwrap_or(&member.docs); + .or(common_docs) + .unwrap_or(&member.docs); - let header = optional_setter_docs( - default, - &some_fn_name, - "wraps the value with `Some` internally", - ); + let option_fn_docs = { + let header = optional_setter_docs( + default, + &some_fn_name, + "wraps the value with `Some` internally", + ); - doc(&header).chain(base_docs.iter().cloned()).collect() - }); + doc(&header).chain(option_fn_docs.iter().cloned()).collect() + }; let some_fn = SetterItem { name: some_fn_name, diff --git a/bon-macros/src/tests/attr_setters.rs b/bon-macros/src/tests/attr_setters.rs new file mode 100644 index 00000000..d0fccbc0 --- /dev/null +++ b/bon-macros/src/tests/attr_setters.rs @@ -0,0 +1,151 @@ +use super::assert_snapshot; +use crate::util::prelude::*; + +#[test] +fn setters_docs_and_vis() { + let actual_tokens = crate::builder::generate_from_derive(quote! { + struct Sut { + /// Docs on the required field itself + #[builder(setters( + vis = "pub(in overridden)", + docs { + /// Docs on the required field setters. + /// Multiline. + } + ))] + required_field: u32, + + /// Docs on the optional field itself + #[builder(setters( + vis = "pub(in overridden)", + docs { + /// Docs on the optional field setters. + /// Multiline. + } + ))] + optional_field: Option, + + /// Docs on the default field itself + #[builder( + setters( + vis = "pub(in overridden)", + docs { + /// 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)", + docs { + /// Docs on some_fn + /// Multiline. + } + ), + option_fn( + vis = "pub(in option_fn_overridden)", + docs { + /// Docs on option_fn + /// Multiline. + } + ) + ) + )] + optional_field_with_specific_overrides: Option, + + #[builder( + setters( + some_fn( + vis = "pub(in some_fn_overridden)", + docs { + /// Docs on some_fn + /// Multiline. + } + ), + option_fn( + vis = "pub(in option_fn_overridden)", + docs { + /// Docs on option_fn + /// Multiline. + } + ) + ), + default = 2 + 2 * 3 + )] + default_field_with_specific_overrides: u32, + + #[builder(setters( + docs { + /// Common docs + /// Multiline. + }, + vis = "pub(in overridden)", + option_fn( + vis = "pub(in option_fn_overridden)", + docs { + /// Docs on option_fn + /// Multiline. + } + ) + ))] + optional_field_with_inherited_overrides: Option, + + + #[builder( + setters( + docs { + /// Common docs + /// Multiline. + }, + vis = "pub(in overridden)", + option_fn( + vis = "pub(in option_fn_overridden)", + docs { + /// Docs on option_fn + /// Multiline. + } + ) + ), + default = 2 + 2 * 3 + )] + default_field_with_inherited_overrides: u32, + } + }); + + 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; + } + + fn_item.block = syn::parse_quote!({}); + + true + } + _ => true, + }); + + assert_snapshot("setters_docs_and_vis", builder_impl); +} diff --git a/bon-macros/src/tests/mod.rs b/bon-macros/src/tests/mod.rs new file mode 100644 index 00000000..b7732fa3 --- /dev/null +++ b/bon-macros/src/tests/mod.rs @@ -0,0 +1,19 @@ +mod syntax_errors; +mod attr_setters; + +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); +} diff --git a/bon-macros/src/tests.rs b/bon-macros/src/tests/syntax_errors.rs similarity index 84% rename from bon-macros/src/tests.rs rename to bon-macros/src/tests/syntax_errors.rs index 857ebf63..01dbda4b 100644 --- a/bon-macros/src/tests.rs +++ b/bon-macros/src/tests/syntax_errors.rs @@ -1,5 +1,5 @@ +use super::snapshot; use crate::util::prelude::*; -use expect_test::expect_file; #[allow(non_camel_case_types)] #[derive(Copy, Clone)] @@ -27,13 +27,7 @@ fn assert_builder_codegen( // There is a syntax error, so we can't prettify it .unwrap_or_else(|_err| actual.to_string()); - let snapshot_path = format!( - "{}/tests/snapshots/{test_name}.rs", - env!("CARGO_MANIFEST_DIR") - ); - let expected = expect_file![snapshot_path]; - - expected.assert_eq(&actual); + snapshot(test_name).assert_eq(&actual); } macro_rules! test_codegen { diff --git a/bon-macros/tests/snapshots/setters_docs_and_vis.rs b/bon-macros/tests/snapshots/setters_docs_and_vis.rs new file mode 100644 index 00000000..2ae8033f --- /dev/null +++ b/bon-macros/tests/snapshots/setters_docs_and_vis.rs @@ -0,0 +1,236 @@ +#[allow(unused_parens)] +#[automatically_derived] +#[allow(deprecated)] +impl SutBuilder { + /**| **Required** | +| -- | + +*/ + /// Docs on the required field setters. + /// Multiline. + #[allow(clippy::inline_always, clippy::impl_trait_in_params)] + #[inline(always)] + pub(in overridden) fn required_field( + mut self, + value: u32, + ) -> SutBuilder> + where + BuilderState::RequiredField: sut_builder::IsUnset, + {} + /**| **Optional** | +| -- | + +**See also** a companion setter that accepts an `Option`: [`maybe_optional_field()`](Self::maybe_optional_field). + +*/ + /// Docs on the optional field setters. + /// Multiline. + #[allow(clippy::inline_always, clippy::impl_trait_in_params)] + #[inline(always)] + pub(in overridden) fn optional_field( + self, + value: u32, + ) -> SutBuilder> + where + BuilderState::OptionalField: sut_builder::IsUnset, + {} + /**| **Optional** | +| -- | + +**See also** a companion setter that wraps the value with `Some` internally: [`optional_field()`](Self::optional_field). + +*/ + /// Docs on the optional field setters. + /// Multiline. + #[allow(clippy::inline_always, clippy::impl_trait_in_params)] + #[inline(always)] + pub(in overridden) fn maybe_optional_field( + mut self, + value: Option, + ) -> SutBuilder> + where + BuilderState::OptionalField: sut_builder::IsUnset, + {} + /**| **Optional** | +| -- | + +**See also** a companion setter that accepts an `Option`: [`maybe_default_field()`](Self::maybe_default_field). + +**Default:** ```2 + 2 * 3```. + +*/ + /// Docs on the default field setters. + /// Multiline. + #[allow(clippy::inline_always, clippy::impl_trait_in_params)] + #[inline(always)] + pub(in overridden) fn default_field( + self, + value: u32, + ) -> SutBuilder> + where + BuilderState::DefaultField: sut_builder::IsUnset, + {} + /**| **Optional** | +| -- | + +**See also** a companion setter that wraps the value with `Some` internally: [`default_field()`](Self::default_field). + +**Default:** ```2 + 2 * 3```. + +*/ + /// Docs on the default field setters. + /// Multiline. + #[allow(clippy::inline_always, clippy::impl_trait_in_params)] + #[inline(always)] + pub(in overridden) fn maybe_default_field( + mut self, + value: Option, + ) -> SutBuilder> + where + BuilderState::DefaultField: sut_builder::IsUnset, + {} + /**| **Optional** | +| -- | + +**See also** a companion setter that accepts an `Option`: [`maybe_optional_field_with_specific_overrides()`](Self::maybe_optional_field_with_specific_overrides). + +*/ + /// Docs on some_fn + /// Multiline. + #[allow(clippy::inline_always, clippy::impl_trait_in_params)] + #[inline(always)] + pub(in some_fn_overridden) fn optional_field_with_specific_overrides( + self, + value: u32, + ) -> SutBuilder> + where + BuilderState::OptionalFieldWithSpecificOverrides: sut_builder::IsUnset, + {} + /**| **Optional** | +| -- | + +**See also** a companion setter that wraps the value with `Some` internally: [`optional_field_with_specific_overrides()`](Self::optional_field_with_specific_overrides). + +*/ + /// Docs on option_fn + /// Multiline. + #[allow(clippy::inline_always, clippy::impl_trait_in_params)] + #[inline(always)] + pub(in option_fn_overridden) fn maybe_optional_field_with_specific_overrides( + mut self, + value: Option, + ) -> SutBuilder> + where + BuilderState::OptionalFieldWithSpecificOverrides: sut_builder::IsUnset, + {} + /**| **Optional** | +| -- | + +**See also** a companion setter that accepts an `Option`: [`maybe_default_field_with_specific_overrides()`](Self::maybe_default_field_with_specific_overrides). + +**Default:** ```2 + 2 * 3```. + +*/ + /// Docs on some_fn + /// Multiline. + #[allow(clippy::inline_always, clippy::impl_trait_in_params)] + #[inline(always)] + pub(in some_fn_overridden) fn default_field_with_specific_overrides( + self, + value: u32, + ) -> SutBuilder> + where + BuilderState::DefaultFieldWithSpecificOverrides: sut_builder::IsUnset, + {} + /**| **Optional** | +| -- | + +**See also** a companion setter that wraps the value with `Some` internally: [`default_field_with_specific_overrides()`](Self::default_field_with_specific_overrides). + +**Default:** ```2 + 2 * 3```. + +*/ + /// Docs on option_fn + /// Multiline. + #[allow(clippy::inline_always, clippy::impl_trait_in_params)] + #[inline(always)] + pub(in option_fn_overridden) fn maybe_default_field_with_specific_overrides( + mut self, + value: Option, + ) -> SutBuilder> + where + BuilderState::DefaultFieldWithSpecificOverrides: sut_builder::IsUnset, + {} + /**| **Optional** | +| -- | + +**See also** a companion setter that accepts an `Option`: [`maybe_optional_field_with_inherited_overrides()`](Self::maybe_optional_field_with_inherited_overrides). + +*/ + /// Common docs + /// Multiline. + #[allow(clippy::inline_always, clippy::impl_trait_in_params)] + #[inline(always)] + pub(in overridden) fn optional_field_with_inherited_overrides( + self, + value: u32, + ) -> SutBuilder> + where + BuilderState::OptionalFieldWithInheritedOverrides: sut_builder::IsUnset, + {} + /**| **Optional** | +| -- | + +**See also** a companion setter that wraps the value with `Some` internally: [`optional_field_with_inherited_overrides()`](Self::optional_field_with_inherited_overrides). + +*/ + /// Docs on option_fn + /// Multiline. + #[allow(clippy::inline_always, clippy::impl_trait_in_params)] + #[inline(always)] + pub(in option_fn_overridden) fn maybe_optional_field_with_inherited_overrides( + mut self, + value: Option, + ) -> SutBuilder> + where + BuilderState::OptionalFieldWithInheritedOverrides: sut_builder::IsUnset, + {} + /**| **Optional** | +| -- | + +**See also** a companion setter that accepts an `Option`: [`maybe_default_field_with_inherited_overrides()`](Self::maybe_default_field_with_inherited_overrides). + +**Default:** ```2 + 2 * 3```. + +*/ + /// Common docs + /// Multiline. + #[allow(clippy::inline_always, clippy::impl_trait_in_params)] + #[inline(always)] + pub(in overridden) fn default_field_with_inherited_overrides( + self, + value: u32, + ) -> SutBuilder> + where + BuilderState::DefaultFieldWithInheritedOverrides: sut_builder::IsUnset, + {} + /**| **Optional** | +| -- | + +**See also** a companion setter that wraps the value with `Some` internally: [`default_field_with_inherited_overrides()`](Self::default_field_with_inherited_overrides). + +**Default:** ```2 + 2 * 3```. + +*/ + /// Docs on option_fn + /// Multiline. + #[allow(clippy::inline_always, clippy::impl_trait_in_params)] + #[inline(always)] + pub(in option_fn_overridden) fn maybe_default_field_with_inherited_overrides( + mut self, + value: Option, + ) -> SutBuilder> + where + BuilderState::DefaultFieldWithInheritedOverrides: sut_builder::IsUnset, + {} +} From ef8d09e1054e97b127fe3e8841833bc6af80c9fd Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 13 Oct 2024 20:12:32 +0000 Subject: [PATCH 086/119] Prevent name collisions --- .../builder/builder_gen/builder_derives.rs | 25 +++++---- .../src/builder/builder_gen/finish_fn.rs | 7 +-- .../src/builder/builder_gen/input_fn.rs | 11 ++-- .../src/builder/builder_gen/input_struct.rs | 4 +- bon-macros/src/builder/builder_gen/mod.rs | 31 ++++++----- bon-macros/src/builder/builder_gen/models.rs | 42 ++++++++++++--- .../src/builder/builder_gen/setters/mod.rs | 9 ++-- bon-macros/src/builder/item_fn.rs | 11 ++-- bon-macros/src/builder/item_impl.rs | 14 +++-- bon-macros/src/builder/mod.rs | 10 +++- .../src/normalization/generics_namespace.rs | 24 +++++++++ bon-macros/src/normalization/impl_traits.rs | 28 ++++++++-- bon-macros/src/normalization/lifetimes.rs | 52 +++++++++++++++---- bon-macros/src/normalization/mod.rs | 2 + bon-macros/src/tests/attr_setters.rs | 2 +- 15 files changed, 205 insertions(+), 67 deletions(-) create mode 100644 bon-macros/src/normalization/generics_namespace.rs diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index dcee8498..7bc01cda 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -52,10 +52,10 @@ impl BuilderGenCtx { let generic_args = &self.generics.args; let builder_ident = &self.builder_type.ident; - let phantom_field = &self.private_builder_fields.phantom; - let receiver_field = &self.private_builder_fields.receiver; - let start_fn_args_field = &self.private_builder_fields.start_fn_args; - let named_members_field = &self.private_builder_fields.named_members; + let phantom_field = &self.idents_pool.phantom; + let receiver_field = &self.idents_pool.receiver; + let start_fn_args_field = &self.idents_pool.start_fn_args; + let named_members_field = &self.idents_pool.named_members; let clone = quote!(::core::clone::Clone); @@ -98,15 +98,17 @@ impl BuilderGenCtx { } }); + let state_var = &self.state_var; + quote! { #[automatically_derived] impl< #(#generics_decl,)* - BuilderState: #state_mod::State + #state_var: #state_mod::State > #clone for #builder_ident< #(#generic_args,)* - BuilderState + #state_var > #where_clause { @@ -131,9 +133,9 @@ impl BuilderGenCtx { } fn derive_debug(&self) -> TokenStream { - let receiver_field = &self.private_builder_fields.receiver; - let start_fn_args_field = &self.private_builder_fields.start_fn_args; - let named_members_field = &self.private_builder_fields.named_members; + let receiver_field = &self.idents_pool.receiver; + let start_fn_args_field = &self.idents_pool.start_fn_args; + let named_members_field = &self.idents_pool.named_members; let format_members = self.members.iter().filter_map(|member| { match member { @@ -189,17 +191,18 @@ impl BuilderGenCtx { 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,)* - BuilderState: #state_mod::State + #state_var: #state_mod::State > #debug for #builder_ident< #(#generic_args,)* - BuilderState + #state_var > #where_clause { diff --git a/bon-macros/src/builder/builder_gen/finish_fn.rs b/bon-macros/src/builder/builder_gen/finish_fn.rs index 62a29a5d..07fdf519 100644 --- a/bon-macros/src/builder/builder_gen/finish_fn.rs +++ b/bon-macros/src/builder/builder_gen/finish_fn.rs @@ -3,8 +3,8 @@ use crate::util::prelude::*; impl super::BuilderGenCtx { fn finish_fn_member_expr(&self, member: &Member) -> TokenStream { - let start_fn_args_field = &self.private_builder_fields.start_fn_args; - let named_members_field = &self.private_builder_fields.named_members; + let start_fn_args_field = &self.idents_pool.start_fn_args; + let named_members_field = &self.idents_pool.named_members; let member = match member { Member::Named(member) => member, @@ -119,6 +119,7 @@ impl super::BuilderGenCtx { .unwrap_or(&self.builder_type.vis); let finish_fn_ident = &self.finish_fn.ident; let output = &self.finish_fn.output; + let state_var = &self.state_var; quote! { #(#attrs)* @@ -136,7 +137,7 @@ impl super::BuilderGenCtx { #must_use #finish_fn_vis #asyncness #unsafety fn #finish_fn_ident(self, #(#finish_fn_params,)*) #output where - BuilderState: #state_mod::IsComplete + #state_var: #state_mod::IsComplete { #(#members_vars_decls)* #body diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index be5f1b14..6e284780 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -4,7 +4,7 @@ use super::{ Member, MemberOrigin, RawMember, }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; -use crate::normalization::{NormalizeSelfTy, SyntaxVariant}; +use crate::normalization::{GenericsNamespace, NormalizeSelfTy, SyntaxVariant}; use crate::parsing::{ItemParams, SpannedKey}; use crate::util::prelude::*; use darling::util::SpannedValue; @@ -13,6 +13,7 @@ use std::rc::Rc; use syn::punctuated::Punctuated; use syn::visit::Visit; use syn::visit_mut::VisitMut; +use std::borrow::Cow; #[derive(Debug, FromMeta)] pub(crate) struct FnInputParams { @@ -60,7 +61,8 @@ impl FromMeta for ExposePositionalFnParams { } } -pub(crate) struct FnInputCtx { +pub(crate) struct FnInputCtx<'a> { + pub(crate) namespace: &'a GenericsNamespace, pub(crate) fn_item: SyntaxVariant, pub(crate) impl_ctx: Option>, pub(crate) params: FnInputParams, @@ -76,7 +78,7 @@ pub(crate) struct ImplCtx { pub(crate) allow_attrs: Vec, } -impl FnInputCtx { +impl FnInputCtx<'_> { fn self_ty_prefix(&self) -> Option { let prefix = self .impl_ctx @@ -438,6 +440,7 @@ impl FnInputCtx { }; BuilderGenCtx::new(BuilderGenCtxParams { + namespace: Cow::Borrowed(self.namespace), members, allow_attrs, @@ -483,7 +486,7 @@ impl FinishFnBody for FnCallBody { .sig .receiver() .map(|_| { - let receiver_field = &ctx.private_builder_fields.receiver; + let receiver_field = &ctx.idents_pool.receiver; quote!(self.#receiver_field.) }) .or_else(|| { diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index c4a10f91..7f4fc975 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -4,11 +4,12 @@ use super::{ RawMember, }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; -use crate::normalization::SyntaxVariant; +use crate::normalization::{GenericsNamespace, SyntaxVariant}; use crate::parsing::{ItemParams, ItemParamsParsing, SpannedKey}; use crate::util::prelude::*; use darling::FromMeta; use syn::visit_mut::VisitMut; +use std::borrow::Cow; #[derive(Debug, FromMeta)] pub(crate) struct StructInputParams { @@ -253,6 +254,7 @@ impl StructInputCtx { }; BuilderGenCtx::new(BuilderGenCtxParams { + namespace: Cow::Owned(GenericsNamespace::default()), members, allow_attrs, diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 19bbf1bb..c0da4bc8 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -105,6 +105,7 @@ impl BuilderGenCtx { 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(); @@ -113,11 +114,11 @@ impl BuilderGenCtx { #[automatically_derived] impl< #(#generics_decl,)* - BuilderState: #state_mod::State + #state_var: #state_mod::State > #builder_ident< #(#generic_args,)* - BuilderState + #state_var > #where_clause { @@ -182,10 +183,10 @@ impl BuilderGenCtx { let where_clause = &generics.where_clause; let generic_args = &self.generics.args; - let phantom_field = &self.private_builder_fields.phantom; - let receiver_field = &self.private_builder_fields.receiver; - let start_fn_args_field = &self.private_builder_fields.start_fn_args; - let named_members_field = &self.private_builder_fields.named_members; + let phantom_field = &self.idents_pool.phantom; + let receiver_field = &self.idents_pool.receiver; + let start_fn_args_field = &self.idents_pool.start_fn_args; + let named_members_field = &self.idents_pool.named_members; let receiver = self.receiver(); @@ -294,6 +295,9 @@ impl BuilderGenCtx { quote!(fn() -> ::core::marker::PhantomData<#ty>) }); + + let state_var = &self.state_var; + quote! { ::core::marker::PhantomData<( // There is an interesting quirk with lifetimes in Rust, which is the @@ -319,9 +323,9 @@ impl BuilderGenCtx { // explanation for it, I just didn't care to research it yet ¯\_(ツ)_/¯. #(#types,)* - // A special case of zero members requires storing `BuilderState` in + // A special case of zero members requires storing the builder state in // phantom data otherwise it would be reported as an unused type parameter. - fn() -> ::core::marker::PhantomData, + fn() -> ::core::marker::PhantomData<#state_var>, )> } } @@ -333,10 +337,10 @@ impl BuilderGenCtx { let where_clause = &self.generics.where_clause; let phantom_data = self.phantom_data(); let state_mod = &self.state_mod.ident; - let phantom_field = &self.private_builder_fields.phantom; - let receiver_field = &self.private_builder_fields.receiver; - let start_fn_args_field = &self.private_builder_fields.start_fn_args; - let named_members_field = &self.private_builder_fields.named_members; + let phantom_field = &self.idents_pool.phantom; + let receiver_field = &self.idents_pool.receiver; + let start_fn_args_field = &self.idents_pool.start_fn_args; + let named_members_field = &self.idents_pool.named_members; let private_field_attrs = quote! { // The fields can't be hidden using Rust's privacy syntax. @@ -386,6 +390,7 @@ impl BuilderGenCtx { let named_members_types = self.named_members().map(NamedMember::underlying_norm_ty); let docs = &self.builder_type.docs; + let state_var = &self.state_var; quote! { #[must_use = #must_use_message] @@ -412,7 +417,7 @@ impl BuilderGenCtx { // 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. - BuilderState: #state_mod::State = #state_mod::Empty + #state_var: #state_mod::State = #state_mod::Empty > #where_clause { diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 06146cee..0e74026a 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -1,7 +1,9 @@ use super::builder_params::{BuilderDerives, OnParams}; use super::member::Member; +use crate::normalization::GenericsNamespace; use crate::parsing::{ItemParams, SpannedKey}; use crate::util::prelude::*; +use std::borrow::Cow; pub(super) trait FinishFnBody { /// Generate the `finish` function body from the ready-made variables. @@ -119,7 +121,13 @@ pub(super) struct Generics { } pub(crate) struct BuilderGenCtx { - pub(super) private_builder_fields: BuilderPrivateFields, + /// Private identifiers that are used in the builder implementation. + /// They are intentionally randomized to prevent users from accessing them. + pub(super) idents_pool: PrivateIdentsPool, + + /// 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 @@ -138,14 +146,15 @@ pub(crate) struct BuilderGenCtx { pub(super) finish_fn: FinishFn, } -pub(super) struct BuilderPrivateFields { +pub(super) struct PrivateIdentsPool { pub(super) phantom: syn::Ident, pub(super) receiver: syn::Ident, pub(super) start_fn_args: syn::Ident, pub(super) named_members: syn::Ident, } -pub(super) struct BuilderGenCtxParams { +pub(super) struct BuilderGenCtxParams<'a> { + pub(super) namespace: Cow<'a, GenericsNamespace>, pub(super) members: Vec, pub(super) allow_attrs: Vec, @@ -170,9 +179,10 @@ pub(super) struct BuilderGenCtxParams { pub(super) finish_fn: FinishFn, } -impl BuilderGenCtx { - pub(super) fn new(params: BuilderGenCtxParams) -> Result { +impl<'a> BuilderGenCtx { + pub(super) fn new(params: BuilderGenCtxParams<'a>) -> Result { let BuilderGenCtxParams { + namespace, members, allow_attrs, on_params, @@ -272,8 +282,26 @@ impl BuilderGenCtx { vis: start_fn.vis.unwrap_or_else(|| builder_type.vis.clone()), }; + 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(|| { + let mut name = format!("{}_", possible_names[0]); + + while namespace.idents.contains(&name) { + name.push('_'); + } + + syn::Ident::new(&name, Span::call_site()) + }) + }; + Ok(Self { - private_builder_fields: BuilderPrivateFields::new(), + state_var, + idents_pool: PrivateIdentsPool::new(), members, allow_attrs, on_params, @@ -287,7 +315,7 @@ impl BuilderGenCtx { } } -impl BuilderPrivateFields { +impl PrivateIdentsPool { fn new() -> Self { use std::collections::hash_map::RandomState; use std::hash::{BuildHasher, Hasher}; diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index dffc7afe..05f63b90 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -221,7 +221,7 @@ impl<'a> SettersCtx<'a> { SetterBody::SetMember { value } => { let index = &self.member.index; - let fields = &self.base.private_builder_fields; + let fields = &self.base.idents_pool; let phantom_field = &fields.phantom; let receiver_field = &fields.receiver; let start_fn_args_field = &fields.start_fn_args; @@ -282,9 +282,10 @@ impl<'a> SettersCtx<'a> { 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> + #builder_ident<#(#generic_args,)* #state_mod::#state_transition<#state_var>> } }; @@ -292,11 +293,13 @@ impl<'a> SettersCtx<'a> { return_type = Self::maybe_wrap_in_result(closure, return_type); } + let state_var = &self.base.state_var; + let where_clause = (!self.member.params.overwritable.is_present()).then(|| { let member_pascal = &self.member.name.pascal; quote! { where - BuilderState::#member_pascal: #state_mod::IsUnset, + #state_var::#member_pascal: #state_mod::IsUnset, } }); diff --git a/bon-macros/src/builder/item_fn.rs b/bon-macros/src/builder/item_fn.rs index 7b9bcaa5..7f6735aa 100644 --- a/bon-macros/src/builder/item_fn.rs +++ b/bon-macros/src/builder/item_fn.rs @@ -4,11 +4,15 @@ use crate::normalization::SyntaxVariant; use crate::util::prelude::*; use syn::visit_mut::VisitMut; -pub(crate) fn generate(params: FnInputParams, orig_fn: syn::ItemFn) -> Result { +pub(crate) fn generate( + params: FnInputParams, + orig_fn: syn::ItemFn, + namespace: &crate::normalization::GenericsNamespace, +) -> Result { let mut norm_fn = orig_fn.clone(); - crate::normalization::NormalizeLifetimes.visit_item_fn_mut(&mut norm_fn); - crate::normalization::NormalizeImplTraits.visit_item_fn_mut(&mut norm_fn); + 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, @@ -16,6 +20,7 @@ pub(crate) fn generate(params: FnInputParams, orig_fn: syn::ItemFn) -> Result 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"); } @@ -48,8 +52,11 @@ pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result Result TokenStream { try_generate_from_derive(item).unwrap_or_else(Error::write_errors) @@ -54,7 +55,12 @@ fn try_generate_from_attr(params: TokenStream, item: TokenStream) -> Result item_fn::generate(FromMeta::from_list(nested_meta)?, item_fn)?, + syn::Item::Fn(item_fn) => { + let mut namespace = GenericsNamespace::default(); + namespace.visit_item_fn(&item_fn); + + item_fn::generate(FromMeta::from_list(nested_meta)?, item_fn, &namespace)? + } syn::Item::Struct(struct_item) => { bail!( &struct_item.struct_token, diff --git a/bon-macros/src/normalization/generics_namespace.rs b/bon-macros/src/normalization/generics_namespace.rs new file mode 100644 index 00000000..eeb979ec --- /dev/null +++ b/bon-macros/src/normalization/generics_namespace.rs @@ -0,0 +1,24 @@ +use std::collections::BTreeSet; + +#[derive(Default, Clone)] +pub(crate) struct GenericsNamespace { + /// Set of identifiers referenced in the syntax element. + pub(crate) idents: BTreeSet, + + /// Set of lifetimes referenced in the syntax element. + pub(crate) lifetimes: BTreeSet, +} + +impl syn::visit::Visit<'_> for GenericsNamespace { + fn visit_ident(&mut self, ident: &syn::Ident) { + self.idents.insert(ident.to_string()); + } + + 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. + } +} diff --git a/bon-macros/src/normalization/impl_traits.rs b/bon-macros/src/normalization/impl_traits.rs index 9d9c27ea..90fbc05c 100644 --- a/bon-macros/src/normalization/impl_traits.rs +++ b/bon-macros/src/normalization/impl_traits.rs @@ -1,9 +1,18 @@ +use super::GenericsNamespace; use crate::util::prelude::*; use syn::visit_mut::VisitMut; -pub(crate) struct NormalizeImplTraits; +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 { +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. @@ -11,7 +20,7 @@ impl VisitMut for NormalizeImplTraits { } fn visit_signature_mut(&mut self, signature: &mut syn::Signature) { - let mut visitor = AssignTypeParams::new(&mut signature.generics); + let mut visitor = AssignTypeParams::new(self, &mut signature.generics); for arg in &mut signature.inputs { visitor.visit_fn_arg_mut(arg); @@ -20,13 +29,15 @@ impl VisitMut for NormalizeImplTraits { } struct AssignTypeParams<'a> { + base: &'a NormalizeImplTraits<'a>, generics: &'a mut syn::Generics, next_type_param_index: usize, } impl<'a> AssignTypeParams<'a> { - fn new(generics: &'a mut syn::Generics) -> Self { + fn new(base: &'a NormalizeImplTraits<'a>, generics: &'a mut syn::Generics) -> Self { Self { + base, generics, next_type_param_index: 1, } @@ -54,7 +65,14 @@ impl VisitMut for AssignTypeParams<'_> { let index = self.next_type_param_index; self.next_type_param_index += 1; - let type_param = format_ident!("ImplTrait{index}"); + let mut type_param = format!("ImplTrait{index}"); + + while self.base.namespace.idents.contains(&type_param) { + type_param.push('_'); + } + + let type_param = syn::Ident::new(&type_param, Span::call_site()); + let impl_trait = std::mem::replace(ty, syn::Type::Path(syn::parse_quote!(#type_param))); let impl_trait = match impl_trait { diff --git a/bon-macros/src/normalization/lifetimes.rs b/bon-macros/src/normalization/lifetimes.rs index e178bde4..d1ab0b36 100644 --- a/bon-macros/src/normalization/lifetimes.rs +++ b/bon-macros/src/normalization/lifetimes.rs @@ -1,26 +1,40 @@ +use super::GenericsNamespace; use crate::util::prelude::*; use syn::visit::Visit; use syn::visit_mut::VisitMut; -#[derive(Default)] -pub(crate) struct NormalizeLifetimes; +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 { +impl VisitMut for NormalizeLifetimes<'_> { fn visit_item_impl_mut(&mut self, impl_block: &mut syn::ItemImpl) { - syn::visit_mut::visit_item_impl_mut(self, impl_block); + for item in &mut impl_block.items { + self.visit_impl_item_mut(item); + } - AssignLifetimes::new("impl", &mut impl_block.generics) + AssignLifetimes::new(self, "impl", &mut impl_block.generics) .visit_type_mut(&mut impl_block.self_ty); } - 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. + 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("fn", &mut signature.generics); + let mut visitor = AssignLifetimes::new(self, "fn", &mut signature.generics); for arg in &mut signature.inputs { visitor.visit_fn_arg_mut(arg); } @@ -70,14 +84,24 @@ impl VisitMut for NormalizeLifetimes { } 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(prefix: &'static str, generics: &'a mut syn::Generics) -> Self { + fn new( + base: &'a NormalizeLifetimes<'a>, + prefix: &'static str, + generics: &'a mut syn::Generics, + ) -> Self { Self { + base, prefix, generics, next_lifetime_index: 1, @@ -153,7 +177,13 @@ impl AssignLifetimes<'_> { let index = self.next_lifetime_index; self.next_lifetime_index += 1; - let lifetime = format!("'{}{index}", self.prefix); + let mut lifetime = format!("'{}{index}", self.prefix); + + // Add `_` suffix to the lifetime to avoid conflicts with existing lifetimes + while self.base.namespace.lifetimes.contains(&lifetime) { + lifetime.push('_'); + } + let lifetime = syn::Lifetime::new(&lifetime, Span::call_site()); let lifetime_param = syn::LifetimeParam::new(lifetime.clone()); diff --git a/bon-macros/src/normalization/mod.rs b/bon-macros/src/normalization/mod.rs index 7a7acd8a..2b5a7996 100644 --- a/bon-macros/src/normalization/mod.rs +++ b/bon-macros/src/normalization/mod.rs @@ -1,9 +1,11 @@ mod cfg; +mod generics_namespace; mod impl_traits; mod lifetimes; mod self_ty; pub(crate) use cfg::*; +pub(crate) use generics_namespace::GenericsNamespace; pub(crate) use impl_traits::NormalizeImplTraits; pub(crate) use lifetimes::NormalizeLifetimes; pub(crate) use self_ty::NormalizeSelfTy; diff --git a/bon-macros/src/tests/attr_setters.rs b/bon-macros/src/tests/attr_setters.rs index d0fccbc0..a826a5d6 100644 --- a/bon-macros/src/tests/attr_setters.rs +++ b/bon-macros/src/tests/attr_setters.rs @@ -126,7 +126,7 @@ fn setters_docs_and_vis() { .iter_mut() .find_map(|item| match item { syn::Item::Impl(impl_item) => (impl_item.self_ty - == syn::parse_quote!(SutBuilder)) + == syn::parse_quote!(SutBuilder)) .then(|| impl_item), _ => None, }) From 7bdf004d73840ead267ce00ae816d352f8986956 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 13 Oct 2024 20:29:05 +0000 Subject: [PATCH 087/119] Autoresolve name conflicts --- .../src/builder/builder_gen/input_struct.rs | 8 ++- .../tests/snapshots/setters_docs_and_vis.rs | 54 +++++++++---------- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 7f4fc975..91f4a823 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -8,8 +8,9 @@ use crate::normalization::{GenericsNamespace, SyntaxVariant}; use crate::parsing::{ItemParams, ItemParamsParsing, SpannedKey}; use crate::util::prelude::*; use darling::FromMeta; -use syn::visit_mut::VisitMut; use std::borrow::Cow; +use syn::visit_mut::VisitMut; +use syn::visit::Visit; #[derive(Debug, FromMeta)] pub(crate) struct StructInputParams { @@ -253,8 +254,11 @@ impl StructInputCtx { } }; + let mut namespace = GenericsNamespace::default(); + namespace.visit_item_struct(&self.struct_item.orig); + BuilderGenCtx::new(BuilderGenCtxParams { - namespace: Cow::Owned(GenericsNamespace::default()), + namespace: Cow::Owned(namespace), members, allow_attrs, diff --git a/bon-macros/tests/snapshots/setters_docs_and_vis.rs b/bon-macros/tests/snapshots/setters_docs_and_vis.rs index 2ae8033f..386b6da3 100644 --- a/bon-macros/tests/snapshots/setters_docs_and_vis.rs +++ b/bon-macros/tests/snapshots/setters_docs_and_vis.rs @@ -1,7 +1,7 @@ #[allow(unused_parens)] #[automatically_derived] #[allow(deprecated)] -impl SutBuilder { +impl SutBuilder { /**| **Required** | | -- | @@ -13,9 +13,9 @@ impl SutBuilder { pub(in overridden) fn required_field( mut self, value: u32, - ) -> SutBuilder> + ) -> SutBuilder> where - BuilderState::RequiredField: sut_builder::IsUnset, + S::RequiredField: sut_builder::IsUnset, {} /**| **Optional** | | -- | @@ -30,9 +30,9 @@ impl SutBuilder { pub(in overridden) fn optional_field( self, value: u32, - ) -> SutBuilder> + ) -> SutBuilder> where - BuilderState::OptionalField: sut_builder::IsUnset, + S::OptionalField: sut_builder::IsUnset, {} /**| **Optional** | | -- | @@ -47,9 +47,9 @@ impl SutBuilder { pub(in overridden) fn maybe_optional_field( mut self, value: Option, - ) -> SutBuilder> + ) -> SutBuilder> where - BuilderState::OptionalField: sut_builder::IsUnset, + S::OptionalField: sut_builder::IsUnset, {} /**| **Optional** | | -- | @@ -66,9 +66,9 @@ impl SutBuilder { pub(in overridden) fn default_field( self, value: u32, - ) -> SutBuilder> + ) -> SutBuilder> where - BuilderState::DefaultField: sut_builder::IsUnset, + S::DefaultField: sut_builder::IsUnset, {} /**| **Optional** | | -- | @@ -85,9 +85,9 @@ impl SutBuilder { pub(in overridden) fn maybe_default_field( mut self, value: Option, - ) -> SutBuilder> + ) -> SutBuilder> where - BuilderState::DefaultField: sut_builder::IsUnset, + S::DefaultField: sut_builder::IsUnset, {} /**| **Optional** | | -- | @@ -102,9 +102,9 @@ impl SutBuilder { pub(in some_fn_overridden) fn optional_field_with_specific_overrides( self, value: u32, - ) -> SutBuilder> + ) -> SutBuilder> where - BuilderState::OptionalFieldWithSpecificOverrides: sut_builder::IsUnset, + S::OptionalFieldWithSpecificOverrides: sut_builder::IsUnset, {} /**| **Optional** | | -- | @@ -119,9 +119,9 @@ impl SutBuilder { pub(in option_fn_overridden) fn maybe_optional_field_with_specific_overrides( mut self, value: Option, - ) -> SutBuilder> + ) -> SutBuilder> where - BuilderState::OptionalFieldWithSpecificOverrides: sut_builder::IsUnset, + S::OptionalFieldWithSpecificOverrides: sut_builder::IsUnset, {} /**| **Optional** | | -- | @@ -138,9 +138,9 @@ impl SutBuilder { pub(in some_fn_overridden) fn default_field_with_specific_overrides( self, value: u32, - ) -> SutBuilder> + ) -> SutBuilder> where - BuilderState::DefaultFieldWithSpecificOverrides: sut_builder::IsUnset, + S::DefaultFieldWithSpecificOverrides: sut_builder::IsUnset, {} /**| **Optional** | | -- | @@ -157,9 +157,9 @@ impl SutBuilder { pub(in option_fn_overridden) fn maybe_default_field_with_specific_overrides( mut self, value: Option, - ) -> SutBuilder> + ) -> SutBuilder> where - BuilderState::DefaultFieldWithSpecificOverrides: sut_builder::IsUnset, + S::DefaultFieldWithSpecificOverrides: sut_builder::IsUnset, {} /**| **Optional** | | -- | @@ -174,9 +174,9 @@ impl SutBuilder { pub(in overridden) fn optional_field_with_inherited_overrides( self, value: u32, - ) -> SutBuilder> + ) -> SutBuilder> where - BuilderState::OptionalFieldWithInheritedOverrides: sut_builder::IsUnset, + S::OptionalFieldWithInheritedOverrides: sut_builder::IsUnset, {} /**| **Optional** | | -- | @@ -191,9 +191,9 @@ impl SutBuilder { pub(in option_fn_overridden) fn maybe_optional_field_with_inherited_overrides( mut self, value: Option, - ) -> SutBuilder> + ) -> SutBuilder> where - BuilderState::OptionalFieldWithInheritedOverrides: sut_builder::IsUnset, + S::OptionalFieldWithInheritedOverrides: sut_builder::IsUnset, {} /**| **Optional** | | -- | @@ -210,9 +210,9 @@ impl SutBuilder { pub(in overridden) fn default_field_with_inherited_overrides( self, value: u32, - ) -> SutBuilder> + ) -> SutBuilder> where - BuilderState::DefaultFieldWithInheritedOverrides: sut_builder::IsUnset, + S::DefaultFieldWithInheritedOverrides: sut_builder::IsUnset, {} /**| **Optional** | | -- | @@ -229,8 +229,8 @@ impl SutBuilder { pub(in option_fn_overridden) fn maybe_default_field_with_inherited_overrides( mut self, value: Option, - ) -> SutBuilder> + ) -> SutBuilder> where - BuilderState::DefaultFieldWithInheritedOverrides: sut_builder::IsUnset, + S::DefaultFieldWithInheritedOverrides: sut_builder::IsUnset, {} } From 1e5924c2cff8f50b58d5dc8e7eb3724c19ea4aa4 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 14 Oct 2024 00:58:10 +0000 Subject: [PATCH 088/119] Add tests for builder state name conflicts --- bon-macros/src/bon.rs | 12 +- bon-macros/src/builder/mod.rs | 1 + .../src/normalization/generics_namespace.rs | 38 +- .../integration/builder/name_conflicts.rs | 90 ----- .../builder/name_conflicts/builder_state.rs | 335 ++++++++++++++++++ .../member_and_type_named_the_same.rs | 36 ++ .../name_conflicts/member_names_state.rs | 50 +++ .../integration/builder/name_conflicts/mod.rs | 3 + .../integration/ui/compile_fail/errors.rs | 7 +- .../integration/ui/compile_fail/errors.stderr | 22 +- .../ui/compile_fail/warnings.stderr | 8 +- 11 files changed, 495 insertions(+), 107 deletions(-) delete mode 100644 bon/tests/integration/builder/name_conflicts.rs create mode 100644 bon/tests/integration/builder/name_conflicts/builder_state.rs create mode 100644 bon/tests/integration/builder/name_conflicts/member_and_type_named_the_same.rs create mode 100644 bon/tests/integration/builder/name_conflicts/member_names_state.rs create mode 100644 bon/tests/integration/builder/name_conflicts/mod.rs diff --git a/bon-macros/src/bon.rs b/bon-macros/src/bon.rs index 63cfe484..855d2a4e 100644 --- a/bon-macros/src/bon.rs +++ b/bon-macros/src/bon.rs @@ -17,11 +17,19 @@ pub(crate) fn try_generate(params: TokenStream, item: TokenStream) -> Result item, + let (params, item) = match ctx.expand_cfg()? { + ExpansionOutput::Expanded { params, item } => (params, item), ExpansionOutput::Recurse(output) => return Ok(output), }; + if !params.is_empty() { + bail!( + ¶ms, + "`#[bon]` attribute does not accept any parameters yet, \ + but it will in the future releases" + ); + } + match item { syn::Item::Impl(item_impl) => builder::item_impl::generate(item_impl), _ => bail!( diff --git a/bon-macros/src/builder/mod.rs b/bon-macros/src/builder/mod.rs index d4fa8e12..15ae7e75 100644 --- a/bon-macros/src/builder/mod.rs +++ b/bon-macros/src/builder/mod.rs @@ -57,6 +57,7 @@ fn try_generate_from_attr(params: TokenStream, item: TokenStream) -> Result { let mut namespace = GenericsNamespace::default(); + namespace.visit_token_stream(params.clone()); namespace.visit_item_fn(&item_fn); item_fn::generate(FromMeta::from_list(nested_meta)?, item_fn, &namespace)? diff --git a/bon-macros/src/normalization/generics_namespace.rs b/bon-macros/src/normalization/generics_namespace.rs index eeb979ec..1dd28af5 100644 --- a/bon-macros/src/normalization/generics_namespace.rs +++ b/bon-macros/src/normalization/generics_namespace.rs @@ -1,6 +1,9 @@ +use crate::util::prelude::*; +use proc_macro2::TokenTree; use std::collections::BTreeSet; +use syn::visit::Visit; -#[derive(Default, Clone)] +#[derive(Debug, Default, Clone)] pub(crate) struct GenericsNamespace { /// Set of identifiers referenced in the syntax element. pub(crate) idents: BTreeSet, @@ -9,11 +12,16 @@ pub(crate) struct GenericsNamespace { pub(crate) lifetimes: BTreeSet, } -impl syn::visit::Visit<'_> for GenericsNamespace { +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()); } @@ -22,3 +30,29 @@ impl syn::visit::Visit<'_> for GenericsNamespace { // Don't recurse into child items. They don't inherit the parent item's generics. } } + +impl GenericsNamespace { + 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(_) => {} + } + } + } +} diff --git a/bon/tests/integration/builder/name_conflicts.rs b/bon/tests/integration/builder/name_conflicts.rs deleted file mode 100644 index 46234bdd..00000000 --- a/bon/tests/integration/builder/name_conflicts.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! @Veetaha encountered a bug when working on big changes to `bon` where -//! the generic params generated by the macro were named the same as the -//! type of the member, and thus leading to a name conflict. This test is -//! here to catch any such simple regression. -use crate::prelude::*; - -struct User; - -#[test] -fn member_and_type_named_the_same_fn() { - #[builder] - fn sut(user: User) { - let _ = user; - } - - sut().user(User).call(); -} - -#[test] -fn member_and_type_named_the_same_struct() { - #[derive(Builder)] - struct Sut { - #[allow(dead_code)] - user: User, - } - - #[bon] - impl Sut { - #[builder] - fn sut(user: User) { - let _ = user; - } - } - - let _ = Sut::builder().user(User).build(); - Sut::sut().user(User).call(); -} - -mod member_names_state { - use crate::prelude::*; - - #[test] - fn test_free_fn() { - #[builder] - fn sut(state: u32, member_state: u32, unset: u32, all_unset: u32) { - let _ = (state, member_state, unset, all_unset); - } - - sut().state(1).member_state(2).unset(3).all_unset(4).call(); - } - - #[test] - fn test_struct() { - #[derive(Builder)] - #[allow(dead_code)] - struct Sut { - state: u32, - member_state: u32, - unset: u32, - all_unset: u32, - } - - let _ = Sut::builder() - .state(1) - .member_state(2) - .unset(3) - .all_unset(4) - .build(); - } - - #[test] - fn test_assoc_method() { - struct Sut; - - #[bon] - impl Sut { - #[builder] - fn sut(state: u32, member_state: u32, unset: u32, all_unset: u32) { - let _ = (state, member_state, unset, all_unset); - } - } - - Sut::sut() - .state(1) - .member_state(2) - .unset(3) - .all_unset(4) - .call(); - } -} diff --git a/bon/tests/integration/builder/name_conflicts/builder_state.rs b/bon/tests/integration/builder/name_conflicts/builder_state.rs new file mode 100644 index 00000000..04db9b41 --- /dev/null +++ b/bon/tests/integration/builder/name_conflicts/builder_state.rs @@ -0,0 +1,335 @@ +mod conflicts_in_bodies { + use crate::prelude::*; + + #[test] + #[allow(clippy::items_after_statements)] + fn test_struct() { + #[derive(Builder, Clone, Copy)] + #[allow(dead_code)] + struct S { + field: u32, + } + + let s = S::builder().field(1).build(); + + #[derive(Builder, Clone, Copy)] + #[allow(dead_code)] + struct State { + field: S, + } + + let state = State::builder().field(s).build(); + + #[derive(Builder, Clone, Copy)] + #[allow(dead_code)] + struct BuilderState { + field1: S, + field2: State, + } + + let builder_state = BuilderState::builder().field1(s).field2(state).build(); + + #[derive(Builder, Clone, Copy)] + #[allow(dead_code)] + #[allow(non_snake_case)] + struct S_ { + field1: S, + field2: State, + field3: BuilderState, + } + + let s_ = S_::builder() + .field1(s) + .field2(state) + .field3(builder_state) + .build(); + + #[derive(Builder, Clone, Copy)] + #[allow(dead_code)] + #[allow(non_snake_case)] + struct S__ { + field1: S, + field2: State, + field3: BuilderState, + field4: S_, + } + + let _ = S__::builder() + .field1(s) + .field2(state) + .field3(builder_state) + .field4(s_) + .build(); + } + + #[test] + #[allow(clippy::items_after_statements)] + fn test_free_fn() { + struct S; + struct State; + struct BuilderState; + + { + #[builder] + fn sut(_field: S) {} + + sut().field(S).call(); + } + + { + #[builder] + fn sut(_field: S) { + let _ = State; + } + + sut().field(S).call(); + } + + { + #[builder] + fn sut(_field1: S, _field2: State) { + let _ = { + { + ((), BuilderState) + } + }; + } + + sut().field1(S).field2(State).call(); + } + + { + #[builder] + fn sut(_field1: S, _field2: State, _field3: BuilderState) {} + + sut::<()>() + .field1(S) + .field2(State) + .field3(BuilderState) + .call(); + } + + { + #[builder] + fn sut(_field1: S, _field2: State, _field3: BuilderState) {} + + sut::<(), ()>() + .field1(S) + .field2(State) + .field3(BuilderState) + .call(); + } + } + + #[test] + #[allow(clippy::items_after_statements)] + fn test_assoc_method() { + struct State; + struct BuilderState; + + { + struct S; + + #[bon] + impl S { + #[builder] + fn sut() {} + } + + S::sut().call(); + } + + { + struct S; + + #[bon] + impl S { + #[builder] + fn sut() { + let _ = State; + } + + #[builder] + fn with_self(&self) { + let _ = self; + } + } + + S::sut().call(); + S.with_self().call(); + } + + { + struct S; + + #[bon] + impl S { + #[builder] + fn sut(_field2: State) {} + + #[builder] + fn with_self(&self) { + let _ = self; + let _ = { + { + ((), BuilderState) + } + }; + } + } + + S::sut().field2(State).call(); + S.with_self().call(); + } + + { + struct S; + + #[bon] + impl S { + #[builder] + fn sut(_field2: State, _field3: BuilderState) {} + } + + S::sut::<()>().field2(State).field3(BuilderState).call(); + } + + { + struct S; + + #[bon] + impl S { + #[builder] + fn sut(_field2: State, _field3: BuilderState) {} + } + + S::sut::<(), ()>().field2(State).field3(BuilderState).call(); + } + } +} + +mod conflicts_in_attrs { + use crate::prelude::*; + + struct S; + + impl S { + fn s(&self) -> u32 { + let _ = self; + 2 + } + } + + struct State; + + impl State { + fn state(&self) -> u32 { + let _ = self; + 2 + } + } + + struct BuilderState; + + impl BuilderState { + fn builder_state(&self) -> u32 { + let _ = self; + 2 + } + } + + #[test] + fn test_struct() { + { + #[derive(Builder)] + #[allow(dead_code)] + struct Sut { + #[builder(with = |s: S| s.s())] + field: u32, + } + + let _ = Sut::builder().field(S).build(); + } + { + #[derive(Builder)] + #[allow(dead_code)] + struct Sut { + #[builder(with = |s: S| s.s())] + field1: u32, + + #[builder(default = State.state())] + field2: u32, + } + + let _ = Sut::builder().field1(S).maybe_field2(Some(43)).build(); + } + { + #[derive(Builder)] + #[allow(dead_code)] + struct Sut { + #[builder(with = |s: S| s.s())] + field1: u32, + + #[builder(default = State.state())] + field2: u32, + + #[builder(skip = BuilderState.builder_state())] + field3: u32, + } + + let _ = Sut::builder().field1(S).maybe_field2(Some(43)).build(); + } + } + + #[test] + fn test_free_fn() { + { + #[builder] + fn sut(#[builder(with = |s: S| s.s())] _field: u32) {} + + sut().field(S).call(); + } + { + #[builder] + fn sut( + #[builder(with = |s: S| s.s())] _field1: u32, + + #[builder(default = State.state())] _field2: u32, + ) { + } + + sut().field1(S).maybe_field2(Some(43)).call(); + } + } + + #[test] + fn test_assoc_method() { + { + struct Sut; + + #[bon] + impl Sut { + #[builder] + fn sut(#[builder(with = |s: S| s.s())] _field: u32) {} + } + + Sut::sut().field(S).call(); + } + { + struct Sut; + + #[bon] + impl Sut { + #[builder] + fn sut( + #[builder(with = |s: S| s.s())] _field1: u32, + + #[builder(default = State.state())] _field2: u32, + ) { + } + } + + Sut::sut().field1(S).maybe_field2(Some(43)).call(); + } + } +} diff --git a/bon/tests/integration/builder/name_conflicts/member_and_type_named_the_same.rs b/bon/tests/integration/builder/name_conflicts/member_and_type_named_the_same.rs new file mode 100644 index 00000000..ee130ea1 --- /dev/null +++ b/bon/tests/integration/builder/name_conflicts/member_and_type_named_the_same.rs @@ -0,0 +1,36 @@ +//! @Veetaha encountered a bug when working on big changes to `bon` where +//! the generic params generated by the macro were named the same as the +//! type of the member, and thus leading to a name conflict. This test is +//! here to catch any such simple regression. +use crate::prelude::*; +struct User; + +#[test] +fn member_and_type_named_the_same_fn() { + #[builder] + fn sut(user: User) { + let _ = user; + } + + sut().user(User).call(); +} + +#[test] +fn member_and_type_named_the_same_struct() { + #[derive(Builder)] + struct Sut { + #[allow(dead_code)] + user: User, + } + + #[bon] + impl Sut { + #[builder] + fn sut(user: User) { + let _ = user; + } + } + + let _ = Sut::builder().user(User).build(); + Sut::sut().user(User).call(); +} diff --git a/bon/tests/integration/builder/name_conflicts/member_names_state.rs b/bon/tests/integration/builder/name_conflicts/member_names_state.rs new file mode 100644 index 00000000..5325b5cf --- /dev/null +++ b/bon/tests/integration/builder/name_conflicts/member_names_state.rs @@ -0,0 +1,50 @@ +use crate::prelude::*; + +#[test] +fn test_free_fn() { + #[builder] + fn sut(state: u32, member_state: u32, unset: u32, empty: u32) { + let _ = (state, member_state, unset, empty); + } + + sut().state(1).member_state(2).unset(3).empty(4).call(); +} + +#[test] +fn test_struct() { + #[derive(Builder)] + #[allow(dead_code)] + struct Sut { + state: u32, + member_state: u32, + unset: u32, + empty: u32, + } + + let _ = Sut::builder() + .state(1) + .member_state(2) + .unset(3) + .empty(4) + .build(); +} + +#[test] +fn test_assoc_method() { + struct Sut; + + #[bon] + impl Sut { + #[builder] + fn sut(state: u32, member_state: u32, unset: u32, empty: u32) { + let _ = (state, member_state, unset, empty); + } + } + + Sut::sut() + .state(1) + .member_state(2) + .unset(3) + .empty(4) + .call(); +} diff --git a/bon/tests/integration/builder/name_conflicts/mod.rs b/bon/tests/integration/builder/name_conflicts/mod.rs new file mode 100644 index 00000000..c8651f75 --- /dev/null +++ b/bon/tests/integration/builder/name_conflicts/mod.rs @@ -0,0 +1,3 @@ +mod member_and_type_named_the_same; +mod member_names_state; +mod builder_state; diff --git a/bon/tests/integration/ui/compile_fail/errors.rs b/bon/tests/integration/ui/compile_fail/errors.rs index e020ede3..c53501d5 100644 --- a/bon/tests/integration/ui/compile_fail/errors.rs +++ b/bon/tests/integration/ui/compile_fail/errors.rs @@ -1,4 +1,4 @@ -use bon::{builder, Builder}; +use bon::{bon, builder, Builder}; use std::collections::{BTreeMap, BTreeSet}; fn main() { @@ -133,3 +133,8 @@ struct InvalidWithExpr { #[builder(with = 42)] x: u32, } + +struct InvalidAttrsForBonMacro; + +#[bon(attrs)] +impl InvalidAttrsForBonMacro {} diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr index 91154b82..16d704a4 100644 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ b/bon/tests/integration/ui/compile_fail/errors.stderr @@ -156,6 +156,12 @@ error: expected a closure e.g. `with = |param: T| expression` 133 | #[builder(with = 42)] | ^^^^ +error: `#[bon]` attribute does not accept any parameters yet, but it will in the future releases + --> tests/integration/ui/compile_fail/errors.rs:139:7 + | +139 | #[bon(attrs)] + | ^^^^^ + warning: unused attribute --> tests/integration/ui/compile_fail/errors.rs:128:1 | @@ -201,11 +207,11 @@ note: required for `SetX` to implement `example_builder::IsComplete` | 24 | #[derive(Builder)] | ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro -note: required by a bound in `ExampleBuilder::::build` +note: required by a bound in `ExampleBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:24:14 | 24 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `ExampleBuilder::::build` + | ^^^^^^^ required by this bound in `ExampleBuilder::::build` 25 | struct Example { | ------- required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -223,11 +229,11 @@ note: required for `SetX` to implement `example_builder::IsComplete` | 24 | #[derive(Builder)] | ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro -note: required by a bound in `ExampleBuilder::::build` +note: required by a bound in `ExampleBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:24:14 | 24 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `ExampleBuilder::::build` + | ^^^^^^^ required by this bound in `ExampleBuilder::::build` 25 | struct Example { | ------- required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -240,11 +246,11 @@ error[E0277]: the member `Set` was already set, but this method requires it t | = help: the trait `IsUnset` is not implemented for `Set` = help: the trait `IsUnset` is implemented for `Unset` -note: required by a bound in `ExampleBuilder::::y` +note: required by a bound in `ExampleBuilder::::y` --> tests/integration/ui/compile_fail/errors.rs:24:14 | 24 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `ExampleBuilder::::y` + | ^^^^^^^ required by this bound in `ExampleBuilder::::y` ... 27 | y: u32, | - required by a bound in this associated function @@ -263,11 +269,11 @@ note: required for `sut_builder::Empty` to implement `sut_builder::IsComplete` | 42 | #[derive(Builder)] | ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro -note: required by a bound in `SutBuilder::::build` +note: required by a bound in `SutBuilder::::build` --> tests/integration/ui/compile_fail/errors.rs:42:18 | 42 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `SutBuilder::::build` + | ^^^^^^^ required by this bound in `SutBuilder::::build` 43 | struct Sut { | --- required by a bound in this associated function = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/bon/tests/integration/ui/compile_fail/warnings.stderr b/bon/tests/integration/ui/compile_fail/warnings.stderr index a64ee93e..ea2d0c22 100644 --- a/bon/tests/integration/ui/compile_fail/warnings.stderr +++ b/bon/tests/integration/ui/compile_fail/warnings.stderr @@ -52,7 +52,7 @@ help: use `let _ = ...` to ignore the resulting value 33 | let _ = Example::builder().x(1); | +++++++ -error: unused return value of `ExampleBuilder::::build` that must be used +error: unused return value of `ExampleBuilder::::build` that must be used --> tests/integration/ui/compile_fail/warnings.rs:34:9 | 34 | Example::builder().x(1).y(2).build(); @@ -64,7 +64,7 @@ help: use `let _ = ...` to ignore the resulting value 34 | let _ = Example::builder().x(1).y(2).build(); | +++++++ -error: unused return value of `ExampleMustUseBuilder::::call` that must be used +error: unused return value of `ExampleMustUseBuilder::::call` that must be used --> tests/integration/ui/compile_fail/warnings.rs:36:9 | 36 | Example::must_use().call(); @@ -75,7 +75,7 @@ help: use `let _ = ...` to ignore the resulting value 36 | let _ = Example::must_use().call(); | +++++++ -error: unused return value of `MustUseBuilder::::call` that must be used +error: unused return value of `MustUseBuilder::::call` that must be used --> tests/integration/ui/compile_fail/warnings.rs:38:9 | 38 | must_use().call(); @@ -97,7 +97,7 @@ help: use `let _ = ...` to ignore the resulting value 39 | let _ = __orig_must_use(); | +++++++ -error: unused return value of `MustUseUnderCfgBuilder::::call` that must be used +error: unused return value of `MustUseUnderCfgBuilder::::call` that must be used --> tests/integration/ui/compile_fail/warnings.rs:47:9 | 47 | must_use_under_cfg().call(); From 1d53e5c1f39e26c0665d2f47f6a3a5defb02d914 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 14 Oct 2024 02:14:45 +0000 Subject: [PATCH 089/119] More tests --- bon-macros/src/builder/builder_gen/models.rs | 10 +- .../src/normalization/generics_namespace.rs | 17 ++++ bon-macros/src/normalization/impl_traits.rs | 11 +-- bon-macros/src/normalization/lifetimes.rs | 14 +-- .../builder/name_conflicts/generics.rs | 94 +++++++++++++++++++ .../integration/builder/name_conflicts/mod.rs | 1 + .../ui/compile_fail/name_conflicts.rs | 21 +++++ .../ui/compile_fail/name_conflicts.stderr | 30 ++++++ 8 files changed, 176 insertions(+), 22 deletions(-) create mode 100644 bon/tests/integration/builder/name_conflicts/generics.rs create mode 100644 bon/tests/integration/ui/compile_fail/name_conflicts.rs create mode 100644 bon/tests/integration/ui/compile_fail/name_conflicts.stderr diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 0e74026a..42ae4df6 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -288,15 +288,7 @@ impl<'a> BuilderGenCtx { .iter() .find(|&&candidate| !namespace.idents.contains(candidate)) .map(|&name| syn::Ident::new(name, Span::call_site())) - .unwrap_or_else(|| { - let mut name = format!("{}_", possible_names[0]); - - while namespace.idents.contains(&name) { - name.push('_'); - } - - syn::Ident::new(&name, Span::call_site()) - }) + .unwrap_or_else(|| namespace.unique_ident(format!("{}_", possible_names[0]))) }; Ok(Self { diff --git a/bon-macros/src/normalization/generics_namespace.rs b/bon-macros/src/normalization/generics_namespace.rs index 1dd28af5..ba6aa33f 100644 --- a/bon-macros/src/normalization/generics_namespace.rs +++ b/bon-macros/src/normalization/generics_namespace.rs @@ -32,6 +32,23 @@ impl Visit<'_> for GenericsNamespace { } impl GenericsNamespace { + pub(crate) fn unique_ident(&self, name: String) -> syn::Ident { + let name = Self::unique_name(&self.idents, name); + syn::Ident::new(&name, Span::call_site()) + } + + pub(crate) fn unique_lifetime(&self, name: String) -> String { + Self::unique_name(&self.lifetimes, name) + } + + /// Adds `_` suffix to the name to avoid conflicts with existing identifiers. + fn unique_name(taken: &BTreeSet, mut ident: String) -> String { + while taken.contains(&ident) { + ident.push('_'); + } + ident + } + 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() { diff --git a/bon-macros/src/normalization/impl_traits.rs b/bon-macros/src/normalization/impl_traits.rs index 90fbc05c..5fad993f 100644 --- a/bon-macros/src/normalization/impl_traits.rs +++ b/bon-macros/src/normalization/impl_traits.rs @@ -65,13 +65,10 @@ impl VisitMut for AssignTypeParams<'_> { let index = self.next_type_param_index; self.next_type_param_index += 1; - let mut type_param = format!("ImplTrait{index}"); - - while self.base.namespace.idents.contains(&type_param) { - type_param.push('_'); - } - - let type_param = syn::Ident::new(&type_param, Span::call_site()); + 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))); diff --git a/bon-macros/src/normalization/lifetimes.rs b/bon-macros/src/normalization/lifetimes.rs index d1ab0b36..615b4c2b 100644 --- a/bon-macros/src/normalization/lifetimes.rs +++ b/bon-macros/src/normalization/lifetimes.rs @@ -177,12 +177,14 @@ impl AssignLifetimes<'_> { let index = self.next_lifetime_index; self.next_lifetime_index += 1; - let mut lifetime = format!("'{}{index}", self.prefix); - - // Add `_` suffix to the lifetime to avoid conflicts with existing lifetimes - while self.base.namespace.lifetimes.contains(&lifetime) { - lifetime.push('_'); - } + 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()); diff --git a/bon/tests/integration/builder/name_conflicts/generics.rs b/bon/tests/integration/builder/name_conflicts/generics.rs new file mode 100644 index 00000000..621f653f --- /dev/null +++ b/bon/tests/integration/builder/name_conflicts/generics.rs @@ -0,0 +1,94 @@ +use crate::prelude::*; + +#[test] +fn lifetimes() { + #[derive(Default)] + #[allow(dead_code)] + struct Sut<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h>( + &'a str, + &'b str, + &'c str, + &'d str, + &'e str, + &'f str, + &'g str, + &'h str, + ); + + #[bon] + impl<'impl1, 'impl1_, 'impl2, 'fn1, 'fn1_, 'fn2> + Sut<'_, '_, 'impl1, 'impl1_, 'impl2, 'fn1, 'fn1_, 'fn2> + { + #[builder] + #[allow(clippy::trivially_copy_pass_by_ref)] + fn sut(_val: &u32, _val2: &u32) {} + } + + Sut::sut().val(&32).val2(&32).call(); +} + +mod impl_trait { + use crate::prelude::*; + + #[test] + fn test_free_fn() { + struct I1; + type I2 = I1; + + impl I1 { + fn get_val(&self) -> u32 { + let _ = self; + 32 + } + } + + { + #[builder] + fn sut(_arg1: impl Copy) -> u32 { + I1.get_val() + } + + sut().arg1(()).call(); + } + + { + #[builder] + fn sut(_arg1: impl Copy, _arg2: impl Sized) -> u32 { + I2 {}.get_val() + } + + sut().arg1(()).arg2(()).call(); + } + } + + #[test] + fn test_assoc_method() { + struct I1; + type I2 = I1; + + impl I1 { + fn get_val(&self) -> u32 { + let _ = self; + 32 + } + } + + #[bon] + impl I1 { + #[builder] + #[allow(clippy::use_self)] + fn sut(_arg1: impl Copy) -> u32 { + I1.get_val() + } + + #[builder] + fn with_self(&self, _arg1: impl Copy, _arg2: impl Sized) -> u32 { + let _ = self; + I2 {}.get_val() + } + } + + I1::sut().arg1(()).call(); + I1.with_self().arg1(()).arg2(()).call(); + } +} diff --git a/bon/tests/integration/builder/name_conflicts/mod.rs b/bon/tests/integration/builder/name_conflicts/mod.rs index c8651f75..15d6a62f 100644 --- a/bon/tests/integration/builder/name_conflicts/mod.rs +++ b/bon/tests/integration/builder/name_conflicts/mod.rs @@ -1,3 +1,4 @@ mod member_and_type_named_the_same; mod member_names_state; mod builder_state; +mod generics; diff --git a/bon/tests/integration/ui/compile_fail/name_conflicts.rs b/bon/tests/integration/ui/compile_fail/name_conflicts.rs new file mode 100644 index 00000000..c3d671fa --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/name_conflicts.rs @@ -0,0 +1,21 @@ +use bon::builder; + +#[builder] +fn body(val: &u32) { + let _: &'fn1 u32 = val; +} + +#[builder] +fn attr_with(#[builder(with = |val: &'fn1 u32| val)] _val: &u32) {} + +#[builder] +fn attr_default( + #[builder(default = { + let val: &'fn1 u32 = &32; + val + })] + _val: &u32, +) { +} + +fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/name_conflicts.stderr b/bon/tests/integration/ui/compile_fail/name_conflicts.stderr new file mode 100644 index 00000000..d3ffe72f --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/name_conflicts.stderr @@ -0,0 +1,30 @@ +error[E0261]: use of undeclared lifetime name `'fn1` + --> tests/integration/ui/compile_fail/name_conflicts.rs:5:13 + | +3 | #[builder] + | - lifetime `'fn1` is missing in item created through this procedural macro +4 | fn body(val: &u32) { +5 | let _: &'fn1 u32 = val; + | ^^^^ undeclared lifetime + +error[E0261]: use of undeclared lifetime name `'fn1` + --> tests/integration/ui/compile_fail/name_conflicts.rs:9:38 + | +8 | #[builder] + | ---------- lifetime `'fn1` is missing in item created through this procedural macro +9 | fn attr_with(#[builder(with = |val: &'fn1 u32| val)] _val: &u32) {} + | ^^^^ - help: consider introducing lifetime `'fn1` here: `<'fn1>` + | | + | undeclared lifetime + +error[E0261]: use of undeclared lifetime name `'fn1` + --> tests/integration/ui/compile_fail/name_conflicts.rs:14:19 + | +11 | #[builder] + | - + | | + | lifetime `'fn1` is missing in item created through this procedural macro + | help: consider introducing lifetime `'fn1` here: `'fn1,` +... +14 | let val: &'fn1 u32 = &32; + | ^^^^ undeclared lifetime From b690c64a32fb26dfccb7ced17cc4f5acbbc6fc33 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 14 Oct 2024 02:16:22 +0000 Subject: [PATCH 090/119] cargo fmt --- bon-macros/src/builder/builder_gen/input_fn.rs | 2 +- bon-macros/src/builder/builder_gen/input_struct.rs | 2 +- bon-macros/src/builder/builder_gen/mod.rs | 1 - bon-macros/src/normalization/impl_traits.rs | 5 +---- bon-macros/src/tests/attr_setters.rs | 6 +++--- bon-macros/src/tests/mod.rs | 2 +- .../builder/name_conflicts/member_names_state.rs | 7 +------ bon/tests/integration/builder/name_conflicts/mod.rs | 4 ++-- 8 files changed, 10 insertions(+), 19 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index 6e284780..a08df9a7 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -9,11 +9,11 @@ use crate::parsing::{ItemParams, SpannedKey}; use crate::util::prelude::*; use darling::util::SpannedValue; use darling::FromMeta; +use std::borrow::Cow; use std::rc::Rc; use syn::punctuated::Punctuated; use syn::visit::Visit; use syn::visit_mut::VisitMut; -use std::borrow::Cow; #[derive(Debug, FromMeta)] pub(crate) struct FnInputParams { diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 91f4a823..08fa07a0 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -9,8 +9,8 @@ use crate::parsing::{ItemParams, ItemParamsParsing, SpannedKey}; use crate::util::prelude::*; use darling::FromMeta; use std::borrow::Cow; -use syn::visit_mut::VisitMut; use syn::visit::Visit; +use syn::visit_mut::VisitMut; #[derive(Debug, FromMeta)] pub(crate) struct StructInputParams { diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index c0da4bc8..74a81b1d 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -295,7 +295,6 @@ impl BuilderGenCtx { quote!(fn() -> ::core::marker::PhantomData<#ty>) }); - let state_var = &self.state_var; quote! { diff --git a/bon-macros/src/normalization/impl_traits.rs b/bon-macros/src/normalization/impl_traits.rs index 5fad993f..ee1392e0 100644 --- a/bon-macros/src/normalization/impl_traits.rs +++ b/bon-macros/src/normalization/impl_traits.rs @@ -65,10 +65,7 @@ impl VisitMut for AssignTypeParams<'_> { 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 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))); diff --git a/bon-macros/src/tests/attr_setters.rs b/bon-macros/src/tests/attr_setters.rs index a826a5d6..78229396 100644 --- a/bon-macros/src/tests/attr_setters.rs +++ b/bon-macros/src/tests/attr_setters.rs @@ -125,9 +125,9 @@ fn setters_docs_and_vis() { .items .iter_mut() .find_map(|item| match item { - syn::Item::Impl(impl_item) => (impl_item.self_ty - == syn::parse_quote!(SutBuilder)) - .then(|| impl_item), + syn::Item::Impl(impl_item) => { + (impl_item.self_ty == syn::parse_quote!(SutBuilder)).then(|| impl_item) + } _ => None, }) .unwrap_or_else(|| { diff --git a/bon-macros/src/tests/mod.rs b/bon-macros/src/tests/mod.rs index b7732fa3..e2595f8e 100644 --- a/bon-macros/src/tests/mod.rs +++ b/bon-macros/src/tests/mod.rs @@ -1,5 +1,5 @@ -mod syntax_errors; mod attr_setters; +mod syntax_errors; use crate::util::prelude::*; use expect_test::{expect_file, ExpectFile}; diff --git a/bon/tests/integration/builder/name_conflicts/member_names_state.rs b/bon/tests/integration/builder/name_conflicts/member_names_state.rs index 5325b5cf..1026ed38 100644 --- a/bon/tests/integration/builder/name_conflicts/member_names_state.rs +++ b/bon/tests/integration/builder/name_conflicts/member_names_state.rs @@ -41,10 +41,5 @@ fn test_assoc_method() { } } - Sut::sut() - .state(1) - .member_state(2) - .unset(3) - .empty(4) - .call(); + Sut::sut().state(1).member_state(2).unset(3).empty(4).call(); } diff --git a/bon/tests/integration/builder/name_conflicts/mod.rs b/bon/tests/integration/builder/name_conflicts/mod.rs index 15d6a62f..b243a0df 100644 --- a/bon/tests/integration/builder/name_conflicts/mod.rs +++ b/bon/tests/integration/builder/name_conflicts/mod.rs @@ -1,4 +1,4 @@ -mod member_and_type_named_the_same; -mod member_names_state; mod builder_state; mod generics; +mod member_and_type_named_the_same; +mod member_names_state; From 90509cc57a8368839c1c097d378ab2bd1d6aea26 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 14 Oct 2024 02:31:33 +0000 Subject: [PATCH 091/119] Update trybuild --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40b15145..f0dc2038 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1093,9 +1093,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.97" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b1e5645f2ee8025c2f1d75e1138f2dd034d74e6ba54620f3c569ba2a2a1ea06" +checksum = "207aa50d36c4be8d8c6ea829478be44a372c6a77669937bb39c698e52f1491e8" dependencies = [ "glob", "serde", From d9d62c09e4ec607b2b615cda3be993c2255ee89e Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 14 Oct 2024 22:25:37 +0000 Subject: [PATCH 092/119] Install `rust-src` for trybuild tests --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88cc7c24..13d18506 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,12 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + # rust-src is required to make sure compile errors on CI are rendered + # the same as locally. The `rust-src` component is installed by default + # locally, and with its presence compile error messages can show snippets + # of rust standard library code. + components: rust-src - run: cargo clippy --all-features --all-targets --locked From 3b161be64a6a0699a983973792d1b1e899ddb978 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 14 Oct 2024 23:07:20 +0000 Subject: [PATCH 093/119] Clean up docs --- website/.vitepress/config.mts | 10 ++-- .../blog/bon-builder-generator-v2-release.md | 6 +- website/blog/bon-builder-v2-1-release.md | 2 +- website/reference/builder.md | 55 ++++++++----------- 4 files changed, 32 insertions(+), 41 deletions(-) diff --git a/website/.vitepress/config.mts b/website/.vitepress/config.mts index f017090c..da2446c1 100644 --- a/website/.vitepress/config.mts +++ b/website/.vitepress/config.mts @@ -152,12 +152,12 @@ export default defineConfig({ text: "Reference", items: [ { - text: "Builder macros", + text: "#[derive(Builder)] / #[builder]", link: "/reference/builder", items: [ { - text: "Top-Level Attributes", - link: "/reference/builder#top-level-attributes", + text: "Item attributes", + link: "/reference/builder#item-attributes", items: [ { text: "builder_type", @@ -186,8 +186,8 @@ export default defineConfig({ ], }, { - text: "Member-Level Attributes", - link: "/reference/builder#member-level-attributes", + text: "Member attributes", + link: "/reference/builder#member-attributes", items: [ { text: "default", diff --git a/website/blog/bon-builder-generator-v2-release.md b/website/blog/bon-builder-generator-v2-release.md index 5988fa27..39f26735 100644 --- a/website/blog/bon-builder-generator-v2-release.md +++ b/website/blog/bon-builder-generator-v2-release.md @@ -27,7 +27,7 @@ The way this is implemented is a big topic that deserves a separate blog post. H It's now possible to reference other members in `skip` and `default` expressions. Only the members declared higher in code are accessible to these expressions. Here is an example of how it works with `skip`: -```rust +```rust compile_fail use bon::builder; #[builder] @@ -100,7 +100,7 @@ Example::builder() Now, to preserve the same behavior, you need to do this: -```rust +```rust compile_fail use bon::builder; #[builder(on(String, into))] // Only this line needs to change @@ -128,7 +128,7 @@ Alternatively, you can add `#[builder(into)]` on top of each field that requires **Example:** -```rust +```rust compile_fail use bon::builder; #[builder] diff --git a/website/blog/bon-builder-v2-1-release.md b/website/blog/bon-builder-v2-1-release.md index 7aca068c..5f7a676e 100644 --- a/website/blog/bon-builder-v2-1-release.md +++ b/website/blog/bon-builder-v2-1-release.md @@ -105,7 +105,7 @@ It became possible after adopting the new design for the generated code (see [op We added `#[must_use]` to the `build()` method for struct builders. Now this produces a warning because the result of `#[build]` is not used: -```rust +```rust compile_fail #[bon::builder] struct Point { x: u32 diff --git a/website/reference/builder.md b/website/reference/builder.md index bf19bf20..fa95373c 100644 --- a/website/reference/builder.md +++ b/website/reference/builder.md @@ -2,59 +2,50 @@ outline: [2, 3] --- -# Builder macros +# `#[derive(Builder)]` / `#[builder]` -There are several ways to generate a builder depending on the syntax you place the builder macro on. +You can generate a builder using three different kinds of syntax (struct, free function, associated method). They all share two common groups of attributes. -**Structs:** +- [Item attributes](#item-attributes) - apply to a `struct` or `fn` declaration itself. +- [Member attributes](#member-attributes) - apply to a `struct` field or `fn` argument. -Use `#[derive(bon::Builder)]` +See examples. Make sure to click through the tabs: -```rust +:::code-group + +```rust [Struct] use bon::Builder; #[derive(Builder)] -#[builder(/* Top-Level Attributes */)] +#[builder(finish_fn = finish)] // <-- this is an item attribute // [!code highlight] struct Example { - #[builder(/* Member-Level Attributes */)] + #[builder(default)] // <-- this is a member attribute // [!code highlight] field: u32 } ``` -**Free functions:** - -Use `#[bon::builder]` - -```rust +```rust [Free function] use bon::builder; -#[builder(/* Top-Level Attributes */)] +#[builder(finish_fn = finish)] // <-- this is an item attribute // [!code highlight] fn example( - #[builder(/* Member-Level Attributes */)] + #[builder(default)] // <-- this is a member attribute // [!code highlight] arg: u32 -) { - // body -} +) { } ``` -**Associated methods:** - -Use `#[bon::bon]` + `#[builder]` on individual methods - -```rust +```rust [Associated method] use bon::bon; struct Example; #[bon] impl Example { - #[builder(/* Top-Level Attributes */)] + #[builder(finish_fn = finish)] // <-- this is an item attribute // [!code highlight] fn example( - #[builder(/* Member-Level Attributes */)] + #[builder(default)] // <-- this is a member attribute // [!code highlight] arg: u32 - ) { - // body - } + ) { } } ``` @@ -68,7 +59,7 @@ Most of the attributes apply to all kinds of syntax. However, some of them are o ::: -## Top-Level Attributes +## Item attributes ### `builder_type` @@ -649,13 +640,13 @@ struct Example { Example::builder() .name("accepts `impl Into`") .path("accepts/impl/into/PathBuf") - // This member doesn't match neither `String` nor `PathBuf`, + // This member doesn't match either `String` or `PathBuf`, // and thus #[builder(into)] was not applied to it .level(100) .build(); ``` -## Member-Level Attributes +## Member attributes ### `default` @@ -887,9 +878,9 @@ See the ["Into Conversions In-Depth"](../guide/patterns/into-conversions-in-dept ::: code-group ```rust [Struct field] -use bon::builder; +use bon::Builder; -#[builder] +#[derive(Builder)] struct Example { #[builder(into)] // [!code highlight] name: String, From 256e3a4b7e52292b7c8f0342486d9aeb14284366 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 14 Oct 2024 23:20:19 +0000 Subject: [PATCH 094/119] Fix lints from nightly --- .github/workflows/ci.yml | 11 +++-------- bon-macros/src/builder/builder_gen/finish_fn.rs | 1 + bon-macros/src/builder/builder_gen/member/named.rs | 2 ++ bon-macros/src/builder/builder_gen/mod.rs | 2 +- bon-macros/src/builder/builder_gen/models.rs | 4 ++-- bon-macros/src/builder/builder_gen/setters/mod.rs | 3 ++- bon-macros/src/parsing/simple_closure.rs | 2 ++ bon-macros/src/util/ty/match_types.rs | 12 ++++++------ 8 files changed, 19 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13d18506..c2fc0ef3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -127,14 +127,9 @@ jobs: toolchain: ${{ matrix.toolchain }} components: clippy - - run: cargo +${{ matrix.toolchain }} clippy --all-features --all-targets --locked - env: - # There is no need for us to stick with the edition 2021 meaning for - # the `expr` fragment in macro rules. - RUSTFLAGS: >- - --deny warnings - --allow unknown-lints - --allow edition-2024-expr-fragment-specifier + - run: | + cargo +${{ matrix.toolchain }} clippy --all-features --all-targets --locked \ + --allow edition-2024-expr-fragment-specifier \ --allow impl-trait-overcaptures cargo-doc: diff --git a/bon-macros/src/builder/builder_gen/finish_fn.rs b/bon-macros/src/builder/builder_gen/finish_fn.rs index 07fdf519..77e90cc6 100644 --- a/bon-macros/src/builder/builder_gen/finish_fn.rs +++ b/bon-macros/src/builder/builder_gen/finish_fn.rs @@ -133,6 +133,7 @@ impl super::BuilderGenCtx { // `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, )] #must_use #finish_fn_vis #asyncness #unsafety fn #finish_fn_ident(self, #(#finish_fn_params,)*) #output diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index 1a9aecb6..d441a73a 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -158,6 +158,8 @@ impl NamedMember { Ok(()) } + // Lint from nightly. `&Option` is used to reduce syntax at the callsite + #[allow(unknown_lints, clippy::ref_option)] fn validate_unused_setters_cfg( overrides: &[&SpannedKey], config: &Option>, diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 74a81b1d..1263bfc4 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -242,7 +242,7 @@ impl BuilderGenCtx { 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 + clippy::missing_const_for_fn, )] #vis fn #start_fn_ident<#(#generics_decl),*>( #receiver diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 42ae4df6..a17a8542 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -179,8 +179,8 @@ pub(super) struct BuilderGenCtxParams<'a> { pub(super) finish_fn: FinishFn, } -impl<'a> BuilderGenCtx { - pub(super) fn new(params: BuilderGenCtxParams<'a>) -> Result { +impl BuilderGenCtx { + pub(super) fn new(params: BuilderGenCtxParams<'_>) -> Result { let BuilderGenCtxParams { namespace, members, diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index 05f63b90..a2ce9f4a 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -315,7 +315,8 @@ impl<'a> SettersCtx<'a> { // 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::impl_trait_in_params, + clippy::missing_const_for_fn, )] #[inline(always)] #vis fn #name(#maybe_mut self, #input) -> #return_type diff --git a/bon-macros/src/parsing/simple_closure.rs b/bon-macros/src/parsing/simple_closure.rs index 0b19f1ac..11b2c33a 100644 --- a/bon-macros/src/parsing/simple_closure.rs +++ b/bon-macros/src/parsing/simple_closure.rs @@ -61,6 +61,8 @@ impl FromMeta for SimpleClosure { } } +// Lint from nightly. `&Option` is used to reduce syntax at the callsite +#[allow(unknown_lints, clippy::ref_option)] fn reject_syntax(name: &'static str, syntax: &Option) -> Result { if let Some(syntax) = syntax { bail!(syntax, "{name} is not allowed here") diff --git a/bon-macros/src/util/ty/match_types.rs b/bon-macros/src/util/ty/match_types.rs index e170d8e9..fc258fc0 100644 --- a/bon-macros/src/util/ty/match_types.rs +++ b/bon-macros/src/util/ty/match_types.rs @@ -67,8 +67,8 @@ fn match_angle_bracketed_generic_args( } fn match_option( - scrutinee: &Option, - pattern: &Option, + scrutinee: Option<&T>, + pattern: Option<&T>, compare: impl Fn(&T, &T) -> Result, ) -> Result { match (scrutinee, &pattern) { @@ -119,8 +119,8 @@ fn match_generic_args( scrutinee.ident == pattern.ident && match_types(&scrutinee.ty, &pattern.ty)? && match_option( - &scrutinee.generics, - &pattern.generics, + scrutinee.generics.as_ref(), + pattern.generics.as_ref(), match_angle_bracketed_generic_args, )? } @@ -132,8 +132,8 @@ fn match_generic_args( scrutinee.ident == pattern.ident && match_option( - &scrutinee.generics, - &pattern.generics, + scrutinee.generics.as_ref(), + pattern.generics.as_ref(), match_angle_bracketed_generic_args, )? && match_exprs(&scrutinee.value, &pattern.value) From d27aa43975ac85a4dcab96f787ef5841cc3431a4 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 14 Oct 2024 23:26:44 +0000 Subject: [PATCH 095/119] Remove noise from codegen snapshot tests --- bon-macros/src/tests/attr_setters.rs | 7 +++++ .../tests/snapshots/setters_docs_and_vis.rs | 26 ------------------- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/bon-macros/src/tests/attr_setters.rs b/bon-macros/src/tests/attr_setters.rs index 78229396..e8e5299d 100644 --- a/bon-macros/src/tests/attr_setters.rs +++ b/bon-macros/src/tests/attr_setters.rs @@ -140,6 +140,13 @@ fn setters_docs_and_vis() { 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 diff --git a/bon-macros/tests/snapshots/setters_docs_and_vis.rs b/bon-macros/tests/snapshots/setters_docs_and_vis.rs index 386b6da3..02c3702d 100644 --- a/bon-macros/tests/snapshots/setters_docs_and_vis.rs +++ b/bon-macros/tests/snapshots/setters_docs_and_vis.rs @@ -8,8 +8,6 @@ impl SutBuilder { */ /// Docs on the required field setters. /// Multiline. - #[allow(clippy::inline_always, clippy::impl_trait_in_params)] - #[inline(always)] pub(in overridden) fn required_field( mut self, value: u32, @@ -25,8 +23,6 @@ impl SutBuilder { */ /// Docs on the optional field setters. /// Multiline. - #[allow(clippy::inline_always, clippy::impl_trait_in_params)] - #[inline(always)] pub(in overridden) fn optional_field( self, value: u32, @@ -42,8 +38,6 @@ impl SutBuilder { */ /// Docs on the optional field setters. /// Multiline. - #[allow(clippy::inline_always, clippy::impl_trait_in_params)] - #[inline(always)] pub(in overridden) fn maybe_optional_field( mut self, value: Option, @@ -61,8 +55,6 @@ impl SutBuilder { */ /// Docs on the default field setters. /// Multiline. - #[allow(clippy::inline_always, clippy::impl_trait_in_params)] - #[inline(always)] pub(in overridden) fn default_field( self, value: u32, @@ -80,8 +72,6 @@ impl SutBuilder { */ /// Docs on the default field setters. /// Multiline. - #[allow(clippy::inline_always, clippy::impl_trait_in_params)] - #[inline(always)] pub(in overridden) fn maybe_default_field( mut self, value: Option, @@ -97,8 +87,6 @@ impl SutBuilder { */ /// Docs on some_fn /// Multiline. - #[allow(clippy::inline_always, clippy::impl_trait_in_params)] - #[inline(always)] pub(in some_fn_overridden) fn optional_field_with_specific_overrides( self, value: u32, @@ -114,8 +102,6 @@ impl SutBuilder { */ /// Docs on option_fn /// Multiline. - #[allow(clippy::inline_always, clippy::impl_trait_in_params)] - #[inline(always)] pub(in option_fn_overridden) fn maybe_optional_field_with_specific_overrides( mut self, value: Option, @@ -133,8 +119,6 @@ impl SutBuilder { */ /// Docs on some_fn /// Multiline. - #[allow(clippy::inline_always, clippy::impl_trait_in_params)] - #[inline(always)] pub(in some_fn_overridden) fn default_field_with_specific_overrides( self, value: u32, @@ -152,8 +136,6 @@ impl SutBuilder { */ /// Docs on option_fn /// Multiline. - #[allow(clippy::inline_always, clippy::impl_trait_in_params)] - #[inline(always)] pub(in option_fn_overridden) fn maybe_default_field_with_specific_overrides( mut self, value: Option, @@ -169,8 +151,6 @@ impl SutBuilder { */ /// Common docs /// Multiline. - #[allow(clippy::inline_always, clippy::impl_trait_in_params)] - #[inline(always)] pub(in overridden) fn optional_field_with_inherited_overrides( self, value: u32, @@ -186,8 +166,6 @@ impl SutBuilder { */ /// Docs on option_fn /// Multiline. - #[allow(clippy::inline_always, clippy::impl_trait_in_params)] - #[inline(always)] pub(in option_fn_overridden) fn maybe_optional_field_with_inherited_overrides( mut self, value: Option, @@ -205,8 +183,6 @@ impl SutBuilder { */ /// Common docs /// Multiline. - #[allow(clippy::inline_always, clippy::impl_trait_in_params)] - #[inline(always)] pub(in overridden) fn default_field_with_inherited_overrides( self, value: u32, @@ -224,8 +200,6 @@ impl SutBuilder { */ /// Docs on option_fn /// Multiline. - #[allow(clippy::inline_always, clippy::impl_trait_in_params)] - #[inline(always)] pub(in option_fn_overridden) fn maybe_default_field_with_inherited_overrides( mut self, value: Option, From 0edb3050d56304b33f6669c49e0d639e900a4267 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 14 Oct 2024 23:43:30 +0000 Subject: [PATCH 096/119] Fix feature name --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2fc0ef3..8e1f6fe7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,8 +88,8 @@ jobs: - run: cargo test --locked --all-features --doc - run: cd bon && cargo test --locked --no-default-features --features= - run: cd bon && cargo test --locked --no-default-features --features=alloc - - run: cd bon && cargo test --locked --no-default-features --features=msrv-1-79-0 - - run: cd bon && cargo test --locked --no-default-features --features=alloc,msrv-1-79-0 + - run: cd bon && cargo test --locked --no-default-features --features=implied-bounds + - run: cd bon && cargo test --locked --no-default-features --features=alloc,implied-bounds test-msrv: runs-on: ${{ matrix.os }}-latest From cea27b2dd7de787aa012be48cf0e7299203a698b Mon Sep 17 00:00:00 2001 From: Veetaha Date: Mon, 14 Oct 2024 23:44:51 +0000 Subject: [PATCH 097/119] Fix unstable jobs --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e1f6fe7..367b4208 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -129,6 +129,7 @@ jobs: - run: | cargo +${{ matrix.toolchain }} clippy --all-features --all-targets --locked \ + -- \ --allow edition-2024-expr-fragment-specifier \ --allow impl-trait-overcaptures From 23014ffc663e14e881d7c39ed6783b78009af717 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 15 Oct 2024 22:15:10 +0000 Subject: [PATCH 098/119] Add `miri` tests --- .github/workflows/ci.yml | 12 ++++++++++++ bon/tests/integration/ui/mod.rs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 367b4208..8e790eab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,6 +133,18 @@ jobs: --allow edition-2024-expr-fragment-specifier \ --allow impl-trait-overcaptures + cargo-miri: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: nightly-2024-10-16 + components: miri + + - run: cargo miri test --locked --all-features --all-targets --workspace --exclude benchmarks + cargo-doc: runs-on: ${{ matrix.os }}-latest diff --git a/bon/tests/integration/ui/mod.rs b/bon/tests/integration/ui/mod.rs index 5ef4f616..e5c3878d 100644 --- a/bon/tests/integration/ui/mod.rs +++ b/bon/tests/integration/ui/mod.rs @@ -1,6 +1,6 @@ +#[cfg(not(miri))] #[test] fn ui() { let t = trybuild::TestCases::new(); t.compile_fail("tests/integration/ui/compile_fail/*.rs"); - t.pass("tests/integration/ui/pass/*.rs"); } From dec17093ee9f6ad17092ab6866c2a114e3e3fd9d Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 15 Oct 2024 22:16:27 +0000 Subject: [PATCH 099/119] Fix miri nightly toolchain version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e790eab..ed74580e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -140,7 +140,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: nightly-2024-10-16 + toolchain: nightly-2024-10-14 components: miri - run: cargo miri test --locked --all-features --all-targets --workspace --exclude benchmarks From 4946c0b3772f7d41cefa87d0489a5a28158c7eaf Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 15 Oct 2024 22:20:19 +0000 Subject: [PATCH 100/119] Fix miri --- .github/workflows/ci.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed74580e..6bd511a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,7 +143,14 @@ jobs: toolchain: nightly-2024-10-14 components: miri - - run: cargo miri test --locked --all-features --all-targets --workspace --exclude benchmarks + - run: | + cargo miri test --locked --all-features --all-targets \ + --workspace --exclude benchmarks + env: + RUSTFLAGS: >- + --deny warnings \ + --allow edition-2024-expr-fragment-specifier \ + --allow impl-trait-overcaptures cargo-doc: runs-on: ${{ matrix.os }}-latest From cb779746bd0925ba461be6bb2a89a007b8d391f3 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Tue, 15 Oct 2024 22:22:43 +0000 Subject: [PATCH 101/119] Fix --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bd511a1..fc6ff184 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -148,8 +148,8 @@ jobs: --workspace --exclude benchmarks env: RUSTFLAGS: >- - --deny warnings \ - --allow edition-2024-expr-fragment-specifier \ + --deny warnings + --allow edition-2024-expr-fragment-specifier --allow impl-trait-overcaptures cargo-doc: From a708936c8e14d8186a39a7ab4f949eb5b21a8569 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 16 Oct 2024 00:32:35 +0000 Subject: [PATCH 102/119] Initial compilation benchmarks --- Cargo.lock | 90 +- Cargo.toml | 10 +- benchmarks/compilation/Cargo.toml | 25 + benchmarks/compilation/README.md | 25 + benchmarks/compilation/codegen/Cargo.toml | 16 + benchmarks/compilation/codegen/src/main.rs | 62 + benchmarks/compilation/results.md | 7 + benchmarks/compilation/run.sh | 13 + benchmarks/compilation/src/lib.rs | 13 + .../compilation/src/structs_100_fields_10.rs | 1700 +++++++++++++++++ benchmarks/run.sh | 24 - benchmarks/{ => runtime}/Cargo.toml | 6 +- benchmarks/{ => runtime}/README.md | 6 +- benchmarks/{ => runtime}/benches/criterion.rs | 6 +- benchmarks/{ => runtime}/benches/iai.rs | 2 +- benchmarks/runtime/run.sh | 22 + benchmarks/{ => runtime}/src/args_10.rs | 0 benchmarks/{ => runtime}/src/args_10_alloc.rs | 0 .../{ => runtime}/src/args_10_structs.rs | 0 benchmarks/{ => runtime}/src/args_20.rs | 0 benchmarks/{ => runtime}/src/args_3.rs | 0 benchmarks/{ => runtime}/src/args_5.rs | 0 benchmarks/{ => runtime}/src/lib.rs | 0 scripts/install/hyperfine.sh | 17 + 24 files changed, 1999 insertions(+), 45 deletions(-) create mode 100644 benchmarks/compilation/Cargo.toml create mode 100644 benchmarks/compilation/README.md create mode 100644 benchmarks/compilation/codegen/Cargo.toml create mode 100644 benchmarks/compilation/codegen/src/main.rs create mode 100644 benchmarks/compilation/results.md create mode 100755 benchmarks/compilation/run.sh create mode 100644 benchmarks/compilation/src/lib.rs create mode 100644 benchmarks/compilation/src/structs_100_fields_10.rs delete mode 100755 benchmarks/run.sh rename benchmarks/{ => runtime}/Cargo.toml (74%) rename benchmarks/{ => runtime}/README.md (82%) rename benchmarks/{ => runtime}/benches/criterion.rs (77%) rename benchmarks/{ => runtime}/benches/iai.rs (54%) create mode 100755 benchmarks/runtime/run.sh rename benchmarks/{ => runtime}/src/args_10.rs (100%) rename benchmarks/{ => runtime}/src/args_10_alloc.rs (100%) rename benchmarks/{ => runtime}/src/args_10_structs.rs (100%) rename benchmarks/{ => runtime}/src/args_20.rs (100%) rename benchmarks/{ => runtime}/src/args_3.rs (100%) rename benchmarks/{ => runtime}/src/args_5.rs (100%) rename benchmarks/{ => runtime}/src/lib.rs (100%) create mode 100755 scripts/install/hyperfine.sh diff --git a/Cargo.lock b/Cargo.lock index f0dc2038..67337bcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,16 +74,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "benchmarks" -version = "0.1.0" -dependencies = [ - "bon", - "cfg-if", - "criterion", - "iai", -] - [[package]] name = "bon" version = "2.3.0" @@ -209,6 +199,25 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +[[package]] +name = "compilation-benchmarks" +version = "0.1.0" +dependencies = [ + "bon", + "cfg-if", + "derive_builder", + "typed-builder", +] + +[[package]] +name = "compilation-benchmarks-codegen" +version = "0.1.0" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", +] + [[package]] name = "countme" version = "3.0.1" @@ -332,6 +341,37 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn", +] + [[package]] name = "dissimilar" version = "1.0.9" @@ -824,6 +864,16 @@ dependencies = [ "text-size", ] +[[package]] +name = "runtime-benchmarks" +version = "0.1.0" +dependencies = [ + "bon", + "cfg-if", + "criterion", + "iai", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1105,6 +1155,26 @@ dependencies = [ "toml", ] +[[package]] +name = "typed-builder" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e14ed59dc8b7b26cacb2a92bad2e8b1f098806063898ab42a3bd121d7d45e75" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560b82d656506509d43abe30e0ba64c56b1953ab3d4fe7ba5902747a7a3cedd5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/Cargo.toml b/Cargo.toml index 1a324a04..29da93e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,13 @@ [workspace] -members = ["benchmarks", "bon", "bon-cli", "bon-macros", "e2e-tests"] +members = [ + "benchmarks/compilation", + "benchmarks/compilation/codegen", + "benchmarks/runtime", + "bon", + "bon-cli", + "bon-macros", + "e2e-tests", +] resolver = "2" # This config is used for the benchmarks diff --git a/benchmarks/compilation/Cargo.toml b/benchmarks/compilation/Cargo.toml new file mode 100644 index 00000000..d79a58ff --- /dev/null +++ b/benchmarks/compilation/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "compilation-benchmarks" + +publish = false + +description = """ + Crate for benchmarking of the compilation time of code generated by + proc-macros in the `bon` crate and other alternatives. +""" + +edition = "2021" +version = "0.1.0" + +[dependencies] +bon = { path = "../../bon", optional = true } +cfg-if = "1.0" +derive_builder = { version = "0.20", optional = true } +typed-builder = { version = "0.20", optional = true } + +[lints] +workspace = true + +[features] +bon-overwritable = ["bon"] +default = [] diff --git a/benchmarks/compilation/README.md b/benchmarks/compilation/README.md new file mode 100644 index 00000000..085c455e --- /dev/null +++ b/benchmarks/compilation/README.md @@ -0,0 +1,25 @@ +# Benchmarks + +This is a collection of compilation time benchmarks for the code generated by `bon` crate and some other alternative builder macro crates. + +## Dependencies + +If you'd like to run the benchmarks yourself, first you need to install the following: + +- [`hyperfine`](https://github.com/sharkdp/hyperfine) CLI + +If you are on Linux, just run the following commands to install the dependencies: + +```bash +./scripts/install/hyperfine.sh +``` + +## Running the benchmarks + +Once you have all the [dependencies](#dependencies) installed you can run the selected benchmark from this directory like this: + +```bash +./run.sh {benchmark_name} +``` + +The `{benchmark_name}` corresponds to the modules in this crate. diff --git a/benchmarks/compilation/codegen/Cargo.toml b/benchmarks/compilation/codegen/Cargo.toml new file mode 100644 index 00000000..efeba9f3 --- /dev/null +++ b/benchmarks/compilation/codegen/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "compilation-benchmarks-codegen" + +publish = false + +description = """ + Generates code for the compliation benchmarks. +""" + +edition = "2021" +version = "0.1.0" + +[dependencies] +anyhow = "1.0" +proc-macro2 = "1.0" +quote = "1.0" diff --git a/benchmarks/compilation/codegen/src/main.rs b/benchmarks/compilation/codegen/src/main.rs new file mode 100644 index 00000000..a3f3c0d3 --- /dev/null +++ b/benchmarks/compilation/codegen/src/main.rs @@ -0,0 +1,62 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use std::path::PathBuf; + +fn main() -> anyhow::Result<()> { + let bench_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?) + .parent() + .unwrap() + .to_path_buf(); + + let src_dir = bench_dir.join("src"); + + let structs_number = 100; + let fields_number = 10; + + std::fs::write( + src_dir.join(format!( + "structs_{structs_number}_fields_{fields_number}.rs" + )), + structs_n_fields_n(structs_number, fields_number).to_string(), + )?; + + let status = std::process::Command::new("cargo") + .arg("fmt") + .current_dir(&bench_dir) + .spawn()? + .wait()?; + + anyhow::ensure!(status.success(), "cargo fmt failed: {status}"); + + Ok(()) +} + +fn structs_n_fields_n(structs_number: usize, fields_number: usize) -> TokenStream { + let field_names = (1..=fields_number) + .map(|i| format_ident!("x{i}")) + .collect::>(); + + (1..=structs_number) + .map(move |i| { + let struct_name = format_ident!("Struct{i}"); + + quote! { + #[cfg_attr( + any( + feature = "bon", + feature = "typed-builder", + feature = "derive_builder", + ), + derive(crate::Builder), + )] + #[cfg_attr( + feature = "bon-overwritable", + builder(on(_, overwritable)), + )] + pub struct #struct_name { + #( #field_names: i32, )* + } + } + }) + .collect() +} diff --git a/benchmarks/compilation/results.md b/benchmarks/compilation/results.md new file mode 100644 index 00000000..18e9377e --- /dev/null +++ b/benchmarks/compilation/results.md @@ -0,0 +1,7 @@ +| Command | Mean [s] | Min [s] | Max [s] | Relative | +|:---|---:|---:|---:|---:| +| `cargo build -p compilation-benchmarks --features=bon` | 2.522 ± 0.069 | 2.467 | 2.710 | 8.13 ± 0.58 | +| `cargo build -p compilation-benchmarks --features=bon-overwritable` | 2.413 ± 0.021 | 2.383 | 2.439 | 7.78 ± 0.52 | +| `cargo build -p compilation-benchmarks --features=typed-builder` | 2.076 ± 0.022 | 2.042 | 2.107 | 6.69 ± 0.45 | +| `cargo build -p compilation-benchmarks --features=derive_builder` | 1.243 ± 0.021 | 1.194 | 1.262 | 4.01 ± 0.27 | +| `cargo build -p compilation-benchmarks --features=` | 0.310 ± 0.020 | 0.258 | 0.332 | 1.00 | diff --git a/benchmarks/compilation/run.sh b/benchmarks/compilation/run.sh new file mode 100755 index 00000000..7634db2a --- /dev/null +++ b/benchmarks/compilation/run.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -euxo pipefail + +bench=${1:-structs_10_args_10} + +hyperfine \ + --setup 'cargo build -p compilation-benchmarks --features={features}' \ + --prepare 'cargo clean -p compilation-benchmarks' \ + --shell=none \ + --export-markdown results.md \ + --parameter-list features bon,bon-overwritable,typed-builder,derive_builder,\ + 'cargo build -p compilation-benchmarks --features={features}' diff --git a/benchmarks/compilation/src/lib.rs b/benchmarks/compilation/src/lib.rs new file mode 100644 index 00000000..e027a0be --- /dev/null +++ b/benchmarks/compilation/src/lib.rs @@ -0,0 +1,13 @@ +#![allow(missing_docs, missing_debug_implementations)] + +cfg_if::cfg_if! { + if #[cfg(feature = "bon")] { + use bon::Builder; + } else if #[cfg(feature = "typed-builder")] { + use typed_builder::TypedBuilder as Builder; + } else if #[cfg(feature = "derive_builder")] { + use derive_builder::Builder; + } +} + +pub mod structs_100_fields_10; diff --git a/benchmarks/compilation/src/structs_100_fields_10.rs b/benchmarks/compilation/src/structs_100_fields_10.rs new file mode 100644 index 00000000..a7e1992b --- /dev/null +++ b/benchmarks/compilation/src/structs_100_fields_10.rs @@ -0,0 +1,1700 @@ +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct1 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct2 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct3 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct4 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct5 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct6 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct7 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct8 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct9 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct10 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct11 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct12 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct13 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct14 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct15 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct16 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct17 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct18 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct19 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct20 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct21 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct22 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct23 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct24 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct25 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct26 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct27 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct28 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct29 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct30 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct31 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct32 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct33 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct34 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct35 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct36 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct37 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct38 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct39 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct40 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct41 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct42 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct43 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct44 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct45 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct46 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct47 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct48 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct49 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct50 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct51 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct52 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct53 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct54 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct55 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct56 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct57 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct58 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct59 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct60 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct61 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct62 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct63 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct64 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct65 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct66 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct67 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct68 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct69 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct70 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct71 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct72 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct73 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct74 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct75 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct76 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct77 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct78 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct79 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct80 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct81 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct82 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct83 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct84 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct85 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct86 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct87 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct88 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct89 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct90 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct91 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct92 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct93 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct94 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct95 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct96 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct97 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct98 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct99 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct100 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, +} diff --git a/benchmarks/run.sh b/benchmarks/run.sh deleted file mode 100755 index 2ecc8a5e..00000000 --- a/benchmarks/run.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash - -set -euxo pipefail - -bench=${1:-args_10_structs} - -export CARGO_INCREMENTAL=0 - -cargo clean - -cargo build --features "$bench" --release -p benchmarks - -cd benchmarks - -cargo asm --features "$bench" --no-color "benchmarks::$bench::builder_bench" > builder.dbg.s || true -cargo asm --features "$bench" --no-color "benchmarks::$bench::regular_bench" > regular.dbg.s || true - -# If vscode is present, show diff: -if command -v code; then - code --diff regular.dbg.s builder.dbg.s -fi - -cargo bench --features "$bench" -p benchmarks --profile release --bench iai -cargo bench --features "$bench" -p benchmarks --profile release --bench criterion diff --git a/benchmarks/Cargo.toml b/benchmarks/runtime/Cargo.toml similarity index 74% rename from benchmarks/Cargo.toml rename to benchmarks/runtime/Cargo.toml index 93830bdc..27fff5a9 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/runtime/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "benchmarks" +name = "runtime-benchmarks" publish = false description = """ - Crate for manual benchmarking of the code generated by proc-macros in the `bon` crate + Crate for benchmarking of the code generated by proc-macros in the `bon` crate. """ edition = "2021" @@ -31,7 +31,7 @@ args_10_structs = [] args_20 = [] [dependencies] -bon = { path = "../bon" } +bon = { path = "../../bon" } cfg-if = "1.0" [dev-dependencies] diff --git a/benchmarks/README.md b/benchmarks/runtime/README.md similarity index 82% rename from benchmarks/README.md rename to benchmarks/runtime/README.md index 28bc5947..2c26674a 100644 --- a/benchmarks/README.md +++ b/benchmarks/runtime/README.md @@ -1,6 +1,6 @@ # Benchmarks -This is a collection of benchmarks for the code generated by `bon` crate. +This is a collection of runtime benchmarks for the code generated by `bon` crate. ## Dependencies @@ -18,10 +18,10 @@ sudo apt install valgrind ## Running the benchmarks -Once you have all the [dependencies](#dependencies) installed you can run the selected benchmark like this: +Once you have all the [dependencies](#dependencies) installed you can run the selected benchmark from this directory like this: ```bash -./benchmarks/run.sh {benchmark_name} +./run.sh {benchmark_name} ``` The `{benchmark_name}` corresponds to the modules in this crate. The number in the benchmark name represents the number of arguments that are passed to the function. diff --git a/benchmarks/benches/criterion.rs b/benchmarks/runtime/benches/criterion.rs similarity index 77% rename from benchmarks/benches/criterion.rs rename to benchmarks/runtime/benches/criterion.rs index 2d753e09..bbaace13 100644 --- a/benchmarks/benches/criterion.rs +++ b/benchmarks/runtime/benches/criterion.rs @@ -1,10 +1,10 @@ #![allow(missing_docs)] -use benchmarks::{builder_bench, regular_bench}; +use runtime_benchmarks::{builder_bench, regular_bench}; fn criterion_bench(c: &mut criterion::Criterion) { - let builder_bench_addr: fn() -> u32 = benchmarks::bench::builder_bench; - let regular_bebch_addr: fn() -> u32 = benchmarks::bench::regular_bench; + let builder_bench_addr: fn() -> u32 = runtime_benchmarks::bench::builder_bench; + let regular_bebch_addr: fn() -> u32 = runtime_benchmarks::bench::regular_bench; let equal = if builder_bench_addr == regular_bebch_addr { "equal" diff --git a/benchmarks/benches/iai.rs b/benchmarks/runtime/benches/iai.rs similarity index 54% rename from benchmarks/benches/iai.rs rename to benchmarks/runtime/benches/iai.rs index 831b8785..b2fb3d68 100644 --- a/benchmarks/benches/iai.rs +++ b/benchmarks/runtime/benches/iai.rs @@ -1,5 +1,5 @@ #![allow(missing_docs)] -use benchmarks::{builder_bench, regular_bench}; +use runtime_benchmarks::{builder_bench, regular_bench}; iai::main!(builder_bench, regular_bench); diff --git a/benchmarks/runtime/run.sh b/benchmarks/runtime/run.sh new file mode 100755 index 00000000..0d33461d --- /dev/null +++ b/benchmarks/runtime/run.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +set -euxo pipefail + +bench=${1:-args_10_structs} + +export CARGO_INCREMENTAL=0 + +cargo clean + +cargo build --features "$bench" --release -p runtime-benchmarks + +cargo asm --features "$bench" --no-color "runtime_benchmarks::$bench::builder_bench" > builder.dbg.s || true +cargo asm --features "$bench" --no-color "runtime_benchmarks::$bench::regular_bench" > regular.dbg.s || true + +# If vscode is present, show diff: +if command -v code; then + code --diff regular.dbg.s builder.dbg.s +fi + +cargo bench --features "$bench" -p runtime-benchmarks --profile release --bench iai +cargo bench --features "$bench" -p runtime-benchmarks --profile release --bench criterion diff --git a/benchmarks/src/args_10.rs b/benchmarks/runtime/src/args_10.rs similarity index 100% rename from benchmarks/src/args_10.rs rename to benchmarks/runtime/src/args_10.rs diff --git a/benchmarks/src/args_10_alloc.rs b/benchmarks/runtime/src/args_10_alloc.rs similarity index 100% rename from benchmarks/src/args_10_alloc.rs rename to benchmarks/runtime/src/args_10_alloc.rs diff --git a/benchmarks/src/args_10_structs.rs b/benchmarks/runtime/src/args_10_structs.rs similarity index 100% rename from benchmarks/src/args_10_structs.rs rename to benchmarks/runtime/src/args_10_structs.rs diff --git a/benchmarks/src/args_20.rs b/benchmarks/runtime/src/args_20.rs similarity index 100% rename from benchmarks/src/args_20.rs rename to benchmarks/runtime/src/args_20.rs diff --git a/benchmarks/src/args_3.rs b/benchmarks/runtime/src/args_3.rs similarity index 100% rename from benchmarks/src/args_3.rs rename to benchmarks/runtime/src/args_3.rs diff --git a/benchmarks/src/args_5.rs b/benchmarks/runtime/src/args_5.rs similarity index 100% rename from benchmarks/src/args_5.rs rename to benchmarks/runtime/src/args_5.rs diff --git a/benchmarks/src/lib.rs b/benchmarks/runtime/src/lib.rs similarity index 100% rename from benchmarks/src/lib.rs rename to benchmarks/runtime/src/lib.rs diff --git a/scripts/install/hyperfine.sh b/scripts/install/hyperfine.sh new file mode 100755 index 00000000..4da5c6be --- /dev/null +++ b/scripts/install/hyperfine.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -euo pipefail + +. "$(dirname "${BASH_SOURCE[0]}")/lib.sh" + +: "${TOOL_VERSION:=1.18.0}" + +base_url=https://github.com/sharkdp/hyperfine/releases/download/v$TOOL_VERSION +file_stem=hyperfine-v$TOOL_VERSION-$arch_rust-unknown-linux-musl + +download_and_decompress \ + "$base_url/$file_stem.tar.gz" \ + --strip-components 1 \ + "$file_stem/hyperfine" + +move_exe_to_path hyperfine From 78fe9b5177d9beee0c3b7c020dca5512d611116e Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 16 Oct 2024 01:17:55 +0000 Subject: [PATCH 103/119] More benches --- benchmarks/compilation/Cargo.toml | 7 +- benchmarks/compilation/codegen/src/main.rs | 6 +- benchmarks/compilation/results.md | 9 +- benchmarks/compilation/run.sh | 20 +- benchmarks/compilation/src/lib.rs | 7 + .../compilation/src/structs_10_fields_50.rs | 570 ++ .../compilation/src/structs_200_fields_20.rs | 5400 +++++++++++++++++ 7 files changed, 6007 insertions(+), 12 deletions(-) create mode 100644 benchmarks/compilation/src/structs_10_fields_50.rs create mode 100644 benchmarks/compilation/src/structs_200_fields_20.rs diff --git a/benchmarks/compilation/Cargo.toml b/benchmarks/compilation/Cargo.toml index d79a58ff..0614490b 100644 --- a/benchmarks/compilation/Cargo.toml +++ b/benchmarks/compilation/Cargo.toml @@ -12,6 +12,7 @@ edition = "2021" version = "0.1.0" [dependencies] +# bon = { version = "2.3", optional = true } bon = { path = "../../bon", optional = true } cfg-if = "1.0" derive_builder = { version = "0.20", optional = true } @@ -21,5 +22,9 @@ typed-builder = { version = "0.20", optional = true } workspace = true [features] -bon-overwritable = ["bon"] default = [] +bon-overwritable = ["bon"] + +structs_100_fields_10 = [] +structs_10_fields_50 = [] +structs_200_fields_20 = [] diff --git a/benchmarks/compilation/codegen/src/main.rs b/benchmarks/compilation/codegen/src/main.rs index a3f3c0d3..0772a562 100644 --- a/benchmarks/compilation/codegen/src/main.rs +++ b/benchmarks/compilation/codegen/src/main.rs @@ -10,8 +10,8 @@ fn main() -> anyhow::Result<()> { let src_dir = bench_dir.join("src"); - let structs_number = 100; - let fields_number = 10; + let structs_number = 200; + let fields_number = 20; std::fs::write( src_dir.join(format!( @@ -20,6 +20,8 @@ fn main() -> anyhow::Result<()> { structs_n_fields_n(structs_number, fields_number).to_string(), )?; + println!("Running cargo fmt in {}", bench_dir.display()); + let status = std::process::Command::new("cargo") .arg("fmt") .current_dir(&bench_dir) diff --git a/benchmarks/compilation/results.md b/benchmarks/compilation/results.md index 18e9377e..eb5b096f 100644 --- a/benchmarks/compilation/results.md +++ b/benchmarks/compilation/results.md @@ -1,7 +1,6 @@ | Command | Mean [s] | Min [s] | Max [s] | Relative | |:---|---:|---:|---:|---:| -| `cargo build -p compilation-benchmarks --features=bon` | 2.522 ± 0.069 | 2.467 | 2.710 | 8.13 ± 0.58 | -| `cargo build -p compilation-benchmarks --features=bon-overwritable` | 2.413 ± 0.021 | 2.383 | 2.439 | 7.78 ± 0.52 | -| `cargo build -p compilation-benchmarks --features=typed-builder` | 2.076 ± 0.022 | 2.042 | 2.107 | 6.69 ± 0.45 | -| `cargo build -p compilation-benchmarks --features=derive_builder` | 1.243 ± 0.021 | 1.194 | 1.262 | 4.01 ± 0.27 | -| `cargo build -p compilation-benchmarks --features=` | 0.310 ± 0.020 | 0.258 | 0.332 | 1.00 | +| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,bon` | 2.316 ± 0.022 | 2.278 | 2.352 | 18.55 ± 0.98 | +| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,typed-builder` | 1.882 ± 0.014 | 1.866 | 1.912 | 15.08 ± 0.79 | +| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,derive_builder` | 1.055 ± 0.019 | 1.023 | 1.094 | 8.45 ± 0.47 | +| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,` | 0.125 ± 0.007 | 0.107 | 0.135 | 1.00 | diff --git a/benchmarks/compilation/run.sh b/benchmarks/compilation/run.sh index 7634db2a..ddc0a013 100755 --- a/benchmarks/compilation/run.sh +++ b/benchmarks/compilation/run.sh @@ -2,12 +2,24 @@ set -euxo pipefail -bench=${1:-structs_10_args_10} +macros=( + bon + # bon-overwritable + typed-builder + derive_builder +) + +suites=( + structs_100_fields_10 + # structs_10_fields_50 + # structs_200_fields_20 +) hyperfine \ - --setup 'cargo build -p compilation-benchmarks --features={features}' \ + --setup 'cargo build -p compilation-benchmarks --features={suite},{macro}' \ --prepare 'cargo clean -p compilation-benchmarks' \ --shell=none \ --export-markdown results.md \ - --parameter-list features bon,bon-overwritable,typed-builder,derive_builder,\ - 'cargo build -p compilation-benchmarks --features={features}' + --parameter-list macro "$(IFS=, ; echo "${macros[*]}")," \ + --parameter-list suite "$(IFS=, ; echo "${suites[*]}")" \ + 'cargo build -p compilation-benchmarks --features={suite},{macro}' diff --git a/benchmarks/compilation/src/lib.rs b/benchmarks/compilation/src/lib.rs index e027a0be..629802c0 100644 --- a/benchmarks/compilation/src/lib.rs +++ b/benchmarks/compilation/src/lib.rs @@ -10,4 +10,11 @@ cfg_if::cfg_if! { } } +#[cfg(feature = "structs_100_fields_10")] pub mod structs_100_fields_10; + +#[cfg(feature = "structs_10_fields_50")] +pub mod structs_10_fields_50; + +#[cfg(feature = "structs_200_fields_20")] +pub mod structs_200_fields_20; diff --git a/benchmarks/compilation/src/structs_10_fields_50.rs b/benchmarks/compilation/src/structs_10_fields_50.rs new file mode 100644 index 00000000..8f3edea1 --- /dev/null +++ b/benchmarks/compilation/src/structs_10_fields_50.rs @@ -0,0 +1,570 @@ +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct1 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, + x21: i32, + x22: i32, + x23: i32, + x24: i32, + x25: i32, + x26: i32, + x27: i32, + x28: i32, + x29: i32, + x30: i32, + x31: i32, + x32: i32, + x33: i32, + x34: i32, + x35: i32, + x36: i32, + x37: i32, + x38: i32, + x39: i32, + x40: i32, + x41: i32, + x42: i32, + x43: i32, + x44: i32, + x45: i32, + x46: i32, + x47: i32, + x48: i32, + x49: i32, + x50: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct2 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, + x21: i32, + x22: i32, + x23: i32, + x24: i32, + x25: i32, + x26: i32, + x27: i32, + x28: i32, + x29: i32, + x30: i32, + x31: i32, + x32: i32, + x33: i32, + x34: i32, + x35: i32, + x36: i32, + x37: i32, + x38: i32, + x39: i32, + x40: i32, + x41: i32, + x42: i32, + x43: i32, + x44: i32, + x45: i32, + x46: i32, + x47: i32, + x48: i32, + x49: i32, + x50: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct3 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, + x21: i32, + x22: i32, + x23: i32, + x24: i32, + x25: i32, + x26: i32, + x27: i32, + x28: i32, + x29: i32, + x30: i32, + x31: i32, + x32: i32, + x33: i32, + x34: i32, + x35: i32, + x36: i32, + x37: i32, + x38: i32, + x39: i32, + x40: i32, + x41: i32, + x42: i32, + x43: i32, + x44: i32, + x45: i32, + x46: i32, + x47: i32, + x48: i32, + x49: i32, + x50: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct4 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, + x21: i32, + x22: i32, + x23: i32, + x24: i32, + x25: i32, + x26: i32, + x27: i32, + x28: i32, + x29: i32, + x30: i32, + x31: i32, + x32: i32, + x33: i32, + x34: i32, + x35: i32, + x36: i32, + x37: i32, + x38: i32, + x39: i32, + x40: i32, + x41: i32, + x42: i32, + x43: i32, + x44: i32, + x45: i32, + x46: i32, + x47: i32, + x48: i32, + x49: i32, + x50: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct5 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, + x21: i32, + x22: i32, + x23: i32, + x24: i32, + x25: i32, + x26: i32, + x27: i32, + x28: i32, + x29: i32, + x30: i32, + x31: i32, + x32: i32, + x33: i32, + x34: i32, + x35: i32, + x36: i32, + x37: i32, + x38: i32, + x39: i32, + x40: i32, + x41: i32, + x42: i32, + x43: i32, + x44: i32, + x45: i32, + x46: i32, + x47: i32, + x48: i32, + x49: i32, + x50: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct6 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, + x21: i32, + x22: i32, + x23: i32, + x24: i32, + x25: i32, + x26: i32, + x27: i32, + x28: i32, + x29: i32, + x30: i32, + x31: i32, + x32: i32, + x33: i32, + x34: i32, + x35: i32, + x36: i32, + x37: i32, + x38: i32, + x39: i32, + x40: i32, + x41: i32, + x42: i32, + x43: i32, + x44: i32, + x45: i32, + x46: i32, + x47: i32, + x48: i32, + x49: i32, + x50: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct7 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, + x21: i32, + x22: i32, + x23: i32, + x24: i32, + x25: i32, + x26: i32, + x27: i32, + x28: i32, + x29: i32, + x30: i32, + x31: i32, + x32: i32, + x33: i32, + x34: i32, + x35: i32, + x36: i32, + x37: i32, + x38: i32, + x39: i32, + x40: i32, + x41: i32, + x42: i32, + x43: i32, + x44: i32, + x45: i32, + x46: i32, + x47: i32, + x48: i32, + x49: i32, + x50: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct8 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, + x21: i32, + x22: i32, + x23: i32, + x24: i32, + x25: i32, + x26: i32, + x27: i32, + x28: i32, + x29: i32, + x30: i32, + x31: i32, + x32: i32, + x33: i32, + x34: i32, + x35: i32, + x36: i32, + x37: i32, + x38: i32, + x39: i32, + x40: i32, + x41: i32, + x42: i32, + x43: i32, + x44: i32, + x45: i32, + x46: i32, + x47: i32, + x48: i32, + x49: i32, + x50: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct9 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, + x21: i32, + x22: i32, + x23: i32, + x24: i32, + x25: i32, + x26: i32, + x27: i32, + x28: i32, + x29: i32, + x30: i32, + x31: i32, + x32: i32, + x33: i32, + x34: i32, + x35: i32, + x36: i32, + x37: i32, + x38: i32, + x39: i32, + x40: i32, + x41: i32, + x42: i32, + x43: i32, + x44: i32, + x45: i32, + x46: i32, + x47: i32, + x48: i32, + x49: i32, + x50: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct10 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, + x21: i32, + x22: i32, + x23: i32, + x24: i32, + x25: i32, + x26: i32, + x27: i32, + x28: i32, + x29: i32, + x30: i32, + x31: i32, + x32: i32, + x33: i32, + x34: i32, + x35: i32, + x36: i32, + x37: i32, + x38: i32, + x39: i32, + x40: i32, + x41: i32, + x42: i32, + x43: i32, + x44: i32, + x45: i32, + x46: i32, + x47: i32, + x48: i32, + x49: i32, + x50: i32, +} diff --git a/benchmarks/compilation/src/structs_200_fields_20.rs b/benchmarks/compilation/src/structs_200_fields_20.rs new file mode 100644 index 00000000..a9d7a81d --- /dev/null +++ b/benchmarks/compilation/src/structs_200_fields_20.rs @@ -0,0 +1,5400 @@ +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct1 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct2 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct3 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct4 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct5 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct6 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct7 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct8 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct9 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct10 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct11 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct12 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct13 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct14 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct15 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct16 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct17 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct18 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct19 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct20 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct21 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct22 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct23 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct24 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct25 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct26 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct27 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct28 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct29 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct30 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct31 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct32 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct33 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct34 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct35 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct36 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct37 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct38 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct39 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct40 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct41 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct42 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct43 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct44 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct45 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct46 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct47 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct48 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct49 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct50 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct51 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct52 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct53 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct54 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct55 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct56 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct57 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct58 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct59 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct60 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct61 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct62 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct63 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct64 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct65 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct66 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct67 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct68 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct69 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct70 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct71 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct72 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct73 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct74 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct75 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct76 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct77 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct78 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct79 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct80 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct81 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct82 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct83 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct84 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct85 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct86 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct87 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct88 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct89 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct90 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct91 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct92 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct93 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct94 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct95 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct96 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct97 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct98 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct99 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct100 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct101 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct102 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct103 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct104 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct105 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct106 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct107 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct108 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct109 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct110 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct111 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct112 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct113 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct114 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct115 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct116 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct117 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct118 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct119 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct120 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct121 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct122 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct123 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct124 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct125 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct126 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct127 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct128 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct129 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct130 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct131 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct132 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct133 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct134 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct135 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct136 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct137 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct138 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct139 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct140 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct141 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct142 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct143 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct144 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct145 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct146 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct147 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct148 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct149 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct150 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct151 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct152 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct153 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct154 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct155 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct156 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct157 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct158 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct159 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct160 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct161 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct162 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct163 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct164 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct165 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct166 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct167 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct168 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct169 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct170 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct171 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct172 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct173 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct174 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct175 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct176 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct177 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct178 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct179 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct180 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct181 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct182 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct183 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct184 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct185 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct186 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct187 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct188 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct189 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct190 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct191 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct192 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct193 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct194 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct195 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct196 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct197 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct198 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct199 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} +#[cfg_attr( + any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), + derive(crate::Builder) +)] +#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] +pub struct Struct200 { + x1: i32, + x2: i32, + x3: i32, + x4: i32, + x5: i32, + x6: i32, + x7: i32, + x8: i32, + x9: i32, + x10: i32, + x11: i32, + x12: i32, + x13: i32, + x14: i32, + x15: i32, + x16: i32, + x17: i32, + x18: i32, + x19: i32, + x20: i32, +} From a0b4f4a51a739433a970024ea592268f03ec2940 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 16 Oct 2024 01:34:06 +0000 Subject: [PATCH 104/119] Run benches --- benchmarks/compilation/results.md | 14 +- benchmarks/compilation/run.sh | 5 +- benchmarks/compilation/src/lib.rs | 3 - .../compilation/src/structs_200_fields_20.rs | 5400 ----------------- 4 files changed, 12 insertions(+), 5410 deletions(-) delete mode 100644 benchmarks/compilation/src/structs_200_fields_20.rs diff --git a/benchmarks/compilation/results.md b/benchmarks/compilation/results.md index eb5b096f..c215eff7 100644 --- a/benchmarks/compilation/results.md +++ b/benchmarks/compilation/results.md @@ -1,6 +1,12 @@ | Command | Mean [s] | Min [s] | Max [s] | Relative | |:---|---:|---:|---:|---:| -| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,bon` | 2.316 ± 0.022 | 2.278 | 2.352 | 18.55 ± 0.98 | -| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,typed-builder` | 1.882 ± 0.014 | 1.866 | 1.912 | 15.08 ± 0.79 | -| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,derive_builder` | 1.055 ± 0.019 | 1.023 | 1.094 | 8.45 ± 0.47 | -| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,` | 0.125 ± 0.007 | 0.107 | 0.135 | 1.00 | +| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,bon` | 2.292 ± 0.025 | 2.257 | 2.329 | 22.43 ± 3.37 | +| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,bon-overwritable` | 2.216 ± 0.019 | 2.186 | 2.242 | 21.69 ± 3.26 | +| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,typed-builder` | 1.884 ± 0.022 | 1.850 | 1.911 | 18.43 ± 2.77 | +| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,derive_builder` | 1.063 ± 0.016 | 1.037 | 1.087 | 10.40 ± 1.57 | +| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,` | 0.123 ± 0.015 | 0.097 | 0.146 | 1.21 ± 0.23 | +| `cargo build -p compilation-benchmarks --features=structs_10_fields_50,bon` | 2.051 ± 0.019 | 2.023 | 2.077 | 20.07 ± 3.02 | +| `cargo build -p compilation-benchmarks --features=structs_10_fields_50,bon-overwritable` | 1.999 ± 0.017 | 1.974 | 2.026 | 19.56 ± 2.94 | +| `cargo build -p compilation-benchmarks --features=structs_10_fields_50,typed-builder` | 2.098 ± 0.021 | 2.055 | 2.124 | 20.53 ± 3.09 | +| `cargo build -p compilation-benchmarks --features=structs_10_fields_50,derive_builder` | 0.453 ± 0.006 | 0.440 | 0.461 | 4.43 ± 0.67 | +| `cargo build -p compilation-benchmarks --features=structs_10_fields_50,` | 0.102 ± 0.015 | 0.088 | 0.133 | 1.00 | diff --git a/benchmarks/compilation/run.sh b/benchmarks/compilation/run.sh index ddc0a013..d149cb3c 100755 --- a/benchmarks/compilation/run.sh +++ b/benchmarks/compilation/run.sh @@ -4,15 +4,14 @@ set -euxo pipefail macros=( bon - # bon-overwritable + bon-overwritable typed-builder derive_builder ) suites=( structs_100_fields_10 - # structs_10_fields_50 - # structs_200_fields_20 + structs_10_fields_50 ) hyperfine \ diff --git a/benchmarks/compilation/src/lib.rs b/benchmarks/compilation/src/lib.rs index 629802c0..91b6b015 100644 --- a/benchmarks/compilation/src/lib.rs +++ b/benchmarks/compilation/src/lib.rs @@ -15,6 +15,3 @@ pub mod structs_100_fields_10; #[cfg(feature = "structs_10_fields_50")] pub mod structs_10_fields_50; - -#[cfg(feature = "structs_200_fields_20")] -pub mod structs_200_fields_20; diff --git a/benchmarks/compilation/src/structs_200_fields_20.rs b/benchmarks/compilation/src/structs_200_fields_20.rs deleted file mode 100644 index a9d7a81d..00000000 --- a/benchmarks/compilation/src/structs_200_fields_20.rs +++ /dev/null @@ -1,5400 +0,0 @@ -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct1 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct2 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct3 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct4 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct5 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct6 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct7 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct8 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct9 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct10 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct11 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct12 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct13 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct14 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct15 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct16 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct17 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct18 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct19 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct20 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct21 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct22 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct23 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct24 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct25 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct26 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct27 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct28 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct29 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct30 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct31 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct32 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct33 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct34 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct35 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct36 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct37 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct38 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct39 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct40 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct41 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct42 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct43 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct44 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct45 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct46 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct47 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct48 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct49 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct50 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct51 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct52 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct53 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct54 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct55 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct56 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct57 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct58 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct59 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct60 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct61 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct62 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct63 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct64 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct65 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct66 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct67 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct68 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct69 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct70 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct71 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct72 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct73 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct74 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct75 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct76 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct77 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct78 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct79 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct80 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct81 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct82 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct83 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct84 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct85 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct86 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct87 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct88 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct89 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct90 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct91 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct92 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct93 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct94 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct95 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct96 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct97 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct98 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct99 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct100 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct101 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct102 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct103 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct104 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct105 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct106 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct107 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct108 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct109 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct110 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct111 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct112 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct113 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct114 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct115 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct116 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct117 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct118 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct119 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct120 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct121 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct122 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct123 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct124 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct125 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct126 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct127 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct128 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct129 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct130 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct131 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct132 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct133 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct134 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct135 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct136 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct137 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct138 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct139 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct140 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct141 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct142 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct143 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct144 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct145 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct146 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct147 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct148 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct149 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct150 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct151 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct152 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct153 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct154 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct155 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct156 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct157 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct158 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct159 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct160 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct161 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct162 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct163 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct164 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct165 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct166 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct167 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct168 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct169 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct170 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct171 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct172 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct173 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct174 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct175 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct176 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct177 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct178 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct179 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct180 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct181 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct182 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct183 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct184 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct185 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct186 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct187 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct188 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct189 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct190 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct191 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct192 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct193 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct194 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct195 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct196 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct197 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct198 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct199 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} -#[cfg_attr( - any(feature = "bon", feature = "typed-builder", feature = "derive_builder",), - derive(crate::Builder) -)] -#[cfg_attr(feature = "bon-overwritable", builder(on(_, overwritable)))] -pub struct Struct200 { - x1: i32, - x2: i32, - x3: i32, - x4: i32, - x5: i32, - x6: i32, - x7: i32, - x8: i32, - x9: i32, - x10: i32, - x11: i32, - x12: i32, - x13: i32, - x14: i32, - x15: i32, - x16: i32, - x17: i32, - x18: i32, - x19: i32, - x20: i32, -} From 6f70a829e0d7986d48d3e0251b7752ba2be9a15e Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 16 Oct 2024 01:51:36 +0000 Subject: [PATCH 105/119] Fix dead_code warning --- benchmarks/compilation/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/compilation/src/lib.rs b/benchmarks/compilation/src/lib.rs index 91b6b015..4ecee7c4 100644 --- a/benchmarks/compilation/src/lib.rs +++ b/benchmarks/compilation/src/lib.rs @@ -1,4 +1,4 @@ -#![allow(missing_docs, missing_debug_implementations)] +#![allow(missing_docs, missing_debug_implementations, dead_code)] cfg_if::cfg_if! { if #[cfg(feature = "bon")] { From 596dea7324976cbb069bfcbede677b582c44eef7 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Wed, 16 Oct 2024 23:34:42 +0000 Subject: [PATCH 106/119] t --- .github/workflows/ci.yml | 2 +- benchmarks/compilation/src/lib.rs | 7 +++- .../src/builder/builder_gen/input_fn.rs | 6 +-- .../src/builder/builder_gen/member/mod.rs | 6 ++- .../src/builder/builder_gen/member/named.rs | 6 +-- .../builder_gen/member/params/setters.rs | 2 +- bon-macros/src/error.rs | 2 +- bon-macros/src/parsing/docs.rs | 4 +- bon-macros/src/parsing/item_params.rs | 12 +++--- bon-macros/src/util/attrs.rs | 10 ++--- .../ui/compile_fail/attr_setters.rs | 6 +-- .../ui/compile_fail/attr_setters.stderr | 10 ++--- .../ui/compile_fail/wrong_delimiters.rs | 24 +++++------ .../ui/compile_fail/wrong_delimiters.stderr | 40 +++++++++---------- e2e-tests/src/lib.rs | 6 +-- website/v1/reference/builder.md | 2 +- 16 files changed, 77 insertions(+), 68 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc6ff184..68b7dbce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,7 +145,7 @@ jobs: - run: | cargo miri test --locked --all-features --all-targets \ - --workspace --exclude benchmarks + --workspace --exclude runtime-benchmarks env: RUSTFLAGS: >- --deny warnings diff --git a/benchmarks/compilation/src/lib.rs b/benchmarks/compilation/src/lib.rs index 4ecee7c4..7674dd09 100644 --- a/benchmarks/compilation/src/lib.rs +++ b/benchmarks/compilation/src/lib.rs @@ -1,4 +1,9 @@ -#![allow(missing_docs, missing_debug_implementations, dead_code)] +#![allow( + missing_docs, + missing_debug_implementations, + dead_code, + rustdoc::missing_crate_level_docs +)] cfg_if::cfg_if! { if #[cfg(feature = "bon")] { diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index a08df9a7..da02cac5 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -239,7 +239,7 @@ impl FnInputCtx<'_> { // // Also remove any `#[builder]` attributes that were meant for this proc macro. orig.attrs - .retain(|attr| !attr.is_doc() && !attr.path().is_ident("builder")); + .retain(|attr| !attr.is_doc_expr() && !attr.path().is_ident("builder")); let prefix = self .self_ty_prefix() @@ -419,7 +419,7 @@ impl FnInputCtx<'_> { .norm .attrs .into_iter() - .filter(<_>::is_doc) + .filter(<_>::is_doc_expr) .collect(), // Override on the start fn to use the the generics from the @@ -519,7 +519,7 @@ impl FinishFnBody for FnCallBody { fn strip_known_attrs_from_args(sig: &mut syn::Signature) { for arg in &mut sig.inputs { arg.attrs_mut() - .retain(|attr| !attr.is_doc() && !attr.path().is_ident("builder")); + .retain(|attr| !attr.is_doc_expr() && !attr.path().is_ident("builder")); } } diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index 38bb0712..f9a4353c 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -178,7 +178,11 @@ impl Member { // 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()).cloned().collect(); + let docs = attrs + .iter() + .filter(|attr| attr.is_doc_expr()) + .cloned() + .collect(); let mut member = NamedMember { index: named_count.into(), diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index d441a73a..fe869a4e 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -170,17 +170,17 @@ impl NamedMember { None => return Ok(()), }; - let overrides = overrides + let overrides_values = overrides .iter() .copied() .map(|over| get_val(&over.value).as_ref()); - if !overrides.clone().all(|over| over.is_some()) { + if !overrides_values.clone().all(|over| over.is_some()) { return Ok(()); } let setters = overrides - .flatten() + .iter() .map(|over| format!("`{}`", over.key)) .join(", "); diff --git a/bon-macros/src/builder/builder_gen/member/params/setters.rs b/bon-macros/src/builder/builder_gen/member/params/setters.rs index 3ed770d2..ea9a51b2 100644 --- a/bon-macros/src/builder/builder_gen/member/params/setters.rs +++ b/bon-macros/src/builder/builder_gen/member/params/setters.rs @@ -23,7 +23,7 @@ pub(crate) struct SettersParams { pub(crate) name: Option>, pub(crate) vis: Option>, - #[darling(default, with = parse_docs, map = Some)] + #[darling(rename = "doc", default, with = parse_docs, map = Some)] pub(crate) docs: Option>>, #[darling(flatten)] diff --git a/bon-macros/src/error.rs b/bon-macros/src/error.rs index bb3fd49c..fd29c90f 100644 --- a/bon-macros/src/error.rs +++ b/bon-macros/src/error.rs @@ -80,7 +80,7 @@ impl Parse for Fallback { input .call(syn::Attribute::parse_outer)? .into_iter() - .filter(|attr| !attr.is_doc() && !attr.path().is_ident("builder")) + .filter(|attr| !attr.is_doc_expr() && !attr.path().is_ident("builder")) .for_each(|attr| attr.to_tokens(&mut output)); } } diff --git a/bon-macros/src/parsing/docs.rs b/bon-macros/src/parsing/docs.rs index 0b3ac9f0..93f6a029 100644 --- a/bon-macros/src/parsing/docs.rs +++ b/bon-macros/src/parsing/docs.rs @@ -18,7 +18,7 @@ pub(crate) fn parse_docs(meta: &syn::Meta) -> Result Result { for attr in attrs { - let doc = match attr.as_doc() { + let doc = match attr.as_doc_expr() { Some(doc) => doc, _ => continue, }; diff --git a/bon-macros/src/parsing/item_params.rs b/bon-macros/src/parsing/item_params.rs index 37c8094c..b0b7f9c6 100644 --- a/bon-macros/src/parsing/item_params.rs +++ b/bon-macros/src/parsing/item_params.rs @@ -57,7 +57,7 @@ impl ItemParamsParsing<'_> { struct Full { name: Option>, vis: Option>, - docs: Option>, + doc: Option>, } let full = crate::parsing::parse_non_empty_paren_meta_list(meta)?; @@ -67,7 +67,7 @@ impl ItemParamsParsing<'_> { Full { name: None, vis: None, - docs: None, + doc: None, } ); @@ -75,15 +75,15 @@ impl ItemParamsParsing<'_> { bail!(meta, "expected at least one parameter in parentheses"); } - let docs = full - .docs - .map(|docs| super::parse_docs(&docs.value)) + let doc = full + .doc + .map(|doc| super::parse_docs(&doc.value)) .transpose()?; let params = ItemParams { name: full.name, vis: full.vis, - docs, + docs: doc, }; Ok(params) diff --git a/bon-macros/src/util/attrs.rs b/bon-macros/src/util/attrs.rs index a68573d8..92503c4c 100644 --- a/bon-macros/src/util/attrs.rs +++ b/bon-macros/src/util/attrs.rs @@ -1,15 +1,15 @@ pub(crate) trait AttributeExt { - fn is_doc(&self) -> bool; - fn as_doc(&self) -> Option<&syn::Expr>; + 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(&self) -> bool { - self.as_doc().is_some() + fn is_doc_expr(&self) -> bool { + self.as_doc_expr().is_some() } - fn as_doc(&self) -> Option<&syn::Expr> { + fn as_doc_expr(&self) -> Option<&syn::Expr> { let attr = match &self.meta { syn::Meta::NameValue(attr) => attr, _ => return None, diff --git a/bon/tests/integration/ui/compile_fail/attr_setters.rs b/bon/tests/integration/ui/compile_fail/attr_setters.rs index da7d971d..697f61bc 100644 --- a/bon/tests/integration/ui/compile_fail/attr_setters.rs +++ b/bon/tests/integration/ui/compile_fail/attr_setters.rs @@ -29,13 +29,13 @@ struct UnusedVisConfig { #[derive(Builder)] struct UnusedDocsConfig { #[builder(setters( - docs { + doc { /// Unused }, - some_fn(docs { + some_fn(doc { /// some_fn docs }), - option_fn(docs { + option_fn(doc { /// option_fn docs }), ))] diff --git a/bon/tests/integration/ui/compile_fail/attr_setters.stderr b/bon/tests/integration/ui/compile_fail/attr_setters.stderr index c9583e37..593a528c 100644 --- a/bon/tests/integration/ui/compile_fail/attr_setters.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_setters.stderr @@ -4,23 +4,23 @@ error: this `name` configuration is unused because all of the `some_fn`, `option 6 | name = littlepip, | ^^^^ -error: this `name` configuration is unused because all of the `name`, `name` setters contain a `name` override +error: this `name` configuration is unused because all of the `some_fn`, `option_fn` setters contain a `name` override --> tests/integration/ui/compile_fail/attr_setters.rs:16:9 | 16 | name = littlepip, | ^^^^ -error: this `vis` configuration is unused because all of the `vis`, `vis` setters contain a `vis` override +error: this `vis` configuration is unused because all of the `some_fn`, `option_fn` setters contain a `vis` override --> tests/integration/ui/compile_fail/attr_setters.rs:25:23 | 25 | #[builder(setters(vis = "pub(crate)", some_fn(vis = ""), option_fn(vis = ""),))] | ^^^ -error: this `docs` configuration is unused because all of the `docs`, `docs` setters contain a `docs` override +error: this `doc` configuration is unused because all of the `some_fn`, `option_fn` setters contain a `doc` override --> tests/integration/ui/compile_fail/attr_setters.rs:32:9 | -32 | docs { - | ^^^^ +32 | doc { + | ^^^ error: `some_fn` setter function applies only to members with `#[builder(default)]` or members of `Option` type (if #[builder(transparent)] is not set) --> tests/integration/ui/compile_fail/attr_setters.rs:47:23 diff --git a/bon/tests/integration/ui/compile_fail/wrong_delimiters.rs b/bon/tests/integration/ui/compile_fail/wrong_delimiters.rs index 8509bcad..1ba55d70 100644 --- a/bon/tests/integration/ui/compile_fail/wrong_delimiters.rs +++ b/bon/tests/integration/ui/compile_fail/wrong_delimiters.rs @@ -15,10 +15,10 @@ struct CurlyBracesInField { #[derive(bon::Builder)] #[builder( - builder_type[docs[]], - state_mod[docs[]], - start_fn[docs[]], - finish_fn[docs[]], + builder_type[doc[]], + state_mod[doc[]], + start_fn[doc[]], + finish_fn[doc[]], )] struct SquareBrackets { #[builder(setters[])] @@ -26,7 +26,7 @@ struct SquareBrackets { } #[derive(bon::Builder)] -struct SquareBracketsInField { +struct SquareBracketsInFieldSetters { #[builder(setters[])] x: u32, } @@ -38,20 +38,20 @@ struct SquareBracketsInField { start_fn(docs[]), finish_fn(docs[]), )] -struct SquareBracketsDocs { - #[builder(setters(docs[]))] +struct SquareBracketsInFieldDoc { + #[builder(setters(doc[]))] x: u32, } #[derive(bon::Builder)] #[builder( - builder_type(docs()), - state_mod(docs()), - start_fn(docs()), - finish_fn(docs()) + builder_type(doc()), + state_mod(doc()), + start_fn(doc()), + finish_fn(doc()) )] struct Parentheses { - #[builder(setters(docs()))] + #[builder(setters(doc()))] x: u32, } diff --git a/bon/tests/integration/ui/compile_fail/wrong_delimiters.stderr b/bon/tests/integration/ui/compile_fail/wrong_delimiters.stderr index 7db423ae..ddf1778b 100644 --- a/bon/tests/integration/ui/compile_fail/wrong_delimiters.stderr +++ b/bon/tests/integration/ui/compile_fail/wrong_delimiters.stderr @@ -31,25 +31,25 @@ error: wrong delimiter, expected parentheses e.g. `setters(...)`, but got curly error: wrong delimiter, expected parentheses e.g. `start_fn(...)`, but got square brackets: `start_fn[...]` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:20:5 | -20 | start_fn[docs[]], +20 | start_fn[doc[]], | ^^^^^^^^ error: wrong delimiter, expected parentheses e.g. `builder_type(...)`, but got square brackets: `builder_type[...]` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:18:5 | -18 | builder_type[docs[]], +18 | builder_type[doc[]], | ^^^^^^^^^^^^ error: wrong delimiter, expected parentheses e.g. `state_mod(...)`, but got square brackets: `state_mod[...]` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:19:5 | -19 | state_mod[docs[]], +19 | state_mod[doc[]], | ^^^^^^^^^ error: wrong delimiter, expected parentheses e.g. `finish_fn(...)`, but got square brackets: `finish_fn[...]` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:21:5 | -21 | finish_fn[docs[]], +21 | finish_fn[doc[]], | ^^^^^^^^^ error: wrong delimiter, expected parentheses e.g. `setters(...)`, but got square brackets: `setters[...]` @@ -58,50 +58,50 @@ error: wrong delimiter, expected parentheses e.g. `setters(...)`, but got square 30 | #[builder(setters[])] | ^^^^^^^ -error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got square brackets: `docs[...]` +error: Unknown field: `docs`. Did you mean `doc`? --> tests/integration/ui/compile_fail/wrong_delimiters.rs:38:14 | 38 | start_fn(docs[]), | ^^^^ -error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got square brackets: `docs[...]` +error: Unknown field: `docs`. Did you mean `doc`? --> tests/integration/ui/compile_fail/wrong_delimiters.rs:36:18 | 36 | builder_type(docs[]), | ^^^^ -error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got square brackets: `docs[...]` +error: Unknown field: `docs`. Did you mean `doc`? --> tests/integration/ui/compile_fail/wrong_delimiters.rs:37:15 | 37 | state_mod(docs[]), | ^^^^ -error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got square brackets: `docs[...]` +error: Unknown field: `docs`. Did you mean `doc`? --> tests/integration/ui/compile_fail/wrong_delimiters.rs:39:15 | 39 | finish_fn(docs[]), | ^^^^ -error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got parentheses: `docs(...)` +error: wrong delimiter, expected curly braces e.g. `doc{...}`, but got parentheses: `doc(...)` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:50:14 | -50 | start_fn(docs()), - | ^^^^ +50 | start_fn(doc()), + | ^^^ -error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got parentheses: `docs(...)` +error: wrong delimiter, expected curly braces e.g. `doc{...}`, but got parentheses: `doc(...)` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:48:18 | -48 | builder_type(docs()), - | ^^^^ +48 | builder_type(doc()), + | ^^^ -error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got parentheses: `docs(...)` +error: wrong delimiter, expected curly braces e.g. `doc{...}`, but got parentheses: `doc(...)` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:49:15 | -49 | state_mod(docs()), - | ^^^^ +49 | state_mod(doc()), + | ^^^ -error: wrong delimiter, expected curly braces e.g. `docs{...}`, but got parentheses: `docs(...)` +error: wrong delimiter, expected curly braces e.g. `doc{...}`, but got parentheses: `doc(...)` --> tests/integration/ui/compile_fail/wrong_delimiters.rs:51:15 | -51 | finish_fn(docs()) - | ^^^^ +51 | finish_fn(doc()) + | ^^^ diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index 73233a2e..d77ef90c 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -33,21 +33,21 @@ pub struct PrivateBuilder { #[derive(Builder)] #[builder( builder_type( - docs { + doc { /// Docs on [`GreeterOverriddenBuilder`] /// the builder type }, name = GreeterOverriddenBuilder, ), start_fn( - docs { + doc { /// Docs on /// [`Self::start_fn_override`] }, name = start_fn_override, ), finish_fn( - docs { + doc { /// Docs on /// [`GreeterOverriddenBuilder::finish_fn_override()`] }, diff --git a/website/v1/reference/builder.md b/website/v1/reference/builder.md index f5b41c2e..28c1ed76 100644 --- a/website/v1/reference/builder.md +++ b/website/v1/reference/builder.md @@ -546,7 +546,7 @@ struct Example { **Applies to:** -Overrdies the name for the setters generated for the member. This is most useful when `#[builder]` is placed on a struct where you'd like to use a different name for the field internally. For functions this attribute makes less sense since it's easy to just create a variable named differently `let new_name = param_name;`. However, this attribute is still supported for functions. +Overrides the name of the member in the builder's setters and type state. This is most useful when `#[derive(Builder)]` is placed on a struct where you'd like to use a different name for the field internally. For functions this attribute makes less sense since it's easy to just create a variable named differently `let new_name = param_name;`. However, this attribute is still supported for functions. **Example:** From 2ceeb6b6e8427b3fb347512e9c9557d4c8fbd9fb Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 17 Oct 2024 00:22:06 +0000 Subject: [PATCH 107/119] Fix tests --- bon-macros/src/tests/attr_setters.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/bon-macros/src/tests/attr_setters.rs b/bon-macros/src/tests/attr_setters.rs index e8e5299d..e72769db 100644 --- a/bon-macros/src/tests/attr_setters.rs +++ b/bon-macros/src/tests/attr_setters.rs @@ -8,7 +8,7 @@ fn setters_docs_and_vis() { /// Docs on the required field itself #[builder(setters( vis = "pub(in overridden)", - docs { + doc { /// Docs on the required field setters. /// Multiline. } @@ -18,7 +18,7 @@ fn setters_docs_and_vis() { /// Docs on the optional field itself #[builder(setters( vis = "pub(in overridden)", - docs { + doc { /// Docs on the optional field setters. /// Multiline. } @@ -29,7 +29,7 @@ fn setters_docs_and_vis() { #[builder( setters( vis = "pub(in overridden)", - docs { + doc { /// Docs on the default field setters. /// Multiline. } @@ -43,14 +43,14 @@ fn setters_docs_and_vis() { setters( some_fn( vis = "pub(in some_fn_overridden)", - docs { + doc { /// Docs on some_fn /// Multiline. } ), option_fn( vis = "pub(in option_fn_overridden)", - docs { + doc { /// Docs on option_fn /// Multiline. } @@ -63,14 +63,14 @@ fn setters_docs_and_vis() { setters( some_fn( vis = "pub(in some_fn_overridden)", - docs { + doc { /// Docs on some_fn /// Multiline. } ), option_fn( vis = "pub(in option_fn_overridden)", - docs { + doc { /// Docs on option_fn /// Multiline. } @@ -81,14 +81,14 @@ fn setters_docs_and_vis() { default_field_with_specific_overrides: u32, #[builder(setters( - docs { + doc { /// Common docs /// Multiline. }, vis = "pub(in overridden)", option_fn( vis = "pub(in option_fn_overridden)", - docs { + doc { /// Docs on option_fn /// Multiline. } @@ -99,14 +99,14 @@ fn setters_docs_and_vis() { #[builder( setters( - docs { + doc { /// Common docs /// Multiline. }, vis = "pub(in overridden)", option_fn( vis = "pub(in option_fn_overridden)", - docs { + doc { /// Docs on option_fn /// Multiline. } From 90fb884cb0a089b5ec1f0ea3551686ec5974d4e0 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 17 Oct 2024 00:59:25 +0000 Subject: [PATCH 108/119] Fix CI --- .github/workflows/ci.yml | 13 +++++++++++-- benchmarks/compilation/Cargo.toml | 4 ++-- benchmarks/compilation/codegen/Cargo.toml | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68b7dbce..e0f87940 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ env: jobs: # Sanity-check that benchmarks work - benchmarks: + runtime-benchmarks: runs-on: ubuntu-latest strategy: matrix: @@ -41,7 +41,16 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@v1 - run: sudo apt-get install -y valgrind - - run: ./benchmarks/run.sh ${{ matrix.benchmark }} + - run: cd ./benchmarks/runtime && ./run.sh ${{ matrix.benchmark }} + + compilation-benchmarks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + - run: ./scripts/install/hyperfine.sh + + - run: cd ./benchmarks/compilation && ./run.sh cargo-lock: runs-on: ubuntu-latest diff --git a/benchmarks/compilation/Cargo.toml b/benchmarks/compilation/Cargo.toml index 0614490b..065142a0 100644 --- a/benchmarks/compilation/Cargo.toml +++ b/benchmarks/compilation/Cargo.toml @@ -22,9 +22,9 @@ typed-builder = { version = "0.20", optional = true } workspace = true [features] -default = [] bon-overwritable = ["bon"] +default = [] structs_100_fields_10 = [] -structs_10_fields_50 = [] +structs_10_fields_50 = [] structs_200_fields_20 = [] diff --git a/benchmarks/compilation/codegen/Cargo.toml b/benchmarks/compilation/codegen/Cargo.toml index efeba9f3..7d305a8a 100644 --- a/benchmarks/compilation/codegen/Cargo.toml +++ b/benchmarks/compilation/codegen/Cargo.toml @@ -4,7 +4,7 @@ name = "compilation-benchmarks-codegen" publish = false description = """ - Generates code for the compliation benchmarks. + Generates code for the compilation benchmarks. """ edition = "2021" From 6acf3f845b10340d8ae21f5019149a5b50cea80c Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 17 Oct 2024 21:47:21 +0000 Subject: [PATCH 109/119] Self-review part1 --- benchmarks/compilation/Cargo.toml | 2 - benchmarks/compilation/codegen/src/main.rs | 4 +- benchmarks/compilation/results.md | 20 +++++----- benchmarks/compilation/run.sh | 1 + bon-macros/Cargo.toml | 38 +------------------ bon-macros/src/bon.rs | 2 +- .../builder/builder_gen/builder_derives.rs | 14 +++---- .../src/builder/builder_gen/finish_fn.rs | 4 +- .../src/builder/builder_gen/input_fn.rs | 2 +- bon-macros/src/builder/builder_gen/mod.rs | 16 ++++---- bon-macros/src/builder/builder_gen/models.rs | 4 +- .../src/builder/builder_gen/setters/mod.rs | 2 +- bon-macros/src/lib.rs | 5 ++- bon/Cargo.toml | 9 +++-- bon/tests/integration/main.rs | 1 + .../integration/ui/compile_fail/errors.stderr | 2 +- rust-toolchain | 2 +- 17 files changed, 48 insertions(+), 80 deletions(-) diff --git a/benchmarks/compilation/Cargo.toml b/benchmarks/compilation/Cargo.toml index 065142a0..63579d58 100644 --- a/benchmarks/compilation/Cargo.toml +++ b/benchmarks/compilation/Cargo.toml @@ -12,7 +12,6 @@ edition = "2021" version = "0.1.0" [dependencies] -# bon = { version = "2.3", optional = true } bon = { path = "../../bon", optional = true } cfg-if = "1.0" derive_builder = { version = "0.20", optional = true } @@ -27,4 +26,3 @@ default = [] structs_100_fields_10 = [] structs_10_fields_50 = [] -structs_200_fields_20 = [] diff --git a/benchmarks/compilation/codegen/src/main.rs b/benchmarks/compilation/codegen/src/main.rs index 0772a562..084b4411 100644 --- a/benchmarks/compilation/codegen/src/main.rs +++ b/benchmarks/compilation/codegen/src/main.rs @@ -10,8 +10,8 @@ fn main() -> anyhow::Result<()> { let src_dir = bench_dir.join("src"); - let structs_number = 200; - let fields_number = 20; + let structs_number = 100; + let fields_number = 10; std::fs::write( src_dir.join(format!( diff --git a/benchmarks/compilation/results.md b/benchmarks/compilation/results.md index c215eff7..a9a5ad25 100644 --- a/benchmarks/compilation/results.md +++ b/benchmarks/compilation/results.md @@ -1,12 +1,12 @@ | Command | Mean [s] | Min [s] | Max [s] | Relative | |:---|---:|---:|---:|---:| -| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,bon` | 2.292 ± 0.025 | 2.257 | 2.329 | 22.43 ± 3.37 | -| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,bon-overwritable` | 2.216 ± 0.019 | 2.186 | 2.242 | 21.69 ± 3.26 | -| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,typed-builder` | 1.884 ± 0.022 | 1.850 | 1.911 | 18.43 ± 2.77 | -| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,derive_builder` | 1.063 ± 0.016 | 1.037 | 1.087 | 10.40 ± 1.57 | -| `cargo build -p compilation-benchmarks --features=structs_100_fields_10,` | 0.123 ± 0.015 | 0.097 | 0.146 | 1.21 ± 0.23 | -| `cargo build -p compilation-benchmarks --features=structs_10_fields_50,bon` | 2.051 ± 0.019 | 2.023 | 2.077 | 20.07 ± 3.02 | -| `cargo build -p compilation-benchmarks --features=structs_10_fields_50,bon-overwritable` | 1.999 ± 0.017 | 1.974 | 2.026 | 19.56 ± 2.94 | -| `cargo build -p compilation-benchmarks --features=structs_10_fields_50,typed-builder` | 2.098 ± 0.021 | 2.055 | 2.124 | 20.53 ± 3.09 | -| `cargo build -p compilation-benchmarks --features=structs_10_fields_50,derive_builder` | 0.453 ± 0.006 | 0.440 | 0.461 | 4.43 ± 0.67 | -| `cargo build -p compilation-benchmarks --features=structs_10_fields_50,` | 0.102 ± 0.015 | 0.088 | 0.133 | 1.00 | +| `structs_100_fields_10 bon` | 2.319 ± 0.021 | 2.278 | 2.350 | 22.80 ± 2.99 | +| `structs_100_fields_10 bon-overwritable` | 2.240 ± 0.021 | 2.203 | 2.274 | 22.03 ± 2.89 | +| `structs_100_fields_10 typed-builder` | 1.849 ± 0.011 | 1.838 | 1.878 | 18.18 ± 2.38 | +| `structs_100_fields_10 derive_builder` | 1.022 ± 0.016 | 0.995 | 1.043 | 10.05 ± 1.32 | +| `structs_100_fields_10 ` | 0.104 ± 0.012 | 0.088 | 0.124 | 1.02 ± 0.18 | +| `structs_10_fields_50 bon` | 2.063 ± 0.017 | 2.044 | 2.100 | 20.28 ± 2.66 | +| `structs_10_fields_50 bon-overwritable` | 2.029 ± 0.029 | 1.998 | 2.102 | 19.95 ± 2.62 | +| `structs_10_fields_50 typed-builder` | 2.076 ± 0.016 | 2.048 | 2.101 | 20.41 ± 2.67 | +| `structs_10_fields_50 derive_builder` | 0.432 ± 0.016 | 0.400 | 0.458 | 4.25 ± 0.58 | +| `structs_10_fields_50 ` | 0.102 ± 0.013 | 0.084 | 0.130 | 1.00 | diff --git a/benchmarks/compilation/run.sh b/benchmarks/compilation/run.sh index d149cb3c..74d5dc8d 100755 --- a/benchmarks/compilation/run.sh +++ b/benchmarks/compilation/run.sh @@ -21,4 +21,5 @@ hyperfine \ --export-markdown results.md \ --parameter-list macro "$(IFS=, ; echo "${macros[*]}")," \ --parameter-list suite "$(IFS=, ; echo "${suites[*]}")" \ + --command-name '{suite} {macro}' \ 'cargo build -p compilation-benchmarks --features={suite},{macro}' diff --git a/bon-macros/Cargo.toml b/bon-macros/Cargo.toml index ee7d2b96..3fc6649d 100644 --- a/bon-macros/Cargo.toml +++ b/bon-macros/Cargo.toml @@ -59,43 +59,7 @@ prettyplease = "0.2" [features] default = [] -# Opts in to the higher MSRV 1.79.0. In this version, Rust stabilized the syntax -# for bounds in associated type position. See the release announcement for more: -# https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.htmlbounds-in-associated-type-position -# -# This feature is useful for the trait `IsComplete` generated by the builder macros. -# When this feature is enabled, the builder macros use the new syntax for bounds in -# associated type position, which enables implied `IsSet` bounds for the type state -# of required members. -# -# To understand why this is useful consider the following example: -# -# ```rust -# #[derive(bon::Builder)] -# struct Example { -# a: u32, -# b: Option, -# } -# -# use example_builder::{IsUnset, IsComplete}; -# -# impl ExampleBuilder { -# fn build_with_default_b(self) -> Example -# where -# State: IsComplete, -# State::B: IsUnset, -# { -# self.b(42).build() -# } -# } -# ``` -# -# This code wouldn't compile without this feature enabled, because `State: IsComplete` -# wouldn't automatically imply `State::A: IsSet`, so the builder type state returned -# after the `self.b()` doesn't imply that the member `a` is set, and thus `build()` -# can't be called. -# -# Huge thanks to @harudagondi for suggesting the name of this feature! +# See the docs on this feature in the `bon`'s crate `Cargo.toml` implied-bounds = [] [dev-dependencies] diff --git a/bon-macros/src/bon.rs b/bon-macros/src/bon.rs index 855d2a4e..a8ebe89b 100644 --- a/bon-macros/src/bon.rs +++ b/bon-macros/src/bon.rs @@ -26,7 +26,7 @@ pub(crate) fn try_generate(params: TokenStream, item: TokenStream) -> Result TokenStream { - let receiver_field = &self.idents_pool.receiver; - let start_fn_args_field = &self.idents_pool.start_fn_args; - let named_members_field = &self.idents_pool.named_members; + let receiver_field = &self.ident_pool.receiver; + let start_fn_args_field = &self.ident_pool.start_fn_args; + let named_members_field = &self.ident_pool.named_members; let format_members = self.members.iter().filter_map(|member| { match member { diff --git a/bon-macros/src/builder/builder_gen/finish_fn.rs b/bon-macros/src/builder/builder_gen/finish_fn.rs index 77e90cc6..fe06bcc0 100644 --- a/bon-macros/src/builder/builder_gen/finish_fn.rs +++ b/bon-macros/src/builder/builder_gen/finish_fn.rs @@ -3,8 +3,8 @@ use crate::util::prelude::*; impl super::BuilderGenCtx { fn finish_fn_member_expr(&self, member: &Member) -> TokenStream { - let start_fn_args_field = &self.idents_pool.start_fn_args; - let named_members_field = &self.idents_pool.named_members; + let start_fn_args_field = &self.ident_pool.start_fn_args; + let named_members_field = &self.ident_pool.named_members; let member = match member { Member::Named(member) => member, diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index da02cac5..5565415e 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -486,7 +486,7 @@ impl FinishFnBody for FnCallBody { .sig .receiver() .map(|_| { - let receiver_field = &ctx.idents_pool.receiver; + let receiver_field = &ctx.ident_pool.receiver; quote!(self.#receiver_field.) }) .or_else(|| { diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 1263bfc4..f3ee3547 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -183,10 +183,10 @@ impl BuilderGenCtx { let where_clause = &generics.where_clause; let generic_args = &self.generics.args; - let phantom_field = &self.idents_pool.phantom; - let receiver_field = &self.idents_pool.receiver; - let start_fn_args_field = &self.idents_pool.start_fn_args; - let named_members_field = &self.idents_pool.named_members; + let phantom_field = &self.ident_pool.phantom; + let receiver_field = &self.ident_pool.receiver; + let start_fn_args_field = &self.ident_pool.start_fn_args; + let named_members_field = &self.ident_pool.named_members; let receiver = self.receiver(); @@ -336,10 +336,10 @@ impl BuilderGenCtx { let where_clause = &self.generics.where_clause; let phantom_data = self.phantom_data(); let state_mod = &self.state_mod.ident; - let phantom_field = &self.idents_pool.phantom; - let receiver_field = &self.idents_pool.receiver; - let start_fn_args_field = &self.idents_pool.start_fn_args; - let named_members_field = &self.idents_pool.named_members; + let phantom_field = &self.ident_pool.phantom; + let receiver_field = &self.ident_pool.receiver; + let start_fn_args_field = &self.ident_pool.start_fn_args; + let named_members_field = &self.ident_pool.named_members; let private_field_attrs = quote! { // The fields can't be hidden using Rust's privacy syntax. diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index a17a8542..6590a50a 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -123,7 +123,7 @@ pub(super) struct Generics { pub(crate) struct BuilderGenCtx { /// Private identifiers that are used in the builder implementation. /// They are intentionally randomized to prevent users from accessing them. - pub(super) idents_pool: PrivateIdentsPool, + pub(super) ident_pool: PrivateIdentsPool, /// Name of the generic variable that holds the builder's state. pub(super) state_var: syn::Ident, @@ -293,7 +293,7 @@ impl BuilderGenCtx { Ok(Self { state_var, - idents_pool: PrivateIdentsPool::new(), + ident_pool: PrivateIdentsPool::new(), members, allow_attrs, on_params, diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index a2ce9f4a..1c4373f1 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -221,7 +221,7 @@ impl<'a> SettersCtx<'a> { SetterBody::SetMember { value } => { let index = &self.member.index; - let fields = &self.base.idents_pool; + let fields = &self.base.ident_pool; let phantom_field = &fields.phantom; let receiver_field = &fields.receiver; let start_fn_args_field = &fields.start_fn_args; diff --git a/bon-macros/src/lib.rs b/bon-macros/src/lib.rs index 4d330e0e..fa92e4ab 100644 --- a/bon-macros/src/lib.rs +++ b/bon-macros/src/lib.rs @@ -9,7 +9,10 @@ clippy::option_if_let_else, clippy::enum_glob_use, clippy::too_many_lines, - clippy::if_not_else + clippy::if_not_else, + + // We can't use the explicit captures syntax due to the MSRV + impl_trait_overcaptures, )] mod bon; diff --git a/bon/Cargo.toml b/bon/Cargo.toml index 6eb6da37..d3c73d9a 100644 --- a/bon/Cargo.toml +++ b/bon/Cargo.toml @@ -64,7 +64,8 @@ default = ["std"] std = ["alloc"] # Opts in to the higher MSRV 1.79.0. In this version, Rust stabilized the syntax -# for bounds in associated type position. See the release announcement for more: +# for bounds in associated type position which can be used to make bounds on generic +# associated types implied. See the release announcement for more: # https://blog.rust-lang.org/2024/06/13/Rust-1.79.0.htmlbounds-in-associated-type-position # # This feature is useful for the trait `IsComplete` generated by the builder macros. @@ -72,7 +73,7 @@ std = ["alloc"] # associated type position, which enables implied `IsSet` bounds for the type state # of required members. # -# To understand why this is useful consider the following example: +# To understand how this can be used consider the following example: # # ```rust # #[derive(bon::Builder)] @@ -96,8 +97,8 @@ std = ["alloc"] # # This code wouldn't compile without this feature enabled, because `State: IsComplete` # wouldn't automatically imply `State::A: IsSet`, so the builder type state returned -# after the `self.b()` doesn't imply that the member `a` is set, and thus `build()` +# after `self.b()` doesn't imply that the member `a` is set, and thus `build()` # can't be called. # -# Huge thanks to @harudagondi for suggesting the name of this feature! +# Huge thanks to @harudagondi for suggesting the name of this cargo feature! implied-bounds = ["bon-macros/implied-bounds"] diff --git a/bon/tests/integration/main.rs b/bon/tests/integration/main.rs index 0a2a323d..a58408f0 100644 --- a/bon/tests/integration/main.rs +++ b/bon/tests/integration/main.rs @@ -8,6 +8,7 @@ clippy::needless_raw_string_hashes, non_local_definitions, missing_docs, + impl_trait_overcaptures, )] #[cfg(feature = "alloc")] diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr index 16d704a4..117afbf9 100644 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ b/bon/tests/integration/ui/compile_fail/errors.stderr @@ -156,7 +156,7 @@ error: expected a closure e.g. `with = |param: T| expression` 133 | #[builder(with = 42)] | ^^^^ -error: `#[bon]` attribute does not accept any parameters yet, but it will in the future releases +error: `#[bon]` attribute does not accept any parameters yet, but it will in future releases --> tests/integration/ui/compile_fail/errors.rs:139:7 | 139 | #[bon(attrs)] diff --git a/rust-toolchain b/rust-toolchain index dbd41264..71fae54f 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.81.0 +1.82.0 From 9175a5ca8f337242fa24bb48839d800b42e79003 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 17 Oct 2024 22:12:23 +0000 Subject: [PATCH 110/119] Refactor items params parsing --- .../builder/builder_gen/builder_params/mod.rs | 17 +++++++++++ bon-macros/src/parsing/item_params.rs | 28 ++++++++----------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/builder_params/mod.rs b/bon-macros/src/builder/builder_gen/builder_params/mod.rs index 29288c5f..3d65de5f 100644 --- a/bon-macros/src/builder/builder_gen/builder_params/mod.rs +++ b/bon-macros/src/builder/builder_gen/builder_params/mod.rs @@ -57,3 +57,20 @@ pub(crate) struct BuilderDerives { #[darling(rename = "Debug")] pub(crate) debug: darling::util::Flag, } + +// #[derive(Debug, Clone, Default)] +// pub(crate) struct BuilderDerive { +// pub(crate) bounds: Vec, +// } + +// impl FromMeta for BuilderDerive { +// fn from_meta(meta: &syn::Meta) -> Result { + +// struct Parsed { + +// bounds: Vec, +// } + + +// } +// } diff --git a/bon-macros/src/parsing/item_params.rs b/bon-macros/src/parsing/item_params.rs index b0b7f9c6..13be6ec0 100644 --- a/bon-macros/src/parsing/item_params.rs +++ b/bon-macros/src/parsing/item_params.rs @@ -30,18 +30,8 @@ pub(crate) struct ItemParamsParsing<'a> { impl ItemParamsParsing<'_> { pub(crate) fn parse(self) -> Result { - let params = Self::params_from_meta(self.meta)?; + let meta = self.meta; - if let Some(context) = self.reject_self_mentions { - if let Some(docs) = ¶ms.docs { - crate::parsing::reject_self_mentions_in_docs(context, docs)?; - } - } - - Ok(params) - } - - fn params_from_meta(meta: &syn::Meta) -> Result { if let syn::Meta::NameValue(meta) = meta { let val = &meta.value; let name = syn::parse2(val.to_token_stream())?; @@ -57,7 +47,9 @@ impl ItemParamsParsing<'_> { struct Full { name: Option>, vis: Option>, - doc: Option>, + + #[darling(default, with = super::parse_docs, map = Some)] + doc: Option>>, } let full = crate::parsing::parse_non_empty_paren_meta_list(meta)?; @@ -75,15 +67,17 @@ impl ItemParamsParsing<'_> { bail!(meta, "expected at least one parameter in parentheses"); } - let doc = full - .doc - .map(|doc| super::parse_docs(&doc.value)) - .transpose()?; + if let Some(context) = self.reject_self_mentions { + if let Some(docs) = &full.doc { + crate::parsing::reject_self_mentions_in_docs(context, docs)?; + } + } + let params = ItemParams { name: full.name, vis: full.vis, - docs: doc, + docs: full.doc, }; Ok(params) From 3e5d9cd7d92cbe2bba5395b4d0b11a33881e46a3 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Thu, 17 Oct 2024 22:53:28 +0000 Subject: [PATCH 111/119] Add bounds config for builder derives --- .../builder/builder_gen/builder_derives.rs | 52 +++++++----- .../builder/builder_gen/builder_params/mod.rs | 37 ++++++--- bon-macros/src/parsing/mod.rs | 41 ++++++++++ .../{builder_derives.rs => attr_derive.rs} | 80 +++++++++++++++++++ bon/tests/integration/builder/mod.rs | 2 +- .../ui/compile_fail/attr_derive.rs | 11 ++- .../ui/compile_fail/attr_derive.stderr | 52 +++++++----- 7 files changed, 220 insertions(+), 55 deletions(-) rename bon/tests/integration/builder/{builder_derives.rs => attr_derive.rs} (77%) diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index 3feeb2f3..3be99809 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -1,4 +1,4 @@ -use super::builder_params::BuilderDerives; +use super::builder_params::{BuilderDerive, BuilderDerives}; use super::BuilderGenCtx; use crate::builder::builder_gen::Member; use crate::util::prelude::*; @@ -10,12 +10,12 @@ impl BuilderGenCtx { let mut tokens = TokenStream::new(); - if clone.is_present() { - tokens.extend(self.derive_clone()); + if let Some(derive) = clone { + tokens.extend(self.derive_clone(derive)); } - if debug.is_present() { - tokens.extend(self.derive_debug()); + if let Some(derive) = debug { + tokens.extend(self.derive_debug(derive)); } tokens @@ -25,16 +25,30 @@ impl BuilderGenCtx { /// 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. - fn where_clause_for_derive(&self, target_trait_bounds: &TokenStream) -> TokenStream { - let target_trait_bounds_predicates = self - .generics - .decl_without_defaults - .iter() - .filter_map(syn::GenericParam::as_type_param) - .map(|param| { - let ident = ¶m.ident; + fn where_clause_for_derive( + &self, + target_trait_bounds: &TokenStream, + derive: &BuilderDerive, + ) -> TokenStream { + let additional_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! { - #ident: #target_trait_bounds + #( #bounds, )* } }); @@ -43,11 +57,11 @@ impl BuilderGenCtx { quote! { where #( #base_predicates, )* - #( #target_trait_bounds_predicates, )* + #additional_predicates } } - fn derive_clone(&self) -> TokenStream { + fn derive_clone(&self, derive: &BuilderDerive) -> TokenStream { let generics_decl = &self.generics.decl_without_defaults; let generic_args = &self.generics.args; let builder_ident = &self.builder_type.ident; @@ -80,7 +94,7 @@ impl BuilderGenCtx { } }); - let where_clause = self.where_clause_for_derive(&clone); + 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| { @@ -132,7 +146,7 @@ impl BuilderGenCtx { } } - fn derive_debug(&self) -> TokenStream { + fn derive_debug(&self, derive: &BuilderDerive) -> TokenStream { let receiver_field = &self.ident_pool.receiver; let start_fn_args_field = &self.ident_pool.start_fn_args; let named_members_field = &self.ident_pool.named_members; @@ -186,7 +200,7 @@ impl BuilderGenCtx { }); let debug = quote!(::core::fmt::Debug); - let where_clause = self.where_clause_for_derive(&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; diff --git a/bon-macros/src/builder/builder_gen/builder_params/mod.rs b/bon-macros/src/builder/builder_gen/builder_params/mod.rs index 3d65de5f..8175ab37 100644 --- a/bon-macros/src/builder/builder_gen/builder_params/mod.rs +++ b/bon-macros/src/builder/builder_gen/builder_params/mod.rs @@ -5,6 +5,7 @@ pub(crate) use on_params::OnParams; use crate::parsing::{ItemParams, ItemParamsParsing}; use crate::util::prelude::*; use darling::FromMeta; +use syn::punctuated::Punctuated; fn parse_finish_fn(meta: &syn::Meta) -> Result { ItemParamsParsing { @@ -52,25 +53,35 @@ pub(crate) struct BuilderParams { #[derive(Debug, Clone, Default, FromMeta)] pub(crate) struct BuilderDerives { #[darling(rename = "Clone")] - pub(crate) clone: darling::util::Flag, + pub(crate) clone: Option, #[darling(rename = "Debug")] - pub(crate) debug: darling::util::Flag, + pub(crate) debug: Option, } -// #[derive(Debug, Clone, Default)] -// pub(crate) struct BuilderDerive { -// pub(crate) bounds: Vec, -// } +#[derive(Debug, Clone, Default)] +pub(crate) struct BuilderDerive { + pub(crate) bounds: Option>, +} -// impl FromMeta for BuilderDerive { -// fn from_meta(meta: &syn::Meta) -> Result { +impl FromMeta for BuilderDerive { + fn from_meta(meta: &syn::Meta) -> Result { + if let syn::Meta::Path(_) = meta { + return Ok(Self { bounds: None }); + } -// struct Parsed { + #[derive(FromMeta)] + struct Parsed { + #[darling(with = crate::parsing::parse_paren_meta_list_with_terminated)] + bounds: Punctuated, + } -// bounds: Vec, -// } + meta.require_list()?.require_parens_delim()?; + let Parsed { bounds } = Parsed::from_meta(meta)?; -// } -// } + Ok(Self { + bounds: Some(bounds), + }) + } +} diff --git a/bon-macros/src/parsing/mod.rs b/bon-macros/src/parsing/mod.rs index a37e1bdc..169d53e0 100644 --- a/bon-macros/src/parsing/mod.rs +++ b/bon-macros/src/parsing/mod.rs @@ -10,6 +10,8 @@ pub(crate) use spanned_key::*; use crate::util::prelude::*; use darling::FromMeta; +use syn::parse::Parser; +use syn::punctuated::Punctuated; pub(crate) fn parse_non_empty_paren_meta_list(meta: &syn::Meta) -> Result { require_non_empty_paren_meta_list(meta)?; @@ -40,3 +42,42 @@ pub(crate) fn require_non_empty_paren_meta_list(meta: &syn::Meta) -> Result { 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`. +#[allow(dead_code)] +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) +} diff --git a/bon/tests/integration/builder/builder_derives.rs b/bon/tests/integration/builder/attr_derive.rs similarity index 77% rename from bon/tests/integration/builder/builder_derives.rs rename to bon/tests/integration/builder/attr_derive.rs index 76d51a0e..072aef9d 100644 --- a/bon/tests/integration/builder/builder_derives.rs +++ b/bon/tests/integration/builder/attr_derive.rs @@ -268,3 +268,83 @@ mod positional_members { ); } } + +mod attr_bounds_empty { + use crate::prelude::*; + + struct NoTraitImpls; + + #[test] + fn test_struct() { + #[derive(Builder)] + #[builder(derive(Clone(bounds()), Debug))] + struct Sut<'a, T> { + _arg: &'a T, + } + + let _ = Sut::builder().arg(&NoTraitImpls).clone(); + } + + #[test] + fn test_free_fn() { + #[builder(derive(Clone(bounds()), Debug))] + fn sut(_arg: &T) {} + + let _ = sut::().arg(&NoTraitImpls).clone(); + } + + #[test] + fn test_assoc_method() { + #[derive(Clone, Debug)] + struct Sut; + + #[bon] + impl Sut { + #[builder(derive(Clone(bounds()), Debug))] + fn sut(_arg: &T) {} + } + + let _ = Sut::sut::().arg(&NoTraitImpls).clone(); + } +} + +mod attr_bounds_non_empty { + use crate::prelude::*; + + struct NoTraitImpls; + + #[test] + fn test_struct() { + #[derive(Builder)] + #[builder(derive(Clone(bounds(&'a T: Clone, &'a &'a T: Clone)), Debug))] + struct Sut<'a, T> { + _arg: &'a T, + } + + let _ = Sut::builder().arg(&NoTraitImpls).clone(); + } + + #[test] + fn test_free_fn() { + #[builder(derive(Clone(bounds(&'a T: Clone, &'a &'a T: Clone)), Debug))] + #[allow(clippy::needless_lifetimes, single_use_lifetimes)] + fn sut<'a, T>(_arg: &'a T) {} + + let _ = sut::().arg(&NoTraitImpls).clone(); + } + + #[test] + fn test_assoc_method() { + #[derive(Clone, Debug)] + struct Sut; + + #[bon] + impl Sut { + #[builder(derive(Clone(bounds(&'a T: Clone, &'a &'a T: Clone)), Debug))] + #[allow(clippy::needless_lifetimes, single_use_lifetimes)] + fn sut<'a, T>(_arg: &'a T) {} + } + + let _ = Sut::sut::().arg(&NoTraitImpls).clone(); + } +} diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index e4c32caf..08decc83 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -7,7 +7,7 @@ mod attr_setters; mod attr_skip; mod attr_transparent; mod attr_with; -mod builder_derives; +mod attr_derive; mod cfgs; mod generics; mod init_order; diff --git a/bon/tests/integration/ui/compile_fail/attr_derive.rs b/bon/tests/integration/ui/compile_fail/attr_derive.rs index e98428cf..59ff92d7 100644 --- a/bon/tests/integration/ui/compile_fail/attr_derive.rs +++ b/bon/tests/integration/ui/compile_fail/attr_derive.rs @@ -40,8 +40,7 @@ impl StructContainsNonTrait { fn method_contains_non_trait( self, - #[builder(start_fn)] - _no_impl_start_fn: NoTraitImpls, + #[builder(start_fn)] _no_impl_start_fn: NoTraitImpls, _no_impls_required: NoTraitImpls, @@ -59,4 +58,12 @@ impl StructContainsNonTrait { #[builder(derive())] struct EmptyDerives {} +#[derive(Builder)] +#[builder(derive(Clone()))] +struct EmptyParamsForDerive {} + +#[derive(Builder)] +#[builder(derive(Clone(bounds {})))] +struct WrongDelimInBounds {} + fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_derive.stderr b/bon/tests/integration/ui/compile_fail/attr_derive.stderr index 60bd0210..a2e18d59 100644 --- a/bon/tests/integration/ui/compile_fail/attr_derive.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_derive.stderr @@ -1,9 +1,21 @@ error: expected at least one parameter in parentheses - --> tests/integration/ui/compile_fail/attr_derive.rs:59:17 + --> tests/integration/ui/compile_fail/attr_derive.rs:58:17 | -59 | #[builder(derive())] +58 | #[builder(derive())] | ^^ +error: Missing field `bounds` + --> tests/integration/ui/compile_fail/attr_derive.rs:62:18 + | +62 | #[builder(derive(Clone()))] + | ^^^^^ + +error: wrong delimiter, expected parentheses e.g. `bounds(...)`, but got curly braces: `bounds{...}` + --> tests/integration/ui/compile_fail/attr_derive.rs:66:24 + | +66 | #[builder(derive(Clone(bounds {})))] + | ^^^^^^ + error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied --> tests/integration/ui/compile_fail/attr_derive.rs:9:23 | @@ -289,10 +301,10 @@ error[E0277]: the trait bound `StructContainsNonTrait: Clone` is not satisfied | ^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `StructContainsNonTrait` error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/attr_derive.rs:44:28 + --> tests/integration/ui/compile_fail/attr_derive.rs:43:49 | -44 | _no_impl_start_fn: NoTraitImpls, - | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` +43 | #[builder(start_fn)] _no_impl_start_fn: NoTraitImpls, + | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | @@ -301,9 +313,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/attr_derive.rs:46:29 + --> tests/integration/ui/compile_fail/attr_derive.rs:45:29 | -46 | _no_impls_required: NoTraitImpls, +45 | _no_impls_required: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_member` @@ -318,9 +330,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/attr_derive.rs:48:35 + --> tests/integration/ui/compile_fail/attr_derive.rs:47:35 | -48 | _no_impl_optional: Option, +47 | _no_impl_optional: Option, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_member` @@ -335,9 +347,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Clone)]` | error[E0277]: the trait bound `NoTraitImpls: Clone` is not satisfied - --> tests/integration/ui/compile_fail/attr_derive.rs:51:30 + --> tests/integration/ui/compile_fail/attr_derive.rs:50:30 | -51 | _no_impl_optional_2: NoTraitImpls, +50 | _no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ the trait `Clone` is not implemented for `NoTraitImpls` | note: required by a bound in `clone_member` @@ -366,10 +378,10 @@ note: required by a bound in `as_dyn_debug` | ^^^^^ required by this bound in `as_dyn_debug` error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/attr_derive.rs:44:28 + --> tests/integration/ui/compile_fail/attr_derive.rs:43:49 | -44 | _no_impl_start_fn: NoTraitImpls, - | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` +43 | #[builder(start_fn)] _no_impl_start_fn: NoTraitImpls, + | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` = note: add `#[derive(Debug)]` to `NoTraitImpls` or manually `impl Debug for NoTraitImpls` @@ -385,9 +397,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/attr_derive.rs:46:29 + --> tests/integration/ui/compile_fail/attr_derive.rs:45:29 | -46 | _no_impls_required: NoTraitImpls, +45 | _no_impls_required: NoTraitImpls, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` @@ -404,9 +416,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/attr_derive.rs:48:35 + --> tests/integration/ui/compile_fail/attr_derive.rs:47:35 | -48 | _no_impl_optional: Option, +47 | _no_impl_optional: Option, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` @@ -423,9 +435,9 @@ help: consider annotating `NoTraitImpls` with `#[derive(Debug)]` | error[E0277]: `NoTraitImpls` doesn't implement `Debug` - --> tests/integration/ui/compile_fail/attr_derive.rs:51:30 + --> tests/integration/ui/compile_fail/attr_derive.rs:50:30 | -51 | _no_impl_optional_2: NoTraitImpls, +50 | _no_impl_optional_2: NoTraitImpls, | ^^^^^^^^^^^^ `NoTraitImpls` cannot be formatted using `{:?}` | = help: the trait `Debug` is not implemented for `NoTraitImpls` From 45038ac969d05867a8e0264971d37cf83efb776d Mon Sep 17 00:00:00 2001 From: Veetaha Date: Fri, 18 Oct 2024 23:26:40 +0000 Subject: [PATCH 112/119] Better docs --- .../src/builder/builder_gen/setters/mod.rs | 3 +- bon-macros/src/parsing/item_params.rs | 1 - .../tests/snapshots/setters_docs_and_vis.rs | 36 ++++++++++++------- bon/tests/integration/builder/mod.rs | 2 +- 4 files changed, 26 insertions(+), 16 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index 1c4373f1..034dec83 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -504,8 +504,7 @@ fn optional_setter_docs( format!( "| **Optional** |\n\ | -- |\n\n\ - **See also** a companion setter that {description}: \ - [`{other_setter}()`](Self::{other_setter}).\ + **See also** [`{other_setter}()`](Self::{other_setter}), which is a companion setter that {description}. \n\n{default}", ) } diff --git a/bon-macros/src/parsing/item_params.rs b/bon-macros/src/parsing/item_params.rs index 13be6ec0..4caf2adc 100644 --- a/bon-macros/src/parsing/item_params.rs +++ b/bon-macros/src/parsing/item_params.rs @@ -73,7 +73,6 @@ impl ItemParamsParsing<'_> { } } - let params = ItemParams { name: full.name, vis: full.vis, diff --git a/bon-macros/tests/snapshots/setters_docs_and_vis.rs b/bon-macros/tests/snapshots/setters_docs_and_vis.rs index 02c3702d..52763184 100644 --- a/bon-macros/tests/snapshots/setters_docs_and_vis.rs +++ b/bon-macros/tests/snapshots/setters_docs_and_vis.rs @@ -18,7 +18,8 @@ impl SutBuilder { /**| **Optional** | | -- | -**See also** a companion setter that accepts an `Option`: [`maybe_optional_field()`](Self::maybe_optional_field). +**See also** [`maybe_optional_field()`](Self::maybe_optional_field), which is a companion setter that accepts an `Option`. + */ /// Docs on the optional field setters. @@ -33,7 +34,8 @@ impl SutBuilder { /**| **Optional** | | -- | -**See also** a companion setter that wraps the value with `Some` internally: [`optional_field()`](Self::optional_field). +**See also** [`optional_field()`](Self::optional_field), which is a companion setter that wraps the value with `Some` internally. + */ /// Docs on the optional field setters. @@ -48,7 +50,8 @@ impl SutBuilder { /**| **Optional** | | -- | -**See also** a companion setter that accepts an `Option`: [`maybe_default_field()`](Self::maybe_default_field). +**See also** [`maybe_default_field()`](Self::maybe_default_field), which is a companion setter that accepts an `Option`. + **Default:** ```2 + 2 * 3```. @@ -65,7 +68,8 @@ impl SutBuilder { /**| **Optional** | | -- | -**See also** a companion setter that wraps the value with `Some` internally: [`default_field()`](Self::default_field). +**See also** [`default_field()`](Self::default_field), which is a companion setter that wraps the value with `Some` internally. + **Default:** ```2 + 2 * 3```. @@ -82,7 +86,8 @@ impl SutBuilder { /**| **Optional** | | -- | -**See also** a companion setter that accepts an `Option`: [`maybe_optional_field_with_specific_overrides()`](Self::maybe_optional_field_with_specific_overrides). +**See also** [`maybe_optional_field_with_specific_overrides()`](Self::maybe_optional_field_with_specific_overrides), which is a companion setter that accepts an `Option`. + */ /// Docs on some_fn @@ -97,7 +102,8 @@ impl SutBuilder { /**| **Optional** | | -- | -**See also** a companion setter that wraps the value with `Some` internally: [`optional_field_with_specific_overrides()`](Self::optional_field_with_specific_overrides). +**See also** [`optional_field_with_specific_overrides()`](Self::optional_field_with_specific_overrides), which is a companion setter that wraps the value with `Some` internally. + */ /// Docs on option_fn @@ -112,7 +118,8 @@ impl SutBuilder { /**| **Optional** | | -- | -**See also** a companion setter that accepts an `Option`: [`maybe_default_field_with_specific_overrides()`](Self::maybe_default_field_with_specific_overrides). +**See also** [`maybe_default_field_with_specific_overrides()`](Self::maybe_default_field_with_specific_overrides), which is a companion setter that accepts an `Option`. + **Default:** ```2 + 2 * 3```. @@ -129,7 +136,8 @@ impl SutBuilder { /**| **Optional** | | -- | -**See also** a companion setter that wraps the value with `Some` internally: [`default_field_with_specific_overrides()`](Self::default_field_with_specific_overrides). +**See also** [`default_field_with_specific_overrides()`](Self::default_field_with_specific_overrides), which is a companion setter that wraps the value with `Some` internally. + **Default:** ```2 + 2 * 3```. @@ -146,7 +154,8 @@ impl SutBuilder { /**| **Optional** | | -- | -**See also** a companion setter that accepts an `Option`: [`maybe_optional_field_with_inherited_overrides()`](Self::maybe_optional_field_with_inherited_overrides). +**See also** [`maybe_optional_field_with_inherited_overrides()`](Self::maybe_optional_field_with_inherited_overrides), which is a companion setter that accepts an `Option`. + */ /// Common docs @@ -161,7 +170,8 @@ impl SutBuilder { /**| **Optional** | | -- | -**See also** a companion setter that wraps the value with `Some` internally: [`optional_field_with_inherited_overrides()`](Self::optional_field_with_inherited_overrides). +**See also** [`optional_field_with_inherited_overrides()`](Self::optional_field_with_inherited_overrides), which is a companion setter that wraps the value with `Some` internally. + */ /// Docs on option_fn @@ -176,7 +186,8 @@ impl SutBuilder { /**| **Optional** | | -- | -**See also** a companion setter that accepts an `Option`: [`maybe_default_field_with_inherited_overrides()`](Self::maybe_default_field_with_inherited_overrides). +**See also** [`maybe_default_field_with_inherited_overrides()`](Self::maybe_default_field_with_inherited_overrides), which is a companion setter that accepts an `Option`. + **Default:** ```2 + 2 * 3```. @@ -193,7 +204,8 @@ impl SutBuilder { /**| **Optional** | | -- | -**See also** a companion setter that wraps the value with `Some` internally: [`default_field_with_inherited_overrides()`](Self::default_field_with_inherited_overrides). +**See also** [`default_field_with_inherited_overrides()`](Self::default_field_with_inherited_overrides), which is a companion setter that wraps the value with `Some` internally. + **Default:** ```2 + 2 * 3```. diff --git a/bon/tests/integration/builder/mod.rs b/bon/tests/integration/builder/mod.rs index 08decc83..bdf83a19 100644 --- a/bon/tests/integration/builder/mod.rs +++ b/bon/tests/integration/builder/mod.rs @@ -1,4 +1,5 @@ mod attr_default; +mod attr_derive; mod attr_expose_positional_fn; mod attr_into; mod attr_on; @@ -7,7 +8,6 @@ mod attr_setters; mod attr_skip; mod attr_transparent; mod attr_with; -mod attr_derive; mod cfgs; mod generics; mod init_order; From fab59f33ea508c9390c7c168d3261f5a4125f48d Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 19 Oct 2024 14:47:46 +0000 Subject: [PATCH 113/119] Self-review part 2 --- .../builder/builder_gen/builder_params/mod.rs | 2 +- .../builder_gen/builder_params/on_params.rs | 4 +- .../src/builder/builder_gen/input_fn.rs | 2 +- .../src/builder/builder_gen/member/named.rs | 29 +- bon-macros/src/parsing/mod.rs | 4 +- bon-macros/src/util/ty/match_types.rs | 4 +- bon/src/private/mod.rs | 12 +- bon/tests/integration/builder/attr_setters.rs | 38 +++ .../integration/builder/attr_transparent.rs | 8 +- .../integration/ui/compile_fail/attr_bon.rs | 11 + .../ui/compile_fail/attr_bon.stderr | 5 + .../ui/compile_fail/attr_builder.rs | 17 ++ .../ui/compile_fail/attr_builder.stderr | 54 +++- .../ui/compile_fail/attr_derive.stderr | 2 +- .../integration/ui/compile_fail/attr_into.rs | 6 + .../ui/compile_fail/attr_into.stderr | 5 + .../integration/ui/compile_fail/attr_on.rs | 27 ++ .../ui/compile_fail/attr_on.stderr | 51 ++++ .../ui/compile_fail/attr_setters.stderr | 2 +- .../integration/ui/compile_fail/attr_skip.rs | 40 +++ .../ui/compile_fail/attr_skip.stderr | 53 ++++ .../ui/compile_fail/attr_start_finish_fn.rs | 11 + .../compile_fail/attr_start_finish_fn.stderr | 11 + .../ui/compile_fail/attr_transparent.rs | 13 + .../ui/compile_fail/attr_transparent.stderr | 22 ++ .../integration/ui/compile_fail/attr_with.rs | 6 + .../ui/compile_fail/attr_with.stderr | 52 ++-- .../ui/compile_fail/broken_intra_doc_links.rs | 74 +++++ .../broken_intra_doc_links.stderr | 53 ++++ .../ui/compile_fail/collections.rs | 10 + .../ui/compile_fail/collections.stderr | 23 ++ .../ui/compile_fail/derive_builder.rs | 12 + .../ui/compile_fail/derive_builder.stderr | 19 ++ .../diagnostic_on_unimplemented.rs | 18 ++ .../diagnostic_on_unimplemented.stderr | 61 ++++ .../integration/ui/compile_fail/errors.rs | 140 --------- .../integration/ui/compile_fail/errors.stderr | 279 ------------------ website/changelog.md | 54 ++++ 38 files changed, 761 insertions(+), 473 deletions(-) create mode 100644 bon/tests/integration/ui/compile_fail/attr_bon.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_bon.stderr create mode 100644 bon/tests/integration/ui/compile_fail/attr_into.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_into.stderr create mode 100644 bon/tests/integration/ui/compile_fail/attr_on.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_on.stderr create mode 100644 bon/tests/integration/ui/compile_fail/attr_skip.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_skip.stderr create mode 100644 bon/tests/integration/ui/compile_fail/attr_start_finish_fn.rs create mode 100644 bon/tests/integration/ui/compile_fail/attr_start_finish_fn.stderr create mode 100644 bon/tests/integration/ui/compile_fail/broken_intra_doc_links.rs create mode 100644 bon/tests/integration/ui/compile_fail/broken_intra_doc_links.stderr create mode 100644 bon/tests/integration/ui/compile_fail/collections.rs create mode 100644 bon/tests/integration/ui/compile_fail/collections.stderr create mode 100644 bon/tests/integration/ui/compile_fail/derive_builder.rs create mode 100644 bon/tests/integration/ui/compile_fail/derive_builder.stderr create mode 100644 bon/tests/integration/ui/compile_fail/diagnostic_on_unimplemented.rs create mode 100644 bon/tests/integration/ui/compile_fail/diagnostic_on_unimplemented.stderr delete mode 100644 bon/tests/integration/ui/compile_fail/errors.rs delete mode 100644 bon/tests/integration/ui/compile_fail/errors.stderr diff --git a/bon-macros/src/builder/builder_gen/builder_params/mod.rs b/bon-macros/src/builder/builder_gen/builder_params/mod.rs index 8175ab37..526b846f 100644 --- a/bon-macros/src/builder/builder_gen/builder_params/mod.rs +++ b/bon-macros/src/builder/builder_gen/builder_params/mod.rs @@ -26,7 +26,7 @@ fn parse_builder_type(meta: &syn::Meta) -> Result { fn parse_state_mod(meta: &syn::Meta) -> Result { ItemParamsParsing { meta, - reject_self_mentions: Some("builder module"), + reject_self_mentions: Some("builder's state module"), } .parse() } diff --git a/bon-macros/src/builder/builder_gen/builder_params/on_params.rs b/bon-macros/src/builder/builder_gen/builder_params/on_params.rs index 6b780b4e..272f166b 100644 --- a/bon-macros/src/builder/builder_gen/builder_params/on_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params/on_params.rs @@ -37,8 +37,8 @@ impl Parse for OnParams { return Err(syn::Error::new_spanned( &rest, "this #[builder(on(type_pattern, ...))] contains no options to override \ - the default behavior for the selected setters like `into`, so it \ - does nothing", + the default behavior for the selected setters like `into`, `overwritable`, \ + so it does nothing", )); } } diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index 5565415e..a899f4a6 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -581,7 +581,7 @@ fn get_must_use_attribute(attrs: &[syn::Attribute]) -> Result Result { - crate::parsing::reject_self_mentions_in_docs("builder struct's impl block", &self.docs)?; - if let Some(default) = &self.params.default { if self.is_special_option_ty() { bail!( @@ -106,6 +104,33 @@ impl NamedMember { } } + let member_docs_not_copied = self + .params + .setters + .as_ref() + .map(|setters| { + if setters.docs.is_some() { + return true; + } + + let SettersFnParams { some_fn, option_fn } = &setters.fns; + matches!( + (some_fn.as_deref(), option_fn.as_deref()), + ( + Some(ItemParams { docs: Some(_), .. }), + Some(ItemParams { 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_params()?; if self.params.transparent.is_present() && !self.ty.norm.is_option() { diff --git a/bon-macros/src/parsing/mod.rs b/bon-macros/src/parsing/mod.rs index 169d53e0..ac36f5ab 100644 --- a/bon-macros/src/parsing/mod.rs +++ b/bon-macros/src/parsing/mod.rs @@ -26,14 +26,14 @@ pub(crate) fn require_non_empty_paren_meta_list(meta: &syn::Meta) -> Result { if meta.tokens.is_empty() { bail!( &meta.delimiter.span().join(), - "expected at least one parameter in parentheses" + "expected parameters in parentheses" ); } } syn::Meta::Path(path) => bail!( &meta, "this empty `#[{0}]` attribute is unexpected; \ - remove it or pass some parameters in parentheses: \ + remove it or pass parameters in parentheses: \ `#[{0}(...)]`", darling::util::path_to_string(path) ), diff --git a/bon-macros/src/util/ty/match_types.rs b/bon-macros/src/util/ty/match_types.rs index fc258fc0..988689fe 100644 --- a/bon-macros/src/util/ty/match_types.rs +++ b/bon-macros/src/util/ty/match_types.rs @@ -139,7 +139,7 @@ fn match_generic_args( && match_exprs(&scrutinee.value, &pattern.value) } - _ => return Err(unsupported_syntax_error(&pattern, "This syntax")), + _ => return Err(unsupported_syntax_error(&pattern, "this syntax")), }; Ok(verdict) @@ -228,7 +228,7 @@ pub(crate) fn match_types(scrutinee: &syn::Type, pattern: &syn::Type) -> Result< Never(_) => matches!(scrutinee, Never(_)), - _ => return Err(unsupported_syntax_error(&pattern, "This syntax")), + _ => return Err(unsupported_syntax_error(&pattern, "this syntax")), }; Ok(verdict) diff --git a/bon/src/private/mod.rs b/bon/src/private/mod.rs index 872d0842..8da87ec7 100644 --- a/bon/src/private/mod.rs +++ b/bon/src/private/mod.rs @@ -7,10 +7,6 @@ // Especially, this doesn't play well with our MSRV. Trait bounds // aren't allowed on const functions in older Rust versions. clippy::missing_const_for_fn, - - // We use `deprecated` as a sign to the user that they shouldn't use - // the type as it's an internal implementation detail. - deprecated, )] /// Used for providing better IDE hints (completions and syntax highlighting). @@ -22,8 +18,8 @@ mod cfg_eval; // This reexport is a private implementation detail and should not be used // directly! This reexport may change or be removed at any time between -// patch releases. Use the export from the builder's state module directly -// instead of using this reexport from `bon::private`. +// patch releases. Use the export from your generated builder's state module +// directly instead of using this reexport from `bon::__private`. pub use crate::builder_state::{IsSet, IsUnset}; pub use rustversion; @@ -51,8 +47,8 @@ pub struct Set(Name); #[rustversion::attr( since(1.78.0), diagnostic::on_unimplemented( - message = "expected the type state for the member `{Name}`, but found `{Self}`", - label = "expected the type state for the member `{Name}`, but found `{Self}`", + message = "expected type state for the member `{Name}`, but got `{Self}`", + label = "expected type state for the member `{Name}`, but got `{Self}`", ) )] pub trait MemberState: Sealed {} diff --git a/bon/tests/integration/builder/attr_setters.rs b/bon/tests/integration/builder/attr_setters.rs index 4e05a52a..a6a6b7b8 100644 --- a/bon/tests/integration/builder/attr_setters.rs +++ b/bon/tests/integration/builder/attr_setters.rs @@ -396,3 +396,41 @@ mod option_fn_name_and_some_fn_name { } } } + +mod self_references_in_docs { + use crate::prelude::*; + + #[test] + fn test_struct() { + /// [`Self`] link + #[derive(Builder)] + struct Sut { + /// [`Self`] link + #[builder(setters(doc {}))] + _arg1: u32, + + /// [`Self`] link + #[builder(setters( + option_fn(doc {}), + some_fn(doc {}) + ))] + _arg2: Option, + } + + let _ = Sut::builder().arg1(42); + } + + #[test] + fn test_free_fn() { + /// [`Self`] link + #[builder] + fn sut( + /// [`Self`] link + #[builder(setters(doc {}))] + _arg1: u32, + ) { + } + + let _ = sut().arg1(42); + } +} diff --git a/bon/tests/integration/builder/attr_transparent.rs b/bon/tests/integration/builder/attr_transparent.rs index 6a28f4ca..b372003a 100644 --- a/bon/tests/integration/builder/attr_transparent.rs +++ b/bon/tests/integration/builder/attr_transparent.rs @@ -12,7 +12,7 @@ fn test_struct() { #[builder(transparent)] generic: Option, - #[builder(into, transparent)] + #[builder(transparent, into)] with_into: Option, #[builder(transparent, default = Some(99))] @@ -56,7 +56,7 @@ fn test_free_fn() { fn sut( #[builder(transparent)] regular: Option, #[builder(transparent)] generic: Option, - #[builder(into, transparent)] with_into: Option, + #[builder(transparent, into)] with_into: Option, #[builder(transparent, default = Some(99))] with_default: Option, #[builder(transparent, default = Some(10))] with_default_2: Option, ) -> impl fmt::Debug { @@ -84,7 +84,7 @@ fn test_assoc_method() { fn sut( #[builder(transparent)] regular: Option, #[builder(transparent)] generic: Option, - #[builder(into, transparent)] with_into: Option, + #[builder(transparent, into)] with_into: Option, #[builder(transparent, default = Some(99))] with_default: Option, #[builder(transparent, default = Some(10))] with_default_2: Option, ) -> impl fmt::Debug { @@ -96,7 +96,7 @@ fn test_assoc_method() { &self, #[builder(transparent)] regular: Option, #[builder(transparent)] generic: Option, - #[builder(into, transparent)] with_into: Option, + #[builder(transparent, into)] with_into: Option, #[builder(transparent, default = Some(99))] with_default: Option, #[builder(transparent, default = Some(10))] with_default_2: Option, ) -> impl fmt::Debug { diff --git a/bon/tests/integration/ui/compile_fail/attr_bon.rs b/bon/tests/integration/ui/compile_fail/attr_bon.rs new file mode 100644 index 00000000..f2d24b9a --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_bon.rs @@ -0,0 +1,11 @@ +use bon::bon; + +struct InvalidAttrsForBonMacro; + +#[bon(attrs)] +impl InvalidAttrsForBonMacro { + #[builder] + fn sut() {} +} + +fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_bon.stderr b/bon/tests/integration/ui/compile_fail/attr_bon.stderr new file mode 100644 index 00000000..312d2b3c --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_bon.stderr @@ -0,0 +1,5 @@ +error: `#[bon]` attribute does not accept any parameters yet, but it will in future releases + --> tests/integration/ui/compile_fail/attr_bon.rs:5:7 + | +5 | #[bon(attrs)] + | ^^^^^ diff --git a/bon/tests/integration/ui/compile_fail/attr_builder.rs b/bon/tests/integration/ui/compile_fail/attr_builder.rs index bf4c84e2..702af06c 100644 --- a/bon/tests/integration/ui/compile_fail/attr_builder.rs +++ b/bon/tests/integration/ui/compile_fail/attr_builder.rs @@ -49,4 +49,21 @@ impl EmptyBuilderAttr { #[builder] struct LegacyBuilderProcMacroAttrOnStruct {} +#[builder] +enum EnumsAreUnsupported {} + fn main() {} + +#[builder] +#[must_use] +#[must_use] +fn double_must_use() {} + +#[builder] +fn destructuring1((x, y): (u32, u32)) { + let _ = x; + let _ = y; +} + +#[builder] +fn destructuring2((_, _): (u32, u32)) {} diff --git a/bon/tests/integration/ui/compile_fail/attr_builder.stderr b/bon/tests/integration/ui/compile_fail/attr_builder.stderr index 84641d3b..3922c979 100644 --- a/bon/tests/integration/ui/compile_fail/attr_builder.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_builder.stderr @@ -10,43 +10,43 @@ error: this empty `#[builder()]` attribute is redundant; remove it 8 | #[builder()] | ^^^^^^^ -error: this empty `#[builder]` attribute is unexpected; remove it or pass some parameters in parentheses: `#[builder(...)]` +error: this empty `#[builder]` attribute is unexpected; remove it or pass parameters in parentheses: `#[builder(...)]` --> tests/integration/ui/compile_fail/attr_builder.rs:13:7 | 13 | #[builder] | ^^^^^^^ -error: expected at least one parameter in parentheses +error: expected parameters in parentheses --> tests/integration/ui/compile_fail/attr_builder.rs:19:14 | 19 | #[builder()] | ^^ -error: this empty `#[builder]` attribute is unexpected; remove it or pass some parameters in parentheses: `#[builder(...)]` +error: this empty `#[builder]` attribute is unexpected; remove it or pass parameters in parentheses: `#[builder(...)]` --> tests/integration/ui/compile_fail/attr_builder.rs:24:41 | 24 | fn fn_empty_member_level_builder_attr(#[builder] _x: u32) {} | ^^^^^^^ -error: expected at least one parameter in parentheses +error: expected parameters in parentheses --> tests/integration/ui/compile_fail/attr_builder.rs:27:60 | 27 | fn fn_empty_member_level_builder_attr_with_parens(#[builder()] _x: u32) {} | ^^ -error: this empty `#[builder]` attribute is unexpected; remove it or pass some parameters in parentheses: `#[builder(...)]` +error: this empty `#[builder]` attribute is unexpected; remove it or pass parameters in parentheses: `#[builder(...)]` --> tests/integration/ui/compile_fail/attr_builder.rs:34:42 | 34 | fn empty_member_level_builder_attr(#[builder] _x: u32) {} | ^^^^^^^ -error: expected at least one parameter in parentheses +error: expected parameters in parentheses --> tests/integration/ui/compile_fail/attr_builder.rs:40:61 | 40 | fn empty_member_level_builder_attr_with_parens(#[builder()] _x: u32) {} | ^^ -error: expected at least one parameter in parentheses +error: expected parameters in parentheses --> tests/integration/ui/compile_fail/attr_builder.rs:45:14 | 45 | #[builder()] @@ -57,3 +57,43 @@ error: to generate a builder for a struct, use `#[derive(bon::Builder)]` instead | 50 | struct LegacyBuilderProcMacroAttrOnStruct {} | ^^^^^^ + +error: only `fn` items are supported by the `#[bon::builder]` attribute + --> tests/integration/ui/compile_fail/attr_builder.rs:52:1 + | +52 | #[builder] + | ^^^^^^^^^^ + | + = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: Found multiple #[must_use], but bon only works with exactly one or zero. + --> tests/integration/ui/compile_fail/attr_builder.rs:59:1 + | +59 | #[must_use] + | ^ + +error: use a simple `identifier: type` syntax for the function argument; destructuring patterns in arguments aren't supported by the `#[builder]` + --> tests/integration/ui/compile_fail/attr_builder.rs:63:19 + | +63 | fn destructuring1((x, y): (u32, u32)) { + | ^^^^^^ + +error: use a simple `identifier: type` syntax for the function argument; destructuring patterns in arguments aren't supported by the `#[builder]` + --> tests/integration/ui/compile_fail/attr_builder.rs:69:19 + | +69 | fn destructuring2((_, _): (u32, u32)) {} + | ^^^^^^ + +warning: unused attribute + --> tests/integration/ui/compile_fail/attr_builder.rs:59:1 + | +59 | #[must_use] + | ^^^^^^^^^^^ help: remove this attribute + | +note: attribute also specified here + --> tests/integration/ui/compile_fail/attr_builder.rs:58:1 + | +58 | #[must_use] + | ^^^^^^^^^^^ + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: `#[warn(unused_attributes)]` on by default diff --git a/bon/tests/integration/ui/compile_fail/attr_derive.stderr b/bon/tests/integration/ui/compile_fail/attr_derive.stderr index a2e18d59..fc60a841 100644 --- a/bon/tests/integration/ui/compile_fail/attr_derive.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_derive.stderr @@ -1,4 +1,4 @@ -error: expected at least one parameter in parentheses +error: expected parameters in parentheses --> tests/integration/ui/compile_fail/attr_derive.rs:58:17 | 58 | #[builder(derive())] diff --git a/bon/tests/integration/ui/compile_fail/attr_into.rs b/bon/tests/integration/ui/compile_fail/attr_into.rs new file mode 100644 index 00000000..65f68a0c --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_into.rs @@ -0,0 +1,6 @@ +use bon::builder; + +#[builder] +fn invalid_into_false(#[builder(into = false)] _x: u32) {} + +fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_into.stderr b/bon/tests/integration/ui/compile_fail/attr_into.stderr new file mode 100644 index 00000000..5f14a887 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_into.stderr @@ -0,0 +1,5 @@ +error: Unexpected type `bool` + --> tests/integration/ui/compile_fail/attr_into.rs:4:40 + | +4 | fn invalid_into_false(#[builder(into = false)] _x: u32) {} + | ^^^^^ diff --git a/bon/tests/integration/ui/compile_fail/attr_on.rs b/bon/tests/integration/ui/compile_fail/attr_on.rs new file mode 100644 index 00000000..7fcafb2b --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_on.rs @@ -0,0 +1,27 @@ +use bon::builder; + +#[builder(on(String, into))] +fn unnecessary_into(#[builder(into)] _x: String) {} + +#[builder(on(String, overwritable))] +fn unnecessary_ovewritable(#[builder(overwritable)] _x: String) {} + +#[builder(on(&dyn std::fmt::Debug, into))] +fn invalid_type_pattern() {} + +#[builder(on(fn(#[attr] a: u32), into))] +fn attrs_in_on_type_pattern() {} + +#[builder(on)] +fn incomplete_on() {} + +#[builder(on())] +fn incomplete_on2() {} + +#[builder(on(_))] +fn incomplete_on3() {} + +#[builder(on(_,))] +fn incomplete_on4() {} + +fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_on.stderr b/bon/tests/integration/ui/compile_fail/attr_on.stderr new file mode 100644 index 00000000..e7ccc8c3 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_on.stderr @@ -0,0 +1,51 @@ +error: this `#[builder(into)]` attribute is redundant, because `into` is already implied for this member via the `#[builder(on(...))]` at the top of the function + --> tests/integration/ui/compile_fail/attr_on.rs:4:31 + | +4 | fn unnecessary_into(#[builder(into)] _x: String) {} + | ^^^^ + +error: this `#[builder(overwritable)]` attribute is redundant, because `overwritable` is already implied for this member via the `#[builder(on(...))]` at the top of the function + --> tests/integration/ui/compile_fail/attr_on.rs:7:38 + | +7 | fn unnecessary_ovewritable(#[builder(overwritable)] _x: String) {} + | ^^^^^^^^^^^^ + +error: this 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. + --> tests/integration/ui/compile_fail/attr_on.rs:9:15 + | +9 | #[builder(on(&dyn std::fmt::Debug, into))] + | ^^^ + +error: nested attributes are not allowed in the type pattern of #[builder(on(type_pattern, ...))] + --> tests/integration/ui/compile_fail/attr_on.rs:12:17 + | +12 | #[builder(on(fn(#[attr] a: u32), into))] + | ^ + +error: this empty `#[on]` attribute is unexpected; remove it or pass parameters in parentheses: `#[on(...)]` + --> tests/integration/ui/compile_fail/attr_on.rs:15:11 + | +15 | #[builder(on)] + | ^^ + +error: expected parameters in parentheses + --> tests/integration/ui/compile_fail/attr_on.rs:18:13 + | +18 | #[builder(on())] + | ^^ + +error: expected `,` + --> tests/integration/ui/compile_fail/attr_on.rs:21:1 + | +21 | #[builder(on(_))] + | ^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: this #[builder(on(type_pattern, ...))] contains no options to override the default behavior for the selected setters like `into`, `overwritable`, so it does nothing + --> tests/integration/ui/compile_fail/attr_on.rs:24:1 + | +24 | #[builder(on(_,))] + | ^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/bon/tests/integration/ui/compile_fail/attr_setters.stderr b/bon/tests/integration/ui/compile_fail/attr_setters.stderr index 593a528c..bf027f56 100644 --- a/bon/tests/integration/ui/compile_fail/attr_setters.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_setters.stderr @@ -46,7 +46,7 @@ error: `option_fn` setter function applies only to members with `#[builder(defau 65 | #[builder(transparent, setters(option_fn = bar))] | ^^^^^^^^^ -error: expected at least one parameter in parentheses +error: expected parameters in parentheses --> tests/integration/ui/compile_fail/attr_setters.rs:71:22 | 71 | #[builder(setters())] diff --git a/bon/tests/integration/ui/compile_fail/attr_skip.rs b/bon/tests/integration/ui/compile_fail/attr_skip.rs new file mode 100644 index 00000000..451653c7 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_skip.rs @@ -0,0 +1,40 @@ +use bon::{builder, Builder}; + +#[derive(Builder)] +struct ConflictingAttrs { + #[builder(skip, into)] + x: u32, +} + +#[derive(Builder)] +struct ConflictingAttrs2 { + #[builder(skip, name = bar)] + x: u32, +} + +#[derive(Builder)] +struct ConflictingAttrs3 { + #[builder(skip, default = 42)] + z: u32, +} + +#[builder] +fn skip_on_fn_is_unsupporetd1(#[builder(skip)] _x: u32) {} +#[builder] +fn skip_on_fn_is_unsupporetd2(#[builder(skip = "skip".to_owned())] _y: String) {} +#[builder] +fn skip_on_fn_is_unsupporetd3(#[builder(skip = vec![42])] _z: Vec) {} + +fn main() { + #[derive(Builder)] + struct SkipGeneratesNoSetter { + #[builder(skip)] + x: u32, + + #[builder(skip = 4)] + y: u32, + } + + SkipGeneratesNoSetter::builder().x(42).build(); + SkipGeneratesNoSetter::builder().y(42).build(); +} diff --git a/bon/tests/integration/ui/compile_fail/attr_skip.stderr b/bon/tests/integration/ui/compile_fail/attr_skip.stderr new file mode 100644 index 00000000..bcf6526c --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_skip.stderr @@ -0,0 +1,53 @@ +error: `skip` attribute can't be specified together with `into` + --> tests/integration/ui/compile_fail/attr_skip.rs:5:15 + | +5 | #[builder(skip, into)] + | ^^^^ + +error: `skip` attribute can't be specified together with `name` + --> tests/integration/ui/compile_fail/attr_skip.rs:11:15 + | +11 | #[builder(skip, name = bar)] + | ^^^^ + +error: `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)]` + --> tests/integration/ui/compile_fail/attr_skip.rs:17:15 + | +17 | #[builder(skip, default = 42)] + | ^^^^ + +error: `skip` attribute is not supported on function arguments; use a local variable instead. + --> tests/integration/ui/compile_fail/attr_skip.rs:22:41 + | +22 | fn skip_on_fn_is_unsupporetd1(#[builder(skip)] _x: u32) {} + | ^^^^ + +error: `skip` attribute is not supported on function arguments; use a local variable instead. + --> tests/integration/ui/compile_fail/attr_skip.rs:24:41 + | +24 | fn skip_on_fn_is_unsupporetd2(#[builder(skip = "skip".to_owned())] _y: String) {} + | ^^^^ + +error: `skip` attribute is not supported on function arguments; use a local variable instead. + --> tests/integration/ui/compile_fail/attr_skip.rs:26:41 + | +26 | fn skip_on_fn_is_unsupporetd3(#[builder(skip = vec![42])] _z: Vec) {} + | ^^^^ + +error[E0599]: no method named `x` found for struct `SkipGeneratesNoSetterBuilder` in the current scope + --> tests/integration/ui/compile_fail/attr_skip.rs:38:38 + | +29 | #[derive(Builder)] + | ------- method `x` not found for this struct +... +38 | SkipGeneratesNoSetter::builder().x(42).build(); + | ^ method not found in `SkipGeneratesNoSetterBuilder` + +error[E0599]: no method named `y` found for struct `SkipGeneratesNoSetterBuilder` in the current scope + --> tests/integration/ui/compile_fail/attr_skip.rs:39:38 + | +29 | #[derive(Builder)] + | ------- method `y` not found for this struct +... +39 | SkipGeneratesNoSetter::builder().y(42).build(); + | ^ method not found in `SkipGeneratesNoSetterBuilder` diff --git a/bon/tests/integration/ui/compile_fail/attr_start_finish_fn.rs b/bon/tests/integration/ui/compile_fail/attr_start_finish_fn.rs new file mode 100644 index 00000000..7d643da1 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_start_finish_fn.rs @@ -0,0 +1,11 @@ +use bon::Builder; + +#[derive(Builder)] +#[builder(start_fn())] +struct EmptyStartFn {} + +#[derive(Builder)] +#[builder(finish_fn())] +struct EmptyFinisFn {} + +fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_start_finish_fn.stderr b/bon/tests/integration/ui/compile_fail/attr_start_finish_fn.stderr new file mode 100644 index 00000000..e8a17698 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/attr_start_finish_fn.stderr @@ -0,0 +1,11 @@ +error: expected parameters in parentheses + --> tests/integration/ui/compile_fail/attr_start_finish_fn.rs:4:19 + | +4 | #[builder(start_fn())] + | ^^ + +error: expected parameters in parentheses + --> tests/integration/ui/compile_fail/attr_start_finish_fn.rs:8:20 + | +8 | #[builder(finish_fn())] + | ^^ diff --git a/bon/tests/integration/ui/compile_fail/attr_transparent.rs b/bon/tests/integration/ui/compile_fail/attr_transparent.rs index 76e199fe..0fbc4236 100644 --- a/bon/tests/integration/ui/compile_fail/attr_transparent.rs +++ b/bon/tests/integration/ui/compile_fail/attr_transparent.rs @@ -33,4 +33,17 @@ struct Valid { fn main() { // Make sure there is no `maybe_` setter generated let _ = Valid::builder().maybe_member(Some(42)); + + // Another way to get transparency + { + type OpaqueOption = Option; + + #[derive(Builder)] + struct Sut { + arg1: OpaqueOption, + } + + // Should not be allowed `OpaqueOption` is required + let _ = Sut::builder().build(); + } } diff --git a/bon/tests/integration/ui/compile_fail/attr_transparent.stderr b/bon/tests/integration/ui/compile_fail/attr_transparent.stderr index 4c7e566d..75366b28 100644 --- a/bon/tests/integration/ui/compile_fail/attr_transparent.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_transparent.stderr @@ -35,3 +35,25 @@ help: there is a method `member` with a similar name | 35 | let _ = Valid::builder().member(Some(42)); | ~~~~~~ + +error[E0277]: the member `Unset` was not set, but this method requires it to be set + --> tests/integration/ui/compile_fail/attr_transparent.rs:47:32 + | +47 | let _ = Sut::builder().build(); + | ^^^^^ the member `Unset` was not set, but this method requires it to be set + | + = help: the trait `IsSet` is not implemented for `Unset`, which is required by `sut_builder::Empty: sut_builder::IsComplete` + = help: the trait `IsSet` is implemented for `Set` +note: required for `sut_builder::Empty` to implement `sut_builder::IsComplete` + --> tests/integration/ui/compile_fail/attr_transparent.rs:41:18 + | +41 | #[derive(Builder)] + | ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro +note: required by a bound in `SutBuilder::::build` + --> tests/integration/ui/compile_fail/attr_transparent.rs:41:18 + | +41 | #[derive(Builder)] + | ^^^^^^^ required by this bound in `SutBuilder::::build` +42 | struct Sut { + | --- required by a bound in this associated function + = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/bon/tests/integration/ui/compile_fail/attr_with.rs b/bon/tests/integration/ui/compile_fail/attr_with.rs index ebae733b..b93c7160 100644 --- a/bon/tests/integration/ui/compile_fail/attr_with.rs +++ b/bon/tests/integration/ui/compile_fail/attr_with.rs @@ -1,5 +1,11 @@ use bon::Builder; +#[derive(Builder)] +struct InvalidWithExpr { + #[builder(with = 42)] + x: u32, +} + #[derive(Builder)] struct ConflictingInto { #[builder(into, with = |x: u32| x + 1)] diff --git a/bon/tests/integration/ui/compile_fail/attr_with.stderr b/bon/tests/integration/ui/compile_fail/attr_with.stderr index d82123af..07b35ec3 100644 --- a/bon/tests/integration/ui/compile_fail/attr_with.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_with.stderr @@ -1,37 +1,43 @@ -error: `with` attribute can't be specified together with `into` - --> tests/integration/ui/compile_fail/attr_with.rs:5:21 +error: expected a closure e.g. `with = |param: T| expression` + --> tests/integration/ui/compile_fail/attr_with.rs:5:15 | -5 | #[builder(into, with = |x: u32| x + 1)] - | ^^^^ +5 | #[builder(with = 42)] + | ^^^^ + +error: `with` attribute can't be specified together with `into` + --> tests/integration/ui/compile_fail/attr_with.rs:11:21 + | +11 | #[builder(into, with = |x: u32| x + 1)] + | ^^^^ error: `for<...>` syntax is not allowed here - --> tests/integration/ui/compile_fail/attr_with.rs:11:22 + --> tests/integration/ui/compile_fail/attr_with.rs:17:22 | -11 | #[builder(with = for<'a> |x: &'a u32| -> u32 { x + 1 })] +17 | #[builder(with = for<'a> |x: &'a u32| -> u32 { x + 1 })] | ^^^ error: `const` keyword is not allowed here - --> tests/integration/ui/compile_fail/attr_with.rs:17:22 + --> tests/integration/ui/compile_fail/attr_with.rs:23:22 | -17 | #[builder(with = const || 1)] +23 | #[builder(with = const || 1)] | ^^^^^ error: `static` keyword is not allowed here - --> tests/integration/ui/compile_fail/attr_with.rs:23:22 + --> tests/integration/ui/compile_fail/attr_with.rs:29:22 | -23 | #[builder(with = static || 1)] +29 | #[builder(with = static || 1)] | ^^^^^^ error: `async` keyword is not allowed here - --> tests/integration/ui/compile_fail/attr_with.rs:29:22 + --> tests/integration/ui/compile_fail/attr_with.rs:35:22 | -29 | #[builder(with = async || 1)] +35 | #[builder(with = async || 1)] | ^^^^^ error: `move` keyword is not allowed here - --> tests/integration/ui/compile_fail/attr_with.rs:35:22 + --> tests/integration/ui/compile_fail/attr_with.rs:41:22 | -35 | #[builder(with = move || 1)] +41 | #[builder(with = move || 1)] | ^^^^ error: expected one of the following: @@ -54,23 +60,23 @@ error: expected one of the following: *underlying type is the type of the member stripped from the `Option` wrapper if this member has an `Option` type and no `#[builder(transparent)]` annotation - --> tests/integration/ui/compile_fail/attr_with.rs:41:34 + --> tests/integration/ui/compile_fail/attr_with.rs:47:34 | -41 | #[builder(with = |x: u32| -> u32 { x + 1 })] +47 | #[builder(with = |x: u32| -> u32 { x + 1 })] | ^^^ error[E0308]: mismatched types - --> tests/integration/ui/compile_fail/attr_with.rs:48:12 + --> tests/integration/ui/compile_fail/attr_with.rs:54:12 | -48 | Ok(value) +54 | Ok(value) | -- ^^^^^ expected `u32`, found `&str` | | | arguments to this enum variant are incorrect | help: the type constructed contains `&str` due to the type of the argument passed - --> tests/integration/ui/compile_fail/attr_with.rs:48:9 + --> tests/integration/ui/compile_fail/attr_with.rs:54:9 | -48 | Ok(value) +54 | Ok(value) | ^^^-----^ | | | this argument influences the type of `Ok` @@ -81,13 +87,13 @@ note: tuple variant defined here | ^^ error[E0308]: mismatched types - --> tests/integration/ui/compile_fail/attr_with.rs:55:62 + --> tests/integration/ui/compile_fail/attr_with.rs:61:62 | -55 | #[builder(with = |value: impl Into<::core::net::IpAddr>| value)] +61 | #[builder(with = |value: impl Into<::core::net::IpAddr>| value)] | ------------------------------ ^^^^^ expected `u32`, found type parameter `impl Into<::core::net::IpAddr>` | | | found this type parameter -56 | value: u32, +62 | value: u32, | --- expected `u32` because of return type | = note: expected type `u32` diff --git a/bon/tests/integration/ui/compile_fail/broken_intra_doc_links.rs b/bon/tests/integration/ui/compile_fail/broken_intra_doc_links.rs new file mode 100644 index 00000000..e289df63 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/broken_intra_doc_links.rs @@ -0,0 +1,74 @@ +use bon::{bon, builder, Builder}; + +#[builder] +fn broken_link_in_arg_docs( + /// [Self] link + _arg: u32, +) { +} + +struct BrokenLinkInArgDocs; + +#[bon] +impl BrokenLinkInArgDocs { + #[builder] + fn broken_link_in_arg_docs( + /// [Self] link + _arg: u32, + ) { + } +} + +#[derive(Builder)] +struct BrokenLinkInFieldDocs { + /// [`Self`] link + field: u32, +} + +#[derive(Builder)] +struct BrokenLinkInSettersDocs { + #[builder(setters(doc { + /// [`Self`] link + }))] + field: u32, +} + +#[derive(Builder)] +struct BrokenLinkInSomeFnDocs { + #[builder(setters( + some_fn(doc { + /// [`Self`] link + }) + ))] + field: Option, +} + +#[derive(Builder)] +struct BrokenLinkInOptionFnDocs { + #[builder(setters( + option_fn(doc { + /// [`Self`] link + }) + ))] + field: Option, +} + +#[derive(Builder)] +#[builder(builder_type(doc { + /// [Self] link +}))] +struct BrokenLinkInBuilderTypeDocs {} + +#[derive(Builder)] +#[builder(finish_fn(doc { + /// [Self] link +}))] +struct BrokenLinkInFinishFnDocs {} + +#[derive(Builder)] +#[builder(state_mod(doc { + /// [Self] link +}))] +struct BrokenLinkInStateModDocs {} + +fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/broken_intra_doc_links.stderr b/bon/tests/integration/ui/compile_fail/broken_intra_doc_links.stderr new file mode 100644 index 00000000..375f62ed --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/broken_intra_doc_links.stderr @@ -0,0 +1,53 @@ +error: the documentation should not reference `Self` because it will be moved to the builder struct's impl block where `Self` changes meaning, which may confuse the reader of this code; use explicit type names instead. + --> tests/integration/ui/compile_fail/broken_intra_doc_links.rs:5:5 + | +5 | /// [Self] link + | ^^^^^^^^^^^^^^^ + +error: the documentation should not reference `Self` because it will be moved to the builder struct's impl block where `Self` changes meaning, which may confuse the reader of this code; use explicit type names instead. + --> tests/integration/ui/compile_fail/broken_intra_doc_links.rs:16:9 + | +16 | /// [Self] link + | ^^^^^^^^^^^^^^^ + +error: the documentation should not reference `Self` because it will be moved to the builder struct's impl block where `Self` changes meaning, which may confuse the reader of this code; use explicit type names instead. + --> tests/integration/ui/compile_fail/broken_intra_doc_links.rs:24:5 + | +24 | /// [`Self`] link + | ^^^^^^^^^^^^^^^^^ + +error: the documentation should not reference `Self` because it will be moved to the builder struct's impl block where `Self` changes meaning, which may confuse the reader of this code; use explicit type names instead. + --> tests/integration/ui/compile_fail/broken_intra_doc_links.rs:31:9 + | +31 | /// [`Self`] link + | ^^^^^^^^^^^^^^^^^ + +error: the documentation should not reference `Self` because it will be moved to the builder struct's impl block where `Self` changes meaning, which may confuse the reader of this code; use explicit type names instead. + --> tests/integration/ui/compile_fail/broken_intra_doc_links.rs:40:13 + | +40 | /// [`Self`] link + | ^^^^^^^^^^^^^^^^^ + +error: the documentation should not reference `Self` because it will be moved to the builder struct's impl block where `Self` changes meaning, which may confuse the reader of this code; use explicit type names instead. + --> tests/integration/ui/compile_fail/broken_intra_doc_links.rs:50:13 + | +50 | /// [`Self`] link + | ^^^^^^^^^^^^^^^^^ + +error: the documentation should not reference `Self` because it will be moved to the builder struct where `Self` changes meaning, which may confuse the reader of this code; use explicit type names instead. + --> tests/integration/ui/compile_fail/broken_intra_doc_links.rs:58:5 + | +58 | /// [Self] link + | ^^^^^^^^^^^^^^^ + +error: the documentation should not reference `Self` because it will be moved to the builder struct's impl block where `Self` changes meaning, which may confuse the reader of this code; use explicit type names instead. + --> tests/integration/ui/compile_fail/broken_intra_doc_links.rs:64:5 + | +64 | /// [Self] link + | ^^^^^^^^^^^^^^^ + +error: the documentation should not reference `Self` because it will be moved to the builder's state module where `Self` changes meaning, which may confuse the reader of this code; use explicit type names instead. + --> tests/integration/ui/compile_fail/broken_intra_doc_links.rs:70:5 + | +70 | /// [Self] link + | ^^^^^^^^^^^^^^^ diff --git a/bon/tests/integration/ui/compile_fail/collections.rs b/bon/tests/integration/ui/compile_fail/collections.rs new file mode 100644 index 00000000..cee20925 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/collections.rs @@ -0,0 +1,10 @@ +use std::collections::{BTreeMap, BTreeSet}; + +fn main() { + let _repeated_keys_in_map: BTreeMap = bon::map! { + "Hello": "Blackjack", + "Hello": "Littlepip", + }; + + let _set: BTreeSet = bon::set!["mintals", "guns", "mintals", "roses"]; +} diff --git a/bon/tests/integration/ui/compile_fail/collections.stderr b/bon/tests/integration/ui/compile_fail/collections.stderr new file mode 100644 index 00000000..ede02ffa --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/collections.stderr @@ -0,0 +1,23 @@ +error: duplicate key in the map + --> tests/integration/ui/compile_fail/collections.rs:5:9 + | +5 | "Hello": "Blackjack", + | ^^^^^^^ + +error: duplicate key in the map + --> tests/integration/ui/compile_fail/collections.rs:6:9 + | +6 | "Hello": "Littlepip", + | ^^^^^^^ + +error: duplicate value in the set + --> tests/integration/ui/compile_fail/collections.rs:9:44 + | +9 | let _set: BTreeSet = bon::set!["mintals", "guns", "mintals", "roses"]; + | ^^^^^^^^^ + +error: duplicate value in the set + --> tests/integration/ui/compile_fail/collections.rs:9:63 + | +9 | let _set: BTreeSet = bon::set!["mintals", "guns", "mintals", "roses"]; + | ^^^^^^^^^ diff --git a/bon/tests/integration/ui/compile_fail/derive_builder.rs b/bon/tests/integration/ui/compile_fail/derive_builder.rs new file mode 100644 index 00000000..2ae1d906 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/derive_builder.rs @@ -0,0 +1,12 @@ +use bon::Builder; + +#[derive(Builder)] +struct TupleStruct(u32, u32); + +#[derive(Builder)] +struct TupleStructsAreUnsupported(u32, u32); + +#[derive(Builder)] +enum EnumsAreUnsupportedWithDerive {} + +fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/derive_builder.stderr b/bon/tests/integration/ui/compile_fail/derive_builder.stderr new file mode 100644 index 00000000..b1c12bc4 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/derive_builder.stderr @@ -0,0 +1,19 @@ +error: Only structs with named fields are supported + --> tests/integration/ui/compile_fail/derive_builder.rs:4:1 + | +4 | struct TupleStruct(u32, u32); + | ^^^^^^ + +error: Only structs with named fields are supported + --> tests/integration/ui/compile_fail/derive_builder.rs:7:1 + | +7 | struct TupleStructsAreUnsupported(u32, u32); + | ^^^^^^ + +error: only `struct` items are supported by the `#[derive(bon::Builder)]` attribute + --> tests/integration/ui/compile_fail/derive_builder.rs:9:10 + | +9 | #[derive(Builder)] + | ^^^^^^^ + | + = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/bon/tests/integration/ui/compile_fail/diagnostic_on_unimplemented.rs b/bon/tests/integration/ui/compile_fail/diagnostic_on_unimplemented.rs new file mode 100644 index 00000000..e13fd64d --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/diagnostic_on_unimplemented.rs @@ -0,0 +1,18 @@ +use bon::Builder; + +fn main() { + #[derive(Builder)] + struct Example { + x: u32, + y: u32, + + #[builder(name = renamed)] + z: u32, + } + + // Test error message about missing members + let _ = Example::builder().x(1).build(); + + // Test error message about repeated setter calls + let _ = Example::builder().y(1).y(2); +} diff --git a/bon/tests/integration/ui/compile_fail/diagnostic_on_unimplemented.stderr b/bon/tests/integration/ui/compile_fail/diagnostic_on_unimplemented.stderr new file mode 100644 index 00000000..e7581b13 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/diagnostic_on_unimplemented.stderr @@ -0,0 +1,61 @@ +error[E0277]: the member `Unset` was not set, but this method requires it to be set + --> tests/integration/ui/compile_fail/diagnostic_on_unimplemented.rs:14:37 + | +14 | let _ = Example::builder().x(1).build(); + | ^^^^^ the member `Unset` was not set, but this method requires it to be set + | + = help: the trait `IsSet` is not implemented for `Unset`, which is required by `SetX: IsComplete` + = help: the trait `IsSet` is implemented for `Set` +note: required for `SetX` to implement `IsComplete` + --> tests/integration/ui/compile_fail/diagnostic_on_unimplemented.rs:4:14 + | +4 | #[derive(Builder)] + | ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro +note: required by a bound in `ExampleBuilder::::build` + --> tests/integration/ui/compile_fail/diagnostic_on_unimplemented.rs:4:14 + | +4 | #[derive(Builder)] + | ^^^^^^^ required by this bound in `ExampleBuilder::::build` +5 | struct Example { + | ------- required by a bound in this associated function + = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the member `Unset` was not set, but this method requires it to be set + --> tests/integration/ui/compile_fail/diagnostic_on_unimplemented.rs:14:37 + | +14 | let _ = Example::builder().x(1).build(); + | ^^^^^ the member `Unset` was not set, but this method requires it to be set + | + = help: the trait `IsSet` is not implemented for `Unset`, which is required by `SetX: IsComplete` + = help: the trait `IsSet` is implemented for `Set` +note: required for `SetX` to implement `IsComplete` + --> tests/integration/ui/compile_fail/diagnostic_on_unimplemented.rs:4:14 + | +4 | #[derive(Builder)] + | ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro +note: required by a bound in `ExampleBuilder::::build` + --> tests/integration/ui/compile_fail/diagnostic_on_unimplemented.rs:4:14 + | +4 | #[derive(Builder)] + | ^^^^^^^ required by this bound in `ExampleBuilder::::build` +5 | struct Example { + | ------- required by a bound in this associated function + = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the member `Set` was already set, but this method requires it to be unset + --> tests/integration/ui/compile_fail/diagnostic_on_unimplemented.rs:17:37 + | +17 | let _ = Example::builder().y(1).y(2); + | ^ the member `Set` was already set, but this method requires it to be unset + | + = help: the trait `IsUnset` is not implemented for `Set` + = help: the trait `IsUnset` is implemented for `Unset` +note: required by a bound in `ExampleBuilder::::y` + --> tests/integration/ui/compile_fail/diagnostic_on_unimplemented.rs:4:14 + | +4 | #[derive(Builder)] + | ^^^^^^^ required by this bound in `ExampleBuilder::::y` +... +7 | y: u32, + | - required by a bound in this associated function + = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/bon/tests/integration/ui/compile_fail/errors.rs b/bon/tests/integration/ui/compile_fail/errors.rs deleted file mode 100644 index c53501d5..00000000 --- a/bon/tests/integration/ui/compile_fail/errors.rs +++ /dev/null @@ -1,140 +0,0 @@ -use bon::{bon, builder, Builder}; -use std::collections::{BTreeMap, BTreeSet}; - -fn main() { - let map: BTreeMap = bon::map! { - "Hello": "Blackjack", - "Hello": "Littlepip", - }; - - let set: BTreeSet = bon::set!["mintals", "guns", "mintals", "roses"]; - - #[derive(Builder)] - struct SkipGeneratesNoSetter { - #[builder(skip)] - x: u32, - - #[builder(skip = 4)] - y: u32, - } - - SkipGeneratesNoSetter::builder().x(42).build(); - SkipGeneratesNoSetter::builder().y(42).build(); - - #[derive(Builder)] - struct Example { - x: u32, - y: u32, - - #[builder(name = renamed)] - z: u32, - } - - // Test error message about missing members - let _ = Example::builder().x(1).build(); - - // Test error message about repeated setter calls - let _ = Example::builder().y(1).y(2); - - { - type OpaqueOption = Option; - - #[derive(Builder)] - struct Sut { - arg1: OpaqueOption, - } - - let _ = Sut::builder().build(); - } -} - -#[derive(Builder)] -struct TupleStruct(u32, u32); - -#[builder] -fn destructuring((x, y): (u32, u32)) { - let _ = x; - let _ = y; -} - -#[builder] -fn unnecessary_into_false(#[builder(into = false)] _x: u32) {} - -#[builder(on(String, into))] -fn unnecessary_into(#[builder(into)] _x: String) {} - -#[builder(on(&dyn std::fmt::Debug, into))] -fn invalid_type_pattern() {} - -#[builder(on(fn(#[attr] a: u32), into))] -fn attrs_in_on_type_pattern() {} - -#[builder(on)] -fn incomplete_on() {} - -#[builder(on())] -fn incomplete_on2() {} - -#[builder(on(_))] -fn incomplete_on3() {} - -#[builder(on(_,))] -fn incomplete_on4() {} - -#[derive(Builder)] -#[builder(start_fn())] -struct EmptyStartFn {} - -#[derive(Builder)] -struct ConflictingAttrs { - #[builder(skip, into)] - x: u32, -} - -#[derive(Builder)] -struct ConflictingAttrs2 { - #[builder(skip, name = bar)] - x: u32, -} - -#[derive(Builder)] -struct ConflictingAttrs3 { - #[builder(skip, default = 42)] - z: u32, -} - -#[builder] -fn skip_on_fn_is_unsupporetd( - #[builder(skip)] _x: u32, - #[builder(skip = "skip".to_owned())] _y: String, - #[builder(skip = vec![42])] _z: Vec, -) { -} - -#[derive(Builder)] -struct TupleStructsAreUnsupported(u32, u32); - -#[builder] -enum EnumsAreUnsupportedWithAttr {} - -#[derive(Builder)] -enum EnumsAreUnsupportedWithDerive {} - -#[builder] -fn destructuring_in_fn_is_unsupported((_, _): (u32, u32)) {} - -#[builder] -#[must_use] -#[must_use] -fn double_must_use() {} - -#[derive(Builder)] -struct InvalidWithExpr { - #[builder(with = 42)] - x: u32, -} - -struct InvalidAttrsForBonMacro; - -#[bon(attrs)] -impl InvalidAttrsForBonMacro {} diff --git a/bon/tests/integration/ui/compile_fail/errors.stderr b/bon/tests/integration/ui/compile_fail/errors.stderr deleted file mode 100644 index 117afbf9..00000000 --- a/bon/tests/integration/ui/compile_fail/errors.stderr +++ /dev/null @@ -1,279 +0,0 @@ -error: duplicate key in the map - --> tests/integration/ui/compile_fail/errors.rs:6:9 - | -6 | "Hello": "Blackjack", - | ^^^^^^^ - -error: duplicate key in the map - --> tests/integration/ui/compile_fail/errors.rs:7:9 - | -7 | "Hello": "Littlepip", - | ^^^^^^^ - -error: duplicate value in the set - --> tests/integration/ui/compile_fail/errors.rs:10:43 - | -10 | let set: BTreeSet = bon::set!["mintals", "guns", "mintals", "roses"]; - | ^^^^^^^^^ - -error: duplicate value in the set - --> tests/integration/ui/compile_fail/errors.rs:10:62 - | -10 | let set: BTreeSet = bon::set!["mintals", "guns", "mintals", "roses"]; - | ^^^^^^^^^ - -error: Only structs with named fields are supported - --> tests/integration/ui/compile_fail/errors.rs:52:1 - | -52 | struct TupleStruct(u32, u32); - | ^^^^^^ - -error: use a simple `identifier: type` syntax for the function argument; destructuring patterns in arguments aren't supported by the `#[builder]` - --> tests/integration/ui/compile_fail/errors.rs:55:18 - | -55 | fn destructuring((x, y): (u32, u32)) { - | ^^^^^^ - -error: Unexpected type `bool` - --> tests/integration/ui/compile_fail/errors.rs:61:44 - | -61 | fn unnecessary_into_false(#[builder(into = false)] _x: u32) {} - | ^^^^^ - -error: this `#[builder(into)]` attribute is redundant, because `into` is already implied for this member via the `#[builder(on(...))]` at the top of the function - --> tests/integration/ui/compile_fail/errors.rs:64:31 - | -64 | fn unnecessary_into(#[builder(into)] _x: String) {} - | ^^^^ - -error: This 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. - --> tests/integration/ui/compile_fail/errors.rs:66:15 - | -66 | #[builder(on(&dyn std::fmt::Debug, into))] - | ^^^ - -error: nested attributes are not allowed in the type pattern of #[builder(on(type_pattern, ...))] - --> tests/integration/ui/compile_fail/errors.rs:69:17 - | -69 | #[builder(on(fn(#[attr] a: u32), into))] - | ^ - -error: this empty `#[on]` attribute is unexpected; remove it or pass some parameters in parentheses: `#[on(...)]` - --> tests/integration/ui/compile_fail/errors.rs:72:11 - | -72 | #[builder(on)] - | ^^ - -error: expected at least one parameter in parentheses - --> tests/integration/ui/compile_fail/errors.rs:75:13 - | -75 | #[builder(on())] - | ^^ - -error: expected `,` - --> tests/integration/ui/compile_fail/errors.rs:78:1 - | -78 | #[builder(on(_))] - | ^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: this #[builder(on(type_pattern, ...))] contains no options to override the default behavior for the selected setters like `into`, so it does nothing - --> tests/integration/ui/compile_fail/errors.rs:81:1 - | -81 | #[builder(on(_,))] - | ^^^^^^^^^^^^^^^^^^ - | - = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: expected at least one parameter in parentheses - --> tests/integration/ui/compile_fail/errors.rs:85:19 - | -85 | #[builder(start_fn())] - | ^^ - -error: `skip` attribute can't be specified together with `into` - --> tests/integration/ui/compile_fail/errors.rs:90:15 - | -90 | #[builder(skip, into)] - | ^^^^ - -error: `skip` attribute can't be specified together with `name` - --> tests/integration/ui/compile_fail/errors.rs:96:15 - | -96 | #[builder(skip, name = bar)] - | ^^^^ - -error: `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)]` - --> tests/integration/ui/compile_fail/errors.rs:102:15 - | -102 | #[builder(skip, default = 42)] - | ^^^^ - -error: `skip` attribute is not supported on function arguments; use a local variable instead. - --> tests/integration/ui/compile_fail/errors.rs:108:15 - | -108 | #[builder(skip)] _x: u32, - | ^^^^ - -error: Only structs with named fields are supported - --> tests/integration/ui/compile_fail/errors.rs:115:1 - | -115 | struct TupleStructsAreUnsupported(u32, u32); - | ^^^^^^ - -error: only `fn` items are supported by the `#[bon::builder]` attribute - --> tests/integration/ui/compile_fail/errors.rs:117:1 - | -117 | #[builder] - | ^^^^^^^^^^ - | - = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: only `struct` items are supported by the `#[derive(bon::Builder)]` attribute - --> tests/integration/ui/compile_fail/errors.rs:120:10 - | -120 | #[derive(Builder)] - | ^^^^^^^ - | - = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: use a simple `identifier: type` syntax for the function argument; destructuring patterns in arguments aren't supported by the `#[builder]` - --> tests/integration/ui/compile_fail/errors.rs:124:39 - | -124 | fn destructuring_in_fn_is_unsupported((_, _): (u32, u32)) {} - | ^^^^^^ - -error: Found multiple #[must_use], but bon only works with exactly one (or less). - --> tests/integration/ui/compile_fail/errors.rs:128:1 - | -128 | #[must_use] - | ^ - -error: expected a closure e.g. `with = |param: T| expression` - --> tests/integration/ui/compile_fail/errors.rs:133:15 - | -133 | #[builder(with = 42)] - | ^^^^ - -error: `#[bon]` attribute does not accept any parameters yet, but it will in future releases - --> tests/integration/ui/compile_fail/errors.rs:139:7 - | -139 | #[bon(attrs)] - | ^^^^^ - -warning: unused attribute - --> tests/integration/ui/compile_fail/errors.rs:128:1 - | -128 | #[must_use] - | ^^^^^^^^^^^ help: remove this attribute - | -note: attribute also specified here - --> tests/integration/ui/compile_fail/errors.rs:127:1 - | -127 | #[must_use] - | ^^^^^^^^^^^ - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: `#[warn(unused_attributes)]` on by default - -error[E0599]: no method named `x` found for struct `SkipGeneratesNoSetterBuilder` in the current scope - --> tests/integration/ui/compile_fail/errors.rs:21:38 - | -12 | #[derive(Builder)] - | ------- method `x` not found for this struct -... -21 | SkipGeneratesNoSetter::builder().x(42).build(); - | ^ method not found in `SkipGeneratesNoSetterBuilder` - -error[E0599]: no method named `y` found for struct `SkipGeneratesNoSetterBuilder` in the current scope - --> tests/integration/ui/compile_fail/errors.rs:22:38 - | -12 | #[derive(Builder)] - | ------- method `y` not found for this struct -... -22 | SkipGeneratesNoSetter::builder().y(42).build(); - | ^ method not found in `SkipGeneratesNoSetterBuilder` - -error[E0277]: the member `Unset` was not set, but this method requires it to be set - --> tests/integration/ui/compile_fail/errors.rs:34:37 - | -34 | let _ = Example::builder().x(1).build(); - | ^^^^^ the member `Unset` was not set, but this method requires it to be set - | - = help: the trait `IsSet` is not implemented for `Unset`, which is required by `SetX: example_builder::IsComplete` - = help: the trait `IsSet` is implemented for `Set` -note: required for `SetX` to implement `example_builder::IsComplete` - --> tests/integration/ui/compile_fail/errors.rs:24:14 - | -24 | #[derive(Builder)] - | ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro -note: required by a bound in `ExampleBuilder::::build` - --> tests/integration/ui/compile_fail/errors.rs:24:14 - | -24 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `ExampleBuilder::::build` -25 | struct Example { - | ------- required by a bound in this associated function - = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the member `Unset` was not set, but this method requires it to be set - --> tests/integration/ui/compile_fail/errors.rs:34:37 - | -34 | let _ = Example::builder().x(1).build(); - | ^^^^^ the member `Unset` was not set, but this method requires it to be set - | - = help: the trait `IsSet` is not implemented for `Unset`, which is required by `SetX: example_builder::IsComplete` - = help: the trait `IsSet` is implemented for `Set` -note: required for `SetX` to implement `example_builder::IsComplete` - --> tests/integration/ui/compile_fail/errors.rs:24:14 - | -24 | #[derive(Builder)] - | ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro -note: required by a bound in `ExampleBuilder::::build` - --> tests/integration/ui/compile_fail/errors.rs:24:14 - | -24 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `ExampleBuilder::::build` -25 | struct Example { - | ------- required by a bound in this associated function - = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the member `Set` was already set, but this method requires it to be unset - --> tests/integration/ui/compile_fail/errors.rs:37:37 - | -37 | let _ = Example::builder().y(1).y(2); - | ^ the member `Set` was already set, but this method requires it to be unset - | - = help: the trait `IsUnset` is not implemented for `Set` - = help: the trait `IsUnset` is implemented for `Unset` -note: required by a bound in `ExampleBuilder::::y` - --> tests/integration/ui/compile_fail/errors.rs:24:14 - | -24 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `ExampleBuilder::::y` -... -27 | y: u32, - | - required by a bound in this associated function - = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the member `Unset` was not set, but this method requires it to be set - --> tests/integration/ui/compile_fail/errors.rs:47:32 - | -47 | let _ = Sut::builder().build(); - | ^^^^^ the member `Unset` was not set, but this method requires it to be set - | - = help: the trait `IsSet` is not implemented for `Unset`, which is required by `sut_builder::Empty: sut_builder::IsComplete` - = help: the trait `IsSet` is implemented for `Set` -note: required for `sut_builder::Empty` to implement `sut_builder::IsComplete` - --> tests/integration/ui/compile_fail/errors.rs:42:18 - | -42 | #[derive(Builder)] - | ^^^^^^^ unsatisfied trait bound introduced in this `derive` macro -note: required by a bound in `SutBuilder::::build` - --> tests/integration/ui/compile_fail/errors.rs:42:18 - | -42 | #[derive(Builder)] - | ^^^^^^^ required by this bound in `SutBuilder::::build` -43 | struct Sut { - | --- required by a bound in this associated function - = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/website/changelog.md b/website/changelog.md index 1ec93e1a..c53cfc54 100644 --- a/website/changelog.md +++ b/website/changelog.md @@ -6,6 +6,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] + +All the breaking changes are very unlikely to actually break your code using the `v2` version of `bon` unless you've been doing some crimes like using `#[doc(hidden)]` or unconventional macro delimiters like `#[builder{}/[]]` instead of `#[builder()]`. See also the "Removed" section about removed/replaced deprecated APIs. + +### Changed + +- Reject unnecessary empty attributes e.g. `#[builder()]` or `#[builder]` with no parameters on a member ([#145](https://github.com/elastio/bon/pull/145)) +- Reject non-empty `#[bon(...)]` attribute. This attribute will accept some parameters in future releases ([#145](https://github.com/elastio/bon/pull/145)) +- Reject square brackets and curly braces delimiters for `builder_type`, `finish_fn`, `start_fn` and `on` attributes syntax. Only parentheses are accepted e.g. `#[builder(finish_fn(...))]` or `#[builder(on(...))]`. This no longer works: `#[builder(finish_fn[...])]` or `#[builder(on{...})]` ([#145](https://github.com/elastio/bon/pull/145)) + +### Removed +- Removed support for `#[bon::builder]` proc-macro attribute on top of a `struct`. Use `#[derive(bon::Builder)]` for that instead. This syntax has been deprecated since `2.1` and it is now removed as part of a major version cleanup ([#145](https://github.com/elastio/bon/pull/145)) + +### Added + +- Add `#[builder(builder_type(vis = "..."))]` that allows overriding the visibility of the builder struct ([#145](https://github.com/elastio/bon/pull/145)) +- Add `#[builder(finish_fn(vis = "..."))]` that allows overriding the visibility of the finishing function ([#145](https://github.com/elastio/bon/pull/145)) +- Add `#[builder(with = closure)]` syntax to customize setters with a custom closure. If the closure returns a `Result<_, E>` the setters become fallible ([#145](https://github.com/elastio/bon/pull/145)) +- Add `#[builder(transparent)]` for `Option` fields to opt out from their special handling which makes `bon` treat them as regular required fields ([#145](https://github.com/elastio/bon/pull/145)) +- Add `#[builder(state_mod)]` to configure the builder's type state API module name, visibility and docs ([#145](https://github.com/elastio/bon/pull/145)) +- Add `#[builder(overwritable)]` and `#[builder(on(..., overwritable)]` to make it possible to call setters multiple times for the same member ([#145](https://github.com/elastio/bon/pull/145)) +- Add `#[builder(setters)]` to fine-tune the setters names, visibility and docs ([#145](https://github.com/elastio/bon/pull/145)) +- Add `#[builder(derive(Clone/Debug(bounds(...))]` to allow overriding trait bounds on the `Clone/Debug` impl block of the builder ([#145](https://github.com/elastio/bon/pull/145)) +- Improve rustdoc output ([#145](https://github.com/elastio/bon/pull/145)) + - Add info that the member is required or optional. + - For members with defaults values show the default value in the docs. + - For optional members provide a link to a companion setter. The docs for `{member}(T)` setter mention the `maybe_{member}(Option)` setter and vice versa. + - Remove `__` prefixes for generic types and lifetimes from internal symbols. Instead, the prefixes added only if the macro detects a name collision. +- Add inheritance of `#[allow()]` and `#[expect()]` lint attributes to all generated items. This is useful to suppress any lints coming from the generated code. Although, lints coming from the generated code are generally considered defects in `bon` and should be reported via a Github issue but this provides an easy temporary workaround the problem ([#145](https://github.com/elastio/bon/pull/145)) + +### Fixed + +- Fixed `#[cfg/cfg_attr()]` not being expanded when used on function arguments with doc comments or other attributes. + + ## [2.3.0](https://github.com/elastio/bon/compare/v2.2.1...v2.3.0) - 2024-09-14 See the [blog post for this release](https://elastio.github.io/bon/blog/bon-builder-v2-3-release) that describes some of the most notable changes in detail. @@ -37,11 +71,13 @@ See the [blog post for this release](https://elastio.github.io/bon/blog/bon-buil There is a CLI to assist in migrating to the new syntax. See the [release blog post](https://elastio.github.io/bon/blog/bon-builder-v2-2-release#derive-builder-syntax-for-structs) for details about that. ### Added + - Add the top-level `#[builder(derive(...))]` attribute to be able to derive `Clone` and `Debug` for the builder type itself ([#113](https://github.com/elastio/bon/pull/113)) - Add support for conditional compilation with `cfg/cfg_attr` ([#99](https://github.com/elastio/bon/pull/99)) ### Fixed + - Fix developer experience in Rust Rover. The new `#[derive(Builder)]` syntax should now be easier for Rust Rover to analyze ([#99](https://github.com/elastio/bon/pull/99)) - Fix a bug where a member of opaque `Option` type (i.e. the `Option` type that was renamed to make the builder macro not detect it as `Option`) was still optional. ([#99](https://github.com/elastio/bon/pull/99)) - Fix code generation for structs with default values for generic parameters ([#108](https://github.com/elastio/bon/pull/108)) @@ -49,9 +85,11 @@ See the [blog post for this release](https://elastio.github.io/bon/blog/bon-buil ## [2.1.1](https://github.com/elastio/bon/compare/v2.1.0...v2.1.1) - 2024-09-03 ### Added + - Set MSRV to 1.70.0. Note that we plan to set an even lower MSRV. This is just an initial attempt to define the MSRV that should be good enough in the meantime while we work on lowering it even more ([#101](https://github.com/elastio/bon/pull/101)) ### Fixed + - Fix lints triggered by generated code such as `private_bounds`, `clippy::missing_const_for_fn` ([#101](https://github.com/elastio/bon/pull/101)) - Add more context to the messages such that it's clear what member isn't set in Rust Analyzer error messages ([#98](https://github.com/elastio/bon/pull/98)) @@ -60,6 +98,7 @@ See the [blog post for this release](https://elastio.github.io/bon/blog/bon-buil See the [blog post for this release](https://elastio.github.io/bon/blog/bon-builder-v2-1-release) that describes some of the most notable changes in detail. ### Added + - `#[must_use]` on the `build()` method for structs and `call()` for functions (if the original function has `#[must_use]`) ([#82](https://github.com/elastio/bon/pull/82)). Thanks [@EdJoPaTo](https://github.com/EdJoPaTo) for the contribution! ### Changed @@ -68,24 +107,29 @@ See the [blog post for this release](https://elastio.github.io/bon/blog/bon-buil - Improve builder() method docs ([#76](https://github.com/elastio/bon/pull/76)). Thanks [@EdJoPaTo](https://github.com/EdJoPaTo) for the contribution! ### Fixed + - Don't warn on `clippy::impl_trait_in_params` ([#80](https://github.com/elastio/bon/pull/80)). Thanks [@EdJoPaTo](https://github.com/EdJoPaTo) for the contribution! - Fix typos in messages and code comments ([#79](https://github.com/elastio/bon/pull/79)). Thanks [@EdJoPaTo](https://github.com/EdJoPaTo) for the contribution! ### Other + - Add more tests for `#[must_use]` ([#87](https://github.com/elastio/bon/pull/87)) ## [2.0.1](https://github.com/elastio/bon/compare/v2.0.0...v2.0.1) - 2024-08-28 ### Docs + - Add a new section ["`None` literals inference"](https://elastio.github.io/bon/guide/patterns/into-conversions-in-depth#none-literals-inference) to docs for "Into Conversions In-Depth" - Fix the docs about the comparison of Into conversions on the ["Alternatives"](http://elastio.github.io/bon/guide/alternatives) page that were not updated during the v2 release ### Fixed + - Fix capturing of generic params that appear only in return types ([#72](https://github.com/elastio/bon/pull/72)) - Fix support for associated types ([#72](https://github.com/elastio/bon/pull/72)) ### Internal + - Add more tests for various edge cases ([#70](https://github.com/elastio/bon/pull/70)) ## [2.0.0](https://github.com/elastio/bon/compare/v1.2.1...v2.0.0) - 2024-08-26 @@ -95,16 +139,19 @@ See the [blog post](https://elastio.github.io/bon/blog/bon-builder-generator-v2- ## [1.2.1](https://github.com/elastio/bon/compare/v1.2.0...v1.2.1) - 2024-08-12 ### Other + - Remove unnecessary const block ([#52](https://github.com/elastio/bon/pull/52)) - Small cleanup ([#51](https://github.com/elastio/bon/pull/51)) ## [1.2.0](https://github.com/elastio/bon/compare/v1.1.0...v1.2.0) - 2024-08-09 ### Added + - Add `#[builder(skip)]` attribute to skip generating setters ([#44](https://github.com/elastio/bon/pull/44)) - Add automatic docs for setters ([#45](https://github.com/elastio/bon/pull/45)) ### Other + - Remove dependencies on `easy-ext`, `heck` and `itertools` ([#42](https://github.com/elastio/bon/pull/42)) ## [1.1.0](https://github.com/elastio/bon/compare/v1.0.6...v1.1.0) - 2024-08-07 @@ -122,35 +169,42 @@ See the [blog post](https://elastio.github.io/bon/blog/bon-builder-generator-v2- ## [1.0.6](https://github.com/elastio/bon/compare/v1.0.5...v1.0.6) - 2024-08-01 ### Fixed + - Explicitly specify the minimum required version of the darling dependency ([#30](https://github.com/elastio/bon/pull/30)) ## [1.0.5](https://github.com/elastio/bon/compare/v1.0.4...v1.0.5) - 2024-07-31 ### Added + - Add `#[must_use]` to the builder and other small improvements ([#26](https://github.com/elastio/bon/pull/26)) ## [1.0.4](https://github.com/elastio/bon/compare/v1.0.3...v1.0.4) - 2024-07-30 ### Fixed + - new() method is now hidden by default and the Builder type name is the same as when `#[builder]` is on top of a `struct` ([#19](https://github.com/elastio/bon/pull/19)) ## [1.0.3](https://github.com/elastio/bon/compare/v1.0.2...v1.0.3) - 2024-07-30 ### Fixed + - Fix missing captured generics on an impl block that aren't referenced in the method ([#17](https://github.com/elastio/bon/pull/17)) ## [1.0.2](https://github.com/elastio/bon/compare/v1.0.1...v1.0.2) - 2024-07-29 ### Fixed + - Fix a bug of the `Default` trait requirement for types under an `Option` ([#13](https://github.com/elastio/bon/pull/13)) - Fix the link to docs.rs to so that it references the latest version ([#11](https://github.com/elastio/bon/pull/11)) ## [1.0.1](https://github.com/elastio/bon/compare/v1.0.0...v1.0.1) - 2024-07-29 ### Fixed + - Fix handling of raw identifiers ([#9](https://github.com/elastio/bon/pull/9)) ### Other + - Add example snippet to the docs for "adding builder to existing code" ([#7](https://github.com/elastio/bon/pull/7)) ## [1.0.0](https://github.com/elastio/bon/tree/v1.0.0) - 2024-07-28 From db8dcd2fc78b548ee41a40aacc6d3969c63429c4 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 19 Oct 2024 15:00:16 +0000 Subject: [PATCH 114/119] Self-review part 3 --- .../builder_gen/builder_params/on_params.rs | 25 ++++++++++--------- .../builder/builder_gen/member/params/mod.rs | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/builder_params/on_params.rs b/bon-macros/src/builder/builder_gen/builder_params/on_params.rs index 272f166b..1acf1d23 100644 --- a/bon-macros/src/builder/builder_gen/builder_params/on_params.rs +++ b/bon-macros/src/builder/builder_gen/builder_params/on_params.rs @@ -32,14 +32,17 @@ impl Parse for OnParams { // field is added to `Parsed` and unused here, then a compiler // warning is emitted. let Parsed { into, overwritable } = &parsed; + let flags = [("into", into), ("overwritable", overwritable)]; - if !into.is_present() && !overwritable.is_present() { - return Err(syn::Error::new_spanned( - &rest, - "this #[builder(on(type_pattern, ...))] contains no options to override \ - the default behavior for the selected setters like `into`, `overwritable`, \ - so it does nothing", - )); + if flags.iter().all(|(_, flag)| !flag.is_present()) { + let flags = flags.iter().map(|(name, _)| format!("`{name}`")).join(", "); + let err = format!( + "this #[builder(on(type_pattern, ...))] contains no options \ + to override the default behavior for the selected setters \ + like {flags}, so it does nothing" + ); + + return Err(syn::Error::new_spanned(&rest, err)); } } @@ -55,9 +58,8 @@ impl Parse for OnParams { let mut find_attr = FindAttr { attr: None }; find_attr.visit_type(&type_pattern); - let attr_in_type_pattern = find_attr.attr; - if let Some(attr) = attr_in_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 \ @@ -65,9 +67,8 @@ impl Parse for OnParams { )); } - // Validate that the 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. + // 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!( diff --git a/bon-macros/src/builder/builder_gen/member/params/mod.rs b/bon-macros/src/builder/builder_gen/member/params/mod.rs index d8bad33c..3beb71c2 100644 --- a/bon-macros/src/builder/builder_gen/member/params/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/params/mod.rs @@ -59,7 +59,7 @@ pub(crate) struct MemberParams { pub(crate) with: Option>, /// Disables the special handling for a member of type `Option`. The - /// member no longer has the default on `None`. It also becomes a required + /// 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) transparent: darling::util::Flag, From 6a100209c26cde760f72bdb4ec51571902ccdfaa Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 19 Oct 2024 20:02:35 +0000 Subject: [PATCH 115/119] Self-review part 4 --- .../builder/builder_gen/builder_derives.rs | 39 ++++---- .../builder/builder_gen/builder_params/mod.rs | 4 +- .../src/builder/builder_gen/finish_fn.rs | 18 ++-- .../src/builder/builder_gen/input_fn.rs | 13 ++- .../builder_gen/member/into_conversion.rs | 8 +- .../src/builder/builder_gen/member/named.rs | 29 +++--- .../member/params/setter_closure.rs | 20 ++-- bon-macros/src/builder/builder_gen/mod.rs | 4 +- .../src/builder/builder_gen/setters/mod.rs | 85 ++++++++--------- bon-macros/src/collections/map.rs | 4 +- .../integration/ui/compile_fail/attr_bon.rs | 9 ++ .../ui/compile_fail/attr_bon.stderr | 6 ++ .../ui/compile_fail/attr_builder.stderr | 2 +- .../integration/ui/compile_fail/attr_with.rs | 19 ++++ .../ui/compile_fail/attr_with.stderr | 91 +++++++++++++++++-- 15 files changed, 241 insertions(+), 110 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index 3be99809..8d7d334a 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -25,12 +25,15 @@ impl BuilderGenCtx { /// 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: &BuilderDerive, ) -> TokenStream { - let additional_predicates = derive + let derive_specific_predicates = derive .bounds .as_ref() .map(ToTokens::to_token_stream) @@ -52,12 +55,12 @@ impl BuilderGenCtx { } }); - let base_predicates = self.generics.where_clause_predicates(); + let inherent_item_predicates = self.generics.where_clause_predicates(); quote! { where - #( #base_predicates, )* - #additional_predicates + #( #inherent_item_predicates, )* + #derive_specific_predicates } } @@ -81,16 +84,20 @@ impl BuilderGenCtx { }); let clone_start_fn_args = self.start_fn_args().next().map(|_| { - let clone_start_fn_args = self.start_fn_args().map(|arg| { - let ty = &arg.base.ty.norm; - let index = &arg.index; - quote! { - <#ty as #clone>::clone(&self.#start_fn_args_field.#index) - } - }); + let types = self.start_fn_args().map(|arg| &arg.base.ty.norm); + let indices = self.start_fn_args().map(|arg| &arg.index); quote! { - #start_fn_args_field: ( #(#clone_start_fn_args,)* ), + // 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` + // ``` + #start_fn_args_field: ( + #( <#types as #clone>::clone(&self.#start_fn_args_field.#indices), )* + ), } }); @@ -100,9 +107,9 @@ impl BuilderGenCtx { let clone_named_members = self.named_members().map(|member| { let member_index = &member.index; - // The type hints here are necessary to get better error messages - // that point directly to the types that don't implement `Clone` - // in the input code using the span info from the type hints. + // 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! { @@ -137,7 +144,7 @@ impl BuilderGenCtx { // one of the members doesn't implement `Clone`. This avoids // a sentence that say smth like // ``` - // required for `(...huge tuple type...)` to implement `Clone` + // required for `(...big type...)` to implement `Clone` // ``` #named_members_field: ( #( #clone_named_members, )* ), } diff --git a/bon-macros/src/builder/builder_gen/builder_params/mod.rs b/bon-macros/src/builder/builder_gen/builder_params/mod.rs index 526b846f..67e9413b 100644 --- a/bon-macros/src/builder/builder_gen/builder_params/mod.rs +++ b/bon-macros/src/builder/builder_gen/builder_params/mod.rs @@ -70,14 +70,14 @@ impl FromMeta for BuilderDerive { 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, } - meta.require_list()?.require_parens_delim()?; - let Parsed { bounds } = Parsed::from_meta(meta)?; Ok(Self { diff --git a/bon-macros/src/builder/builder_gen/finish_fn.rs b/bon-macros/src/builder/builder_gen/finish_fn.rs index fe06bcc0..199ddf34 100644 --- a/bon-macros/src/builder/builder_gen/finish_fn.rs +++ b/bon-macros/src/builder/builder_gen/finish_fn.rs @@ -3,9 +3,6 @@ use crate::util::prelude::*; impl super::BuilderGenCtx { fn finish_fn_member_expr(&self, member: &Member) -> TokenStream { - let start_fn_args_field = &self.ident_pool.start_fn_args; - let named_members_field = &self.ident_pool.named_members; - let member = match member { Member::Named(member) => member, Member::Skipped(member) => { @@ -18,6 +15,8 @@ impl super::BuilderGenCtx { } Member::StartFnArg(member) => { let index = &member.index; + let start_fn_args_field = &self.ident_pool.start_fn_args; + return quote! { self.#start_fn_args_field.#index }; } Member::FinishFnArg(member) => { @@ -27,6 +26,7 @@ impl super::BuilderGenCtx { let index = &member.index; + let named_members_field = &self.ident_pool.named_members; let member_field = quote! { self.#named_members_field.#index }; @@ -58,7 +58,6 @@ impl super::BuilderGenCtx { 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; } @@ -66,12 +65,16 @@ impl super::BuilderGenCtx { quote! { unsafe { // SAFETY: we know that the member is set because we are in - // the `finish` function because this method uses the trait - // bounds of `IsSet` for every required member. It's also + // 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. ::core::option::Option::unwrap_unchecked(#member_field) } } @@ -92,6 +95,9 @@ impl super::BuilderGenCtx { // 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! { diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index a899f4a6..67450acd 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -112,10 +112,15 @@ impl FnInputCtx<'_> { None => return Ok(None), }; - if let [attr, ..] = receiver.attrs.as_slice() { + 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, - "attributes on the receiver are not supported in the #[builder] macro" + "#[builder] attributes on the receiver are not supported" ); } @@ -581,7 +586,7 @@ fn get_must_use_attribute(attrs: &[syn::Attribute]) -> Result Result TokenStream { - let has_into = self.params.into.is_present(); let ty = &self.ty.norm; let ident = &self.ident; - if has_into { + if self.params.into.is_present() { quote! { #ident: impl Into<#ty> } } else { quote! { #ident: #ty } @@ -62,12 +61,11 @@ impl PositionalFnArgMember { } pub(crate) fn maybe_into_ident_expr(&self) -> TokenStream { - let has_into = self.params.into.is_present(); let ident = &self.ident; - if has_into { + if self.params.into.is_present() { quote! { - ::core::convert::Into::into(#ident) + Into::into(#ident) } } else { ident.to_token_stream() diff --git a/bon-macros/src/builder/builder_gen/member/named.rs b/bon-macros/src/builder/builder_gen/member/named.rs index b64e57d5..04e4a3c4 100644 --- a/bon-macros/src/builder/builder_gen/member/named.rs +++ b/bon-macros/src/builder/builder_gen/member/named.rs @@ -25,7 +25,7 @@ pub(crate) struct MemberName { /// `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. It can only specify the snake case + /// 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, @@ -153,19 +153,16 @@ impl NamedMember { if self.is_required() { let SettersFnParams { some_fn, option_fn } = &setters.fns; - let unexpected_setter = [option_fn, some_fn].into_iter().find_map(Option::as_ref); + let unexpected_setter = option_fn.as_ref().or(some_fn.as_ref()); - let setter = match unexpected_setter { - Some(setter) => setter, - None => return Ok(()), - }; - - bail!( - &setter.key, - "`{}` setter function applies only to members with `#[builder(default)]` \ - or members of `Option` type (if #[builder(transparent)] is not set)", - setter.key - ); + 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(transparent)] is not set)", + setter.key + ); + } } if let SettersFnParams { @@ -183,7 +180,7 @@ impl NamedMember { Ok(()) } - // Lint from nightly. `&Option` is used to reduce syntax at the callsite + // Lint from nightly. `&Option` is used to reduce syntax at the call site #[allow(unknown_lints, clippy::ref_option)] fn validate_unused_setters_cfg( overrides: &[&SpannedKey], @@ -224,7 +221,7 @@ impl NamedMember { } /// Returns `false` if the member has a default value. It means this member - /// is required to be set before the building can be finished. + /// is required to be set before building can be finished. pub(crate) fn is_required(&self) -> bool { self.params.default.is_none() && !self.is_special_option_ty() } @@ -232,7 +229,7 @@ impl NamedMember { /// 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 the building can be finished. + /// default values are set before building can be finished. pub(crate) fn is_stateful(&self) -> bool { self.is_required() || !self.params.overwritable.is_present() } diff --git a/bon-macros/src/builder/builder_gen/member/params/setter_closure.rs b/bon-macros/src/builder/builder_gen/member/params/setter_closure.rs index 60d6d0ed..e93e5102 100644 --- a/bon-macros/src/builder/builder_gen/member/params/setter_closure.rs +++ b/bon-macros/src/builder/builder_gen/member/params/setter_closure.rs @@ -7,22 +7,23 @@ 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*; + 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 + 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* + 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 type doesn't have to be named `Result` exactly, the only requirement is + 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 has an `Option` type and no `#[builder(transparent)]` annotation"; +(*) underlying type is the type of the member stripped from the `Option` wrapper + if this member is of `Option` type and no `#[builder(transparent)]` annotation + is present"; #[derive(Debug)] pub(crate) struct SetterClosure { @@ -68,12 +69,12 @@ impl FromMeta for SetterClosure { let ty = ty .as_generic_angle_bracketed_path(|last_segment| { // We allow for arbitrary `Result` type variations - // including + // including custom type aliases like `ApiResult<_>` last_segment.to_string().ends_with("Result") }) .ok_or_else(err)?; - if ty.args.len() != 1 && ty.args.len() != 2 { + if !(1..=2).contains(&ty.args.len()) { return Err(err()); } @@ -94,6 +95,9 @@ impl FromMeta for SetterClosure { 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() diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index f3ee3547..3283c3e4 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -244,10 +244,10 @@ impl BuilderGenCtx { // const operations. clippy::missing_const_for_fn, )] - #vis fn #start_fn_ident<#(#generics_decl),*>( + #vis fn #start_fn_ident< #(#generics_decl),* >( #receiver #(#start_fn_params,)* - ) -> #builder_ident<#(#generic_args,)*> + ) -> #builder_ident< #(#generic_args,)* > #where_clause { #ide_hints diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs index 034dec83..dab66e3d 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -23,23 +23,23 @@ impl<'a> SettersCtx<'a> { fn setter_for_required_member(&self, item: SetterItem) -> TokenStream { let input; - let value; + let expr; let member_type = self.member.ty.norm.as_ref(); if let Some(closure) = &self.member.params.with { input = Self::underlying_input_from_closure(closure); - value = self.member_value_from_closure(closure); + expr = self.member_expr_from_closure(closure); } else if self.member.params.into.is_present() { input = quote!(value: impl Into<#member_type>); - value = quote!(Into::into(value)); + expr = quote!(Into::into(value)); } else { input = quote!(value: #member_type); - value = quote!(value); + expr = quote!(value); }; let body = SetterBody::SetMember { - value: quote!(Some(#value)), + expr: quote!(::core::option::Option::Some(#expr)), }; self.setter_method(Setter { @@ -80,7 +80,7 @@ impl<'a> SettersCtx<'a> { imp: SetterImpl { input: quote!(value: Option<#underlying_ty>), body: SetterBody::SetMember { - value: if self.member.params.into.is_present() { + expr: if self.member.params.into.is_present() { quote! { Option::map(value, Into::into) } @@ -111,7 +111,7 @@ impl<'a> SettersCtx<'a> { } }; - let idents = tuple_if_many(quote!( #( #idents ),* )); + let ident_maybe_tuple = tuple_if_many(quote!( #( #idents ),* )); let some_fn = Setter { item: items.some_fn, @@ -121,7 +121,7 @@ impl<'a> SettersCtx<'a> { body: { let option_fn_name = &items.option_fn.name; quote! { - self.#option_fn_name(Some(#idents)) + self.#option_fn_name(Some(#ident_maybe_tuple)) } }, }, @@ -135,11 +135,13 @@ impl<'a> SettersCtx<'a> { quote!(value: Option<#input_types>) }, body: SetterBody::SetMember { - value: { - let value = self.member_value_from_closure(closure); + expr: { + let expr = self.member_expr_from_closure(closure); quote! { + // Not using `Option::map` here because the `#expr` + // can contain a `?` operator for a fallible operation. match value { - Some(#idents) => Some(#value), + Some(#ident_maybe_tuple) => Some(#expr), None => None, } } @@ -172,12 +174,12 @@ impl<'a> SettersCtx<'a> { } } - fn member_value_from_closure(&self, closure: &SetterClosure) -> TokenStream { + fn member_expr_from_closure(&self, closure: &SetterClosure) -> TokenStream { let body = &closure.body; let ty = self.member.underlying_norm_ty().to_token_stream(); - let output = Self::maybe_wrap_in_result(closure, ty.to_token_stream()); + let output = Self::maybe_wrap_in_result(closure, ty); // Avoid wrapping the body in a block if it's already a block. let body = if matches!(body.as_ref(), syn::Expr::Block(_)) { @@ -218,16 +220,18 @@ impl<'a> SettersCtx<'a> { let body = match imp.body { SetterBody::Forward { body } => body, - SetterBody::SetMember { value } => { - let index = &self.member.index; - - let fields = &self.base.ident_pool; - let phantom_field = &fields.phantom; - let receiver_field = &fields.receiver; - let start_fn_args_field = &fields.start_fn_args; - let named_members_field = &fields.named_members; - - let mut output = if self.member.is_stateful() { + SetterBody::SetMember { expr } => { + let idents = &self.base.ident_pool; + let phantom_field = &idents.phantom; + let receiver_field = &idents.receiver; + let start_fn_args_field = &idents.start_fn_args; + let named_members_field = &idents.named_members; + + let mut output = if !self.member.is_stateful() { + quote! { + self + } + } else { let builder_ident = &self.base.builder_type.ident; let maybe_receiver_field = self @@ -249,10 +253,6 @@ impl<'a> SettersCtx<'a> { #named_members_field: self.#named_members_field, } } - } else { - quote! { - self - } }; let result_output = self @@ -267,8 +267,9 @@ impl<'a> SettersCtx<'a> { output = quote!(#result_path::Ok(#output)); } + let index = &self.member.index; quote! { - self.#named_members_field.#index = #value; + self.#named_members_field.#index = #expr; #output } } @@ -293,13 +294,11 @@ impl<'a> SettersCtx<'a> { return_type = Self::maybe_wrap_in_result(closure, return_type); } - let state_var = &self.base.state_var; - let where_clause = (!self.member.params.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, + where #state_var::#member_pascal: #state_mod::IsUnset, } }); @@ -343,7 +342,7 @@ enum SetterBody { Forward { body: TokenStream }, /// The setter sets the member as usual and transitions the builder state. - SetMember { value: TokenStream }, + SetMember { expr: TokenStream }, } enum SettersItems { @@ -364,11 +363,8 @@ struct SetterItem { impl SettersItems { fn new(ctx: &SettersCtx<'_>) -> Self { - let SettersCtx { - member, - base: builder_gen, - } = ctx; - let builder_type = &builder_gen.builder_type; + let SettersCtx { member, base } = ctx; + let builder_type = &base.builder_type; let params = member.params.setters.as_ref(); @@ -407,16 +403,21 @@ impl SettersItems { .cloned() .unwrap_or_else(|| { let base_name = common_name.unwrap_or(&member.name.snake); - // Preserve the original identifier span to make IDE's - // "go to definition" works correctly - format_ident!("maybe_{}", base_name) + // 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}"), base_name.span()) }); let default = member.params.default.as_deref().and_then(|default| { let default = default .clone() .or_else(|| well_known_default(&member.ty.norm)) - .unwrap_or_else(|| syn::parse_quote!(Default::default())); + .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); diff --git a/bon-macros/src/collections/map.rs b/bon-macros/src/collections/map.rs index eb195a8e..9947a113 100644 --- a/bon-macros/src/collections/map.rs +++ b/bon-macros/src/collections/map.rs @@ -23,8 +23,8 @@ pub(crate) fn generate(entries: Punctuated<(Expr, Expr), Token![,]>) -> TokenStr let items = entries.into_iter().map(|(key, value)| { quote!(( - ::core::convert::Into::into(#key), - ::core::convert::Into::into(#value), + Into::into(#key), + Into::into(#value), )) }); diff --git a/bon/tests/integration/ui/compile_fail/attr_bon.rs b/bon/tests/integration/ui/compile_fail/attr_bon.rs index f2d24b9a..c84acc9e 100644 --- a/bon/tests/integration/ui/compile_fail/attr_bon.rs +++ b/bon/tests/integration/ui/compile_fail/attr_bon.rs @@ -8,4 +8,13 @@ impl InvalidAttrsForBonMacro { fn sut() {} } +struct BuilderAttrOnReceiver; + +#[bon] +impl BuilderAttrOnReceiver { + #[builder] + fn sut(#[builder] &self) {} +} + + fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_bon.stderr b/bon/tests/integration/ui/compile_fail/attr_bon.stderr index 312d2b3c..28613d73 100644 --- a/bon/tests/integration/ui/compile_fail/attr_bon.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_bon.stderr @@ -3,3 +3,9 @@ error: `#[bon]` attribute does not accept any parameters yet, but it will in fut | 5 | #[bon(attrs)] | ^^^^^ + +error: #[builder] attributes on the receiver are not supported + --> tests/integration/ui/compile_fail/attr_bon.rs:16:12 + | +16 | fn sut(#[builder] &self) {} + | ^ diff --git a/bon/tests/integration/ui/compile_fail/attr_builder.stderr b/bon/tests/integration/ui/compile_fail/attr_builder.stderr index 3922c979..625f3eff 100644 --- a/bon/tests/integration/ui/compile_fail/attr_builder.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_builder.stderr @@ -66,7 +66,7 @@ error: only `fn` items are supported by the `#[bon::builder]` attribute | = note: this error originates in the attribute macro `builder` (in Nightly builds, run with -Z macro-backtrace for more info) -error: Found multiple #[must_use], but bon only works with exactly one or zero. +error: found multiple #[must_use], but bon only works with exactly one or zero. --> tests/integration/ui/compile_fail/attr_builder.rs:59:1 | 59 | #[must_use] diff --git a/bon/tests/integration/ui/compile_fail/attr_with.rs b/bon/tests/integration/ui/compile_fail/attr_with.rs index b93c7160..c1a0d1c6 100644 --- a/bon/tests/integration/ui/compile_fail/attr_with.rs +++ b/bon/tests/integration/ui/compile_fail/attr_with.rs @@ -62,4 +62,23 @@ struct InvalidReturnTypeInImplTraitClosure { value: u32, } +#[derive(Builder)] +struct NoGenericArgsResultReturnType1 { + #[builder(with = |value: u32| -> Result {})] + value: u32, +} + +#[derive(Builder)] +struct NoGenericArgsResultReturnType2 { + #[builder(with = |value: u32| -> Result<> {})] + value: u32, +} + +#[derive(Builder)] +struct ThreeGenericArgsResultReturnType { + #[builder(with = |value: u32| -> ::core::result::Result {})] + value: u32, +} + + fn main() {} diff --git a/bon/tests/integration/ui/compile_fail/attr_with.stderr b/bon/tests/integration/ui/compile_fail/attr_with.stderr index 07b35ec3..f9954c81 100644 --- a/bon/tests/integration/ui/compile_fail/attr_with.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_with.stderr @@ -44,27 +44,106 @@ error: 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*; + 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 + 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* + 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 type doesn't have to be named `Result` exactly, the only requirement is + 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 has an `Option` type and no `#[builder(transparent)]` annotation + (*) underlying type is the type of the member stripped from the `Option` wrapper + if this member is of `Option` type and no `#[builder(transparent)]` annotation + is present --> tests/integration/ui/compile_fail/attr_with.rs:47:34 | 47 | #[builder(with = |x: u32| -> u32 { x + 1 })] | ^^^ +error: 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(transparent)]` annotation + is present + --> tests/integration/ui/compile_fail/attr_with.rs:67:38 + | +67 | #[builder(with = |value: u32| -> Result {})] + | ^^^^^^ + +error: 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(transparent)]` annotation + is present + --> tests/integration/ui/compile_fail/attr_with.rs:73:38 + | +73 | #[builder(with = |value: u32| -> Result<> {})] + | ^^^^^^ + +error: 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(transparent)]` annotation + is present + --> tests/integration/ui/compile_fail/attr_with.rs:79:38 + | +79 | #[builder(with = |value: u32| -> ::core::result::Result {})] + | ^ + error[E0308]: mismatched types --> tests/integration/ui/compile_fail/attr_with.rs:54:12 | From be13024c0ae79a984dbfe33670b4609c34ee2b6d Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 19 Oct 2024 21:50:35 +0000 Subject: [PATCH 116/119] Self-revie part 5 --- .../src/builder/builder_gen/finish_fn.rs | 8 +-- .../src/builder/builder_gen/input_fn.rs | 64 +++++++++--------- .../src/builder/builder_gen/input_struct.rs | 22 ++----- .../builder_gen/member/into_conversion.rs | 2 +- bon-macros/src/builder/builder_gen/mod.rs | 66 +++++++++++-------- bon-macros/src/builder/builder_gen/models.rs | 55 +++++++++++++--- .../src/builder/builder_gen/state_mod.rs | 50 ++++++++------ bon-macros/src/builder/item_impl.rs | 6 +- bon-macros/src/builder/mod.rs | 2 +- bon-macros/src/normalization/mod.rs | 29 ++------ .../src/normalization/syntax_variant.rs | 28 ++++++++ .../ui/compile_fail/attr_builder.stderr | 8 +-- 12 files changed, 197 insertions(+), 143 deletions(-) create mode 100644 bon-macros/src/normalization/syntax_variant.rs diff --git a/bon-macros/src/builder/builder_gen/finish_fn.rs b/bon-macros/src/builder/builder_gen/finish_fn.rs index 199ddf34..4a274853 100644 --- a/bon-macros/src/builder/builder_gen/finish_fn.rs +++ b/bon-macros/src/builder/builder_gen/finish_fn.rs @@ -20,7 +20,7 @@ impl super::BuilderGenCtx { return quote! { self.#start_fn_args_field.#index }; } Member::FinishFnArg(member) => { - return member.maybe_into_ident_expr(); + return member.init_expr(); } }; @@ -118,11 +118,7 @@ impl super::BuilderGenCtx { let unsafety = &self.finish_fn.unsafety; let must_use = &self.finish_fn.must_use; let attrs = &self.finish_fn.attrs; - let finish_fn_vis = self - .finish_fn - .vis - .as_ref() - .unwrap_or(&self.builder_type.vis); + 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; diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index 67450acd..5ec08a71 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -1,7 +1,8 @@ use super::builder_params::BuilderParams; +use super::models::FinishFnParams; use super::{ - AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, - Member, MemberOrigin, RawMember, + AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFnBody, Generics, Member, + MemberOrigin, RawMember, }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; use crate::normalization::{GenericsNamespace, NormalizeSelfTy, SyntaxVariant}; @@ -234,9 +235,28 @@ impl FnInputCtx<'_> { }) // By default we don't want to expose the positional function, so we // hide it under a generated name to avoid name conflicts. + // + // We don't use a random name here because the name of this 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. The name of the span + // may be indexed in some logs database (e.g. Grafana Loki). If the name + // of the span changes the DB index may grow and also log queries won't + // be stable. .unwrap_or_else(|| format_ident!("__orig_{}", orig_ident.raw_name())); - strip_known_attrs_from_args(&mut orig.sig); + // 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")); + } // 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 @@ -312,30 +332,27 @@ impl FnInputCtx<'_> { let builder_ident = self.builder_ident(); - let typed_args = self + let members = self .fn_item - .apply_ref(|fn_item| fn_item.sig.inputs.iter().filter_map(syn::FnArg::as_typed)); - - let members = typed_args - .norm - .zip(typed_args.orig) - .map(|(norm_arg, orig_arg)| { - let pat = match norm_arg.pat.as_ref() { + .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!( - &orig_arg.pat, + &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: norm_arg.ty.clone(), - orig: orig_arg.ty.clone(), + norm: arg.norm.ty.clone(), + orig: arg.orig.ty.clone(), }; Ok(RawMember { - attrs: &norm_arg.attrs, + attrs: &arg.norm.attrs, ident: pat.ident.clone(), ty, }) @@ -386,7 +403,7 @@ impl FnInputCtx<'_> { }] }); - let finish_fn = FinishFn { + let finish_fn = FinishFnParams { ident: finish_fn_ident, vis: finish_fn_vis.map(SpannedKey::into_value), unsafety: self.fn_item.norm.sig.unsafety, @@ -513,21 +530,6 @@ impl FinishFnBody for FnCallBody { } } -/// 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. -fn strip_known_attrs_from_args(sig: &mut syn::Signature) { - for arg in &mut sig.inputs { - arg.attrs_mut() - .retain(|attr| !attr.is_doc_expr() && !attr.path().is_ident("builder")); - } -} - /// 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( diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 08fa07a0..6514be16 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -1,6 +1,6 @@ use super::builder_params::BuilderParams; use super::{ - AssocMethodCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, Member, MemberOrigin, + AssocMethodCtx, BuilderGenCtx, FinishFnBody, Generics, Member, MemberOrigin, RawMember, }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; @@ -11,6 +11,7 @@ use darling::FromMeta; use std::borrow::Cow; use syn::visit::Visit; use syn::visit_mut::VisitMut; +use super::models::FinishFnParams; #[derive(Debug, FromMeta)] pub(crate) struct StructInputParams { @@ -48,20 +49,7 @@ impl StructInputParams { ), }; - if !matches!(meta.delimiter, syn::MacroDelimiter::Paren(_)) { - bail!( - &meta, - "wrong delimiter {:?}, expected `#[builder(...)]` syntax", - meta.delimiter - ); - } - - if meta.tokens.is_empty() { - bail!( - &meta, - "this empty `#[builder()]` attribute is redundant; remove it" - ); - } + crate::parsing::require_non_empty_paren_meta_list(&attr.meta)?; let meta = darling::ast::NestedMeta::parse_meta_list(meta.tokens.clone())?; @@ -189,7 +177,7 @@ impl StructInputCtx { .unwrap_or_else(|| syn::Ident::new("build", start_fn_ident.span())); let struct_ty = &self.struct_ty; - let finish_fn = FinishFn { + let finish_fn = FinishFnParams { ident: finish_fn_ident, vis: finish_fn_vis.map(SpannedKey::into_value), unsafety: None, @@ -203,7 +191,7 @@ impl StructInputCtx { .map(SpannedKey::into_value) .unwrap_or_else(|| { vec![syn::parse_quote! { - /// Finishes building and returns the requested object + /// Finish building and return the requested object }] }), }; diff --git a/bon-macros/src/builder/builder_gen/member/into_conversion.rs b/bon-macros/src/builder/builder_gen/member/into_conversion.rs index a1c65666..bffb259f 100644 --- a/bon-macros/src/builder/builder_gen/member/into_conversion.rs +++ b/bon-macros/src/builder/builder_gen/member/into_conversion.rs @@ -60,7 +60,7 @@ impl PositionalFnArgMember { } } - pub(crate) fn maybe_into_ident_expr(&self) -> TokenStream { + pub(crate) fn init_expr(&self) -> TokenStream { let ident = &self.ident; if self.params.into.is_present() { diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 3283c3e4..41c23c75 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -11,9 +11,7 @@ pub(crate) mod input_struct; use crate::util::prelude::*; use member::{Member, MemberOrigin, NamedMember, RawMember, StartFnArgMember}; -use models::{ - AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFn, FinishFnBody, Generics, -}; +use models::{AssocMethodCtx, AssocMethodReceiverCtx, BuilderGenCtx, FinishFnBody, Generics}; use setters::SettersCtx; pub(crate) struct MacroOutput { @@ -46,7 +44,7 @@ impl BuilderGenCtx { let builder_derives = self.builder_derives(); let default_allows = syn::parse_quote!(#[allow( - // We have a `deprecated` lint on any `bon::private` items which we + // We have a `deprecated` lint on all `bon::private` items which we // use in the generated code extensively deprecated )]); @@ -208,7 +206,7 @@ impl BuilderGenCtx { let mut start_fn_arg_exprs = self .start_fn_args() - .map(|member| member.base.maybe_into_ident_expr()) + .map(|member| member.base.init_expr()) .peekable(); let start_fn_args_field_init = start_fn_arg_exprs.peek().is_some().then(|| { @@ -278,8 +276,7 @@ impl BuilderGenCtx { .as_ref() .map(|ctx| ctx.self_ty.as_ref()); - let generic_args = &self.generics.args; - let generic_types = generic_args.iter().filter_map(|arg| match arg { + let generic_types = self.generics.args.iter().filter_map(|arg| match arg { syn::GenericArgument::Type(ty) => Some(ty), _ => None, }); @@ -291,7 +288,12 @@ impl BuilderGenCtx { .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 + // 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>) }); @@ -299,12 +301,21 @@ impl BuilderGenCtx { 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, + // 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 we then generate an impl block (simplified): + // and then we generate an impl block (simplified): // // ``` // impl<'a, T, U> for Foo @@ -321,10 +332,6 @@ impl BuilderGenCtx { // 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,)* - - // A special case of zero members requires storing the builder state in - // phantom data otherwise it would be reported as an unused type parameter. - fn() -> ::core::marker::PhantomData<#state_var>, )> } } @@ -341,22 +348,29 @@ impl BuilderGenCtx { let start_fn_args_field = &self.ident_pool.start_fn_args; let named_members_field = &self.ident_pool.named_members; - let private_field_attrs = quote! { - // The fields can't be hidden using Rust's privacy syntax. - // The details about this are described in [the blog post] - // (https://elastio.github.io/bon/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. - #[doc(hidden)] - #[deprecated = - "this field is an implementation detail; it should not be used directly; \ + // The fields can't be hidden using Rust's privacy syntax. + // The details about this are described in the blog post: + // https://elastio.github.io/bon/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 \ 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://discord.gg/QcBYSamw4c)"] + (https://discord.gg/QcBYSamw4c)"; + + quote! { + #[doc(hidden)] + #[deprecated = #deprecated_msg] + } }; let receiver_field = self.receiver().map(|receiver| { diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index 6590a50a..83f54c29 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -31,7 +31,7 @@ pub(super) struct FinishFn { pub(super) ident: syn::Ident, /// Visibility override specified by the user - pub(super) vis: Option, + pub(super) vis: syn::Visibility, /// Additional attributes to apply to the item pub(super) attrs: Vec, @@ -44,6 +44,20 @@ pub(super) struct FinishFn { 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) must_use: Option, + pub(super) body: Box, + pub(super) output: syn::ReturnType, +} + pub(super) struct StartFn { pub(super) ident: syn::Ident, pub(super) vis: syn::Visibility, @@ -58,8 +72,7 @@ pub(super) struct StartFn { pub(super) struct StartFnParams { pub(super) ident: syn::Ident, - /// If present overrides the default visibility taken from the original item - /// the builder is generated for + /// If present overrides the default visibility derived from the builder's type. pub(super) vis: Option, /// Additional attributes to apply to the item @@ -146,6 +159,15 @@ pub(crate) struct BuilderGenCtx { pub(super) finish_fn: FinishFn, } +/// Identifiers that are private to the builder implementation. The users should +/// not use them directly. They are randomly generated to prevent users from +/// using them. Even if they try to reference them, they won't be able to re-compile +/// their code because the names of these identifiers are regenerated on every +/// macro run. +/// +/// This is an unfortunate workaround due to the limitations of defining the +/// builder type inside of a nested module. See more details on this problem in +/// pub(super) struct PrivateIdentsPool { pub(super) phantom: syn::Ident, pub(super) receiver: syn::Ident, @@ -176,7 +198,7 @@ pub(super) struct BuilderGenCtxParams<'a> { pub(super) builder_type: BuilderTypeParams, pub(super) state_mod: ItemParams, pub(super) start_fn: StartFnParams, - pub(super) finish_fn: FinishFn, + pub(super) finish_fn: FinishFnParams, } impl BuilderGenCtx { @@ -201,7 +223,7 @@ impl BuilderGenCtx { derives: builder_type.derives, docs: builder_type.docs.unwrap_or_else(|| { let doc = format!( - "Use builder syntax to set the inputs and finish with [`Self::{}()`].", + "Use builder syntax to set the inputs and finish with [`{0}()`](Self::{0}()).", finish_fn.ident ); @@ -212,14 +234,14 @@ impl BuilderGenCtx { }; let state_mod = { - let ident_overridden = state_mod.name.is_some(); + 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 ident_overridden { + if is_ident_overridden { bail!( &ident, "the builder module name must be different from the builder type name" @@ -234,7 +256,7 @@ impl BuilderGenCtx { 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 = ...)]`" + `#[builder(state_mod = {{new_name}})]`" ); } @@ -250,7 +272,7 @@ impl BuilderGenCtx { // 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 type generic type state parameter signature. + // 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()?; @@ -277,9 +299,20 @@ impl BuilderGenCtx { let start_fn = StartFn { ident: start_fn.ident, + vis: start_fn.vis.unwrap_or_else(|| builder_type.vis.clone()), attrs: start_fn.attrs, generics: start_fn.generics, - vis: start_fn.vis.unwrap_or_else(|| builder_type.vis.clone()), + }; + + 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, + must_use: finish_fn.must_use, + body: finish_fn.body, + output: finish_fn.output, }; let state_var = { @@ -315,6 +348,7 @@ impl PrivateIdentsPool { // Thanks @orhun for the article https://blog.orhun.dev/zero-deps-random-in-rust/ let random = RandomState::new().build_hasher().finish(); + // Totally random words. All coincidences are purely accidental 😸 let random_words = [ "amethyst", "applejack", @@ -338,6 +372,7 @@ impl PrivateIdentsPool { "pinkie", "pipp", "rainbow", + "rampage", "rarity", "roseluck", "scootaloo", diff --git a/bon-macros/src/builder/builder_gen/state_mod.rs b/bon-macros/src/builder/builder_gen/state_mod.rs index 93a16ea1..ca093d26 100644 --- a/bon-macros/src/builder/builder_gen/state_mod.rs +++ b/bon-macros/src/builder/builder_gen/state_mod.rs @@ -24,8 +24,8 @@ impl<'a> StateModGenCtx<'a> { .map(|member| &member.name.pascal) .collect(), - // A method without `self` makes the trait non-object safe, - // which is convenient, because we want that in this case. + // 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; @@ -84,6 +84,9 @@ impl<'a> StateModGenCtx<'a> { } 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()); @@ -95,15 +98,20 @@ impl<'a> StateModGenCtx<'a> { 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.\n\n\ - [`State::{member_pascal}`]: State::{member_pascal}", + 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(::core::marker::PhantomData S>); + #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| { @@ -124,7 +132,7 @@ impl<'a> StateModGenCtx<'a> { state_impls.push(quote! { #[doc(hidden)] - impl State for #struct_ident { + impl State for #struct_ident { #( type #stateful_members_pascal = #states; )* @@ -144,8 +152,6 @@ impl<'a> StateModGenCtx<'a> { #( #set_members_structs )* - // Put it under an anonymous const to make it possible to collapse - // all this boilerplate when viewing the generated code. #[doc(hidden)] impl State for Empty { #( @@ -164,10 +170,7 @@ impl<'a> StateModGenCtx<'a> { format!( "Type state of the member `{member_snake}`.\n\ \n\ - It can implement either [`IsSet`] or [`IsUnset`].\n\ - \n\ - [`IsSet`]: ::bon::IsSet\n\ - [`IsUnset`]: ::bon::IsUnset", + It can implement either [`IsSet`] or [`IsUnset`]", ) }); @@ -183,10 +186,7 @@ impl<'a> StateModGenCtx<'a> { \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.\n\ - \n\ - [`IsSet`]: ::bon::IsSet - [`IsUnset`]: ::bon::IsUnset" + the `Set*` structs available in this module." }; let docs = format!( @@ -213,6 +213,10 @@ impl<'a> StateModGenCtx<'a> { .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, )* > @@ -254,12 +258,16 @@ impl<'a> StateModGenCtx<'a> { 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 = - "this is an implementation detail and should not be \ - used directly; use the Set* type aliases to control the \ - state of members instead" - ] + #[deprecated = #deprecated_msg] #[doc(hidden)] #[allow(non_camel_case_types)] mod members { diff --git a/bon-macros/src/builder/item_impl.rs b/bon-macros/src/builder/item_impl.rs index 75d7ac5e..0dd21938 100644 --- a/bon-macros/src/builder/item_impl.rs +++ b/bon-macros/src/builder/item_impl.rs @@ -93,8 +93,8 @@ pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result unreachable!(), }; - let norm_fn = impl_item_fn_into_fn_item(norm_fn)?; - let orig_fn = impl_item_fn_into_fn_item(orig_fn)?; + 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 meta = orig_fn .attrs @@ -146,7 +146,7 @@ pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result Result { +fn conv_impl_item_fn_into_fn_item(func: syn::ImplItemFn) -> Result { let syn::ImplItemFn { attrs, vis, diff --git a/bon-macros/src/builder/mod.rs b/bon-macros/src/builder/mod.rs index 15ae7e75..3d777150 100644 --- a/bon-macros/src/builder/mod.rs +++ b/bon-macros/src/builder/mod.rs @@ -66,7 +66,7 @@ fn try_generate_from_attr(params: TokenStream, item: TokenStream) -> Result bail!( diff --git a/bon-macros/src/normalization/mod.rs b/bon-macros/src/normalization/mod.rs index 2b5a7996..db7e8beb 100644 --- a/bon-macros/src/normalization/mod.rs +++ b/bon-macros/src/normalization/mod.rs @@ -3,28 +3,11 @@ mod generics_namespace; mod impl_traits; mod lifetimes; mod self_ty; +mod syntax_variant; pub(crate) use cfg::*; -pub(crate) use generics_namespace::GenericsNamespace; -pub(crate) use impl_traits::NormalizeImplTraits; -pub(crate) use lifetimes::NormalizeLifetimes; -pub(crate) use self_ty::NormalizeSelfTy; - -/// 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) use generics_namespace::*; +pub(crate) use impl_traits::*; +pub(crate) use lifetimes::*; +pub(crate) use self_ty::*; +pub(crate) use syntax_variant::*; diff --git a/bon-macros/src/normalization/syntax_variant.rs b/bon-macros/src/normalization/syntax_variant.rs new file mode 100644 index 00000000..b12ab236 --- /dev/null +++ b/bon-macros/src/normalization/syntax_variant.rs @@ -0,0 +1,28 @@ +/// 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 }) + } +} diff --git a/bon/tests/integration/ui/compile_fail/attr_builder.stderr b/bon/tests/integration/ui/compile_fail/attr_builder.stderr index 625f3eff..f7a69b79 100644 --- a/bon/tests/integration/ui/compile_fail/attr_builder.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_builder.stderr @@ -4,11 +4,11 @@ error: this empty `#[builder]` attribute is redundant; remove it 4 | #[builder] | ^^^^^^^ -error: this empty `#[builder()]` attribute is redundant; remove it - --> tests/integration/ui/compile_fail/attr_builder.rs:8:3 +error: expected parameters in parentheses + --> tests/integration/ui/compile_fail/attr_builder.rs:8:10 | 8 | #[builder()] - | ^^^^^^^ + | ^^ error: this empty `#[builder]` attribute is unexpected; remove it or pass parameters in parentheses: `#[builder(...)]` --> tests/integration/ui/compile_fail/attr_builder.rs:13:7 @@ -52,7 +52,7 @@ error: expected parameters in parentheses 45 | #[builder()] | ^^ -error: to generate a builder for a struct, use `#[derive(bon::Builder)]` instead; `#[bon::builder]` syntax is supported only for functions +error: to generate a builder for a struct, use `#[derive(bon::Builder)]` instead; `#[bon::builder]` syntax is supported only for functions starting with bon v3 --> tests/integration/ui/compile_fail/attr_builder.rs:50:1 | 50 | struct LegacyBuilderProcMacroAttrOnStruct {} From 24996e0ded05b2e1beee8c14ca060c38b4c2e540 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sat, 19 Oct 2024 22:18:50 +0000 Subject: [PATCH 117/119] Self-review part 6 --- .../src/builder/builder_gen/input_struct.rs | 2 +- .../src/builder/builder_gen/member/mod.rs | 2 +- bon-macros/src/builder/item_impl.rs | 2 +- bon-macros/src/collections/map.rs | 4 +- .../src/normalization/generics_namespace.rs | 4 +- bon-macros/src/normalization/lifetimes.rs | 4 +- bon-macros/src/parsing/item_params.rs | 15 +--- bon-macros/src/parsing/mod.rs | 4 +- bon-macros/src/parsing/simple_closure.rs | 3 + bon-macros/src/util/path.rs | 10 --- bon-macros/src/util/ty/mod.rs | 4 +- .../builder/name_conflicts/generics.rs | 70 ++++++++++++------- 12 files changed, 61 insertions(+), 63 deletions(-) diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 6514be16..2f8a82f0 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -49,7 +49,7 @@ impl StructInputParams { ), }; - crate::parsing::require_non_empty_paren_meta_list(&attr.meta)?; + crate::parsing::require_non_empty_paren_meta_list_or_name_value(&attr.meta)?; let meta = darling::ast::NestedMeta::parse_meta_list(meta.tokens.clone())?; diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index f9a4353c..d75eeced 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -108,7 +108,7 @@ impl Member { .map(|member| { for attr in member.attrs { if attr.meta.path().is_ident("builder") { - crate::parsing::require_non_empty_paren_meta_list(&attr.meta)?; + crate::parsing::require_non_empty_paren_meta_list_or_name_value(&attr.meta)?; } } diff --git a/bon-macros/src/builder/item_impl.rs b/bon-macros/src/builder/item_impl.rs index 0dd21938..1e3e63c5 100644 --- a/bon-macros/src/builder/item_impl.rs +++ b/bon-macros/src/builder/item_impl.rs @@ -102,7 +102,7 @@ pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result) -> TokenStr let items = entries.into_iter().map(|(key, value)| { quote!(( - Into::into(#key), - Into::into(#value), + ::core::convert::Into::into(#key), + ::core::convert::Into::into(#value), )) }); diff --git a/bon-macros/src/normalization/generics_namespace.rs b/bon-macros/src/normalization/generics_namespace.rs index ba6aa33f..e709605c 100644 --- a/bon-macros/src/normalization/generics_namespace.rs +++ b/bon-macros/src/normalization/generics_namespace.rs @@ -5,10 +5,10 @@ use syn::visit::Visit; #[derive(Debug, Default, Clone)] pub(crate) struct GenericsNamespace { - /// Set of identifiers referenced in the syntax element. + /// Set of identifiers referenced in the syntax node. pub(crate) idents: BTreeSet, - /// Set of lifetimes referenced in the syntax element. + /// Set of lifetimes referenced in the syntax node. pub(crate) lifetimes: BTreeSet, } diff --git a/bon-macros/src/normalization/lifetimes.rs b/bon-macros/src/normalization/lifetimes.rs index 615b4c2b..294d3b7b 100644 --- a/bon-macros/src/normalization/lifetimes.rs +++ b/bon-macros/src/normalization/lifetimes.rs @@ -19,7 +19,7 @@ impl VisitMut for NormalizeLifetimes<'_> { self.visit_impl_item_mut(item); } - AssignLifetimes::new(self, "impl", &mut impl_block.generics) + AssignLifetimes::new(self, "i", &mut impl_block.generics) .visit_type_mut(&mut impl_block.self_ty); } @@ -34,7 +34,7 @@ impl VisitMut for NormalizeLifetimes<'_> { } fn visit_signature_mut(&mut self, signature: &mut syn::Signature) { - let mut visitor = AssignLifetimes::new(self, "fn", &mut signature.generics); + let mut visitor = AssignLifetimes::new(self, "f", &mut signature.generics); for arg in &mut signature.inputs { visitor.visit_fn_arg_mut(arg); } diff --git a/bon-macros/src/parsing/item_params.rs b/bon-macros/src/parsing/item_params.rs index 4caf2adc..441a5bc4 100644 --- a/bon-macros/src/parsing/item_params.rs +++ b/bon-macros/src/parsing/item_params.rs @@ -52,20 +52,7 @@ impl ItemParamsParsing<'_> { doc: Option>>, } - let full = crate::parsing::parse_non_empty_paren_meta_list(meta)?; - - let is_empty = matches!( - full, - Full { - name: None, - vis: None, - doc: None, - } - ); - - if is_empty { - bail!(meta, "expected at least one parameter in parentheses"); - } + 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 { diff --git a/bon-macros/src/parsing/mod.rs b/bon-macros/src/parsing/mod.rs index ac36f5ab..6b8477e1 100644 --- a/bon-macros/src/parsing/mod.rs +++ b/bon-macros/src/parsing/mod.rs @@ -14,11 +14,11 @@ use syn::parse::Parser; use syn::punctuated::Punctuated; pub(crate) fn parse_non_empty_paren_meta_list(meta: &syn::Meta) -> Result { - require_non_empty_paren_meta_list(meta)?; + require_non_empty_paren_meta_list_or_name_value(meta)?; T::from_meta(meta) } -pub(crate) fn require_non_empty_paren_meta_list(meta: &syn::Meta) -> Result { +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()?; diff --git a/bon-macros/src/parsing/simple_closure.rs b/bon-macros/src/parsing/simple_closure.rs index 11b2c33a..69b21252 100644 --- a/bon-macros/src/parsing/simple_closure.rs +++ b/bon-macros/src/parsing/simple_closure.rs @@ -2,6 +2,9 @@ use crate::util::prelude::*; use darling::FromMeta; use syn::spanned::Spanned; +/// 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, diff --git a/bon-macros/src/util/path.rs b/bon-macros/src/util/path.rs index fc2c75b4..db4dc282 100644 --- a/bon-macros/src/util/path.rs +++ b/bon-macros/src/util/path.rs @@ -1,9 +1,6 @@ pub(crate) trait PathExt { /// Check if the path starts with the given segment. fn starts_with_segment(&self, desired_segment: &str) -> bool; - - /// Check if the path ends with the given segment. - fn ends_with_segment(&self, desired_segment: &str) -> bool; } impl PathExt for syn::Path { @@ -13,11 +10,4 @@ impl PathExt for syn::Path { .map(|first| first.ident == desired_segment) .unwrap_or(false) } - - fn ends_with_segment(&self, desired_segment: &str) -> bool { - self.segments - .last() - .map(|last| last.ident == desired_segment) - .unwrap_or(false) - } } diff --git a/bon-macros/src/util/ty/mod.rs b/bon-macros/src/util/ty/mod.rs index bedcdd9b..38c09327 100644 --- a/bon-macros/src/util/ty/mod.rs +++ b/bon-macros/src/util/ty/mod.rs @@ -91,9 +91,7 @@ impl TypeExt for syn::Type { } fn is_option(&self) -> bool { - self.as_path_no_qself() - .map(|path| path.ends_with_segment("Option")) - .unwrap_or(false) + self.option_type_param().is_some() } fn peel(&self) -> &Self { diff --git a/bon/tests/integration/builder/name_conflicts/generics.rs b/bon/tests/integration/builder/name_conflicts/generics.rs index 621f653f..80c22eb8 100644 --- a/bon/tests/integration/builder/name_conflicts/generics.rs +++ b/bon/tests/integration/builder/name_conflicts/generics.rs @@ -1,32 +1,52 @@ -use crate::prelude::*; - -#[test] -fn lifetimes() { - #[derive(Default)] - #[allow(dead_code)] - struct Sut<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h>( - &'a str, - &'b str, - &'c str, - &'d str, - &'e str, - &'f str, - &'g str, - &'h str, - ); - - #[bon] - impl<'impl1, 'impl1_, 'impl2, 'fn1, 'fn1_, 'fn2> - Sut<'_, '_, 'impl1, 'impl1_, 'impl2, 'fn1, 'fn1_, 'fn2> - { +mod lifetimes { + use crate::prelude::*; + + #[test] + fn test_free_fn() { #[builder] - #[allow(clippy::trivially_copy_pass_by_ref)] - fn sut(_val: &u32, _val2: &u32) {} + #[allow( + single_use_lifetimes, + clippy::needless_lifetimes, + clippy::trivially_copy_pass_by_ref + )] + fn sut<'f1, 'f1_, 'f2>( + _x1: &u32, + _x2: &'f1 u32, + _x3: &'f1_ u32, + _x4: &'f2 u32, + _x5: &u32, + ) -> u32 { + 32 + } + + sut().x1(&32).x2(&32).x3(&32).x4(&32).x5(&32).call(); } - Sut::sut().val(&32).val2(&32).call(); -} + #[test] + fn test_assoc_method() { + #[derive(Default)] + #[allow(dead_code)] + struct Sut<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h>( + &'a str, + &'b str, + &'c str, + &'d str, + &'e str, + &'f str, + &'g str, + &'h str, + ); + #[bon] + impl<'i1, 'i1_, 'i2, 'f1, 'f1_, 'f2> Sut<'_, '_, 'i1, 'i1_, 'i2, 'f1, 'f1_, 'f2> { + #[builder] + #[allow(clippy::trivially_copy_pass_by_ref)] + fn sut(_val: &u32, _val2: &u32) {} + } + + Sut::sut().val(&32).val2(&32).call(); + } +} mod impl_trait { use crate::prelude::*; From c47a2e675f738fb7579a67875b3e45b787125a5a Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 20 Oct 2024 00:24:00 +0000 Subject: [PATCH 118/119] Self-review part 7 --- Cargo.lock | 4 +- bon-macros/Cargo.toml | 7 +- bon-macros/src/bon.rs | 4 +- .../src/builder/builder_gen/input_struct.rs | 5 +- .../src/builder/builder_gen/member/mod.rs | 4 +- bon-macros/src/builder/builder_gen/mod.rs | 4 +- bon-macros/src/builder/item_impl.rs | 4 +- bon-macros/src/builder/mod.rs | 10 +- bon-macros/src/error.rs | 114 ++++-- bon-macros/src/util/visibility.rs | 4 +- .../ui/compile_fail/attr_builder.stderr | 14 - e2e-tests/src/attr_with.rs | 16 + e2e-tests/src/lib.rs | 2 +- e2e-tests/src/v3_design.rs | 328 ------------------ website/changelog.md | 6 +- 15 files changed, 135 insertions(+), 391 deletions(-) create mode 100644 e2e-tests/src/attr_with.rs delete mode 100644 e2e-tests/src/v3_design.rs diff --git a/Cargo.lock b/Cargo.lock index 67337bcb..8774fd16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -712,9 +712,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" dependencies = [ "unicode-ident", ] diff --git a/bon-macros/Cargo.toml b/bon-macros/Cargo.toml index 3fc6649d..9ea686bd 100644 --- a/bon-macros/Cargo.toml +++ b/bon-macros/Cargo.toml @@ -48,8 +48,11 @@ darling = "0.20.10" # conversions to share the same dependency. ident_case = "1" -proc-macro2 = "1" -quote = "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"] } diff --git a/bon-macros/src/bon.rs b/bon-macros/src/bon.rs index a8ebe89b..51b115f7 100644 --- a/bon-macros/src/bon.rs +++ b/bon-macros/src/bon.rs @@ -3,8 +3,8 @@ use crate::normalization::{ExpandCfg, ExpansionOutput}; use crate::util::prelude::*; pub(crate) fn generate(params: TokenStream, item: TokenStream) -> TokenStream { - try_generate(params, item.clone()) - .unwrap_or_else(|err| crate::error::error_into_token_stream(err, item)) + crate::error::with_fallback(item.clone(), || try_generate(params, item)) + .unwrap_or_else(std::convert::identity) } pub(crate) fn try_generate(params: TokenStream, item: TokenStream) -> Result { diff --git a/bon-macros/src/builder/builder_gen/input_struct.rs b/bon-macros/src/builder/builder_gen/input_struct.rs index 2f8a82f0..45aef731 100644 --- a/bon-macros/src/builder/builder_gen/input_struct.rs +++ b/bon-macros/src/builder/builder_gen/input_struct.rs @@ -1,7 +1,7 @@ use super::builder_params::BuilderParams; +use super::models::FinishFnParams; use super::{ - AssocMethodCtx, BuilderGenCtx, FinishFnBody, Generics, Member, MemberOrigin, - RawMember, + AssocMethodCtx, BuilderGenCtx, FinishFnBody, Generics, Member, MemberOrigin, RawMember, }; use crate::builder::builder_gen::models::{BuilderGenCtxParams, BuilderTypeParams, StartFnParams}; use crate::normalization::{GenericsNamespace, SyntaxVariant}; @@ -11,7 +11,6 @@ use darling::FromMeta; use std::borrow::Cow; use syn::visit::Visit; use syn::visit_mut::VisitMut; -use super::models::FinishFnParams; #[derive(Debug, FromMeta)] pub(crate) struct StructInputParams { diff --git a/bon-macros/src/builder/builder_gen/member/mod.rs b/bon-macros/src/builder/builder_gen/member/mod.rs index d75eeced..976fc41f 100644 --- a/bon-macros/src/builder/builder_gen/member/mod.rs +++ b/bon-macros/src/builder/builder_gen/member/mod.rs @@ -108,7 +108,9 @@ impl Member { .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)?; + crate::parsing::require_non_empty_paren_meta_list_or_name_value( + &attr.meta, + )?; } } diff --git a/bon-macros/src/builder/builder_gen/mod.rs b/bon-macros/src/builder/builder_gen/mod.rs index 41c23c75..040d2bad 100644 --- a/bon-macros/src/builder/builder_gen/mod.rs +++ b/bon-macros/src/builder/builder_gen/mod.rs @@ -67,8 +67,8 @@ impl BuilderGenCtx { &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\ + https://github.com/elastio/bon;\n\ + syntax error in generated code: {err:#?};\n\ generated code:\n\ ```rust {other_items_str}\n\ diff --git a/bon-macros/src/builder/item_impl.rs b/bon-macros/src/builder/item_impl.rs index 1e3e63c5..09dd3da5 100644 --- a/bon-macros/src/builder/item_impl.rs +++ b/bon-macros/src/builder/item_impl.rs @@ -102,7 +102,9 @@ pub(crate) fn generate(mut orig_impl_block: syn::ItemImpl) -> Result Result { } pub(crate) fn generate_from_attr(params: TokenStream, item: TokenStream) -> TokenStream { - try_generate_from_attr(params.clone(), item.clone()).unwrap_or_else(|err| { - [ - generate_completion_triggers(params), - crate::error::error_into_token_stream(err, item), - ] - .concat() + crate::error::with_fallback(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 { @@ -57,6 +54,7 @@ fn try_generate_from_attr(params: TokenStream, item: TokenStream) -> Result { let mut namespace = GenericsNamespace::default(); + namespace.visit_token_stream(params.clone()); namespace.visit_item_fn(&item_fn); diff --git a/bon-macros/src/error.rs b/bon-macros/src/error.rs index fd29c90f..a66a3194 100644 --- a/bon-macros/src/error.rs +++ b/bon-macros/src/error.rs @@ -1,16 +1,55 @@ use crate::util::prelude::*; -use proc_macro2::TokenTree; +use proc_macro2::{Group, TokenTree}; +use std::panic::AssertUnwindSafe; use syn::parse::Parse; -/// Handle the error returned from the macro logic. This 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. -pub(crate) fn error_into_token_stream(err: Error, item: TokenStream) -> TokenStream { - let compile_error = err.write_errors(); +/// 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 with_fallback( + item: TokenStream, + imp: impl FnOnce() -> Result, +) -> Result { + std::panic::catch_unwind(AssertUnwindSafe(imp)) + .unwrap_or_else(|err| { + let msg = err + .downcast::<&str>() + .map(|msg| msg.to_string()) + .or_else(|err| err.downcast::().map(|msg| *msg)) + .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 { + format!( + "bug in the crate `bon` (proc-macro panicked): `{msg}`;\n\ + please report this issue at our Github repository: \ + https://github.com/elastio/bon" + ) + }; + + 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) + 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 @@ -41,29 +80,14 @@ impl Parse for Fallback { match &tt { TokenTree::Group(group) => { let fallback: Self = syn::parse2(group.stream())?; - let new_group = - proc_macro2::Group::new(group.delimiter(), fallback.output); + 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(_) => { - // Workaround for the RA bug where it generates an invalid Punct token tree with - // the character `{`, which is amplified by a bug in `proc_macro2` where its `Punct` - // doesn't panic early on invalid `Punct`. - // - // If this `extend` panics it means there are some invalid token trees in the input. - // We can't do anything about it, and we just ignore them. - // - // ## 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) - let _can_panic = - std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - output.extend([tt]); - })); + output.extend([tt]); } } @@ -91,3 +115,41 @@ impl ToTokens for Fallback { 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. + match std::panic::catch_unwind(AssertUnwindSafe(|| tokens.next())) { + Ok(tt) => return tt.map(recurse), + Err(_) => continue, + } + } + }) + .collect() +} diff --git a/bon-macros/src/util/visibility.rs b/bon-macros/src/util/visibility.rs index c5da7998..dc854da5 100644 --- a/bon-macros/src/util/visibility.rs +++ b/bon-macros/src/util/visibility.rs @@ -16,7 +16,7 @@ pub(crate) trait VisibilityExt { /// - `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]: + /// 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 @@ -28,7 +28,7 @@ pub(crate) trait VisibilityExt { /// 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 + /// [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; } diff --git a/bon/tests/integration/ui/compile_fail/attr_builder.stderr b/bon/tests/integration/ui/compile_fail/attr_builder.stderr index f7a69b79..6996c6e6 100644 --- a/bon/tests/integration/ui/compile_fail/attr_builder.stderr +++ b/bon/tests/integration/ui/compile_fail/attr_builder.stderr @@ -83,17 +83,3 @@ error: use a simple `identifier: type` syntax for the function argument; destruc | 69 | fn destructuring2((_, _): (u32, u32)) {} | ^^^^^^ - -warning: unused attribute - --> tests/integration/ui/compile_fail/attr_builder.rs:59:1 - | -59 | #[must_use] - | ^^^^^^^^^^^ help: remove this attribute - | -note: attribute also specified here - --> tests/integration/ui/compile_fail/attr_builder.rs:58:1 - | -58 | #[must_use] - | ^^^^^^^^^^^ - = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! - = note: `#[warn(unused_attributes)]` on by default diff --git a/e2e-tests/src/attr_with.rs b/e2e-tests/src/attr_with.rs new file mode 100644 index 00000000..77e76bec --- /dev/null +++ b/e2e-tests/src/attr_with.rs @@ -0,0 +1,16 @@ +use bon::Builder; +use std::collections::BTreeMap; + +#[derive(Builder)] +pub struct AttrWith { + #[builder(with = |iter: impl IntoIterator| Vec::from_iter(iter))] + _vec: Vec, + + #[builder(with = |iter: impl IntoIterator, u32)>| { + iter + .into_iter() + .map(|(k, v)| (k.into(), v)) + .collect() + })] + _map: BTreeMap, +} diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index d77ef90c..71995a87 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -2,10 +2,10 @@ //! We don't need all the aggressive lints that we use for public crates. #![allow(missing_debug_implementations, missing_docs)] +pub mod attr_with; pub mod macro_rules_wrapper_test; pub mod missing_docs_test; pub mod state_mod_pub; -// pub mod v3_design; mod reexports; diff --git a/e2e-tests/src/v3_design.rs b/e2e-tests/src/v3_design.rs deleted file mode 100644 index 2b1f50f7..00000000 --- a/e2e-tests/src/v3_design.rs +++ /dev/null @@ -1,328 +0,0 @@ -struct Point { - x: f64, - y: f64, -} - -#[builder( - builder_type( - name = ExampleBuilder, - docs { - /// Docs for the builder - }, - ), - - state_mod( - vis = "pub", - name = name_override, - docs { - /// Docs for the state module - }, - // Deprecate the state module - deprecated = "..." - ), - - on( - Point, - - // (v3.3) - // - // Overrides the setters generated for the `Point` type. - with = |x: f64, y: f64| Point { x, y }, - ), - - // (v3.4) - // - // Makes the builder exhaustive (all members must be set). Doesn't change - // the setters that are generated, only adds more bounds on the `finish_fn` - // to require all members to be set. - // - // Maybe there should be an alias for this pattern `#[builder(exhaustive)]`? - // Will there be frequent use cases for this? - on(_, required), - - // More predicates in `on(...)` - on(not(required()), overwritable) -)] -pub struct Example { - /// Docs on member - #[builder( - name = foo, - - // The `visibility` at this level may be confusing, because it doesn't - // influence the the visibility of "state" items. - // - // (v3.0) - // vis = "", - - // (v3.0) - // - // Override the name in the state - state = PointOverride, - - // (v3.1+) - // - // Advanced state configuration - // state( - // name = PointOverride, - - // deprecated = "This state is no longer available, the field is overwritable", - // assoc_type = NameOverride, - // assoc_type( - // name = NameOverride - // docs { - // /// Docs for associated type - // }, - // ), - // transition_alias = NameOverride, - // transition_alias( - // name = NameOverride, - // docs { - // /// Docs for transition alias - // }, - // ) - // ), - - // Closure override - // - // For optional members, the `maybe_` setter will accept an `Option<(..args)>` - // if there is more than one argument. - with = |x: f64, y: f64| Point { x, y }, - - // Makes the setter fallible - with = |x: f64, y: f64| -> Result<_> { Ok(Point { x, y }) } - - // (v3.0) - // - // Overrides the name, visibility and docs of the default setters - setters( - name = point_internal, - vis = "", - - // By default copied to all members - docs { - /// Docs for the setters - } - - // (v3.1) - // - // Overrides specific to the `{member}` setter that wraps the value with `Some` - // - // Other names: `by_value`, `arg_value`, `plain` - some_fn( - name = point_internal, - vis = "", - docs { ... } - ), - - // (v3.1) - // - // Overrides specific to the `maybe_{member}` setter. - // - // Other names: `by_option`, `arg_option` - option_fn( - name = maybe_point_internal, - vis = "", - docs { ... } - ) - - // Other names: `arg_absent` - true_fn( - // ... - ), - - bool_fn( - // ... - ) - ), - - // (v3.2) - // - // These must work for regular members and start_fn args (custom fields?, v3.3) - // Consider exposing `start_fn` args and overwritable optional fields as regular - // private (!) fields on the builder additionally. This will allow for more flexibility - // in the builder impl block. - // - // &T, Option<&T> - getter, - - // (v3.2) - // - // &Option - getter(transparent), - - // (v3.2) - // &mut T, Option<&mut T> - getter(mut), - - // (v3.2) - // - // &mut Option - getter(transparent, mut) - - // (v3.2) - // - // Deref to &str, Option<&str> - getter(deref(&str)), - - // (v3.2) - // - // Deref to &mut str, Option<&mut str> - getter(deref(&mut str)), - - // (v3.2) - // - // AsRef to &str - getter(as_ref(&str)), - - // (v3.2) - // - // `Option::as_ref() -> Option<&T>` - getter(as_ref), - - // (v3.2) - // - // `>::as_ref() -> Option<&_>` - getter(as_ref(&str)), - - - // (v3.2) - // - // Getter by `Copy` -> `T` - getter(copy), - - // (v3.2) - // - // Getter by `Clone` -> `T` - getter(clone), - - getter( - name = getter_name, - vis = "", - docs { - /// Docs for getter_name - } - deprecated, - copy, - ) - - // Multiple getters need to have name specified explicitly - // getter(name = getter_name_1, copy), - getter(name = getter_name_2), - - // Custom readonly getter. Accepts a readonly reference and transforms it. - getter = |value: &_| -> Ty { expr } - - // Custom mutable getter. Accepts a mutable reference and transforms it. - getter = |value: &mut _| -> Ty { expr } - - // Give a name to the getter if there are several getters - getter(name = foo, with = |value: &mut _| -> Ty { expr }), - )] - point: Point, - - // v3.0 - #[builder(overwritable)] - overwritable: u32, - - #[builder( - field = vec![], - field(name = overridden_name, vis = "pub", docs { ... }, init = vec![]), - deprecated(reason = "saasd"), - )] - #[deprecated = "Use `overridden_name` instead"] - pub custom_state: Vec, - - // Generates two setters for booleans: - // - `my_lovely_flag() -> true` - // - `with_my_lovely_flag(bool)` - // - // It also automatically implies that the setters are optional to call. - // The default value is `false` automatically. The `#[builder(default)]` - // attribute is not allowed with the `flag` attribute. - #[builder(flag)] - my_lovely_flag: bool, - - // The same as `#[builder(flag)]` but additionally requires the caller - // to call the setter for this member explicitly. - #[builder(flag, required)] - my_required_flag: bool, - - // Opts out from the special handling for `Option`. Generates only - // a single setter that accepts `Option` as a value. It's required - // to call the setter. - #[builder(transparent)] - required_option: Option, - - // Still generates a pair of setters (arg_value, arg_option), but requires - // calling ant of these setters. - #[builder(required)] - exhaustive_option: Option, - - // Still generates a pair of setters (arg_value, arg_option), but requires - // calling ant of these setters. - #[builder(required, default = 32)] - exhaustive_default: u32, -} - -// Use cases: -struct UseCases { - // (v3.0) - // - // Generate private setters with names `[maybe_]point_internal` and - // preserve the public name in the `state` as `Point`. - #[builder(setters(name = point_internal, vis = ""))] - point: Point, - - // (v3.0) - #[builder(setters(docs { - /// Docs for the setters - }))] - override_docs_for_default_setters: Option, - - #[builder(setters( - some_fn(docs { - /// Docs for the some setter - }), - maybe_fn(docs { - /// Docs for the maybe setter - }) - ))] - override_docs_for_maybe_setter: Option, - - // (v3.1) - #[builder(with = |iter: impl IntoIterator| iter.into_iter().collect())] - #[builder(with = |iter: impl IntoIterator| Vec::from_iter(iter))] - take_into_iter: Vec, - - // (v3.1) - #[builder( - setters( - name = member_internal, - vis = "", - docs { - /// ... - } - ) - )] - take_several_args: Point, -} - -impl ExampleBuilder { - pub fn my_point(self, x: f64, y: f64) -> ExampleBuilder> - where - State::Point: example_builder::IsUnset, - { - self.point(Point { x, y }) - } -} - -#[bon] -impl Example { - // Prevent shadowing the `new` function with the builder syntax. - #[builder(separate)] - fn new() {} -} - -// A rename prevents shadowing the `example` function with the builder syntax -#[builder(start_fn = example_builder)] -fn example() {} diff --git a/website/changelog.md b/website/changelog.md index c53cfc54..19afebdc 100644 --- a/website/changelog.md +++ b/website/changelog.md @@ -33,13 +33,17 @@ All the breaking changes are very unlikely to actually break your code using the - For members with defaults values show the default value in the docs. - For optional members provide a link to a companion setter. The docs for `{member}(T)` setter mention the `maybe_{member}(Option)` setter and vice versa. - Remove `__` prefixes for generic types and lifetimes from internal symbols. Instead, the prefixes added only if the macro detects a name collision. -- Add inheritance of `#[allow()]` and `#[expect()]` lint attributes to all generated items. This is useful to suppress any lints coming from the generated code. Although, lints coming from the generated code are generally considered defects in `bon` and should be reported via a Github issue but this provides an easy temporary workaround the problem ([#145](https://github.com/elastio/bon/pull/145)) +- Add inheritance of `#[allow()]` and `#[expect()]` lint attributes to all generated items. This is useful to suppress any lints coming from the generated code. Although, lints coming from the generated code are generally considered defects in `bon` and should be reported via a Github issue but this provides an easy temporary workaround the problem ([#145](https://github.com/elastio/bon/pull/145)) ### Fixed - Fixed `#[cfg/cfg_attr()]` not being expanded when used on function arguments with doc comments or other attributes. +### Other + +- Added graceful internal panic handling. If some `bon` macro panics due to an internal bug, the macro will try to still generate a fallback for IDEs to still provide intellisense ([#145](https://github.com/elastio/bon/pull/145)) + ## [2.3.0](https://github.com/elastio/bon/compare/v2.2.1...v2.3.0) - 2024-09-14 See the [blog post for this release](https://elastio.github.io/bon/blog/bon-builder-v2-3-release) that describes some of the most notable changes in detail. From 7e9709e1dd3d434b906a3c6493fdd9922f4ccc04 Mon Sep 17 00:00:00 2001 From: Veetaha Date: Sun, 20 Oct 2024 00:40:44 +0000 Subject: [PATCH 119/119] Self-review part 8 --- e2e-tests/src/lib.rs | 2 +- e2e-tests/src/missing_docs_test.rs | 2 +- website/changelog.md | 4 ++-- website/reference/builder.md | 2 +- website/v1/reference/builder.md | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index 71995a87..f5f263a1 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -118,7 +118,7 @@ pub fn documented( #[builder(default = Greeter::start_fn_override() .name( - "Some intentionally big expression to test the overflow to \ + "Some intentionally big expression to test the fallback to \ a code fence in the default value docs" .to_owned() ) diff --git a/e2e-tests/src/missing_docs_test.rs b/e2e-tests/src/missing_docs_test.rs index b7107572..f8a274b3 100644 --- a/e2e-tests/src/missing_docs_test.rs +++ b/e2e-tests/src/missing_docs_test.rs @@ -47,7 +47,7 @@ impl Struct { } /// [`GenericStruct`] docs -// #[derive(bon::Builder)] +#[derive(bon::Builder)] pub struct GenericStruct { // Docs on setters for struct fields are autogenerated // So missing docs here shouldn't be reported diff --git a/website/changelog.md b/website/changelog.md index 19afebdc..4445ffe0 100644 --- a/website/changelog.md +++ b/website/changelog.md @@ -6,14 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - -All the breaking changes are very unlikely to actually break your code using the `v2` version of `bon` unless you've been doing some crimes like using `#[doc(hidden)]` or unconventional macro delimiters like `#[builder{}/[]]` instead of `#[builder()]`. See also the "Removed" section about removed/replaced deprecated APIs. +All the breaking changes are very unlikely to actually break your code that was written against the `v2` version of `bon` unless you've been doing some crimes like using items marked as `#[doc(hidden)]` or using unconventional macro delimiters like `#[builder{}/[]]` instead of `#[builder()]`. See also the "Removed" section about removed/replaced deprecated APIs that you most likely never used. ### Changed - Reject unnecessary empty attributes e.g. `#[builder()]` or `#[builder]` with no parameters on a member ([#145](https://github.com/elastio/bon/pull/145)) - Reject non-empty `#[bon(...)]` attribute. This attribute will accept some parameters in future releases ([#145](https://github.com/elastio/bon/pull/145)) - Reject square brackets and curly braces delimiters for `builder_type`, `finish_fn`, `start_fn` and `on` attributes syntax. Only parentheses are accepted e.g. `#[builder(finish_fn(...))]` or `#[builder(on(...))]`. This no longer works: `#[builder(finish_fn[...])]` or `#[builder(on{...})]` ([#145](https://github.com/elastio/bon/pull/145)) +- `#[builder(derive(Clone, Debug))]` now generates impl blocks that follow the behavior of standard `Clone` and `Debug` derives in that it conservatively adds `Clone/Debug` trait bounds for all the generic types declared on the original item (struct or function). See the *Added* section for details on the way to override these bounds with `#[builder(derive(Clone/Debug(bounds(...))))]`. ### Removed - Removed support for `#[bon::builder]` proc-macro attribute on top of a `struct`. Use `#[derive(bon::Builder)]` for that instead. This syntax has been deprecated since `2.1` and it is now removed as part of a major version cleanup ([#145](https://github.com/elastio/bon/pull/145)) diff --git a/website/reference/builder.md b/website/reference/builder.md index fa95373c..6784f223 100644 --- a/website/reference/builder.md +++ b/website/reference/builder.md @@ -966,7 +966,7 @@ Example::example() **Applies to:** -Overrides the name of the setters generated for the member. This is most useful with the struct syntax where you'd like to use a different name for the field internally. For functions this attribute makes less sense since it's easy to just create a variable named differently `let new_name = param_name;`. However, this attribute is still supported for functions. +Overrides the name of the member in the builder's setters and type state. This is most useful when with struct syntax (`#[derive(Builder)]`) where you'd like to use a different name for the field internally. For functions this attribute makes less sense since it's easy to just create a variable named differently `let new_name = param_name;`. However, this attribute is still supported on function arguments. **Example:** diff --git a/website/v1/reference/builder.md b/website/v1/reference/builder.md index 28c1ed76..f5b41c2e 100644 --- a/website/v1/reference/builder.md +++ b/website/v1/reference/builder.md @@ -546,7 +546,7 @@ struct Example { **Applies to:** -Overrides the name of the member in the builder's setters and type state. This is most useful when `#[derive(Builder)]` is placed on a struct where you'd like to use a different name for the field internally. For functions this attribute makes less sense since it's easy to just create a variable named differently `let new_name = param_name;`. However, this attribute is still supported for functions. +Overrdies the name for the setters generated for the member. This is most useful when `#[builder]` is placed on a struct where you'd like to use a different name for the field internally. For functions this attribute makes less sense since it's easy to just create a variable named differently `let new_name = param_name;`. However, this attribute is still supported for functions. **Example:**