Implement bindings for forward declared types (incomplete types) outside of namespaces.
Only TODO left for forward declarations is to implement namespace support. It looks like namespaces aren't far enough along for me to implement that, so I'm marking this bug as fixed for now.
PiperOrigin-RevId: 443217097
diff --git a/rs_bindings_from_cc/importer.cc b/rs_bindings_from_cc/importer.cc
index 3ca53e4..7a06e11 100644
--- a/rs_bindings_from_cc/importer.cc
+++ b/rs_bindings_from_cc/importer.cc
@@ -718,14 +718,25 @@
return ImportUnsupportedItem(record_decl,
"Nested classes are not supported yet");
}
- // Make sure the record has a definition that we'll be able to call
- // ASTContext::getASTRecordLayout() on.
- record_decl = record_decl->getDefinition();
- if (!record_decl || record_decl->isInvalidDecl() ||
- !record_decl->isCompleteDefinition()) {
+ if (record_decl->isInvalidDecl()) {
return std::nullopt;
}
+ std::optional<Identifier> record_name = GetTranslatedIdentifier(record_decl);
+ if (!record_name.has_value()) {
+ return std::nullopt;
+ }
+
+ if (clang::CXXRecordDecl* complete = record_decl->getDefinition()) {
+ record_decl = complete;
+ } else {
+ CRUBIT_CHECK(!record_decl->isCompleteDefinition());
+ type_mapper_.Insert(record_decl);
+ return IncompleteRecord{.cc_name = std::string(record_name->Ident()),
+ .id = GenerateItemId(record_decl),
+ .owning_target = GetOwningTarget(record_decl)};
+ }
+
// To compute the memory layout of the record, it needs to be a concrete type,
// not a template.
if (record_decl->getDescribedClassTemplate() ||
@@ -753,11 +764,6 @@
override_alignment = true;
}
- std::optional<Identifier> record_name = GetTranslatedIdentifier(record_decl);
- if (!record_name.has_value()) {
- return std::nullopt;
- }
-
absl::StatusOr<std::vector<Field>> fields = ImportFields(record_decl);
if (!fields.ok()) {
return ImportUnsupportedItem(record_decl, fields.status().ToString());
diff --git a/rs_bindings_from_cc/ir.cc b/rs_bindings_from_cc/ir.cc
index 064c50a..fb80237 100644
--- a/rs_bindings_from_cc/ir.cc
+++ b/rs_bindings_from_cc/ir.cc
@@ -359,6 +359,18 @@
};
}
+llvm::json::Value IncompleteRecord::ToJson() const {
+ llvm::json::Object record{
+ {"cc_name", cc_name},
+ {"id", id},
+ {"owning_target", owning_target},
+ };
+
+ return llvm::json::Object{
+ {"IncompleteRecord", std::move(record)},
+ };
+}
+
llvm::json::Value Record::ToJson() const {
std::vector<llvm::json::Value> json_item_ids;
json_item_ids.reserve(child_item_ids.size());
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index 9f6ab40..b0ae71a 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -575,6 +575,14 @@
std::vector<ItemId> child_item_ids;
};
+// A forward-declared record (e.g. `struct Foo;`)
+struct IncompleteRecord {
+ llvm::json::Value ToJson() const;
+ std::string cc_name;
+ ItemId id;
+ BazelLabel owning_target;
+};
+
struct Enumerator {
llvm::json::Value ToJson() const;
@@ -677,8 +685,8 @@
std::vector<HeaderName> used_headers;
BazelLabel current_target;
- using Item = std::variant<Func, Record, Enum, TypeAlias, UnsupportedItem,
- Comment, Namespace>;
+ using Item = std::variant<Func, Record, IncompleteRecord, Enum, TypeAlias,
+ UnsupportedItem, Comment, Namespace>;
std::vector<Item> items;
std::vector<ItemId> top_level_item_ids;
};
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index 1a59edb..0e4cd67 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -348,6 +348,13 @@
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
+pub struct IncompleteRecord {
+ pub cc_name: String,
+ pub id: ItemId,
+ pub owning_target: BazelLabel,
+}
+
+#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Record {
pub rs_name: String,
pub cc_name: String,
@@ -461,6 +468,7 @@
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub enum Item {
Func(Func),
+ IncompleteRecord(IncompleteRecord),
Record(Record),
Enum(Enum),
TypeAlias(TypeAlias),
@@ -472,10 +480,11 @@
impl Item {
fn id(&self) -> ItemId {
match self {
- Item::Record(record) => record.id,
- Item::TypeAlias(type_alias) => type_alias.id,
Item::Func(func) => func.id,
+ Item::IncompleteRecord(record) => record.id,
+ Item::Record(record) => record.id,
Item::Enum(enum_) => enum_.id,
+ Item::TypeAlias(type_alias) => type_alias.id,
Item::UnsupportedItem(unsupported) => unsupported.id,
Item::Comment(comment) => comment.id,
Item::Namespace(namespace) => namespace.id,
diff --git a/rs_bindings_from_cc/ir_from_cc_test.rs b/rs_bindings_from_cc/ir_from_cc_test.rs
index 78de6d1..7c6b3f3 100644
--- a/rs_bindings_from_cc/ir_from_cc_test.rs
+++ b/rs_bindings_from_cc/ir_from_cc_test.rs
@@ -1469,6 +1469,8 @@
fn test_top_level_items() {
let ir = ir_from_cc(
r#"
+ struct ForwardDeclaredTopLevelStruct;
+ struct TopLevelStruct;
struct TopLevelStruct {};
// Top level comment
@@ -1491,6 +1493,11 @@
top_level_items,
vec![
quote! {
+ IncompleteRecord {
+ ... cc_name: "ForwardDeclaredTopLevelStruct" ...
+ }
+ },
+ quote! {
Record {
... rs_name: "TopLevelStruct" ...
}
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index 39485d6..06ed344 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -856,6 +856,17 @@
Ok(!ty_implements_copy)
}
+/// Generates Rust source code for a given incomplete record declaration.
+fn generate_incomplete_record(incomplete_record: &IncompleteRecord) -> Result<TokenStream> {
+ let ident = make_rs_ident(&incomplete_record.cc_name);
+ let name = &incomplete_record.cc_name;
+ Ok(quote! {
+ forward_declare::forward_declare!(
+ pub #ident __SPACE__ = __SPACE__ forward_declare::symbol!(#name)
+ );
+ })
+}
+
/// Generates Rust source code for a given `Record` and associated assertions as
/// a tuple.
fn generate_record(
@@ -1290,6 +1301,18 @@
}
}
},
+ Item::IncompleteRecord(incomplete_record) => {
+ if !ir.is_current_target(&incomplete_record.owning_target)
+ && !ir.is_stdlib_target(&incomplete_record.owning_target)
+ {
+ GeneratedItem { ..Default::default() }
+ } else {
+ GeneratedItem {
+ item: generate_incomplete_record(incomplete_record)?,
+ ..Default::default()
+ }
+ }
+ }
Item::Record(record) => {
if !ir.is_current_target(&record.owning_target)
&& !ir.is_stdlib_target(&record.owning_target)
@@ -1323,9 +1346,7 @@
Item::Comment(comment) => {
GeneratedItem { item: generate_comment(comment)?, ..Default::default() }
}
- Item::Namespace(namespace) => {
- generate_namespace(namespace, ir, overloaded_funcs)?
- }
+ Item::Namespace(namespace) => generate_namespace(namespace, ir, overloaded_funcs)?,
};
Ok(generated_item)
@@ -1507,6 +1528,14 @@
return_type: Box<RsTypeKind<'ir>>,
param_types: Vec<RsTypeKind<'ir>>,
},
+ /// An incomplete record type.
+ IncompleteRecord {
+ incomplete_record: &'ir IncompleteRecord,
+
+ /// The imported crate this comes from, or None if the current crate.
+ crate_ident: Option<Ident>,
+ },
+ /// A complete record type.
Record {
record: &'ir Record,
/// The imported crate this comes from, or None if the current crate.
@@ -1555,6 +1584,10 @@
ty
);
match ir.item_for_type(ty)? {
+ Item::IncompleteRecord(incomplete_record) => RsTypeKind::IncompleteRecord {
+ incomplete_record,
+ crate_ident: rs_imported_crate_name(&incomplete_record.owning_target, ir),
+ },
Item::Record(record) => RsTypeKind::new_record(record, ir),
Item::TypeAlias(type_alias) => RsTypeKind::TypeAlias {
type_alias,
@@ -1630,6 +1663,7 @@
/// Returns true if the type is known to be `Unpin`, false otherwise.
pub fn is_unpin(&self) -> bool {
match self {
+ RsTypeKind::IncompleteRecord { .. } => false,
RsTypeKind::Record { record, .. } => record.is_unpin(),
RsTypeKind::TypeAlias { underlying_type, .. } => underlying_type.is_unpin(),
_ => true,
@@ -1722,6 +1756,7 @@
RsTypeKind::Reference { mutability: Mutability::Const, .. } => true,
RsTypeKind::Reference { mutability: Mutability::Mut, .. } => false,
RsTypeKind::RvalueReference { .. } => false,
+ RsTypeKind::IncompleteRecord { .. } => false,
RsTypeKind::Record { record, .. } => should_derive_copy(record),
RsTypeKind::TypeAlias { underlying_type, .. } => underlying_type.implements_copy(),
RsTypeKind::Other { type_args, .. } => {
@@ -1823,6 +1858,11 @@
let return_frag = return_type.format_as_return_type_fragment();
quote! { extern #abi fn( #( #param_types ),* ) #return_frag }
}
+ RsTypeKind::IncompleteRecord { incomplete_record, crate_ident } => {
+ let record_ident = make_rs_ident(&incomplete_record.cc_name);
+ let crate_ident = crate_ident.iter();
+ quote! {#(#crate_ident::)* #record_ident}
+ }
RsTypeKind::Record { record, crate_ident } => {
let record_ident = make_rs_ident(&record.rs_name);
let crate_ident = crate_ident.iter();
@@ -1861,7 +1901,9 @@
None => None,
Some(curr) => {
match curr {
- RsTypeKind::Unit | RsTypeKind::Record { .. } => (),
+ RsTypeKind::Unit
+ | RsTypeKind::IncompleteRecord { .. }
+ | RsTypeKind::Record { .. } => {}
RsTypeKind::Pointer { pointee, .. } => self.todo.push(pointee),
RsTypeKind::Reference { referent, .. } => self.todo.push(referent),
RsTypeKind::RvalueReference { referent, .. } => self.todo.push(referent),
@@ -1997,8 +2039,8 @@
record.fields.iter().filter(|f| f.access == AccessSpecifier::Public).map(|field| {
let field_ident = format_cc_ident(&field.identifier.identifier);
let offset = Literal::usize_unsuffixed(field.offset);
- // The IR contains the offset in bits, while `CRUBIT_OFFSET_OF` returns the offset in
- // bytes, so we need to convert.
+ // The IR contains the offset in bits, while `CRUBIT_OFFSET_OF` returns the
+ // offset in bytes, so we need to convert.
quote! {
static_assert(CRUBIT_OFFSET_OF(#field_ident, class #record_ident) * 8 == #offset);
}
@@ -3127,7 +3169,8 @@
field2: [rust_std::mem::MaybeUninit<u8>; 2],
pub z: i16,
}
- });
+ }
+ );
assert_rs_matches!(
rs_api,
quote! {
@@ -4780,6 +4823,23 @@
}
#[test]
+ fn test_forward_declared() -> Result<()> {
+ let ir = ir_from_cc(
+ r#"#pragma clang lifetime_elision
+ struct ForwardDeclared;"#,
+ )?;
+ let rs_api = generate_rs_api(&ir)?;
+ assert_rs_matches!(
+ rs_api,
+ quote! {
+ forward_declare::forward_declare!(pub ForwardDeclared = forward_declare::symbol!("ForwardDeclared"));
+ }
+ );
+ assert_rs_not_matches!(rs_api, quote! {struct ForwardDeclared});
+ Ok(())
+ }
+
+ #[test]
fn test_namespace_module_items() -> Result<()> {
let rs_api_impl = generate_rs_api(&ir_from_cc(
r#"
diff --git a/rs_bindings_from_cc/test/golden/types.h b/rs_bindings_from_cc/test/golden/types.h
index c925aab..9abfc93 100644
--- a/rs_bindings_from_cc/test/golden/types.h
+++ b/rs_bindings_from_cc/test/golden/types.h
@@ -12,6 +12,8 @@
struct SomeStruct final {};
+struct ForwardDeclaredStruct;
+
union EmptyUnion {};
struct FieldTypeTestStruct final {
@@ -79,6 +81,8 @@
// TODO(b/226580208): Uncomment when these don't cause struct import to fail.
// SomeStruct&& struct_rvalue_ref_field;
// const SomeStruct&& const_struct_rvalue_ref_field;
+
+ ForwardDeclaredStruct* forward_declared_ptr_field;
};
union NonEmptyUnion {
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 3d136cb..7eb2b1c 100644
--- a/rs_bindings_from_cc/test/golden/types_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/types_rs_api.rs
@@ -54,6 +54,8 @@
// Error while generating bindings for item 'SomeStruct::operator=':
// Bindings for this kind of operator are not supported
+forward_declare::forward_declare!(pub ForwardDeclaredStruct = forward_declare::symbol!("ForwardDeclaredStruct"));
+
#[derive(Clone, Copy)]
#[repr(C)]
pub union EmptyUnion {
@@ -84,11 +86,11 @@
}
}
-// rs_bindings_from_cc/test/golden/types.h;l=15
+// rs_bindings_from_cc/test/golden/types.h;l=17
// Error while generating bindings for item 'EmptyUnion::operator=':
// Bindings for this kind of operator are not supported
-// rs_bindings_from_cc/test/golden/types.h;l=15
+// rs_bindings_from_cc/test/golden/types.h;l=17
// Error while generating bindings for item 'EmptyUnion::operator=':
// Bindings for this kind of operator are not supported
@@ -146,6 +148,10 @@
pub const_struct_ptr_field: *const SomeStruct,
pub struct_ref_field: *mut SomeStruct,
pub const_struct_ref_field: *const SomeStruct,
+ /// TODO(b/226580208): Uncomment when these don't cause struct import to fail.
+ /// SomeStruct&& struct_rvalue_ref_field;
+ /// const SomeStruct&& const_struct_rvalue_ref_field;
+ pub forward_declared_ptr_field: *mut ForwardDeclaredStruct,
}
forward_declare::unsafe_define!(
forward_declare::symbol!("FieldTypeTestStruct"),
@@ -163,10 +169,6 @@
}
}
-// TODO(b/226580208): Uncomment when these don't cause struct import to fail.
-// SomeStruct&& struct_rvalue_ref_field;
-// const SomeStruct&& const_struct_rvalue_ref_field;
-
#[derive(Clone, Copy)]
#[repr(C)]
pub union NonEmptyUnion {
@@ -201,11 +203,11 @@
}
}
-// rs_bindings_from_cc/test/golden/types.h;l=84
+// rs_bindings_from_cc/test/golden/types.h;l=88
// Error while generating bindings for item 'NonEmptyUnion::operator=':
// Bindings for this kind of operator are not supported
-// rs_bindings_from_cc/test/golden/types.h;l=84
+// rs_bindings_from_cc/test/golden/types.h;l=88
// Error while generating bindings for item 'NonEmptyUnion::operator=':
// Bindings for this kind of operator are not supported
@@ -275,7 +277,7 @@
static_assertions::assert_not_impl_all!(EmptyUnion: Drop);
};
-const _: () = assert!(rust_std::mem::size_of::<FieldTypeTestStruct>() == 280usize);
+const _: () = assert!(rust_std::mem::size_of::<FieldTypeTestStruct>() == 288usize);
const _: () = assert!(rust_std::mem::align_of::<FieldTypeTestStruct>() == 8usize);
const _: () = {
static_assertions::assert_impl_all!(FieldTypeTestStruct: Clone);
@@ -337,6 +339,7 @@
const _: () = assert!(offset_of!(FieldTypeTestStruct, const_struct_ptr_field) * 8 == 2048usize);
const _: () = assert!(offset_of!(FieldTypeTestStruct, struct_ref_field) * 8 == 2112usize);
const _: () = assert!(offset_of!(FieldTypeTestStruct, const_struct_ref_field) * 8 == 2176usize);
+const _: () = assert!(offset_of!(FieldTypeTestStruct, forward_declared_ptr_field) * 8 == 2240usize);
const _: () = assert!(rust_std::mem::size_of::<NonEmptyUnion>() == 8usize);
const _: () = assert!(rust_std::mem::align_of::<NonEmptyUnion>() == 8usize);
diff --git a/rs_bindings_from_cc/test/golden/types_rs_api_impl.cc b/rs_bindings_from_cc/test/golden/types_rs_api_impl.cc
index de86812..c7344b1 100644
--- a/rs_bindings_from_cc/test/golden/types_rs_api_impl.cc
+++ b/rs_bindings_from_cc/test/golden/types_rs_api_impl.cc
@@ -110,7 +110,7 @@
static_assert(sizeof(class EmptyUnion) == 1);
static_assert(alignof(class EmptyUnion) == 1);
-static_assert(sizeof(class FieldTypeTestStruct) == 280);
+static_assert(sizeof(class FieldTypeTestStruct) == 288);
static_assert(alignof(class FieldTypeTestStruct) == 8);
static_assert(CRUBIT_OFFSET_OF(bool_field, class FieldTypeTestStruct) * 8 == 0);
static_assert(CRUBIT_OFFSET_OF(char_field, class FieldTypeTestStruct) * 8 == 8);
@@ -246,6 +246,10 @@
class FieldTypeTestStruct) *
8 ==
2176);
+static_assert(CRUBIT_OFFSET_OF(forward_declared_ptr_field,
+ class FieldTypeTestStruct) *
+ 8 ==
+ 2240);
static_assert(sizeof(class NonEmptyUnion) == 8);
static_assert(alignof(class NonEmptyUnion) == 8);
diff --git a/rs_bindings_from_cc/test/struct/forward_declarations/BUILD b/rs_bindings_from_cc/test/struct/forward_declarations/BUILD
index 74da684..10baa72 100644
--- a/rs_bindings_from_cc/test/struct/forward_declarations/BUILD
+++ b/rs_bindings_from_cc/test/struct/forward_declarations/BUILD
@@ -6,13 +6,34 @@
cc_library(
name = "definition",
+ srcs = ["definition.cc"],
hdrs = ["definition.h"],
)
+cc_library(
+ name = "declaration_1",
+ hdrs = ["declaration_1.h"],
+ deps = [
+ ":definition", # build_cleaner: keep
+ ],
+)
+
+cc_library(
+ name = "declaration_2",
+ hdrs = ["declaration_2.h"],
+ deps = [
+ ":definition", # build_cleaner: keep
+ ],
+)
+
rust_test(
name = "forward_declarations_test",
srcs = ["forward_declarations_test.rs"],
- cc_deps = [":definition"],
+ cc_deps = [
+ ":declaration_1",
+ ":declaration_2",
+ ":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/declaration_1.h b/rs_bindings_from_cc/test/struct/forward_declarations/declaration_1.h
new file mode 100644
index 0000000..5f1ebe0
--- /dev/null
+++ b/rs_bindings_from_cc/test/struct/forward_declarations/declaration_1.h
@@ -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
+#ifndef THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_STRUCT_FORWARD_DECLARATIONS_DECLARATION_1_H_
+#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_STRUCT_FORWARD_DECLARATIONS_DECLARATION_1_H_
+
+#pragma clang lifetime_elision
+
+struct UnpinStruct;
+struct NonunpinStruct;
+
+int ReadUnpinStruct(const UnpinStruct& s);
+void WriteUnpinStruct(UnpinStruct& s, int value);
+
+int ReadNonunpinStruct(const NonunpinStruct& s);
+void WriteNonunpinStruct(NonunpinStruct& s, int value);
+
+#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_STRUCT_FORWARD_DECLARATIONS_DECLARATION_1_H_
diff --git a/rs_bindings_from_cc/test/struct/forward_declarations/declaration_2.h b/rs_bindings_from_cc/test/struct/forward_declarations/declaration_2.h
new file mode 100644
index 0000000..cd3fa8b
--- /dev/null
+++ b/rs_bindings_from_cc/test/struct/forward_declarations/declaration_2.h
@@ -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
+#ifndef THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_STRUCT_FORWARD_DECLARATIONS_DECLARATION_2_H_
+#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_STRUCT_FORWARD_DECLARATIONS_DECLARATION_2_H_
+
+#pragma clang lifetime_elision
+
+struct UnpinStruct;
+struct NonunpinStruct;
+
+int ReadUnpinStruct(const UnpinStruct& s);
+void WriteUnpinStruct(UnpinStruct& s, int value);
+
+int ReadNonunpinStruct(const NonunpinStruct& s);
+void WriteNonunpinStruct(NonunpinStruct& s, int value);
+
+#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_STRUCT_FORWARD_DECLARATIONS_DECLARATION_2_H_
diff --git a/rs_bindings_from_cc/test/struct/forward_declarations/definition.cc b/rs_bindings_from_cc/test/struct/forward_declarations/definition.cc
new file mode 100644
index 0000000..a876d4e
--- /dev/null
+++ b/rs_bindings_from_cc/test/struct/forward_declarations/definition.cc
@@ -0,0 +1,9 @@
+// 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 "rs_bindings_from_cc/test/struct/forward_declarations/definition.h"
+
+int ReadUnpinStruct(const UnpinStruct& s) { return s.field; }
+void WriteUnpinStruct(UnpinStruct& s, int value) { s.field = value; }
+int ReadNonunpinStruct(const NonunpinStruct& s) { return s.field; }
+void WriteNonunpinStruct(NonunpinStruct& s, int value) { s.field = value; }
diff --git a/rs_bindings_from_cc/test/struct/forward_declarations/definition.h b/rs_bindings_from_cc/test/struct/forward_declarations/definition.h
index 65df9a1..311de17 100644
--- a/rs_bindings_from_cc/test/struct/forward_declarations/definition.h
+++ b/rs_bindings_from_cc/test/struct/forward_declarations/definition.h
@@ -8,24 +8,19 @@
struct UnpinStruct final {
UnpinStruct() = default;
+ UnpinStruct(int value) : field(value) {}
int field = 0;
};
struct NonunpinStruct /* non-final */ {
NonunpinStruct() = default;
+ NonunpinStruct(int value) : field(value) {}
int field = 0;
};
-inline int ReadCompleteUnpinStruct(const UnpinStruct& s) { return s.field; }
-inline void WriteCompleteUnpinStruct(UnpinStruct& s, int value) {
- s.field = value;
-}
-
-inline int ReadCompleteNonunpinStruct(const NonunpinStruct& s) {
- return s.field;
-}
-inline void WriteCompleteNonunpinStruct(NonunpinStruct& s, int value) {
- s.field = value;
-}
+int ReadUnpinStruct(const UnpinStruct& s);
+void WriteUnpinStruct(UnpinStruct& s, int value);
+int ReadNonunpinStruct(const NonunpinStruct& s);
+void WriteNonunpinStruct(NonunpinStruct& s, int 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
index 31dcdf5..712f07f 100644
--- 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
@@ -2,27 +2,179 @@
// 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 forward_declare::IncompleteCast as _;
use std::pin::Pin;
-// Rust user forward declarations.
-forward_declare!(pub IncompleteUnpinStruct = symbol!("UnpinStruct"));
-forward_declare!(pub IncompleteNonunpinStruct = symbol!("NonunpinStruct"));
-
+/// Given a complete UnpinStruct, all APIs accepting a (possibly incomplete)
+/// UnpinStruct work (with an incomplete_cast()).
#[test]
-fn test_unpin_struct() {
- let mut s = definition::UnpinStruct::default();
- let mut incomplete_s: Pin<&mut IncompleteUnpinStruct> = (&mut s).incomplete_cast();
- definition::WriteCompleteUnpinStruct(incomplete_s.as_mut().incomplete_cast(), 42);
- assert_eq!(definition::ReadCompleteUnpinStruct(incomplete_s.as_mut().incomplete_cast()), 42);
+fn test_read_complete_unpin() {
+ let s = definition::UnpinStruct { field: 42 };
+ let s = &s;
+
+ // The normal way to call it, if you have a complete type (and know it).
+ assert_eq!(definition::ReadUnpinStruct(s), 42);
+
+ // Self-cast: if either the argument, or the parameter, was or will be
+ // incomplete.
+ assert_eq!(definition::ReadUnpinStruct(s.incomplete_cast()), 42);
+
+ // Actual conversion.
+ assert_eq!(declaration_1::ReadUnpinStruct(s.incomplete_cast()), 42);
+ assert_eq!(declaration_2::ReadUnpinStruct(s.incomplete_cast()), 42);
}
+/// Given a complete UnpinStruct, all APIs accepting a (possibly incomplete)
+/// mut UnpinStruct work (with an incomplete_cast()).
#[test]
-fn test_nonunpin_struct() {
+fn test_write_complete_unpin() {
+ let mut s = definition::UnpinStruct { field: 42 };
+ let s = &mut s;
+
+ // The normal way to call it, if you have a complete type (and know it).
+ definition::WriteUnpinStruct(s, 0);
+ assert_eq!(definition::ReadUnpinStruct(s), 0);
+
+ // Self-cast: if either the argument, or the parameter, was or will be
+ // incomplete.
+ definition::WriteUnpinStruct(s.incomplete_cast(), 1);
+ assert_eq!(definition::ReadUnpinStruct(s), 1);
+
+ // Actual conversions.
+ declaration_1::WriteUnpinStruct(s.incomplete_cast(), 2);
+ assert_eq!(definition::ReadUnpinStruct(s), 2);
+ declaration_2::WriteUnpinStruct(s.incomplete_cast(), 2);
+ assert_eq!(definition::ReadUnpinStruct(s), 2);
+}
+
+/// Given an incomplete UnpinStruct, all APIs accepting a (possibly
+/// incomplete) UnpinStruct work (with an incomplete_cast()).
+#[test]
+fn test_read_incomplete_unpin() {
+ let s = definition::UnpinStruct { field: 42 };
+ let decl1_s: &declaration_1::UnpinStruct = (&s).incomplete_cast();
+
+ // Cast from incomplete to complete:
+ assert_eq!(definition::ReadUnpinStruct(decl1_s.incomplete_cast()), 42);
+
+ // No cast necessary if it's the same forward declaration.
+ assert_eq!(declaration_1::ReadUnpinStruct(&*decl1_s), 42);
+ // Buit a self-cast also works:
+ assert_eq!(declaration_1::ReadUnpinStruct(decl1_s.incomplete_cast()), 42);
+
+ // Cast from incomplete to different-incomplete:
+ assert_eq!(declaration_2::ReadUnpinStruct(decl1_s.incomplete_cast()), 42);
+}
+
+/// Given an incomplete UnpinStruct, all APIs accepting a (possibly
+/// incomplete) mut UnpinStruct work (with an incomplete_cast()).
+#[test]
+fn test_write_incomplete_unpin() {
+ let mut s = definition::UnpinStruct { field: 42 };
+ let mut decl1_s: Pin<&mut declaration_1::UnpinStruct> = (&mut s).incomplete_cast();
+
+ // Cast from incomplete to complete:
+ definition::WriteUnpinStruct(decl1_s.as_mut().incomplete_cast(), 0);
+ assert_eq!(declaration_1::ReadUnpinStruct(&*decl1_s), 0);
+
+ // No cast necessary if it's the same forward declaration.
+ declaration_1::WriteUnpinStruct(decl1_s.as_mut(), 1);
+ assert_eq!(declaration_1::ReadUnpinStruct(&*decl1_s), 1);
+ // But a self-cast also works.
+ declaration_1::WriteUnpinStruct(decl1_s.as_mut(), 2);
+ assert_eq!(declaration_1::ReadUnpinStruct(&*decl1_s), 2);
+
+ // Cast from incomplete to different-incomplete:
+ declaration_2::WriteUnpinStruct(decl1_s.as_mut().incomplete_cast(), 3);
+ assert_eq!(declaration_1::ReadUnpinStruct(&*decl1_s), 3);
+}
+
+/// Given a complete NonunpinStruct, all APIs accepting a (possibly incomplete)
+/// NonunpinStruct work (with an incomplete_cast()).
+#[test]
+fn test_read_complete_nonunpin() {
ctor::emplace! {
- let mut s = definition::NonunpinStruct::ctor_new(());
+ let mut s = definition::NonunpinStruct::ctor_new(42);
}
- let mut incomplete_s: Pin<&mut IncompleteNonunpinStruct> = s.as_mut().incomplete_cast();
- definition::WriteCompleteNonunpinStruct(incomplete_s.as_mut().incomplete_cast(), 42);
- assert_eq!(definition::ReadCompleteNonunpinStruct((&*incomplete_s).incomplete_cast()), 42);
+
+ // The normal way to call it, if you have a complete type (and know it).
+ assert_eq!(definition::ReadNonunpinStruct(&*s), 42);
+
+ // Self-cast: if either the argument, or the parameter, was or will be
+ // incomplete.
+ assert_eq!(definition::ReadNonunpinStruct(s.as_mut().incomplete_cast()), 42);
+
+ // Actual conversion.
+ assert_eq!(declaration_1::ReadNonunpinStruct(s.as_mut().incomplete_cast()), 42);
+ assert_eq!(declaration_2::ReadNonunpinStruct(s.as_mut().incomplete_cast()), 42);
+}
+
+/// Given a complete NonunpinStruct, all APIs accepting a (possibly incomplete)
+/// mut NonunpinStruct work (with an incomplete_cast()).
+#[test]
+fn test_write_complete_nonunpin() {
+ ctor::emplace! {
+ let mut s = definition::NonunpinStruct::ctor_new(42);
+ }
+
+ // The normal way to call it, if you have a complete type (and know it).
+ definition::WriteNonunpinStruct(s.as_mut(), 0);
+ assert_eq!(definition::ReadNonunpinStruct(&*s), 0);
+
+ // Self-cast: if either the argument, or the parameter, was or will be
+ // incomplete.
+ definition::WriteNonunpinStruct(s.as_mut().incomplete_cast(), 1);
+ assert_eq!(definition::ReadNonunpinStruct(&*s), 1);
+
+ // Actual conversions.
+ declaration_1::WriteNonunpinStruct(s.as_mut().incomplete_cast(), 2);
+ assert_eq!(definition::ReadNonunpinStruct(&*s), 2);
+ declaration_2::WriteNonunpinStruct(s.as_mut().incomplete_cast(), 2);
+ assert_eq!(definition::ReadNonunpinStruct(&*s), 2);
+}
+
+/// Given an incomplete NonunpinStruct, all APIs accepting a (possibly
+/// incomplete) NonunpinStruct work (with an incomplete_cast()).
+#[test]
+fn test_read_incomplete_nonunpin() {
+ ctor::emplace! {
+ let mut s = definition::NonunpinStruct::ctor_new(42);
+ }
+ let mut decl1_s: Pin<&mut declaration_1::NonunpinStruct> = s.incomplete_cast();
+
+ // Cast from incomplete to complete:
+ assert_eq!(definition::ReadNonunpinStruct(decl1_s.as_mut().incomplete_cast()), 42);
+
+ // No cast necessary if it's the same forward declaration.
+ assert_eq!(declaration_1::ReadNonunpinStruct(&*decl1_s), 42);
+ // Buit a self-cast also works:
+ assert_eq!(declaration_1::ReadNonunpinStruct(decl1_s.as_mut().incomplete_cast()), 42);
+
+ // Cast from incomplete to different-incomplete:
+ assert_eq!(declaration_2::ReadNonunpinStruct(decl1_s.as_mut().incomplete_cast()), 42);
+}
+
+/// Given an incomplete NonunpinStruct, all APIs accepting a (possibly
+/// incomplete) mut NonunpinStruct work (with an incomplete_cast()).
+#[test]
+fn test_write_incomplete_nonunpin() {
+ ctor::emplace! {
+ let mut s = definition::NonunpinStruct::ctor_new(42);
+ }
+ let mut decl1_s: Pin<&mut declaration_1::NonunpinStruct> = s.incomplete_cast();
+
+ // Cast from incomplete to complete:
+ definition::WriteNonunpinStruct(decl1_s.as_mut().incomplete_cast(), 0);
+ assert_eq!(declaration_1::ReadNonunpinStruct(&*decl1_s), 0);
+
+ // No cast necessary if it's the same forward declaration.
+ declaration_1::WriteNonunpinStruct(decl1_s.as_mut(), 1);
+ assert_eq!(declaration_1::ReadNonunpinStruct(&*decl1_s), 1);
+ // But a self-cast also works.
+ declaration_1::WriteNonunpinStruct(decl1_s.as_mut(), 2);
+ assert_eq!(declaration_1::ReadNonunpinStruct(&*decl1_s), 2);
+
+ // Cast from incomplete to different-incomplete:
+ declaration_2::WriteNonunpinStruct(decl1_s.as_mut().incomplete_cast(), 3);
+ assert_eq!(declaration_1::ReadNonunpinStruct(&*decl1_s), 3);
}