Implement the "definition" half of forward declarations / incomplete types.
This allows Rust code to forward-declare a C++ type, but we don't yet map forward declarations in C++ to forward declarations in Rust.
Note also that this doesn't test support for `!Unpin` types, as I encountered some quirks along the way -- might need a couple more impls to make this work, will investigate.
PiperOrigin-RevId: 440987359
diff --git a/rs_bindings_from_cc/BUILD b/rs_bindings_from_cc/BUILD
index 9a9546d..c9aacf0 100644
--- a/rs_bindings_from_cc/BUILD
+++ b/rs_bindings_from_cc/BUILD
@@ -50,6 +50,7 @@
# Rust code.
"@crates_io//memoffset/v0_6:memoffset_unstable_const",
"//rs_bindings_from_cc/support:ctor",
+ "//rs_bindings_from_cc/support:forward_declare",
"@crates_io//pin_project/v1:pin_project", # used by ctor macros
# Required for `Copy` trait assertions added to the generated Rust
# code.
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index 513824e..62f0cc3 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -987,6 +987,13 @@
quote! {}
};
+ // TODO(b/227442773): After namespace support is added, use the fully-namespaced
+ // name.
+ let incomplete_symbol = &record.cc_name;
+ let incomplete_definition = quote! {
+ forward_declare::unsafe_define!(forward_declare::symbol!(#incomplete_symbol), #ident);
+ };
+
let empty_struct_placeholder_field =
if record.fields.is_empty() && record.base_size.unwrap_or(0) == 0 {
quote! {
@@ -1034,6 +1041,8 @@
#empty_struct_placeholder_field
}
+ #incomplete_definition
+
#no_unique_address_accessors
#base_class_into
@@ -3048,7 +3057,10 @@
field2: [rust_std::mem::MaybeUninit<u8>; 2],
pub z: i16,
}
-
+ });
+ assert_rs_matches!(
+ rs_api,
+ quote! {
impl Struct {
pub fn field1(&self) -> &Field1 {
unsafe {&* (&self.field1 as *const _ as *const Field1)}
diff --git a/rs_bindings_from_cc/test/golden/clang_attrs_rs_api.rs b/rs_bindings_from_cc/test/golden/clang_attrs_rs_api.rs
index 4745bc5..bb860bc 100644
--- a/rs_bindings_from_cc/test/golden/clang_attrs_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/clang_attrs_rs_api.rs
@@ -22,6 +22,7 @@
/// Prevent empty C++ struct being zero-size in Rust.
placeholder: rust_std::mem::MaybeUninit<u8>,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("HasCustomAlignment"), HasCustomAlignment);
impl !Unpin for HasCustomAlignment {}
@@ -49,6 +50,10 @@
pub struct HasFieldWithCustomAlignment {
pub field: HasCustomAlignment,
}
+forward_declare::unsafe_define!(
+ forward_declare::symbol!("HasFieldWithCustomAlignment"),
+ HasFieldWithCustomAlignment
+);
impl !Unpin for HasFieldWithCustomAlignment {}
@@ -78,6 +83,10 @@
/// Prevent empty C++ struct being zero-size in Rust.
placeholder: rust_std::mem::MaybeUninit<u8>,
}
+forward_declare::unsafe_define!(
+ forward_declare::symbol!("InheritsFromBaseWithCustomAlignment"),
+ InheritsFromBaseWithCustomAlignment
+);
impl<'a> From<&'a InheritsFromBaseWithCustomAlignment> for &'a HasCustomAlignment {
fn from(x: &'a InheritsFromBaseWithCustomAlignment) -> Self {
unsafe { &*((x as *const _ as *const u8).offset(0) as *const HasCustomAlignment) }
@@ -111,6 +120,10 @@
/// Prevent empty C++ struct being zero-size in Rust.
placeholder: rust_std::mem::MaybeUninit<u8>,
}
+forward_declare::unsafe_define!(
+ forward_declare::symbol!("HasCustomAlignmentWithGnuAttr"),
+ HasCustomAlignmentWithGnuAttr
+);
impl !Unpin for HasCustomAlignmentWithGnuAttr {}
diff --git a/rs_bindings_from_cc/test/golden/comment_rs_api.rs b/rs_bindings_from_cc/test/golden/comment_rs_api.rs
index f6a94e1..f070dc3 100644
--- a/rs_bindings_from_cc/test/golden/comment_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/comment_rs_api.rs
@@ -32,6 +32,7 @@
/// Another field
pub j: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("Foo"), Foo);
impl Default for Foo {
#[inline(always)]
@@ -90,6 +91,7 @@
pub struct Bar {
pub i: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("Bar"), Bar);
impl Default for Bar {
#[inline(always)]
@@ -127,6 +129,7 @@
pub struct HasNoComments {
pub i: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("HasNoComments"), HasNoComments);
impl Default for HasNoComments {
#[inline(always)]
diff --git a/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs b/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs
index 8506741..2b7553c 100644
--- a/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs
@@ -26,6 +26,7 @@
/// A field.
pub i: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("DocCommentSlashes"), DocCommentSlashes);
impl<'b> From<ctor::RvalueReference<'b, DocCommentSlashes>> for DocCommentSlashes {
#[inline(always)]
@@ -107,6 +108,7 @@
/// A field
pub i: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("DocCommentBang"), DocCommentBang);
impl Default for DocCommentBang {
#[inline(always)]
@@ -147,6 +149,10 @@
/// A field
pub i: i32,
}
+forward_declare::unsafe_define!(
+ forward_declare::symbol!("MultilineCommentTwoStars"),
+ MultilineCommentTwoStars
+);
impl Default for MultilineCommentTwoStars {
#[inline(always)]
@@ -187,6 +193,7 @@
/// A field
pub i: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("LineComment"), LineComment);
impl Default for LineComment {
#[inline(always)]
@@ -227,6 +234,7 @@
/// A field
pub i: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("MultilineOneStar"), MultilineOneStar);
impl Default for MultilineOneStar {
#[inline(always)]
diff --git a/rs_bindings_from_cc/test/golden/escaping_keywords_rs_api.rs b/rs_bindings_from_cc/test/golden/escaping_keywords_rs_api.rs
index ced4f4a..e6eff10 100644
--- a/rs_bindings_from_cc/test/golden/escaping_keywords_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/escaping_keywords_rs_api.rs
@@ -22,6 +22,7 @@
pub struct r#type {
pub r#dyn: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("type"), r#type);
impl Default for r#type {
#[inline(always)]
diff --git a/rs_bindings_from_cc/test/golden/inheritance_rs_api.rs b/rs_bindings_from_cc/test/golden/inheritance_rs_api.rs
index 3522e20..341cb89 100644
--- a/rs_bindings_from_cc/test/golden/inheritance_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/inheritance_rs_api.rs
@@ -25,6 +25,7 @@
/// Prevent empty C++ struct being zero-size in Rust.
placeholder: rust_std::mem::MaybeUninit<u8>,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("Base0"), Base0);
impl !Unpin for Base0 {}
@@ -53,6 +54,7 @@
b1_1_: i64,
b1_2_: u8,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("Base1"), Base1);
impl !Unpin for Base1 {}
@@ -80,6 +82,7 @@
pub struct Base2 {
b2_1_: i16,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("Base2"), Base2);
impl !Unpin for Base2 {}
@@ -109,6 +112,7 @@
__base_class_subobjects: [rust_std::mem::MaybeUninit<u8>; 12],
pub derived_1: u8,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("Derived"), Derived);
impl<'a> From<&'a Derived> for &'a Base0 {
fn from(x: &'a Derived) -> Self {
unsafe { &*((x as *const _ as *const u8).offset(0) as *const Base0) }
diff --git a/rs_bindings_from_cc/test/golden/item_order_rs_api.rs b/rs_bindings_from_cc/test/golden/item_order_rs_api.rs
index 445e018..8dc520b 100644
--- a/rs_bindings_from_cc/test/golden/item_order_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/item_order_rs_api.rs
@@ -22,6 +22,7 @@
pub struct FirstStruct {
pub field: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("FirstStruct"), FirstStruct);
impl Default for FirstStruct {
#[inline(always)]
@@ -63,6 +64,7 @@
pub struct SecondStruct {
pub field: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("SecondStruct"), SecondStruct);
impl Default for SecondStruct {
#[inline(always)]
diff --git a/rs_bindings_from_cc/test/golden/no_elided_lifetimes_rs_api.rs b/rs_bindings_from_cc/test/golden/no_elided_lifetimes_rs_api.rs
index 2d60078..42335f9 100644
--- a/rs_bindings_from_cc/test/golden/no_elided_lifetimes_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/no_elided_lifetimes_rs_api.rs
@@ -28,6 +28,7 @@
/// Prevent empty C++ struct being zero-size in Rust.
placeholder: rust_std::mem::MaybeUninit<u8>,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("S"), S);
// rs_bindings_from_cc/test/golden/no_elided_lifetimes.h;l=10
// Error while generating bindings for item 'S::S':
diff --git a/rs_bindings_from_cc/test/golden/nontrivial_type_rs_api.rs b/rs_bindings_from_cc/test/golden/nontrivial_type_rs_api.rs
index 86cc357..93b28f8 100644
--- a/rs_bindings_from_cc/test/golden/nontrivial_type_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/nontrivial_type_rs_api.rs
@@ -26,6 +26,7 @@
pub struct Nontrivial {
pub field: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("Nontrivial"), Nontrivial);
impl !Unpin for Nontrivial {}
@@ -115,6 +116,7 @@
pub struct NontrivialInline {
pub field: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("NontrivialInline"), NontrivialInline);
impl !Unpin for NontrivialInline {}
@@ -205,6 +207,7 @@
pub struct NontrivialMembers {
pub nontrivial_member: rust_std::mem::ManuallyDrop<Nontrivial>,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("NontrivialMembers"), NontrivialMembers);
impl !Unpin for NontrivialMembers {}
diff --git a/rs_bindings_from_cc/test/golden/polymorphic_rs_api.rs b/rs_bindings_from_cc/test/golden/polymorphic_rs_api.rs
index 374276e..3991361 100644
--- a/rs_bindings_from_cc/test/golden/polymorphic_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/polymorphic_rs_api.rs
@@ -22,6 +22,7 @@
/// Prevent empty C++ struct being zero-size in Rust.
placeholder: rust_std::mem::MaybeUninit<u8>,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("PolymorphicClass"), PolymorphicClass);
impl !Unpin for PolymorphicClass {}
diff --git a/rs_bindings_from_cc/test/golden/private_members_rs_api.rs b/rs_bindings_from_cc/test/golden/private_members_rs_api.rs
index c6b3703..1785edd 100644
--- a/rs_bindings_from_cc/test/golden/private_members_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/private_members_rs_api.rs
@@ -23,6 +23,7 @@
pub public_member_variable_: i32,
private_member_variable_: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("SomeClass"), SomeClass);
impl Default for SomeClass {
#[inline(always)]
diff --git a/rs_bindings_from_cc/test/golden/static_methods_rs_api.rs b/rs_bindings_from_cc/test/golden/static_methods_rs_api.rs
index 32f251b..21c08f9 100644
--- a/rs_bindings_from_cc/test/golden/static_methods_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/static_methods_rs_api.rs
@@ -22,6 +22,7 @@
pub struct SomeClass {
field_: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("SomeClass"), SomeClass);
impl Default for SomeClass {
#[inline(always)]
diff --git a/rs_bindings_from_cc/test/golden/trivial_type_rs_api.rs b/rs_bindings_from_cc/test/golden/trivial_type_rs_api.rs
index 2e61e7e..0186f36 100644
--- a/rs_bindings_from_cc/test/golden/trivial_type_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/trivial_type_rs_api.rs
@@ -24,6 +24,7 @@
pub struct Trivial {
pub trivial_field: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("Trivial"), Trivial);
impl Default for Trivial {
#[inline(always)]
@@ -62,6 +63,10 @@
pub struct TrivialWithDefaulted {
pub trivial_field: i32,
}
+forward_declare::unsafe_define!(
+ forward_declare::symbol!("TrivialWithDefaulted"),
+ TrivialWithDefaulted
+);
impl Default for TrivialWithDefaulted {
#[inline(always)]
@@ -99,6 +104,7 @@
pub struct TrivialNonfinal {
pub trivial_field: i32,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("TrivialNonfinal"), TrivialNonfinal);
impl !Unpin for TrivialNonfinal {}
diff --git a/rs_bindings_from_cc/test/golden/types_rs_api.rs b/rs_bindings_from_cc/test/golden/types_rs_api.rs
index d678482..771ae4c 100644
--- a/rs_bindings_from_cc/test/golden/types_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/types_rs_api.rs
@@ -23,6 +23,7 @@
/// Prevent empty C++ struct being zero-size in Rust.
placeholder: rust_std::mem::MaybeUninit<u8>,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("SomeStruct"), SomeStruct);
impl Default for SomeStruct {
#[inline(always)]
@@ -60,6 +61,7 @@
/// Prevent empty C++ struct being zero-size in Rust.
placeholder: rust_std::mem::MaybeUninit<u8>,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("EmptyUnion"), EmptyUnion);
impl Default for EmptyUnion {
#[inline(always)]
@@ -146,6 +148,10 @@
pub struct_ref_field: *mut SomeStruct,
pub const_struct_ref_field: *const SomeStruct,
}
+forward_declare::unsafe_define!(
+ forward_declare::symbol!("FieldTypeTestStruct"),
+ FieldTypeTestStruct
+);
impl<'b> From<ctor::RvalueReference<'b, FieldTypeTestStruct>> for FieldTypeTestStruct {
#[inline(always)]
@@ -172,6 +178,7 @@
pub int32_field: i32,
pub int64_field: i64,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("NonEmptyUnion"), NonEmptyUnion);
impl Default for NonEmptyUnion {
#[inline(always)]
diff --git a/rs_bindings_from_cc/test/golden/unsupported_rs_api.rs b/rs_bindings_from_cc/test/golden/unsupported_rs_api.rs
index f76e5bc..944392d 100644
--- a/rs_bindings_from_cc/test/golden/unsupported_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/unsupported_rs_api.rs
@@ -21,6 +21,10 @@
pub struct NontrivialCustomType {
pub i: i32,
}
+forward_declare::unsafe_define!(
+ forward_declare::symbol!("NontrivialCustomType"),
+ NontrivialCustomType
+);
impl !Unpin for NontrivialCustomType {}
@@ -68,6 +72,7 @@
/// Prevent empty C++ struct being zero-size in Rust.
placeholder: rust_std::mem::MaybeUninit<u8>,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("ContainingStruct"), ContainingStruct);
impl Default for ContainingStruct {
#[inline(always)]
diff --git a/rs_bindings_from_cc/test/golden/user_of_base_class_rs_api.rs b/rs_bindings_from_cc/test/golden/user_of_base_class_rs_api.rs
index af9f2ad..db6a8ce 100644
--- a/rs_bindings_from_cc/test/golden/user_of_base_class_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/user_of_base_class_rs_api.rs
@@ -28,6 +28,7 @@
__base_class_subobjects: [rust_std::mem::MaybeUninit<u8>; 12],
pub derived_1: u8,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("Derived2"), Derived2);
impl<'a> From<&'a Derived2> for &'a Base0 {
fn from(x: &'a Derived2) -> Self {
unsafe { &*((x as *const _ as *const u8).offset(0) as *const Base0) }
diff --git a/rs_bindings_from_cc/test/golden/user_of_imported_type_rs_api.rs b/rs_bindings_from_cc/test/golden/user_of_imported_type_rs_api.rs
index 0d7d3dc..e5e1dba 100644
--- a/rs_bindings_from_cc/test/golden/user_of_imported_type_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/user_of_imported_type_rs_api.rs
@@ -27,6 +27,7 @@
pub struct UserOfImportedType {
pub trivial: *mut trivial_type_cc::Trivial,
}
+forward_declare::unsafe_define!(forward_declare::symbol!("UserOfImportedType"), UserOfImportedType);
impl Default for UserOfImportedType {
#[inline(always)]
diff --git a/rs_bindings_from_cc/test/struct/forward_declarations/BUILD b/rs_bindings_from_cc/test/struct/forward_declarations/BUILD
new file mode 100644
index 0000000..74da684
--- /dev/null
+++ b/rs_bindings_from_cc/test/struct/forward_declarations/BUILD
@@ -0,0 +1,20 @@
+"""End-to-end test of forward declarations."""
+
+load("@rules_rust//rust:defs.bzl", "rust_test")
+
+licenses(["notice"])
+
+cc_library(
+ name = "definition",
+ hdrs = ["definition.h"],
+)
+
+rust_test(
+ name = "forward_declarations_test",
+ srcs = ["forward_declarations_test.rs"],
+ cc_deps = [":definition"],
+ deps = [
+ "//rs_bindings_from_cc/support:ctor",
+ "//rs_bindings_from_cc/support:forward_declare",
+ ],
+)
diff --git a/rs_bindings_from_cc/test/struct/forward_declarations/definition.h b/rs_bindings_from_cc/test/struct/forward_declarations/definition.h
new file mode 100644
index 0000000..b052d3f
--- /dev/null
+++ b/rs_bindings_from_cc/test/struct/forward_declarations/definition.h
@@ -0,0 +1,19 @@
+// 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
+#ifndef THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_STRUCT_FORWARD_DECLARATIONS_DEFINITION_H_
+#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_STRUCT_FORWARD_DECLARATIONS_DEFINITION_H_
+
+#pragma clang lifetime_elision
+
+struct UnpinStruct final {
+ UnpinStruct() = default;
+ int field = 0;
+};
+
+inline int ReadCompleteUnpinStruct(const UnpinStruct& s) { return s.field; }
+inline void WriteCompleteUnpinStruct(UnpinStruct& s, int value) {
+ s.field = value;
+}
+
+#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_STRUCT_FORWARD_DECLARATIONS_DEFINITION_H_
diff --git a/rs_bindings_from_cc/test/struct/forward_declarations/forward_declarations_test.rs b/rs_bindings_from_cc/test/struct/forward_declarations/forward_declarations_test.rs
new file mode 100644
index 0000000..4c2a0c3
--- /dev/null
+++ b/rs_bindings_from_cc/test/struct/forward_declarations/forward_declarations_test.rs
@@ -0,0 +1,18 @@
+// 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
+use ctor::CtorNew as _;
+use forward_declare::{forward_declare, symbol, IncompleteCast};
+use std::pin::Pin;
+
+// Rust user forward declarations.
+forward_declare!(pub IncompleteUnpinStruct = symbol!("UnpinStruct"));
+forward_declare!(pub IncompleteNonunpinStruct = symbol!("NonunpinStruct"));
+
+#[test]
+fn test_unpin_struct() {
+ let mut s = definition::UnpinStruct::default();
+ let incomplete_s: &mut IncompleteUnpinStruct = (&mut s).incomplete_cast();
+ definition::WriteCompleteUnpinStruct(incomplete_s.incomplete_cast(), 42);
+ assert_eq!(definition::ReadCompleteUnpinStruct(incomplete_s.incomplete_cast()), 42);
+}