Support nontrivial structs and destructors in `extern_c`.

PiperOrigin-RevId: 602638268
Change-Id: I54c26e81075026c76f102f4e338d894578860425
diff --git a/rs_bindings_from_cc/importers/cxx_record.cc b/rs_bindings_from_cc/importers/cxx_record.cc
index a724116..35567a0 100644
--- a/rs_bindings_from_cc/importers/cxx_record.cc
+++ b/rs_bindings_from_cc/importers/cxx_record.cc
@@ -208,6 +208,8 @@
           return true;
         } else if (clang::isa<clang::FinalAttr>(attr)) {
           return true;
+        } else if (clang::isa<clang::TrivialABIAttr>(attr)) {
+          return true;
         } else if (auto* visibility =
                        clang::dyn_cast<clang::VisibilityAttr>(&attr);
                    visibility && record_decl->isInStdNamespace()) {
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index 5cf358c..f8bd356 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -2929,26 +2929,42 @@
     match item {
         Item::UnsupportedItem(..) => {}
         Item::Func(func) => {
-            for t in func.types() {
-                let t = db.rs_type_kind(t.rs_type.clone())?;
-                crubit_features |= t.required_crubit_features(&db.ir())?
-            }
-            if func.is_extern_c {
+            if func.name == UnqualifiedIdentifier::Destructor {
+                // We support destructors in extern_c even though they use some features we
+                // don't generally support with that feature set, because in this
+                // particular case, it's safe.
                 crubit_features |= ir::CrubitFeature::ExternC;
+                // Rather than walking the parameters -- which are not supported in extern_c
+                // (`&mut self`) -- we just look up the single `this` parameter's feature
+                // requirements.
+                let Some(meta) = &func.member_func_metadata else {
+                    bail!("Destructor without `this`");
+                };
+                let ir = db.ir();
+                let record: &Item = ir.find_decl(meta.record_id)?;
+                crubit_features |= crubit_features_for_item(db, record)?;
             } else {
-                crubit_features |= ir::CrubitFeature::Experimental;
-            }
-            if !func.has_c_calling_convention
-                || func.is_noreturn
-                || func.nodiscard.is_some()
-                || func.deprecated.is_some()
-            {
-                crubit_features |= ir::CrubitFeature::Experimental;
-            }
-            for param in &func.params {
-                if param.unknown_attr.is_some() {
+                for t in func.types() {
+                    let t = db.rs_type_kind(t.rs_type.clone())?;
+                    crubit_features |= t.required_crubit_features(&db.ir())?
+                }
+                if func.is_extern_c {
+                    crubit_features |= ir::CrubitFeature::ExternC;
+                } else {
                     crubit_features |= ir::CrubitFeature::Experimental;
                 }
+                if !func.has_c_calling_convention
+                    || func.is_noreturn
+                    || func.nodiscard.is_some()
+                    || func.deprecated.is_some()
+                {
+                    crubit_features |= ir::CrubitFeature::Experimental;
+                }
+                for param in &func.params {
+                    if param.unknown_attr.is_some() {
+                        crubit_features |= ir::CrubitFeature::Experimental;
+                    }
+                }
             }
         }
         Item::Record(record) => {
diff --git a/rs_bindings_from_cc/test/extern_c/BUILD b/rs_bindings_from_cc/test/extern_c/BUILD
index c83a31a..82e5b8e 100644
--- a/rs_bindings_from_cc/test/extern_c/BUILD
+++ b/rs_bindings_from_cc/test/extern_c/BUILD
@@ -7,6 +7,7 @@
     name = "has_bindings",
     hdrs = ["has_bindings.h"],
     aspect_hints = ["//features:extern_c"],
+    deps = ["@absl//absl/base:core_headers"],
 )
 
 crubit_test_cc_library(
diff --git a/rs_bindings_from_cc/test/extern_c/has_bindings.h b/rs_bindings_from_cc/test/extern_c/has_bindings.h
index e288982..ea2cd1f 100644
--- a/rs_bindings_from_cc/test/extern_c/has_bindings.h
+++ b/rs_bindings_from_cc/test/extern_c/has_bindings.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_EXTERN_C_ALLOWED_H_
 #define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_EXTERN_C_ALLOWED_H_
 
+#include "absl/base/attributes.h"
 namespace crubit::has_bindings {
 extern "C" {
 
@@ -16,6 +17,17 @@
 
 using StructAlias = Struct;
 
+struct ABSL_ATTRIBUTE_TRIVIAL_ABI NontrivialStruct {
+  int* x;
+  ~NontrivialStruct() {
+    // this can do anything, but we'll do something silly for the sake of
+    // example.
+    if (x != nullptr) {
+      *x = 42;
+    }
+  }
+};
+
 enum Enum {
   kEnumerator = 0,
   // This doesn't receive bindings, because the enumerator has an unrecognized
diff --git a/rs_bindings_from_cc/test/extern_c/has_bindings_test.rs b/rs_bindings_from_cc/test/extern_c/has_bindings_test.rs
index 5554093..20c6c56 100644
--- a/rs_bindings_from_cc/test/extern_c/has_bindings_test.rs
+++ b/rs_bindings_from_cc/test/extern_c/has_bindings_test.rs
@@ -26,6 +26,17 @@
 }
 
 #[test]
+fn test_nontrivial_struct() {
+    let mut i = 0;
+    {
+        let s = has_bindings::NontrivialStruct { x: &mut i };
+        let _s2 = s; // can still treat it like a normal Rust value!
+    }
+    // and the destructor gets invoked!
+    assert_eq!(i, 42);
+}
+
+#[test]
 fn test_user_enum() {
     let _: has_bindings::Enum = has_bindings::Enum::kEnumerator;
     // Can't really assert this due to how value_exists works, sadly.