Suffix field names with underscore if their names conflict with other member function names.

In Rust, the following is allowed:

```
struct X {
   a: i32
}

impl X {
 fn a(self: &Self) -> i32 {
 self.a
 }
}
```

But C++ doesn't allow the fields and the member functions to have the same names in Class/Struct. When the conflicts happen, we rename the fields assuming that the member functions serve as getters/setters.

PiperOrigin-RevId: 671035288
Change-Id: I72bfaa607971bd64aa8db3a02068dcd5a8257a9c
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs
index aecb12e..f7a77f7 100644
--- a/cc_bindings_from_rs/bindings.rs
+++ b/cc_bindings_from_rs/bindings.rs
@@ -2037,6 +2037,7 @@
 fn format_fields<'tcx>(
     db: &dyn BindingsGenerator<'tcx>,
     core: &AdtCoreBindings<'tcx>,
+    member_function_names: &HashSet<String>,
 ) -> ApiSnippets {
     let tcx = db.tcx();
 
@@ -2115,8 +2116,15 @@
                         cpp_type: db.format_ty_for_cc(ty, TypeLocation::Other)?,
                     })
                 });
-                let name = field_def.ident(tcx);
-                let cc_name = format_cc_ident(name.as_str())
+                let name = field_def.ident(tcx).to_string();
+                let cc_name = if member_function_names.contains(&name) {
+                    // TODO: Handle the case of name_ itself also being taken? e.g. the Rust struct
+                    // struct S {a: i32, a_: i32} impl S { fn a() {} fn a_() {} fn a__(){}.
+                    format!("{name}_")
+                } else {
+                    name.clone()
+                };
+                let cc_name = format_cc_ident(cc_name.as_str())
                     .unwrap_or_else(|_err| format_ident!("__field{index}").into_token_stream());
                 let rs_name = {
                     let name_starts_with_digit = name
@@ -2833,6 +2841,7 @@
     let move_ctor_and_assignment_snippets =
         db.format_move_ctor_and_assignment_operator(core.clone()).unwrap_or_else(|err| err);
 
+    let mut member_function_names = HashSet::<String>::new();
     let impl_items_snippets = tcx
         .inherent_impls(core.def_id)
         .into_iter()
@@ -2852,7 +2861,17 @@
                 return None;
             }
             let result = match impl_item_ref.kind {
-                AssocItemKind::Fn { .. } => db.format_fn(def_id).map(Some),
+                AssocItemKind::Fn { .. } => {
+                    let result = db.format_fn(def_id);
+                    if result.is_ok() {
+                        let cpp_name = FullyQualifiedName::new(tcx, def_id.into())
+                            .cpp_name
+                            .unwrap()
+                            .to_string();
+                        member_function_names.insert(cpp_name);
+                    }
+                    result.map(Some)
+                }
                 AssocItemKind::Const => format_const(db, def_id).map(Some),
                 other => Err(anyhow!("Unsupported `impl` item kind: {other:?}")),
             };
@@ -2878,7 +2897,7 @@
         main_api: fields_main_api,
         cc_details: fields_cc_details,
         rs_details: fields_rs_details,
-    } = format_fields(db, &core);
+    } = format_fields(db, &core, &member_function_names);
 
     let alignment = Literal::u64_unsuffixed(core.alignment_in_bytes);
     let size = Literal::u64_unsuffixed(core.size_in_bytes);
@@ -8533,6 +8552,58 @@
     }
 
     #[test]
+    fn test_format_item_rename_field_with_conflicting_name() {
+        let test_src = r#"
+        pub struct X {
+            pub a: i32,
+            b: i32,
+            #[allow(dead_code)]
+            c: i32,
+        }
+
+        impl X {
+            pub fn a(&self) -> i32 {
+                self.a
+            }
+            pub fn b(&self) -> i32 {
+                self.b
+            }
+        }
+        "#;
+
+        test_format_item(test_src, "X", |result| {
+            let result = result.unwrap().unwrap();
+            let main_api = &result.main_api;
+            assert!(!main_api.prereqs.is_empty());
+            assert_cc_matches!(
+                main_api.tokens,
+                quote! {
+                    std::int32_t a_;
+                }
+            );
+            assert_cc_matches!(
+                main_api.tokens,
+                quote! {
+                    std::int32_t b_;
+                }
+            );
+            assert_cc_matches!(
+                main_api.tokens,
+                quote! {
+                    std::int32_t c;
+                }
+            );
+            // Check that the fields are not renamed in the Rust side.
+            assert_rs_matches!(
+                result.rs_details,
+                quote! {
+                    ::core::mem::offset_of!(::rust_out::X, a) == 0
+                }
+            );
+        })
+    }
+
+    #[test]
     fn test_must_use_attr_for_struct_msg() {
         let test_src = r#"
         #[must_use = "foo"]
diff --git a/cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions.rs b/cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions.rs
new file mode 100644
index 0000000..03bc992
--- /dev/null
+++ b/cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions.rs
@@ -0,0 +1,19 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#![allow(dead_code)]
+pub struct X {
+    pub a: i32,
+    b: i32,
+    c: i32,
+}
+
+impl X {
+    pub fn a(&self) -> i32 {
+        self.a
+    }
+    pub fn b(&self) -> i32 {
+        self.b
+    }
+}
diff --git a/cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions_cc_api.h b/cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions_cc_api.h
new file mode 100644
index 0000000..1e4cf89
--- /dev/null
+++ b/cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions_cc_api.h
@@ -0,0 +1,104 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Automatically @generated C++ bindings for the following Rust crate:
+// struct_with_conflicting_fields_and_member_functions_rust
+
+// clang-format off
+#pragma once
+
+#include "support/internal/attribute_macros.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <type_traits>
+
+namespace struct_with_conflicting_fields_and_member_functions_rust {
+
+// Generated from:
+// cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions.rs;l=6
+struct
+    CRUBIT_INTERNAL_RUST_TYPE(
+        ":: struct_with_conflicting_fields_and_member_functions_rust :: "
+        "X") alignas(4) [[clang::trivial_abi]] X final {
+ public:
+  // `X` doesn't implement the `Default` trait
+  X() = delete;
+
+  // No custom `Drop` impl and no custom \"drop glue\" required
+  ~X() = default;
+  X(X&&) = default;
+  X& operator=(X&&) = default;
+
+  // `X` doesn't implement the `Clone` trait
+  X(const X&) = delete;
+  X& operator=(const X&) = delete;
+
+  // Generated from:
+  // cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions.rs;l=13
+  std::int32_t a() const [[clang::annotate_type("lifetime", "__anon1")]];
+
+  // Generated from:
+  // cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions.rs;l=16
+  std::int32_t b() const [[clang::annotate_type("lifetime", "__anon1")]];
+
+ public:
+  union {
+    // Generated from:
+    // cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions.rs;l=7
+    std::int32_t a_;
+  };
+
+ private:
+  union {
+    // Generated from:
+    // cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions.rs;l=8
+    std::int32_t b_;
+  };
+
+ private:
+  union {
+    // Generated from:
+    // cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions.rs;l=9
+    std::int32_t c;
+  };
+
+ private:
+  static void __crubit_field_offset_assertions();
+};
+
+static_assert(
+    sizeof(X) == 12,
+    "Verify that ADT layout didn't change since this header got generated");
+static_assert(
+    alignof(X) == 4,
+    "Verify that ADT layout didn't change since this header got generated");
+static_assert(std::is_trivially_destructible_v<X>);
+static_assert(std::is_trivially_move_constructible_v<X>);
+static_assert(std::is_trivially_move_assignable_v<X>);
+namespace __crubit_internal {
+extern "C" std::int32_t __crubit_thunk_a(
+    ::struct_with_conflicting_fields_and_member_functions_rust::
+        X const& [[clang::annotate_type("lifetime", "__anon1")]]);
+}
+inline std::int32_t X::a() const
+    [[clang::annotate_type("lifetime", "__anon1")]] {
+  return __crubit_internal::__crubit_thunk_a(*this);
+}
+
+namespace __crubit_internal {
+extern "C" std::int32_t __crubit_thunk_b(
+    ::struct_with_conflicting_fields_and_member_functions_rust::
+        X const& [[clang::annotate_type("lifetime", "__anon1")]]);
+}
+inline std::int32_t X::b() const
+    [[clang::annotate_type("lifetime", "__anon1")]] {
+  return __crubit_internal::__crubit_thunk_b(*this);
+}
+inline void X::__crubit_field_offset_assertions() {
+  static_assert(0 == offsetof(X, a_));
+  static_assert(4 == offsetof(X, b_));
+  static_assert(8 == offsetof(X, c));
+}
+}  // namespace struct_with_conflicting_fields_and_member_functions_rust
diff --git a/cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions_cc_api_impl.rs b/cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions_cc_api_impl.rs
new file mode 100644
index 0000000..7ec0563
--- /dev/null
+++ b/cc_bindings_from_rs/test/golden/struct_with_conflicting_fields_and_member_functions_cc_api_impl.rs
@@ -0,0 +1,30 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Automatically @generated C++ bindings for the following Rust crate:
+// struct_with_conflicting_fields_and_member_functions_rust
+
+#![allow(improper_ctypes_definitions)]
+
+const _: () = assert!(
+    ::std::mem::size_of::<::struct_with_conflicting_fields_and_member_functions_rust::X>() == 12
+);
+const _: () = assert!(
+    ::std::mem::align_of::<::struct_with_conflicting_fields_and_member_functions_rust::X>() == 4
+);
+#[no_mangle]
+extern "C" fn __crubit_thunk_a<'__anon1>(
+    __self: &'__anon1 ::struct_with_conflicting_fields_and_member_functions_rust::X,
+) -> i32 {
+    ::struct_with_conflicting_fields_and_member_functions_rust::X::a(__self)
+}
+#[no_mangle]
+extern "C" fn __crubit_thunk_b<'__anon1>(
+    __self: &'__anon1 ::struct_with_conflicting_fields_and_member_functions_rust::X,
+) -> i32 {
+    ::struct_with_conflicting_fields_and_member_functions_rust::X::b(__self)
+}
+const _: () = assert!(
+    ::core::mem::offset_of!(::struct_with_conflicting_fields_and_member_functions_rust::X, a) == 0
+);