Repository: elastio/bon (Rust)

Issue:
Builder breaks when field is named `self_`
Version: 3.3.1

Code:
```rust
#[derive(bon::Builder)]
struct Test {
    self_: String
}
```

Message:

```
error: 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;                                                            
       syntax error in generated code: Error(
           "expected identifier, found keyword `Self`",
       );
       generated code:
       ```rust
                       #[allow(unnameable_types, unreachable_pub, clippy :: redundant_pub_crate)]
       #[doc =
       "Tools for manipulating the type state of [`TestBuilder`].\n\nSee the [detailed guide](https://bon-rs.com/guide/typestate-api) that describes how all the pieces here fit together."]
       mod test_builder
       {
           #[doc(inline)] pub(super) use :: bon :: __ :: { IsSet, IsUnset }; use ::
           bon :: __ :: { Set, Unset }; mod sealed
           { pub(in super :: super) struct Sealed; }
           #[doc =
           "Builder's type state specifies if members are set or not (unset).\n\nYou 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."]
           pub(super) trait State : :: core :: marker :: Sized
           {
               #[doc =
               "Type state of the member `self_`.\n\nIt can implement either [`IsSet`] or [`IsUnset`]"]
               type Self; #[doc(hidden)] const SEALED : sealed :: Sealed;
           }
           #[doc =
           "Marker trait that indicates that all required members are set.\n\nIn this state, you can finish building by calling the method [`TestBuilder::build()`](super::TestBuilder::build())"]
           pub(super) trait IsComplete : State
           { #[doc(hidden)] const SEALED : sealed :: Sealed; } #[doc(hidden)] impl <
           S : State > IsComplete for S where S :: Self : IsSet,
           { const SEALED : sealed :: Sealed = sealed :: Sealed; }
           #[deprecated =
           "this should not be used directly; it is an implementation detail; use the Set* type aliases to control the state of members instead"]
           #[doc(hidden)] #[allow(non_camel_case_types)] mod members
           { pub(in super :: super) struct self_(()); }
           #[doc =
           r" Represents a [`State`] that has [`IsUnset`] implemented for all members."]
           #[doc = r""]
           #[doc =
           r" This is the initial state of the builder before any setters are called."]
           pub(super) struct Empty(());
           #[doc =
           "Represents a [`State`] that has [`IsSet`] implemented for [`State::Self`].\n\nThe state for all other members is left the same as in the input state."]
           pub(super) struct SetSelf < S : State = Empty >
           (:: core :: marker :: PhantomData < fn() -> S >); #[doc(hidden)] impl
           State for Empty
           {
               type Self = Unset < members :: self_ > ; const SEALED : sealed ::
               Sealed = sealed :: Sealed;
           } #[doc(hidden)] impl < S : State > State for SetSelf < S >
           {
               type Self = Set < members :: self_ > ; const SEALED : sealed :: Sealed
               = sealed :: Sealed;
           }
       }
       #[must_use =
       "the builder does nothing until you call `build()` on it to finish building"]
       #[doc =
       "Use builder syntax to set the inputs and finish with [`build()`](Self::build())."]
       #[allow(unused_parens)]
       #[allow(clippy :: struct_field_names, clippy :: type_complexity)] struct
       TestBuilder < S : test_builder :: State = test_builder :: Empty >
       {
           #[doc(hidden)]
           #[deprecated =
           "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)"]
           __unsafe_private_phantom : :: core :: marker :: PhantomData <
           (fn() -> S, fn() -> :: core :: marker :: PhantomData < Test < > > ,) > ,
           #[doc(hidden)]
           #[deprecated =
           "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)"]
           __unsafe_private_named : (:: core :: option :: Option < String > ,),
       } #[allow(unused_parens)] #[automatically_derived] impl < S : test_builder ::
       State > TestBuilder < S >
       {
           #[doc = r" Finish building and return the requested object"]
           #[inline(always)]
           #[allow(clippy :: inline_always, clippy :: future_not_send, clippy ::
           missing_const_for_fn,)]
           #[must_use = "building a struct without using it is likely a bug"] fn
           build(self,) -> Test < > where S : test_builder :: IsComplete
           {
               let self_ : String = unsafe
               {
                   :: core :: option :: Option ::
                   unwrap_unchecked(self.__unsafe_private_named.0)
               }; Test { self_, }
           } #[doc = "_**Required.**_\n\n"]
           #[allow(clippy :: inline_always, clippy :: impl_trait_in_params, clippy ::
           missing_const_for_fn,)] #[inline(always)] fn
           self_(mut self, value : String) -> TestBuilder < test_builder :: SetSelf <
           S >> where S :: Self : test_builder :: IsUnset,
           {
               self.__unsafe_private_named.0 = :: core :: option :: Option ::
               Some(value); TestBuilder
               {
                   __unsafe_private_phantom : :: core :: marker :: PhantomData,
                   __unsafe_private_named : self.__unsafe_private_named,
               }
           }
       }
       ```
 --> random\src\lib.rs:4:10
  |
4 | #[derive(bon::Builder)]
  |          ^^^^^^^^^^^^
  |
  = note: this error originates in the derive macro `bon::Builder` (in Nightly builds, run with -Z macro-backtrace for more info)
```


<!-- Please keep this note for the community at the end of the issue -->

### A note for the community from the maintainers

Please vote on this issue by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to help the maintainers with prioritizing it. You may add a comment describing your real use case related to this issue for us to better understand the problem domain.



Discussion:
Thank you for reporting this! I've made a fix to the macro so that it generates `Self_` instead of `Self` when turing the member name into pascal case. `Self` is the only uppercase keyword in Rust, so this was a special case. The fix was released in a new patch 	`3.3.2`



Patch:
diff --git a/bon-macros/src/util/ident.rs b/bon-macros/src/util/ident.rs
index c68d87f7..12a85fbc 100644
--- a/bon-macros/src/util/ident.rs
+++ b/bon-macros/src/util/ident.rs
@@ -37,7 +37,14 @@ impl IdentExt for syn::Ident {
         // https://internals.rust-lang.org/t/raw-identifiers-dont-work-for-all-identifiers/9094
         //
         // So no need to handle raw identifiers here.
-        let renamed = RenameRule::PascalCase.apply_to_field(self.raw_name());
+        let mut renamed = RenameRule::PascalCase.apply_to_field(self.raw_name());
+
+        // Make sure `Self` keyword isn't generated.
+        // This may happen if the input was `self_`, for example.
+        if renamed == "Self" {
+            renamed.push('_');
+        }
+
         Self::new(&renamed, Span::call_site())
     }
 


Test patch:
diff --git a/bon/tests/integration/builder/raw_idents.rs b/bon/tests/integration/builder/raw_idents.rs
index 5fb2b081..791beb12 100644
--- a/bon/tests/integration/builder/raw_idents.rs
+++ b/bon/tests/integration/builder/raw_idents.rs
@@ -92,3 +92,16 @@ fn test_fn() {
         }
     }
 }
+
+// This is based on the issue https://github.com/elastio/bon/issues/237
+#[test]
+fn test_self_underscore_bug_237() {
+    #[derive(Builder)]
+    struct Sut {
+        self_: u32,
+    }
+
+    let builder: SutBuilder<sut_builder::SetSelf_> = Sut::builder().self_(42);
+
+    assert_eq!(builder.build().self_, 42);
+}