Do not derive Copy and Clone for unions that have variants that are not
copy/clone.

PiperOrigin-RevId: 450618926
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index 1c2ae7f..ead4220 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -401,7 +401,9 @@
     /// TODO(b/200067242): Actually force mut references to !is_unpin to be
     /// Pin<&mut T>.
     pub fn is_unpin(&self) -> bool {
-        self.is_trivial_abi && !self.is_inheritable
+        // TODO(b/233603159): not all unions are unpin, remove `record.is_union` once
+        // `ctor` supports unions.
+        self.is_union || (self.is_trivial_abi && !self.is_inheritable)
     }
 }
 
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index 28b6dbb..e97b2c7 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -1206,6 +1206,8 @@
     } else {
         quote! { struct }
     };
+    // TODO(b/233603159): not all unions are unpin, remove `record.is_union` once
+    // `ctor` supports unions.
     let recursively_pinned_attribute = if record.is_unpin() {
         quote! {}
     } else {
@@ -1366,6 +1368,9 @@
     record.is_unpin()
         && record.copy_constructor.access == ir::AccessSpecifier::Public
         && record.copy_constructor.definition == SpecialMemberDefinition::Trivial
+        // Unions are Copy/Clone only if all their data members are.
+        // TODO(b/233604311): Properly detect if imported fields are Copy/Clone. The code below assumes that only opaque fields are non trivial.
+        && (!record.is_union || record.fields.iter().all(|f| f.type_.is_ok()))
 }
 
 fn should_derive_copy(record: &Record) -> bool {
@@ -4248,6 +4253,30 @@
     }
 
     #[test]
+    fn test_nontrivial_unions() -> Result<()> {
+        let ir = ir_from_cc_dependency(
+            r#"
+            union UnionWithNontrivialField {
+                NonTrivialStruct my_field;
+            };
+            "#,
+            r#"
+            struct NonTrivialStruct {
+                NonTrivialStruct(NonTrivialStruct&&);
+            };
+            "#,
+        )?;
+        let rs_api = generate_bindings_tokens(&ir)?.rs_api;
+
+        assert_rs_not_matches!(rs_api, quote! {derive ( ... Copy ... )});
+        assert_rs_not_matches!(rs_api, quote! {derive ( ... Clone ... )});
+        // TODO(b/233603159): not all unions are unpin, add a test verifying that unions
+        // get recursively pinned correctly once `ctor` supports unions.
+        assert_rs_not_matches!(rs_api, quote! {recursively_pinned});
+        Ok(())
+    }
+
+    #[test]
     fn test_empty_struct() -> Result<()> {
         let ir = ir_from_cc(
             r#"
diff --git a/rs_bindings_from_cc/test/golden/unions.h b/rs_bindings_from_cc/test/golden/unions.h
index 77cfbb3..0f0ddb7 100644
--- a/rs_bindings_from_cc/test/golden/unions.h
+++ b/rs_bindings_from_cc/test/golden/unions.h
@@ -6,6 +6,14 @@
 #define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_UNIONS_H_
 
 union EmptyUnion {};
+
+struct Nontrivial final {
+  explicit Nontrivial();
+  Nontrivial(Nontrivial&&);
+
+  int field;
+};
+
 union NonEmptyUnion {
   bool bool_field;
   char char_field;
@@ -13,4 +21,13 @@
   long long long_long_field;
 };
 
+union NonCopyUnion {
+  bool trivial_member;
+  Nontrivial nontrivial_member;
+};
+
+union UnionWithOpaqueField {
+  char constant_array_field_not_yet_supported[42];
+};
+
 #endif  // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_UNIONS_H_
diff --git a/rs_bindings_from_cc/test/golden/unions_rs_api.rs b/rs_bindings_from_cc/test/golden/unions_rs_api.rs
index 4c878c1..e2e86e9 100644
--- a/rs_bindings_from_cc/test/golden/unions_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/unions_rs_api.rs
@@ -5,7 +5,7 @@
 // Automatically @generated Rust bindings for C++ target
 // //rs_bindings_from_cc/test/golden:unions_cc
 #![rustfmt::skip]
-#![feature(const_ptr_offset_from, custom_inner_attributes)]
+#![feature(const_ptr_offset_from, custom_inner_attributes, negative_impls)]
 #![allow(non_camel_case_types)]
 #![allow(non_snake_case)]
 #![allow(non_upper_case_globals)]
@@ -44,6 +44,22 @@
 // Error while generating bindings for item 'EmptyUnion::operator=':
 // Parameter #0 is not supported: Unsupported type 'union EmptyUnion &&': Unsupported type: && without lifetime
 
+#[ctor::recursively_pinned]
+#[repr(C)]
+pub struct Nontrivial {
+    __non_field_data: [crate::rust_std::mem::MaybeUninit<u8>; 0],
+    pub field: i32,
+}
+forward_declare::unsafe_define!(forward_declare::symbol!("Nontrivial"), crate::Nontrivial);
+
+// rs_bindings_from_cc/test/golden/unions.h;l=11
+// Error while generating bindings for item 'Nontrivial::Nontrivial':
+// Unsafe constructors (e.g. with no elided or explicit lifetimes) are intentionally not supported
+
+// rs_bindings_from_cc/test/golden/unions.h;l=12
+// Error while generating bindings for item 'Nontrivial::Nontrivial':
+// Parameter #0 is not supported: Unsupported type 'struct Nontrivial &&': Unsupported type: && without lifetime
+
 #[derive(Clone, Copy)]
 #[repr(C)]
 pub union NonEmptyUnion {
@@ -54,26 +70,64 @@
 }
 forward_declare::unsafe_define!(forward_declare::symbol!("NonEmptyUnion"), crate::NonEmptyUnion);
 
-// rs_bindings_from_cc/test/golden/unions.h;l=9
+// rs_bindings_from_cc/test/golden/unions.h;l=17
 // Error while generating bindings for item 'NonEmptyUnion::NonEmptyUnion':
 // Unsafe constructors (e.g. with no elided or explicit lifetimes) are intentionally not supported
 
-// rs_bindings_from_cc/test/golden/unions.h;l=9
+// rs_bindings_from_cc/test/golden/unions.h;l=17
 // Error while generating bindings for item 'NonEmptyUnion::NonEmptyUnion':
 // Unsafe constructors (e.g. with no elided or explicit lifetimes) are intentionally not supported
 
-// rs_bindings_from_cc/test/golden/unions.h;l=9
+// rs_bindings_from_cc/test/golden/unions.h;l=17
 // Error while generating bindings for item 'NonEmptyUnion::NonEmptyUnion':
 // Parameter #0 is not supported: Unsupported type 'union NonEmptyUnion &&': Unsupported type: && without lifetime
 
-// rs_bindings_from_cc/test/golden/unions.h;l=9
+// rs_bindings_from_cc/test/golden/unions.h;l=17
 // Error while generating bindings for item 'NonEmptyUnion::operator=':
 // Bindings for this kind of operator are not supported
 
-// rs_bindings_from_cc/test/golden/unions.h;l=9
+// rs_bindings_from_cc/test/golden/unions.h;l=17
 // Error while generating bindings for item 'NonEmptyUnion::operator=':
 // Parameter #0 is not supported: Unsupported type 'union NonEmptyUnion &&': Unsupported type: && without lifetime
 
+#[repr(C)]
+pub union NonCopyUnion {
+    pub trivial_member: bool,
+    pub nontrivial_member: crate::rust_std::mem::ManuallyDrop<crate::Nontrivial>,
+}
+forward_declare::unsafe_define!(forward_declare::symbol!("NonCopyUnion"), crate::NonCopyUnion);
+
+#[repr(C)]
+pub union UnionWithOpaqueField {
+    /// Reason for representing this field as a blob of bytes:
+    /// Unsupported type 'char[42]': Unsupported clang::Type class 'ConstantArray'
+    constant_array_field_not_yet_supported: [crate::rust_std::mem::MaybeUninit<u8>; 42],
+}
+forward_declare::unsafe_define!(
+    forward_declare::symbol!("UnionWithOpaqueField"),
+    crate::UnionWithOpaqueField
+);
+
+// rs_bindings_from_cc/test/golden/unions.h;l=29
+// Error while generating bindings for item 'UnionWithOpaqueField::UnionWithOpaqueField':
+// Unsafe constructors (e.g. with no elided or explicit lifetimes) are intentionally not supported
+
+// rs_bindings_from_cc/test/golden/unions.h;l=29
+// Error while generating bindings for item 'UnionWithOpaqueField::UnionWithOpaqueField':
+// Unsafe constructors (e.g. with no elided or explicit lifetimes) are intentionally not supported
+
+// rs_bindings_from_cc/test/golden/unions.h;l=29
+// Error while generating bindings for item 'UnionWithOpaqueField::UnionWithOpaqueField':
+// Parameter #0 is not supported: Unsupported type 'union UnionWithOpaqueField &&': Unsupported type: && without lifetime
+
+// rs_bindings_from_cc/test/golden/unions.h;l=29
+// Error while generating bindings for item 'UnionWithOpaqueField::operator=':
+// Bindings for this kind of operator are not supported
+
+// rs_bindings_from_cc/test/golden/unions.h;l=29
+// Error while generating bindings for item 'UnionWithOpaqueField::operator=':
+// Parameter #0 is not supported: Unsupported type 'union UnionWithOpaqueField &&': Unsupported type: && without lifetime
+
 // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_UNIONS_H_
 
 const _: () = assert!(rust_std::mem::size_of::<Option<&i32>>() == rust_std::mem::size_of::<&i32>());
@@ -90,6 +144,16 @@
     static_assertions::assert_not_impl_all!(crate::EmptyUnion: Drop);
 };
 
+const _: () = assert!(rust_std::mem::size_of::<crate::Nontrivial>() == 4);
+const _: () = assert!(rust_std::mem::align_of::<crate::Nontrivial>() == 4);
+const _: () = {
+    static_assertions::assert_not_impl_all!(crate::Nontrivial: Copy);
+};
+const _: () = {
+    static_assertions::assert_not_impl_all!(crate::Nontrivial: Drop);
+};
+const _: () = assert!(memoffset_unstable_const::offset_of!(crate::Nontrivial, field) == 0);
+
 const _: () = assert!(rust_std::mem::size_of::<crate::NonEmptyUnion>() == 8);
 const _: () = assert!(rust_std::mem::align_of::<crate::NonEmptyUnion>() == 8);
 const _: () = {
@@ -113,3 +177,24 @@
 const _: () = {
     static_assertions::assert_impl_all!(i64: Copy);
 };
+
+const _: () = assert!(rust_std::mem::size_of::<crate::NonCopyUnion>() == 4);
+const _: () = assert!(rust_std::mem::align_of::<crate::NonCopyUnion>() == 4);
+const _: () = {
+    static_assertions::assert_not_impl_all!(crate::NonCopyUnion: Copy);
+};
+const _: () = {
+    static_assertions::assert_not_impl_all!(crate::NonCopyUnion: Drop);
+};
+const _: () = {
+    static_assertions::assert_impl_all!(bool: Copy);
+};
+
+const _: () = assert!(rust_std::mem::size_of::<crate::UnionWithOpaqueField>() == 42);
+const _: () = assert!(rust_std::mem::align_of::<crate::UnionWithOpaqueField>() == 1);
+const _: () = {
+    static_assertions::assert_not_impl_all!(crate::UnionWithOpaqueField: Copy);
+};
+const _: () = {
+    static_assertions::assert_not_impl_all!(crate::UnionWithOpaqueField: Drop);
+};
diff --git a/rs_bindings_from_cc/test/golden/unions_rs_api_impl.cc b/rs_bindings_from_cc/test/golden/unions_rs_api_impl.cc
index 0857f98..bb14518 100644
--- a/rs_bindings_from_cc/test/golden/unions_rs_api_impl.cc
+++ b/rs_bindings_from_cc/test/golden/unions_rs_api_impl.cc
@@ -26,6 +26,9 @@
     union EmptyUnion* __this, const union EmptyUnion& __param_0) {
   return __this->operator=(std::forward<decltype(__param_0)>(__param_0));
 }
+extern "C" void __rust_thunk___ZN10NontrivialD1Ev(class Nontrivial* __this) {
+  std::destroy_at(std::forward<decltype(__this)>(__this));
+}
 extern "C" void __rust_thunk___ZN13NonEmptyUnionC1Ev(
     union NonEmptyUnion* __this) {
   crubit::construct_at(std::forward<decltype(__this)>(__this));
@@ -43,10 +46,38 @@
     union NonEmptyUnion* __this, const union NonEmptyUnion& __param_0) {
   return __this->operator=(std::forward<decltype(__param_0)>(__param_0));
 }
+extern "C" void __rust_thunk___ZN12NonCopyUnionD1Ev(
+    union NonCopyUnion* __this) {
+  std::destroy_at(std::forward<decltype(__this)>(__this));
+}
+extern "C" void __rust_thunk___ZN20UnionWithOpaqueFieldC1Ev(
+    union UnionWithOpaqueField* __this) {
+  crubit::construct_at(std::forward<decltype(__this)>(__this));
+}
+extern "C" void __rust_thunk___ZN20UnionWithOpaqueFieldC1ERKS_(
+    union UnionWithOpaqueField* __this,
+    const union UnionWithOpaqueField& __param_0) {
+  crubit::construct_at(std::forward<decltype(__this)>(__this),
+                       std::forward<decltype(__param_0)>(__param_0));
+}
+extern "C" void __rust_thunk___ZN20UnionWithOpaqueFieldD1Ev(
+    union UnionWithOpaqueField* __this) {
+  std::destroy_at(std::forward<decltype(__this)>(__this));
+}
+extern "C" union UnionWithOpaqueField&
+__rust_thunk___ZN20UnionWithOpaqueFieldaSERKS_(
+    union UnionWithOpaqueField* __this,
+    const union UnionWithOpaqueField& __param_0) {
+  return __this->operator=(std::forward<decltype(__param_0)>(__param_0));
+}
 
 static_assert(sizeof(union EmptyUnion) == 1);
 static_assert(alignof(union EmptyUnion) == 1);
 
+static_assert(sizeof(class Nontrivial) == 4);
+static_assert(alignof(class Nontrivial) == 4);
+static_assert(CRUBIT_OFFSET_OF(field, class Nontrivial) == 0);
+
 static_assert(sizeof(union NonEmptyUnion) == 8);
 static_assert(alignof(union NonEmptyUnion) == 8);
 static_assert(CRUBIT_OFFSET_OF(bool_field, union NonEmptyUnion) == 0);
@@ -54,4 +85,14 @@
 static_assert(CRUBIT_OFFSET_OF(int_field, union NonEmptyUnion) == 0);
 static_assert(CRUBIT_OFFSET_OF(long_long_field, union NonEmptyUnion) == 0);
 
+static_assert(sizeof(union NonCopyUnion) == 4);
+static_assert(alignof(union NonCopyUnion) == 4);
+static_assert(CRUBIT_OFFSET_OF(trivial_member, union NonCopyUnion) == 0);
+static_assert(CRUBIT_OFFSET_OF(nontrivial_member, union NonCopyUnion) == 0);
+
+static_assert(sizeof(union UnionWithOpaqueField) == 42);
+static_assert(alignof(union UnionWithOpaqueField) == 1);
+static_assert(CRUBIT_OFFSET_OF(constant_array_field_not_yet_supported,
+                               union UnionWithOpaqueField) == 0);
+
 #pragma clang diagnostic pop