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