Emit `default` copy constructor and assignment operator for `Copy` types
PiperOrigin-RevId: 537148175
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs
index 7ea68e2..2a2f687 100644
--- a/cc_bindings_from_rs/bindings.rs
+++ b/cc_bindings_from_rs/bindings.rs
@@ -1176,13 +1176,6 @@
ensure!(size_in_bytes != 0, "Zero-sized types (ZSTs) are not supported (b/258259459)");
let core = quote! {
- // TODO(b/258249993): Provide `default` copy constructor and assignment operator if
- // the wrapped type is `Copy` on Rust side.
- // TODO(b/259741191): If the wrapped type implements the `Clone` trait, then we should
- // *consider* calling `clone` from the copy constructor and `clone_from` from the copy
- // assignment operator.
- #cc_short_name(const #cc_short_name&) = delete;
-
// The generated bindings have to follow Rust move semantics:
// * All Rust types are memcpy-movable (e.g. <internal link>/constructors.html says
// that "Every type must be ready for it to be blindly memcopied to somewhere else
@@ -1224,7 +1217,6 @@
//
// (Move assignment operator has another set of concerns and constraints - see the
// comment for the move constructor above).
- #cc_short_name& operator=(const #cc_short_name&) = delete;
#cc_short_name& operator=(#cc_short_name&&) = delete;
// TODO(b/258251148): Support custom `Drop` impls and drop glue.
@@ -1526,6 +1518,44 @@
Ok(ApiSnippets { main_api, cc_details, rs_details })
}
+/// Formats the copy constructor and the copy-assignment operator for an ADT if
+/// possible (i.e. if the `Clone` trait is implemented for the ADT). Returns an
+/// error otherwise (e.g. if there is no `Clone` impl).
+fn format_copy_ctor_and_assignment_operator(
+ input: &Input,
+ core: &AdtCoreBindings,
+) -> Result<ApiSnippets> {
+ let tcx = input.tcx;
+ let ty = tcx.type_of(core.def_id).subst_identity();
+
+ let is_copy = {
+ // TODO(b/259749095): Support non-empty set of generic parameters.
+ let param_env = ty::ParamEnv::empty();
+
+ // TODO(b/259749095): Once generic ADTs are supported, `is_copy_modulo_regions`
+ // might need to be replaced with a more thorough check - see
+ // b/258249993#comment4.
+ ty.is_copy_modulo_regions(tcx, param_env)
+ };
+
+ if is_copy {
+ let cc_struct_name = &core.cc_short_name;
+ let msg = "Rust types that are `Copy` get trivial, `default` C++ copy constructor \
+ and assignment operator.";
+ let main_api = CcSnippet::new(quote! {
+ __NEWLINE__ __COMMENT__ #msg
+ #cc_struct_name(const #cc_struct_name&) = default; __NEWLINE__
+ #cc_struct_name& operator=(const #cc_struct_name&) = default;
+ });
+ return Ok(ApiSnippets { main_api, ..Default::default() });
+ }
+
+ // TODO(b/259741191): Implement bindings for `Clone::clone` and
+ // `Clone::clone_from`.
+ let _trait_impl = find_core_trait_impl(tcx, ty, sym::Clone)?;
+ bail!("Bindings for the `Clone` trait are not supported yet (b/259741191)");
+}
+
/// Formats an algebraic data type (an ADT - a struct, an enum, or a union)
/// represented by `core`. This function is infallible - after
/// `format_adt_core` returns success we have committed to emitting C++ bindings
@@ -1553,6 +1583,22 @@
});
let ApiSnippets {
+ main_api: copy_ctor_and_assignment_main_api,
+ cc_details: copy_ctor_and_assignment_cc_details,
+ rs_details: copy_ctor_and_assignment_rs_details,
+ } = format_copy_ctor_and_assignment_operator(input, core).unwrap_or_else(|err| {
+ let msg = format!("{err:#}");
+ ApiSnippets {
+ main_api: CcSnippet::new(quote! {
+ __NEWLINE__ __COMMENT__ #msg
+ #adt_cc_name(const #adt_cc_name&) = delete; __NEWLINE__
+ #adt_cc_name& operator=(const #adt_cc_name&) = delete;
+ }),
+ ..Default::default()
+ }
+ });
+
+ let ApiSnippets {
main_api: fields_main_api,
cc_details: fields_cc_details,
rs_details: fields_rs_details,
@@ -1608,6 +1654,8 @@
let mut prereqs = CcPrerequisites::default();
prereqs.includes.insert(input.support_header("internal/attribute_macros.h"));
let default_ctor_main_api = default_ctor_main_api.into_tokens(&mut prereqs);
+ let copy_ctor_and_assignment_main_api =
+ copy_ctor_and_assignment_main_api.into_tokens(&mut prereqs);
let impl_items_main_api = if impl_items_main_api.tokens.is_empty() {
quote! {}
} else {
@@ -1624,6 +1672,7 @@
#keyword #(#attributes)* #adt_cc_name final {
public:
#default_ctor_main_api
+ #copy_ctor_and_assignment_main_api
#core
#impl_items_main_api
#fields_main_api
@@ -1635,6 +1684,8 @@
let cc_details = {
let mut prereqs = CcPrerequisites::default();
let default_ctor_cc_details = default_ctor_cc_details.into_tokens(&mut prereqs);
+ let copy_ctor_and_assignment_cc_details =
+ copy_ctor_and_assignment_cc_details.into_tokens(&mut prereqs);
let impl_items_cc_details = impl_items_cc_details.into_tokens(&mut prereqs);
let fields_cc_details = fields_cc_details.into_tokens(&mut prereqs);
prereqs.defs.insert(local_def_id);
@@ -1650,6 +1701,7 @@
"Verify that struct layout didn't change since this header got generated");
__NEWLINE__
#default_ctor_cc_details
+ #copy_ctor_and_assignment_cc_details
#impl_items_cc_details
#fields_cc_details
},
@@ -1661,6 +1713,7 @@
const _: () = assert!(::std::mem::size_of::<#adt_rs_name>() == #size);
const _: () = assert!(::std::mem::align_of::<#adt_rs_name>() == #alignment);
#default_ctor_rs_details
+ #copy_ctor_and_assignment_rs_details
#impl_items_rs_details
#fields_rs_details
}
@@ -3565,14 +3618,14 @@
__COMMENT__ "`SomeStruct` doesn't implement the `Default` trait"
SomeStruct() = delete;
- // In this test there is no `Copy` implementation / derive.
+ __COMMENT__ "`SomeStruct` doesn't implement the `Clone` trait"
SomeStruct(const SomeStruct&) = delete;
+ SomeStruct& operator=(const SomeStruct&) = delete;
// All Rust types are trivially-movable.
SomeStruct(SomeStruct&&) = default;
// Assignment operators are disabled for now.
- SomeStruct& operator=(const SomeStruct&) = delete;
SomeStruct& operator=(SomeStruct&&) = delete;
// In this test there is no custom `Drop`, so C++ can also
@@ -3630,14 +3683,14 @@
__COMMENT__ "`TupleStruct` doesn't implement the `Default` trait"
TupleStruct() = delete;
- // In this test there is no `Copy` implementation / derive.
+ __COMMENT__ "`TupleStruct` doesn't implement the `Clone` trait"
TupleStruct(const TupleStruct&) = delete;
+ TupleStruct& operator=(const TupleStruct&) = delete;
// All Rust types are trivially-movable.
TupleStruct(TupleStruct&&) = default;
// Assignment operators are disabled for now.
- TupleStruct& operator=(const TupleStruct&) = delete;
TupleStruct& operator=(TupleStruct&&) = delete;
// In this test there is no custom `Drop`, so C++ can also
@@ -4116,6 +4169,80 @@
}
#[test]
+ fn test_format_item_struct_with_copy_trait() {
+ let test_src = r#"
+ #[derive(Clone, Copy)]
+ pub struct Point(i32, i32);
+ "#;
+ let msg = "Rust types that are `Copy` get trivial, `default` C++ copy constructor \
+ and assignment operator.";
+ test_format_item(test_src, "Point", |result| {
+ let result = result.unwrap().unwrap();
+ let main_api = &result.main_api;
+ assert_cc_matches!(
+ main_api.tokens,
+ quote! {
+ ...
+ struct ... Point final {
+ ...
+ public:
+ ...
+ __COMMENT__ #msg
+ Point(const Point&) = default;
+ Point& operator=(const Point&) = default;
+ ...
+ };
+ }
+ );
+
+ // Trivial copy doesn't require any C++/Rust details.
+ assert_cc_not_matches!(result.cc_details.tokens, quote! { Point::Point(const Point&) },);
+ assert_cc_not_matches!(
+ result.cc_details.tokens,
+ quote! { Point::operator=(const Point&) },
+ );
+ assert_rs_not_matches!(result.rs_details, quote! { Copy });
+ assert_rs_not_matches!(result.rs_details, quote! { copy });
+ });
+ }
+
+ #[test]
+ fn test_format_item_struct_with_clone_trait() {
+ let test_src = r#"
+ // `Copy` trait is covered in `test_format_item_struct_with_copy_trait`.
+ #[derive(Clone)]
+ pub struct Point(i32, i32);
+ "#;
+ let msg = "Bindings for the `Clone` trait are not supported yet (b/259741191)";
+ test_format_item(test_src, "Point", |result| {
+ let result = result.unwrap().unwrap();
+ let main_api = &result.main_api;
+ assert_cc_matches!(
+ main_api.tokens,
+ quote! {
+ ...
+ struct ... Point final {
+ ...
+ public:
+ ...
+ // TODO(b/259741191): Fix test expectations below after implementing
+ // support for `Clone` trait.
+
+ __COMMENT__ #msg
+ Point(const Point&) = delete;
+ Point& operator=(const Point&) = delete;
+ ...
+ };
+ }
+ );
+ // TODO(b/259741191): Add
+ // `assert_cc_matches(result.cc_details.tokens, ...)`.
+ // TODO(b/259741191): Add
+ // `assert_rs_matches(result.rs_details.tokens, ...)`.
+ });
+ }
+
+ #[test]
fn test_format_item_unsupported_struct_with_name_that_is_reserved_keyword() {
let test_src = r#"
#[allow(non_camel_case_types)]
@@ -4370,14 +4497,14 @@
__COMMENT__ "`SomeEnum` doesn't implement the `Default` trait"
SomeEnum() = delete;
- // In this test there is no `Copy` implementation / derive.
+ __COMMENT__ "`SomeEnum` doesn't implement the `Clone` trait"
SomeEnum(const SomeEnum&) = delete;
+ SomeEnum& operator=(const SomeEnum&) = delete;
// All Rust types are trivially-movable.
SomeEnum(SomeEnum&&) = default;
// Assignment operators are disabled for now.
- SomeEnum& operator=(const SomeEnum&) = delete;
SomeEnum& operator=(SomeEnum&&) = delete;
// In this test there is no custom `Drop`, so C++ can also
@@ -4435,14 +4562,14 @@
__COMMENT__ "`Point` doesn't implement the `Default` trait"
Point() = delete;
- // In this test there is no `Copy` implementation / derive.
+ __COMMENT__ "`Point` doesn't implement the `Clone` trait"
Point(const Point&) = delete;
+ Point& operator=(const Point&) = delete;
// All Rust types are trivially-movable.
Point(Point&&) = default;
// Assignment operators are disabled for now.
- Point& operator=(const Point&) = delete;
Point& operator=(Point&&) = delete;
// In this test there is no custom `Drop`, so C++ can also
@@ -4513,14 +4640,14 @@
__COMMENT__ "`SomeUnion` doesn't implement the `Default` trait"
SomeUnion() = delete;
- // In this test there is no `Copy` implementation / derive.
+ __COMMENT__ "`SomeUnion` doesn't implement the `Clone` trait"
SomeUnion(const SomeUnion&) = delete;
+ SomeUnion& operator=(const SomeUnion&) = delete;
// All Rust types are trivially-movable.
SomeUnion(SomeUnion&&) = default;
// Assignment operators are disabled for now.
- SomeUnion& operator=(const SomeUnion&) = delete;
SomeUnion& operator=(SomeUnion&&) = delete;
// In this test there is no custom `Drop`, so C++ can also
diff --git a/cc_bindings_from_rs/test/known_traits/copy/BUILD b/cc_bindings_from_rs/test/known_traits/copy/BUILD
new file mode 100644
index 0000000..86a2f0c
--- /dev/null
+++ b/cc_bindings_from_rs/test/known_traits/copy/BUILD
@@ -0,0 +1,39 @@
+"""End-to-end tests of `cc_bindings_from_rs`, focusing on the `Copy` trait"""
+
+load(
+ "@rules_rust//rust:defs.bzl",
+ "rust_library",
+)
+load(
+ "//cc_bindings_from_rs/bazel_support:cc_bindings_from_rust_rule.bzl",
+ "cc_bindings_from_rust",
+)
+
+
+package(default_applicable_licenses = ["//:license"])
+
+licenses(["notice"])
+
+rust_library(
+ name = "copy",
+ testonly = 1,
+ srcs = ["copy.rs"],
+ deps = [
+ "//common:rust_allocator_shims",
+ ],
+)
+
+cc_bindings_from_rust(
+ name = "copy_cc_api",
+ testonly = 1,
+ crate = ":copy",
+)
+
+cc_test(
+ name = "copy_test",
+ srcs = ["copy_test.cc"],
+ deps = [
+ ":copy_cc_api",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
diff --git a/cc_bindings_from_rs/test/known_traits/copy/copy.rs b/cc_bindings_from_rs/test/known_traits/copy/copy.rs
new file mode 100644
index 0000000..6f0aa81
--- /dev/null
+++ b/cc_bindings_from_rs/test/known_traits/copy/copy.rs
@@ -0,0 +1,46 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+//! This crate is used as a test input for `cc_bindings_from_rs` and the
+//! generated C++ bindings are then tested via `default_test.cc`.
+
+/// Test of an explicit impl of a trait: `impl Default for SomeStruct`.
+pub mod explicit_impl {
+ pub struct SomeStruct(i32);
+
+ impl Clone for SomeStruct {
+ fn clone(&self) -> Self {
+ Self(self.0)
+ }
+ }
+
+ impl Copy for SomeStruct {}
+
+ pub fn create_struct(i: i32) -> SomeStruct {
+ SomeStruct(i)
+ }
+
+ pub fn extract_int(s: SomeStruct) -> i32 {
+ s.0
+ }
+}
+
+/// Test of a derived impl of a trait: `#[derive(Default)]`.
+pub mod derived_impl {
+ #[derive(Clone, Copy)]
+ pub struct SomeStruct(i32);
+
+ pub fn create_struct(i: i32) -> SomeStruct {
+ SomeStruct(i)
+ }
+
+ pub fn extract_int(s: SomeStruct) -> i32 {
+ s.0
+ }
+}
+
+/// Test of a missing impl of a trait.
+pub mod no_impl {
+ pub struct SomeStruct(i32);
+}
diff --git a/cc_bindings_from_rs/test/known_traits/copy/copy_test.cc b/cc_bindings_from_rs/test/known_traits/copy/copy_test.cc
new file mode 100644
index 0000000..8b4beeb
--- /dev/null
+++ b/cc_bindings_from_rs/test/known_traits/copy/copy_test.cc
@@ -0,0 +1,45 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <type_traits>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "cc_bindings_from_rs/test/known_traits/copy/copy_cc_api.h"
+
+namespace crubit {
+namespace {
+
+TEST(CopyTest, ExplicitImpl) {
+ namespace tests = copy::explicit_impl;
+ tests::SomeStruct s = tests::create_struct(123);
+
+ // The next line invokes the copy C++ constructor.
+ tests::SomeStruct copy(s);
+ static_assert(std::is_trivially_copy_constructible_v<tests::SomeStruct>);
+
+ // Minimal verification that the copy constructor worked as expected.
+ EXPECT_EQ(123, tests::extract_int(std::move(copy)));
+}
+
+TEST(CopyTest, DerivedImpl) {
+ namespace tests = copy::derived_impl;
+ tests::SomeStruct s = tests::create_struct(123);
+
+ // The next line invokes the copy C++ constructor.
+ tests::SomeStruct copy(s);
+ static_assert(std::is_trivially_copy_constructible_v<tests::SomeStruct>);
+
+ // Minimal verification that the copy constructor worked as expected.
+ EXPECT_EQ(123, tests::extract_int(std::move(copy)));
+}
+
+TEST(CopyTest, NoImpl) {
+ namespace tests = copy::no_impl;
+ static_assert(!std::is_copy_constructible_v<tests::SomeStruct>);
+}
+
+} // namespace
+} // namespace crubit
diff --git a/docs/bindings/reference/operator_overloading.md b/docs/bindings/reference/operator_overloading.md
index b6bdda1..b71e34d 100644
--- a/docs/bindings/reference/operator_overloading.md
+++ b/docs/bindings/reference/operator_overloading.md
@@ -12,9 +12,13 @@
The following special member functions and traits are mapped bidirectionally:
-C++ | Rust | Notes
-------------------- | --------- | -------------------------
-Default constructor | `Default` | C++ types must be `Unpin`
+| C++ | Rust | Notes |
+| ------------------------ | --------- | ------------------------------------ |
+| Default constructor | `Default` | |
+| Trivial copy constructor | `Copy` | Rust bindings for C++ require that |
+: : : the C++ type is non-abstract and has :
+: : : a public, trivial copy constructor :
+: : : and destructor. :
## One-way map of C++ special member functions into Rust traits
@@ -24,12 +28,6 @@
| C++ API | Rust bindings | Notes |
| ------------------------- | ------------- | -------------------------------- |
-| Trivial copy constructor | `Copy` | Also requires that the C++ type |
-: : : is non-abstract and has a :
-: : : public, trivial copy constructor :
-: : : and destructor. :
-: : : <br>TODO(b/258249993)\: Provide :
-: : : bidirectional map. :
| Custom copy constructor | `Clone` | TODO(b/259741191): Provide |
: : : bidirectional map. :
| Destructor | `Drop` | TODO(b/258251148): Provide |
diff --git a/docs/bindings/reference/pointers_and_references.md b/docs/bindings/reference/pointers_and_references.md
index f80f6e3..3c6d4a8 100644
--- a/docs/bindings/reference/pointers_and_references.md
+++ b/docs/bindings/reference/pointers_and_references.md
@@ -94,6 +94,10 @@
a references to that value. This applies to Rust shared references (e.g. `&T`)
and to exclusive references (e.g. `&mut T`).
+Examples of C++ features that may mutate a value that Rust holds a reference to:
+
+* Using copy assignment operator of C++ value that Rust has a reference to.
+
TODO(b/258249993): After `cc_bindings_from_rs` generates assignment operators,
explicitly document them here as an example.