diff --git a/bon-macros/src/builder/builder_gen/builder_decl.rs b/bon-macros/src/builder/builder_gen/builder_decl.rs index 10b6acdd..c4758549 100644 --- a/bon-macros/src/builder/builder_gen/builder_decl.rs +++ b/bon-macros/src/builder/builder_gen/builder_decl.rs @@ -9,10 +9,6 @@ impl super::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.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; // The fields can't be hidden using Rust's privacy syntax. // The details about this are described in the blog post: @@ -27,7 +23,8 @@ impl super::BuilderGenCtx { // 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 \ + this field should not be used directly; it's an implementation detail, and \ + if you access it directly, you may break some internal unsafe invariants; \ if you found yourself needing it, then you are probably doing something wrong; \ feel free to open an issue/discussion in our GitHub repository \ (https://github.com/elastio/bon) or ask for help in our Discord server \ @@ -43,7 +40,7 @@ impl super::BuilderGenCtx { let ty = &receiver.without_self_keyword; quote! { #private_field_attrs - #receiver_field: #ty, + __unsafe_private_receiver: #ty, } }); @@ -62,7 +59,7 @@ impl super::BuilderGenCtx { let start_fn_args_field = start_fn_arg_types.peek().is_some().then(|| { quote! { #private_field_attrs - #start_fn_args_field: (#(#start_fn_arg_types,)*), + __unsafe_private_start_fn_args: (#(#start_fn_arg_types,)*), } }); @@ -104,7 +101,7 @@ impl super::BuilderGenCtx { #where_clause { #private_field_attrs - #phantom_field: #phantom_data, + __unsafe_private_phantom: #phantom_data, #receiver_field #start_fn_args_field @@ -112,7 +109,7 @@ impl super::BuilderGenCtx { #( #custom_fields_idents: #custom_fields_types, )* #private_field_attrs - #named_members_field: ( + __unsafe_private_named: ( #( ::core::option::Option<#named_members_types>, )* diff --git a/bon-macros/src/builder/builder_gen/builder_derives.rs b/bon-macros/src/builder/builder_gen/builder_derives.rs index b49d8d37..240cdec7 100644 --- a/bon-macros/src/builder/builder_gen/builder_derives.rs +++ b/bon-macros/src/builder/builder_gen/builder_derives.rs @@ -70,17 +70,12 @@ impl BuilderGenCtx { let generic_args = &self.generics.args; let builder_ident = &self.builder_type.ident; - 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 clone = quote!(::core::clone::Clone); let clone_receiver = self.receiver().map(|receiver| { let ty = &receiver.without_self_keyword; quote! { - #receiver_field: <#ty as #clone>::clone(&self.#receiver_field), + __unsafe_private_receiver: <#ty as #clone>::clone(&self.__unsafe_private_receiver), } }); @@ -96,8 +91,8 @@ impl BuilderGenCtx { // ``` // required for `(...big type...)` to implement `Clone` // ``` - #start_fn_args_field: ( - #( <#types as #clone>::clone(&self.#start_fn_args_field.#indices), )* + __unsafe_private_start_fn_args: ( + #( <#types as #clone>::clone(&self.__unsafe_private_start_fn_args.#indices), )* ), } }); @@ -115,7 +110,7 @@ impl BuilderGenCtx { quote! { #bon::__::derives::clone_member::<#ty>( - &self.#named_members_field.#member_index + &self.__unsafe_private_named.#member_index ) } }); @@ -148,7 +143,7 @@ impl BuilderGenCtx { { fn clone(&self) -> Self { Self { - #phantom_field: ::core::marker::PhantomData, + __unsafe_private_phantom: ::core::marker::PhantomData, #clone_receiver #clone_start_fn_args #( #clone_fields, )* @@ -160,7 +155,7 @@ impl BuilderGenCtx { // ``` // required for `(...big type...)` to implement `Clone` // ``` - #named_members_field: ( #( #clone_named_members, )* ), + __unsafe_private_named: ( #( #clone_named_members, )* ), } } } @@ -168,9 +163,6 @@ impl BuilderGenCtx { } fn derive_debug(&self, derive: &DeriveConfig) -> 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; let bon = &self.bon; let format_members = self.members.iter().filter_map(|member| { @@ -183,7 +175,7 @@ impl BuilderGenCtx { output.field( #member_ident_str, #bon::__::derives::as_dyn_debug::<#member_ty>( - &self.#start_fn_args_field.#member_index + &self.__unsafe_private_start_fn_args.#member_index ) ); }) @@ -206,7 +198,7 @@ impl BuilderGenCtx { let member_ident_str = &member.name.snake_raw_str; let member_ty = member.underlying_norm_ty(); Some(quote! { - if let Some(value) = &self.#named_members_field.#member_index { + if let Some(value) = &self.__unsafe_private_named.#member_index { output.field( #member_ident_str, #bon::__::derives::as_dyn_debug::<#member_ty>(value) @@ -228,7 +220,7 @@ impl BuilderGenCtx { output.field( "self", #bon::__::derives::as_dyn_debug::<#ty>( - &self.#receiver_field + &self.__unsafe_private_receiver ) ); } diff --git a/bon-macros/src/builder/builder_gen/finish_fn.rs b/bon-macros/src/builder/builder_gen/finish_fn.rs index 338685e2..a445139f 100644 --- a/bon-macros/src/builder/builder_gen/finish_fn.rs +++ b/bon-macros/src/builder/builder_gen/finish_fn.rs @@ -2,7 +2,7 @@ use super::member::{Member, PosFnMember}; use crate::util::prelude::*; impl super::BuilderGenCtx { - fn finish_fn_member_expr(&self, member: &Member) -> TokenStream { + fn finish_fn_member_expr(member: &Member) -> TokenStream { let member = match member { Member::Named(member) => member, Member::Skip(member) => { @@ -14,9 +14,8 @@ impl super::BuilderGenCtx { } Member::StartFn(member) => { let index = &member.index; - let start_fn_args_field = &self.ident_pool.start_fn_args; - return quote! { self.#start_fn_args_field.#index }; + return quote! { self.__unsafe_private_start_fn_args.#index }; } Member::FinishFn(member) => { return member @@ -31,9 +30,8 @@ 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 + self.__unsafe_private_named.#index }; let default = member @@ -88,7 +86,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 diff --git a/bon-macros/src/builder/builder_gen/input_fn.rs b/bon-macros/src/builder/builder_gen/input_fn.rs index 62d1cf68..10e28708 100644 --- a/bon-macros/src/builder/builder_gen/input_fn.rs +++ b/bon-macros/src/builder/builder_gen/input_fn.rs @@ -454,10 +454,7 @@ impl FinishFnBody for FnCallBody { let prefix = self .sig .receiver() - .map(|_| { - let receiver_field = &ctx.ident_pool.receiver; - quote!(self.#receiver_field.) - }) + .map(|_| quote!(self.__unsafe_private_receiver.)) .or_else(|| { let self_ty = &self.impl_ctx.as_deref()?.self_ty; Some(quote!(<#self_ty>::)) diff --git a/bon-macros/src/builder/builder_gen/models.rs b/bon-macros/src/builder/builder_gen/models.rs index c0d5c667..4083930c 100644 --- a/bon-macros/src/builder/builder_gen/models.rs +++ b/bon-macros/src/builder/builder_gen/models.rs @@ -135,10 +135,6 @@ pub(crate) struct BuilderGenCtx { /// Path to the `bon` crate. pub(super) bon: syn::Path, - /// Private identifiers that are used in the builder implementation. - /// They are intentionally randomized to prevent users from accessing them. - pub(super) ident_pool: PrivateIdentsPool, - /// Name of the generic variable that holds the builder's state. pub(super) state_var: syn::Ident, @@ -160,22 +156,6 @@ 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, - pub(super) start_fn_args: syn::Ident, - pub(super) named_members: syn::Ident, -} - pub(super) struct BuilderGenCtxParams<'a> { pub(crate) bon: Option, pub(super) namespace: Cow<'a, GenericsNamespace>, @@ -333,7 +313,6 @@ impl BuilderGenCtx { Ok(Self { bon: bon.unwrap_or_else(|| syn::parse_quote!(::bon)), state_var, - ident_pool: PrivateIdentsPool::new(), members, allow_attrs, on, @@ -347,69 +326,6 @@ impl BuilderGenCtx { } } -impl PrivateIdentsPool { - 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(); - - // Totally random words. All coincidences are purely accidental 😸 - 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", - "rampage", - "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 cde0ec1d..993d2f45 100644 --- a/bon-macros/src/builder/builder_gen/setters/mod.rs +++ b/bon-macros/src/builder/builder_gen/setters/mod.rs @@ -342,12 +342,6 @@ impl<'a> SettersCtx<'a> { let body = match imp.body { SetterBody::Forward { body } => body, 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 @@ -355,26 +349,24 @@ impl<'a> SettersCtx<'a> { } else { let builder_ident = &self.base.builder_type.ident; - let maybe_receiver_field = self - .base - .receiver() - .map(|_| quote!(#receiver_field: self.#receiver_field,)); + let maybe_receiver_field = self.base.receiver().map( + |_| quote!(__unsafe_private_receiver: self.__unsafe_private_receiver,), + ); - let maybe_start_fn_args_field = self - .base - .start_fn_args() - .next() - .map(|_| quote!(#start_fn_args_field: self.#start_fn_args_field,)); + let maybe_start_fn_args_field = + self.base.start_fn_args().next().map( + |_| quote!(__unsafe_private_start_fn_args: self.__unsafe_private_start_fn_args,), + ); let custom_fields_idents = self.base.custom_fields().map(|field| &field.ident); quote! { #builder_ident { - #phantom_field: ::core::marker::PhantomData, + __unsafe_private_phantom: ::core::marker::PhantomData, #( #custom_fields_idents: self.#custom_fields_idents, )* #maybe_receiver_field #maybe_start_fn_args_field - #named_members_field: self.#named_members_field, + __unsafe_private_named: self.__unsafe_private_named, } } }; @@ -393,7 +385,7 @@ impl<'a> SettersCtx<'a> { let index = &self.member.index; quote! { - self.#named_members_field.#index = #expr; + self.__unsafe_private_named.#index = #expr; #output } } diff --git a/bon-macros/src/builder/builder_gen/start_fn.rs b/bon-macros/src/builder/builder_gen/start_fn.rs index 829e71b0..728e4b74 100644 --- a/bon-macros/src/builder/builder_gen/start_fn.rs +++ b/bon-macros/src/builder/builder_gen/start_fn.rs @@ -21,17 +21,12 @@ impl super::BuilderGenCtx { let where_clause = &generics.where_clause; let generic_args = &self.generics.args; - 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(); let receiver_field_init = receiver.map(|receiver| { let self_token = &receiver.with_self_keyword.self_token; quote! { - #receiver_field: #self_token, + __unsafe_private_receiver: #self_token, } }); @@ -68,7 +63,7 @@ impl super::BuilderGenCtx { let start_fn_args_field_init = start_fn_args.peek().is_some().then(|| { let idents = start_fn_args.map(|member| &member.base.ident); quote! { - #start_fn_args_field: (#(#idents,)*), + __unsafe_private_start_fn_args: (#(#idents,)*), } }); @@ -129,11 +124,11 @@ impl super::BuilderGenCtx { #( #custom_fields_vars )* #builder_ident { - #phantom_field: ::core::marker::PhantomData, + __unsafe_private_phantom: ::core::marker::PhantomData, #( #custom_fields_idents, )* #receiver_field_init #start_fn_args_field_init - #named_members_field: #named_members_field_init, + __unsafe_private_named: #named_members_field_init, } } } diff --git a/bon/tests/integration/ui/compile_fail/private_fields_access.rs b/bon/tests/integration/ui/compile_fail/private_fields_access.rs new file mode 100644 index 00000000..5610ff92 --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/private_fields_access.rs @@ -0,0 +1,24 @@ +#![deny(warnings)] + +struct Sut; + +#[bon::bon] +impl Sut { + #[builder] + fn sut(self, #[builder(start_fn)] _x1: u32, _x2: u32) {} +} + +fn main() { + let sut = Sut.sut(99); + + // Previously, there was an attempt to generate names for private fields + // with randomness to ensure users don't try to access them. This however, + // conflicts with caching in some build systems. See the following issue + // for details: https://github.com/elastio/bon/issues/218 + let SutSutBuilder { + __unsafe_private_phantom: _, + __unsafe_private_start_fn_args: _, + __unsafe_private_receiver: _, + __unsafe_private_named: _, + } = sut; +} diff --git a/bon/tests/integration/ui/compile_fail/private_fields_access.stderr b/bon/tests/integration/ui/compile_fail/private_fields_access.stderr new file mode 100644 index 00000000..6d491d2a --- /dev/null +++ b/bon/tests/integration/ui/compile_fail/private_fields_access.stderr @@ -0,0 +1,30 @@ +error: use of deprecated field `SutSutBuilder::__unsafe_private_phantom`: this field should not be used directly; it's an implementation detail, and if you access it directly, you may break some internal unsafe invariants; if you found yourself needing it, then you are probably doing something wrong; feel free to open an issue/discussion in our GitHub repository (https://github.com/elastio/bon) or ask for help in our Discord server (https://bon-rs.com/discord) + --> tests/integration/ui/compile_fail/private_fields_access.rs:19:9 + | +19 | __unsafe_private_phantom: _, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> tests/integration/ui/compile_fail/private_fields_access.rs:1:9 + | +1 | #![deny(warnings)] + | ^^^^^^^^ + = note: `#[deny(deprecated)]` implied by `#[deny(warnings)]` + +error: use of deprecated field `SutSutBuilder::__unsafe_private_start_fn_args`: this field should not be used directly; it's an implementation detail, and if you access it directly, you may break some internal unsafe invariants; if you found yourself needing it, then you are probably doing something wrong; feel free to open an issue/discussion in our GitHub repository (https://github.com/elastio/bon) or ask for help in our Discord server (https://bon-rs.com/discord) + --> tests/integration/ui/compile_fail/private_fields_access.rs:20:9 + | +20 | __unsafe_private_start_fn_args: _, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: use of deprecated field `SutSutBuilder::__unsafe_private_receiver`: this field should not be used directly; it's an implementation detail, and if you access it directly, you may break some internal unsafe invariants; if you found yourself needing it, then you are probably doing something wrong; feel free to open an issue/discussion in our GitHub repository (https://github.com/elastio/bon) or ask for help in our Discord server (https://bon-rs.com/discord) + --> tests/integration/ui/compile_fail/private_fields_access.rs:21:9 + | +21 | __unsafe_private_receiver: _, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: use of deprecated field `SutSutBuilder::__unsafe_private_named`: this field should not be used directly; it's an implementation detail, and if you access it directly, you may break some internal unsafe invariants; if you found yourself needing it, then you are probably doing something wrong; feel free to open an issue/discussion in our GitHub repository (https://github.com/elastio/bon) or ask for help in our Discord server (https://bon-rs.com/discord) + --> tests/integration/ui/compile_fail/private_fields_access.rs:22:9 + | +22 | __unsafe_private_named: _, + | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/website/src/guide/basics/optional-members.md b/website/src/guide/basics/optional-members.md index d8ed96ff..3a18b7e9 100644 --- a/website/src/guide/basics/optional-members.md +++ b/website/src/guide/basics/optional-members.md @@ -109,6 +109,6 @@ let result = example() You can also reference other members in the default expression. See [`#[builder(default)]`](../../reference/builder/member/default#evaluation-context) reference for details. -## Conditional building +## Conditional Building Now that you know how optional members work you can check out the [Conditional Building](../patterns/conditional-building) design patterns or continue studying other features of `bon` by following the "Next page" link at the bottom. diff --git a/website/src/reference/builder/top-level/state_mod.md b/website/src/reference/builder/top-level/state_mod.md index dce393f1..2a6ea0d2 100644 --- a/website/src/reference/builder/top-level/state_mod.md +++ b/website/src/reference/builder/top-level/state_mod.md @@ -26,7 +26,7 @@ Overrides name, visibility and docs for the builder's [typestate API](../../../g ## `name` -The default name for the builder typestate API module is a `snake_case` version from the name of the [`builder_type`](./builder_type#name). +The default name for the builder typestate API module is a `snake_case` version of the name of the [`builder_type`](./builder_type#name). ## `vis`