Repository: elastio/bon (Rust)

Issue:
optional `r#type` yields invalid identifier
Hello,

I think I hit an edge-case that #9 does not properly handle.

The following struct definition:

```rs
enum VariableType {
    Flow,
    Temperature,
    Pressure,
}

#[derive(bon::Builder)]
struct Variable {
    r#type: Option<VariableType>,
    name: String,
    key: String,
}
```

Will panic with the following error:

```
proc-macro derive panicked
   message: `"maybe_r#type"` is not a valid identifier
```

It works if I remove the `Option` or rename my field to `variable_type`

<!-- 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!

The fix is pending a 3.0 release (https://github.com/elastio/bon/issues/156)
`3.0.0-rc` version was published. I'll prepare a blog post for the release and switch it to `3.0.0` on November 13-th



Patch:
diff --git a/bon-macros/src/builder/builder_gen/setters/mod.rs b/bon-macros/src/builder/builder_gen/setters/mod.rs
index 3fbc78ac..b21897b8 100644
--- a/bon-macros/src/builder/builder_gen/setters/mod.rs
+++ b/bon-macros/src/builder/builder_gen/setters/mod.rs
@@ -529,7 +529,7 @@ impl SettersItems {
                 // 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())
+                syn::Ident::new(&format!("maybe_{}", base_name.raw_name()), base_name.span())
             });
 
         let default = member.config.default.as_deref().and_then(|default| {
diff --git a/website/src/changelog.md b/website/src/changelog.md
index fa1062f8..19d75447 100644
--- a/website/src/changelog.md
+++ b/website/src/changelog.md
@@ -89,6 +89,7 @@ All the breaking changes are very unlikely to actually break your code that was
 ### Fixed
 
 -   Fix `#[cfg/cfg_attr()]` not being expanded when used on function arguments with doc comments or other attributes.
+-   Fix raw identifiers in optional/default members ([#175](https://github.com/elastio/bon/pull/175))
 
 ### Other
 


Test patch:
diff --git a/bon/tests/integration/builder/raw_idents.rs b/bon/tests/integration/builder/raw_idents.rs
index 01f530e6..5fb2b081 100644
--- a/bon/tests/integration/builder/raw_idents.rs
+++ b/bon/tests/integration/builder/raw_idents.rs
@@ -3,34 +3,58 @@ use crate::prelude::*;
 
 #[test]
 #[allow(non_camel_case_types)]
-fn struct_case() {
-    #[derive(Builder)]
-    struct r#Type {
-        r#type: u32,
+fn test_struct() {
+    {
+        #[derive(Builder)]
+        struct r#Type {
+            r#type: u32,
 
-        #[builder(name = r#while)]
-        other: u32,
-    }
+            #[builder(name = r#while)]
+            other: u32,
+        }
 
-    let actual = r#Type::builder().r#type(42).r#while(100).build();
+        let actual = r#Type::builder().r#type(42).r#while(100).build();
 
-    assert_eq!(actual.r#type, 42);
-    assert_eq!(actual.other, 100);
+        assert_eq!(actual.r#type, 42);
+        assert_eq!(actual.other, 100);
 
-    #[derive(Builder)]
-    #[builder(builder_type = r#type, state_mod = r#mod)]
-    #[allow(clippy::items_after_statements, dead_code)]
-    struct Sut {
-        r#while: u32,
+        #[derive(Builder)]
+        #[builder(builder_type = r#type, state_mod = r#mod)]
+        #[allow(clippy::items_after_statements, dead_code)]
+        struct Sut {
+            r#while: u32,
+        }
+
+        let _actual: r#type = Sut::builder();
+        let _actual: r#type<r#mod::SetWhile> = Sut::builder().r#while(32);
     }
+    // This is based on the issue https://github.com/elastio/bon/issues/174
+    {
+        {
+            #[derive(Builder)]
+            struct Sut {
+                r#type: Option<u32>,
+            }
+
+            let value = Sut::builder().maybe_type(Some(2)).build();
+            assert_eq!(value.r#type, Some(2));
+        }
+        {
+            #[derive(Builder)]
+            struct Sut {
+                #[builder(default)]
+                r#type: u32,
+            }
 
-    let _actual: r#type = Sut::builder();
-    let _actual: r#type<r#mod::SetWhile> = Sut::builder().r#while(32);
+            let value = Sut::builder().maybe_type(Some(2)).build();
+            assert_eq!(value.r#type, 2);
+        }
+    }
 }
 
 #[test]
 #[allow(non_camel_case_types)]
-fn fn_case() {
+fn test_fn() {
     {
         #[builder]
         fn r#type(r#type: u32, #[builder(name = r#while)] other: u32) {
@@ -46,4 +70,25 @@ fn fn_case() {
 
         let _: r#type = sut();
     }
+    // This is based on the issue https://github.com/elastio/bon/issues/174
+    {
+        {
+            #[builder]
+            fn sut(r#type: Option<u32>) -> Option<u32> {
+                r#type
+            }
+
+            let value = sut().maybe_type(Some(2)).call();
+            assert_eq!(value, Some(2));
+        }
+        {
+            #[builder]
+            fn sut(#[builder(default)] r#type: u32) -> u32 {
+                r#type
+            }
+
+            let value = sut().maybe_type(Some(2)).call();
+            assert_eq!(value, 2);
+        }
+    }
 }