C++ IR: Add access specifiers to fields and populate in AstVisitor.

The access specifiers aren't being serialized to JSON yet, so no need to change
the JSON side (that will happen in a followup CL).

PiperOrigin-RevId: 397254542
diff --git a/rs_bindings_from_cc/ast_visitor.cc b/rs_bindings_from_cc/ast_visitor.cc
index 56640b5..f9d9bf6 100644
--- a/rs_bindings_from_cc/ast_visitor.cc
+++ b/rs_bindings_from_cc/ast_visitor.cc
@@ -14,8 +14,10 @@
 #include "third_party/absl/strings/string_view.h"
 #include "third_party/llvm/llvm-project/clang/include/clang/AST/ASTContext.h"
 #include "third_party/llvm/llvm-project/clang/include/clang/AST/Decl.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/AST/DeclCXX.h"
 #include "third_party/llvm/llvm-project/clang/include/clang/AST/Mangle.h"
 #include "third_party/llvm/llvm-project/clang/include/clang/AST/Type.h"
+#include "third_party/llvm/llvm-project/clang/include/clang/Basic/Specifiers.h"
 #include "third_party/llvm/llvm-project/llvm/include/llvm/Support/Casting.h"
 
 namespace rs_bindings_from_cc {
@@ -54,11 +56,40 @@
   return true;
 }
 
+static AccessSpecifier TranslateAccessSpecifier(clang::AccessSpecifier access) {
+  switch (access) {
+    case clang::AS_public:
+      return kPublic;
+    case clang::AS_protected:
+      return kProtected;
+    case clang::AS_private:
+      return kPrivate;
+    case clang::AS_none:
+      // We should never be encoding a "none" access specifier in IR.
+      assert(false);
+      // We have to return something. Conservatively return private so we don't
+      // inadvertently make a private member variable accessible in Rust.
+      return kPrivate;
+  }
+}
+
 bool AstVisitor::VisitRecordDecl(clang::RecordDecl* record_decl) {
   std::vector<Field> fields;
+  clang::AccessSpecifier default_access = clang::AS_public;
+  if (const auto* cxx_record_decl =
+          clang::dyn_cast<clang::CXXRecordDecl>(record_decl)) {
+    if (cxx_record_decl->isClass()) {
+      default_access = clang::AS_private;
+    }
+  }
   for (const clang::FieldDecl* field_decl : record_decl->fields()) {
+    clang::AccessSpecifier access = field_decl->getAccess();
+    if (access == clang::AS_none) {
+      access = default_access;
+    }
     fields.push_back({.identifier = GetTranslatedName(field_decl),
-                      .type = ConvertType(field_decl->getType())});
+                      .type = ConvertType(field_decl->getType()),
+                      .access = TranslateAccessSpecifier(access)});
   }
   ir_.records.push_back({GetTranslatedName(record_decl), std::move(fields)});
   return true;
diff --git a/rs_bindings_from_cc/ast_visitor_test.cc b/rs_bindings_from_cc/ast_visitor_test.cc
index 12a8970..2f23bd9 100644
--- a/rs_bindings_from_cc/ast_visitor_test.cc
+++ b/rs_bindings_from_cc/ast_visitor_test.cc
@@ -191,5 +191,49 @@
   EXPECT_EQ(second.type.rs_name, "i32");
 }
 
+TEST(AstVisitorTest, MemberVariableAccessSpecifiers) {
+  IR ir = ImportCode({R"(
+    struct SomeStruct {
+      int default_access_int;
+    public:
+      int public_int;
+    protected:
+      int protected_int;
+    private:
+      int private_int;
+    };
+
+    class SomeClass {
+      int default_access_int;
+    };
+  )"},
+                     {});
+  EXPECT_THAT(ir.functions, SizeIs(0));
+  ASSERT_THAT(ir.records, SizeIs(2));
+
+  Record some_struct = ir.records[0];
+  EXPECT_EQ(some_struct.Ident().Ident(), "SomeStruct");
+  ASSERT_THAT(some_struct.Fields(), SizeIs(4));
+  Field field0 = some_struct.Fields()[0];
+  EXPECT_EQ(field0.identifier.Ident(), "default_access_int");
+  EXPECT_EQ(field0.access, kPublic);
+  Field field1 = some_struct.Fields()[1];
+  EXPECT_EQ(field1.identifier.Ident(), "public_int");
+  EXPECT_EQ(field1.access, kPublic);
+  Field field2 = some_struct.Fields()[2];
+  EXPECT_EQ(field2.identifier.Ident(), "protected_int");
+  EXPECT_EQ(field2.access, kProtected);
+  Field field3 = some_struct.Fields()[3];
+  EXPECT_EQ(field3.identifier.Ident(), "private_int");
+  EXPECT_EQ(field3.access, kPrivate);
+
+  Record some_class = ir.records[1];
+  EXPECT_EQ(some_class.Ident().Ident(), "SomeClass");
+  ASSERT_THAT(some_class.Fields(), SizeIs(1));
+  field0 = some_class.Fields()[0];
+  EXPECT_EQ(field0.identifier.Ident(), "default_access_int");
+  EXPECT_EQ(field0.access, kPrivate);
+}
+
 }  // namespace
 }  // namespace rs_bindings_from_cc
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index fe958d0..89d05c3 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -118,12 +118,20 @@
   bool is_inline;
 };
 
+// Access specifier for a member or base class.
+enum AccessSpecifier {
+  kPublic,
+  kProtected,
+  kPrivate,
+};
+
 // A field (non-static member variable) of a record.
 struct Field {
   nlohmann::json ToJson() const;
 
   Identifier identifier;
   Type type;
+  AccessSpecifier access;
 };
 
 // A record (struct, class, union).