Add trivial construction flags to the IR.

These can be used in followup CLs to influence a few things:

* Triviality of move is sufficient (though strictly not necessary) for relocatability. In particular, we'll want to infer that a type is trivially relocatable if it's either `is_trivial_abi` **or** has a (public?) trivial move constructor.
* Triviality of a public copy constructor is necessary (and, together with trivial relocatability, sufficient) to derive Copy.
* Existence of copy at all is necessary to implement Clone.

Since these are interrelated, and for convenience, I did them all at once in one CL.

PiperOrigin-RevId: 400951362
diff --git a/rs_bindings_from_cc/ast_visitor.cc b/rs_bindings_from_cc/ast_visitor.cc
index 4ed72bf..131f938 100644
--- a/rs_bindings_from_cc/ast_visitor.cc
+++ b/rs_bindings_from_cc/ast_visitor.cc
@@ -92,11 +92,55 @@
 bool AstVisitor::VisitRecordDecl(clang::RecordDecl* record_decl) {
   std::vector<Field> fields;
   clang::AccessSpecifier default_access = clang::AS_public;
+  // The definition is always rewritten, but default access to `kPublic` in case
+  // it is implicitly defined.
+  SpecialMemberFunc copy_ctor = {
+      .definition = SpecialMemberFunc::Definition::kTrivial,
+      .access = kPublic,
+  };
+  SpecialMemberFunc move_ctor = {
+      .definition = SpecialMemberFunc::Definition::kTrivial,
+      .access = kPublic,
+  };
   if (const auto* cxx_record_decl =
           clang::dyn_cast<clang::CXXRecordDecl>(record_decl)) {
     if (cxx_record_decl->isClass()) {
       default_access = clang::AS_private;
     }
+
+    if (cxx_record_decl->hasTrivialCopyConstructor()) {
+      copy_ctor.definition = SpecialMemberFunc::Definition::kTrivial;
+    } else if (cxx_record_decl->hasNonTrivialCopyConstructor()) {
+      copy_ctor.definition = SpecialMemberFunc::Definition::kNontrivial;
+    } else {
+      // I don't think the copy ctor can be implicitly deleted, but just in
+      // case...
+      copy_ctor.definition = SpecialMemberFunc::Definition::kDeleted;
+    }
+
+    if (cxx_record_decl->hasTrivialMoveConstructor()) {
+      move_ctor.definition = SpecialMemberFunc::Definition::kTrivial;
+    } else if (cxx_record_decl->hasNonTrivialMoveConstructor()) {
+      move_ctor.definition = SpecialMemberFunc::Definition::kNontrivial;
+    } else {
+      // The move constructor can be **implicitly deleted** (and so not subject
+      // to the below loop over ctors), e.g. by the presence by a copy ctor.
+      move_ctor.definition = SpecialMemberFunc::Definition::kDeleted;
+    }
+
+    for (clang::CXXConstructorDecl* ctor_decl : cxx_record_decl->ctors()) {
+      if (ctor_decl->isCopyConstructor()) {
+        copy_ctor.access = TranslateAccessSpecifier(ctor_decl->getAccess());
+        if (ctor_decl->isDeleted()) {
+          copy_ctor.definition = SpecialMemberFunc::Definition::kDeleted;
+        }
+      } else if (ctor_decl->isMoveConstructor()) {
+        move_ctor.access = TranslateAccessSpecifier(ctor_decl->getAccess());
+        if (ctor_decl->isDeleted()) {
+          move_ctor.definition = SpecialMemberFunc::Definition::kDeleted;
+        }
+      }
+    }
   }
   const clang::ASTRecordLayout& layout =
       record_decl->getASTContext().getASTRecordLayout(record_decl);
@@ -121,6 +165,8 @@
              .fields = std::move(fields),
              .size = layout.getSize().getQuantity(),
              .alignment = layout.getAlignment().getQuantity(),
+             .copy_constructor = copy_ctor,
+             .move_constructor = move_ctor,
              .is_trivial_abi = record_decl->canPassInRegisters()});
   return true;
 }
diff --git a/rs_bindings_from_cc/ast_visitor_test.cc b/rs_bindings_from_cc/ast_visitor_test.cc
index 12665fe..735fd17 100644
--- a/rs_bindings_from_cc/ast_visitor_test.cc
+++ b/rs_bindings_from_cc/ast_visitor_test.cc
@@ -154,6 +154,23 @@
   return false;
 }
 
+// Matches a Record with a copy_constructor that matches all given matchers.
+template <typename... Args>
+auto CopyConstructor(const Args&... matchers) {
+  return testing::Field("copy_constructor", &Record::copy_constructor,
+                        AllOf(matchers...));
+}
+
+// Matches a Record with a move_constructor that matches all given matchers.
+template <typename... Args>
+auto MoveConstructor(const Args&... matchers) {
+  return testing::Field("move_constructor", &Record::move_constructor,
+                        AllOf(matchers...));
+}
+
+// Matches a SpecialMemberFunc that has the given definition.
+MATCHER_P(DefinitionIs, definition, "") { return arg.definition == definition; }
+
 // Matches a Field that has the given access specifier.
 MATCHER_P(AccessIs, access, "") { return arg.access == access; }
 
@@ -260,6 +277,121 @@
                                   FieldType(IsInt()), OffsetIs(32)))))));
 }
 
+TEST(AstVisitorTest, TrivialCopyConstructor) {
+  absl::string_view file =
+      "struct Implicit {};\n"
+      "struct Defaulted { Defaulted(const Defaulted&) = default; };\n";
+  IR ir = IrFromCc({file}, {});
+
+  EXPECT_THAT(ir.items, SizeIs(2));
+  EXPECT_THAT(ir.items, Each(VariantWith<Record>(CopyConstructor(DefinitionIs(
+                            SpecialMemberFunc::Definition::kTrivial)))));
+}
+
+TEST(AstVisitorTest, NontrivialCopyConstructor) {
+  absl::string_view file = "struct Defined { Defined(const Defined&);};\n";
+  // TODO(b/202113881): "struct MemberImplicit { Defined x; };\n"
+  // TODO(b/202113881): "struct MemberDefaulted { MemberDefaulted(const
+  // MemberDefaulted&) = default; Defined x; };\n"
+  IR ir = IrFromCc({file}, {});
+  EXPECT_THAT(ir.items, SizeIs(1));
+  EXPECT_THAT(ir.items, Each(VariantWith<Record>(CopyConstructor(DefinitionIs(
+                            SpecialMemberFunc::Definition::kNontrivial)))));
+}
+
+TEST(AstVisitorTest, DeletedCopyConstructor) {
+  absl::string_view file =
+      "struct Deleted { Deleted(const Deleted&) = delete;};\n"
+      // TODO(b/202113881): "struct DeletedByMember { Deleted x; };\n"
+      "struct DeletedByCtorDef { DeletedByCtorDef(DeletedByCtorDef&&) {} };\n";
+  IR ir = IrFromCc({file}, {});
+
+  EXPECT_THAT(ir.items, SizeIs(2));
+  EXPECT_THAT(ir.items, Each(VariantWith<Record>(CopyConstructor(DefinitionIs(
+                            SpecialMemberFunc::Definition::kDeleted)))));
+}
+
+TEST(AstVisitorTest, PublicCopyConstructor) {
+  absl::string_view file =
+      "class Implicit {};\n"
+      "struct Defaulted { Defaulted(const Defaulted&) = default; };\n"
+      "class Section { public: Section(const Section&) = default; };\n";
+  IR ir = IrFromCc({file}, {});
+
+  EXPECT_THAT(ir.items, SizeIs(3));
+  EXPECT_THAT(ir.items,
+              Each(VariantWith<Record>(CopyConstructor(AccessIs(kPublic)))));
+}
+
+TEST(AstVisitorTest, PrivateCopyConstructor) {
+  absl::string_view file =
+      "class Defaulted { Defaulted(const Defaulted&) = default; };\n"
+      "struct Section { private: Section(const Section&) = default; };\n";
+  IR ir = IrFromCc({file}, {});
+
+  EXPECT_THAT(ir.items, SizeIs(2));
+  EXPECT_THAT(ir.items,
+              Each(VariantWith<Record>(CopyConstructor(AccessIs(kPrivate)))));
+}
+
+TEST(AstVisitorTest, TrivialMoveConstructor) {
+  absl::string_view file =
+      "struct Implicit {};\n"
+      "struct Defaulted { Defaulted(Defaulted&&) = default; };\n";
+  IR ir = IrFromCc({file}, {});
+
+  EXPECT_THAT(ir.items, SizeIs(2));
+  EXPECT_THAT(ir.items, Each(VariantWith<Record>(MoveConstructor(DefinitionIs(
+                            SpecialMemberFunc::Definition::kTrivial)))));
+}
+
+TEST(AstVisitorTest, NontrivialMoveConstructor) {
+  absl::string_view file = "struct Defined { Defined(Defined&&);};\n";
+  // TODO(b/202113881): "struct MemberImplicit { Defined x; };\n"
+  // TODO(b/202113881): "struct MemberDefaulted { MemberDefaulted(
+  // MemberDefaulted&&) = default; Defined x; };\n"
+  IR ir = IrFromCc({file}, {});
+  EXPECT_THAT(ir.items, SizeIs(1));
+  EXPECT_THAT(ir.items, Each(VariantWith<Record>(MoveConstructor(DefinitionIs(
+                            SpecialMemberFunc::Definition::kNontrivial)))));
+}
+
+TEST(AstVisitorTest, DeletedMoveConstructor) {
+  absl::string_view file =
+      "struct Deleted { Deleted(Deleted&&) = delete;};\n"
+      // TODO(b/202113881): "struct DeletedByMember { Deleted x; };\n"
+      "struct SuppressedByCtorDef {"
+      " SuppressedByCtorDef(const SuppressedByCtorDef&) {}};\n";
+  IR ir = IrFromCc({file}, {});
+
+  EXPECT_THAT(ir.items, SizeIs(2));
+  EXPECT_THAT(ir.items, Each(VariantWith<Record>(MoveConstructor(DefinitionIs(
+                            SpecialMemberFunc::Definition::kDeleted)))));
+}
+
+TEST(AstVisitorTest, PublicMoveConstructor) {
+  absl::string_view file =
+      "class Implicit {};\n"
+      "struct Defaulted { Defaulted(Defaulted&&) = default; };\n"
+      "class Section { public: Section(Section&&) = default; };\n";
+  IR ir = IrFromCc({file}, {});
+
+  EXPECT_THAT(ir.items, SizeIs(3));
+  EXPECT_THAT(ir.items,
+              Each(VariantWith<Record>(MoveConstructor(AccessIs(kPublic)))));
+}
+
+TEST(AstVisitorTest, PrivateMoveConstructor) {
+  absl::string_view file =
+      "class Defaulted { Defaulted(Defaulted&&) = default; };\n"
+      "struct Section { private: Section(Section&&) = default; };\n";
+  IR ir = IrFromCc({file}, {});
+
+  EXPECT_THAT(ir.items, SizeIs(2));
+  EXPECT_THAT(ir.items,
+              Each(VariantWith<Record>(MoveConstructor(AccessIs(kPrivate)))));
+}
+
 TEST(AstVisitorTest, MemberVariableAccessSpecifiers) {
   IR ir = IrFromCc({R"(
     struct SomeStruct {
diff --git a/rs_bindings_from_cc/ir.cc b/rs_bindings_from_cc/ir.cc
index a85685a..51a3e75 100644
--- a/rs_bindings_from_cc/ir.cc
+++ b/rs_bindings_from_cc/ir.cc
@@ -110,6 +110,30 @@
   return result;
 }
 
+static std::string SpecialMemberDefinitionToString(
+    SpecialMemberFunc::Definition def) {
+  switch (def) {
+    case SpecialMemberFunc::Definition::kTrivial:
+      return "Trivial";
+    case SpecialMemberFunc::Definition::kNontrivial:
+      return "Nontrivial";
+    case SpecialMemberFunc::Definition::kDeleted:
+      return "Deleted";
+  }
+}
+
+std::ostream& operator<<(std::ostream& o,
+                         const SpecialMemberFunc::Definition& definition) {
+  return o << SpecialMemberDefinitionToString(definition);
+}
+
+nlohmann::json SpecialMemberFunc::ToJson() const {
+  nlohmann::json result;
+  result["definition"] = SpecialMemberDefinitionToString(definition);
+  result["access"] = AccessToString(access);
+  return result;
+}
+
 nlohmann::json Record::ToJson() const {
   std::vector<nlohmann::json> json_fields;
   json_fields.reserve(fields.size());
@@ -122,6 +146,8 @@
   record["fields"] = std::move(json_fields);
   record["size"] = size;
   record["alignment"] = alignment;
+  record["copy_constructor"] = copy_constructor.ToJson();
+  record["move_constructor"] = move_constructor.ToJson();
   record["is_trivial_abi"] = is_trivial_abi;
 
   nlohmann::json item;
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index 08663f8..9654b39 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -208,6 +208,27 @@
   return o << f.ToJson();
 }
 
+/// Information about special member functions.
+struct SpecialMemberFunc {
+  enum class Definition : char {
+    kTrivial,
+    kNontrivial,
+    kDeleted,
+  };
+
+  nlohmann::json ToJson() const;
+
+  Definition definition = Definition::kTrivial;
+  AccessSpecifier access = AccessSpecifier::kPublic;
+};
+
+std::ostream& operator<<(std::ostream& o,
+                         const SpecialMemberFunc::Definition& definition);
+
+inline std::ostream& operator<<(std::ostream& o, const SpecialMemberFunc& f) {
+  return o << f.ToJson();
+}
+
 // A record (struct, class, union).
 struct Record {
   nlohmann::json ToJson() const;
@@ -218,6 +239,10 @@
   int64_t size;
   int64_t alignment;
 
+  // Special member functions.
+  SpecialMemberFunc copy_constructor = {};
+  SpecialMemberFunc move_constructor = {};
+
   // Whether this type is passed by value as if it were a trivial type (the same
   // as it would be if it were a struct in C).
   //
diff --git a/rs_bindings_from_cc/ir_test.cc b/rs_bindings_from_cc/ir_test.cc
index c6b40cf..991edfe 100644
--- a/rs_bindings_from_cc/ir_test.cc
+++ b/rs_bindings_from_cc/ir_test.cc
@@ -108,6 +108,14 @@
                     ],
                     "size": 12,
                     "alignment": 4,
+                    "copy_constructor": {
+                        "definition": "Nontrivial",
+                        "access": "Private"
+                    },
+                    "move_constructor": {
+                        "definition": "Deleted",
+                        "access": "Protected"
+                    },
                     "is_trivial_abi": true
                 }}
             ]
@@ -144,6 +152,16 @@
                                },
                            .size = 12,
                            .alignment = 4,
+                           .copy_constructor =
+                               SpecialMemberFunc{
+                                   .definition = SpecialMemberFunc::Definition::
+                                       kNontrivial,
+                                   .access = kPrivate},
+                           .move_constructor =
+                               SpecialMemberFunc{
+                                   .definition =
+                                       SpecialMemberFunc::Definition::kDeleted,
+                                   .access = kProtected},
                            .is_trivial_abi = true}}};
   EXPECT_EQ(ir.ToJson(), expected);
 }