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);
+}