Fields of unsupported type can be represented as opaque blobs of bytes.
After this CL the IR::Field::type value is wrapped in absl::StatusOr (or
Result<_, String> on Rust side) - an error means an unsupported type.
PiperOrigin-RevId: 449355244
diff --git a/rs_bindings_from_cc/BUILD b/rs_bindings_from_cc/BUILD
index db38b32..293ae6b 100644
--- a/rs_bindings_from_cc/BUILD
+++ b/rs_bindings_from_cc/BUILD
@@ -251,6 +251,7 @@
"//common:check",
"//common:strong_int",
"@absl//container:flat_hash_map",
+ "@absl//status:statusor",
"@absl//strings",
"@llvm///clang:ast",
"@llvm///llvm:Support",
diff --git a/rs_bindings_from_cc/importers/cxx_record.cc b/rs_bindings_from_cc/importers/cxx_record.cc
index 0ddb5b9..029e4c1 100644
--- a/rs_bindings_from_cc/importers/cxx_record.cc
+++ b/rs_bindings_from_cc/importers/cxx_record.cc
@@ -97,7 +97,7 @@
}
for (const Field& field : *fields) {
- if (field.is_no_unique_address) {
+ if (field.is_no_unique_address || !field.type.ok()) {
override_alignment = true;
break;
}
@@ -144,13 +144,9 @@
ictx_.ctx_.getASTRecordLayout(record_decl);
for (const clang::FieldDecl* field_decl : record_decl->fields()) {
std::optional<clang::tidy::lifetimes::ValueLifetimes> no_lifetimes;
- auto type =
+ absl::StatusOr<MappedType> type =
temp_import_mapper.ConvertQualType(field_decl->getType(), no_lifetimes);
- if (!type.ok()) {
- return absl::UnimplementedError(absl::Substitute(
- "Type of field '$0' is not supported: $1",
- field_decl->getNameAsString(), type.status().message()));
- }
+
clang::AccessSpecifier access = field_decl->getAccess();
if (access == clang::AS_none) {
access = default_access;
@@ -166,7 +162,7 @@
{.identifier = field_name ? *std::move(field_name)
: llvm::Optional<Identifier>(llvm::None),
.doc_comment = ictx_.GetComment(field_decl),
- .type = *type,
+ .type = std::move(type),
.access = TranslateAccessSpecifier(access),
.offset = layout.getFieldOffset(field_decl->getFieldIndex()),
.size = ictx_.ctx_.getTypeSize(field_decl->getType()),
diff --git a/rs_bindings_from_cc/importers/cxx_record.h b/rs_bindings_from_cc/importers/cxx_record.h
index 5ffafb2..f9c8012 100644
--- a/rs_bindings_from_cc/importers/cxx_record.h
+++ b/rs_bindings_from_cc/importers/cxx_record.h
@@ -15,6 +15,8 @@
std::optional<IR::Item> Import(clang::CXXRecordDecl*);
private:
+ // TODO(b/226580208): absl::StatusOr is obsolete below / ImportFields cannot
+ // fail.
absl::StatusOr<std::vector<Field>> ImportFields(clang::CXXRecordDecl*);
std::vector<BaseClass> GetUnambiguousPublicBases(
const clang::CXXRecordDecl& record_decl) const;
diff --git a/rs_bindings_from_cc/ir.cc b/rs_bindings_from_cc/ir.cc
index 5e2f533..4495826 100644
--- a/rs_bindings_from_cc/ir.cc
+++ b/rs_bindings_from_cc/ir.cc
@@ -36,6 +36,14 @@
return llvm::json::Value(string_type.value());
}
+template <class T>
+llvm::json::Value toJSON(const absl::StatusOr<T>& t) {
+ if (t.ok()) {
+ return llvm::json::Object{{"Ok", *t}};
+ }
+ return llvm::json::Object{{"Err", std::string(t.status().message())}};
+}
+
llvm::json::Value HeaderName::ToJson() const {
return llvm::json::Object{
{"name", name_},
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index 9c5431b..d6f3394 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -22,6 +22,7 @@
#include <vector>
#include "absl/container/flat_hash_map.h"
+#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "common/check.h"
#include "common/strong_int.h"
@@ -479,7 +480,7 @@
llvm::Optional<Identifier> identifier;
llvm::Optional<std::string> doc_comment;
- MappedType type;
+ absl::StatusOr<MappedType> type;
AccessSpecifier access;
// Field offset in bits.
uint64_t offset;
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index 5b407bb..7821c3a 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -309,7 +309,7 @@
pub identifier: Option<Identifier>,
pub doc_comment: Option<String>,
#[serde(rename(deserialize = "type"))]
- pub type_: MappedType,
+ pub type_: Result<MappedType, String>,
pub access: AccessSpecifier,
pub offset: usize,
pub size: usize,
diff --git a/rs_bindings_from_cc/ir_from_cc_test.rs b/rs_bindings_from_cc/ir_from_cc_test.rs
index 9fc2817..10f1aec 100644
--- a/rs_bindings_from_cc/ir_from_cc_test.rs
+++ b/rs_bindings_from_cc/ir_from_cc_test.rs
@@ -359,7 +359,7 @@
quote! {
Field {
identifier: Some("ptr") ...
- type_: MappedType {
+ type_: Ok(MappedType {
rs_type: RsType {
name: Some("*mut") ...
type_args: [RsType {
@@ -378,7 +378,7 @@
}],
decl_id: None,
},
- } ...
+ }) ...
}
}
);
@@ -576,9 +576,10 @@
let fields = ir.records().next().unwrap().fields.iter();
let type_mapping: HashMap<_, _> = fields
.map(|f| {
+
(
- f.type_.cc_type.name.as_ref().unwrap().as_str(),
- f.type_.rs_type.name.as_ref().unwrap().as_str(),
+ f.type_.as_ref().unwrap().cc_type.name.as_ref().unwrap().as_str(),
+ f.type_.as_ref().unwrap().rs_type.name.as_ref().unwrap().as_str(),
)
})
.collect();
@@ -779,10 +780,10 @@
fields: [Field {
identifier: Some("value"), ...
doc_comment: Some("Doc comment of `value` field."), ...
- type_: MappedType {
+ type_: Ok(MappedType {
rs_type: RsType { name: Some("i32"), ... },
cc_type: CcType { name: Some("int"), ... },
- },
+ }),
access: Public,
offset: 0, ...
}], ...
@@ -890,10 +891,10 @@
fields: [Field {
identifier: Some("value"), ...
doc_comment: Some("Doc comment of the `value` field specialization for T=int."), ...
- type_: MappedType {
+ type_: Ok(MappedType {
rs_type: RsType { name: Some("i32"), ... },
cc_type: CcType { name: Some("int"), ... },
- },
+ }),
access: Public,
offset: 0, ...
}], ...
@@ -1564,13 +1565,15 @@
}
#[test]
-fn test_record_with_unsupported_field() -> Result<()> {
+fn test_record_with_unsupported_field_type() -> Result<()> {
// Using a nested struct because it's currently not supported.
// But... any other unsupported type would also work for this test.
let ir = ir_from_cc(
r#"
struct StructWithUnsupportedField {
struct NestedStruct {};
+
+ // Doc comment for `my_field`.
NestedStruct my_field;
};
"#,
@@ -1578,11 +1581,36 @@
assert_ir_matches!(
ir,
quote! {
- UnsupportedItem(UnsupportedItem {
- name: "StructWithUnsupportedField",
- message: "UNIMPLEMENTED: Type of field 'my_field' is not supported: Unsupported type 'struct StructWithUnsupportedField::NestedStruct': No generated bindings found for 'NestedStruct'",
+ Record {
+ rs_name: "StructWithUnsupportedField",
+ cc_name: "StructWithUnsupportedField",
+ ...
+ fields: [Field {
+ identifier: Some("my_field"),
+ doc_comment: Some("Doc comment for `my_field`."),
+ type_: Err(
+ "Unsupported type 'struct StructWithUnsupportedField::NestedStruct': No generated bindings found for 'NestedStruct'",
+ ),
+ access: Public,
+ offset: 0,
+ size: 8,
+ is_no_unique_address: false,
+ }],
+ ...
+ size: 1,
+ alignment: 1,
+ ...
+ }
+ }
+ );
+ assert_ir_matches!(
+ ir,
+ quote! {
+ UnsupportedItem {
+ name: "StructWithUnsupportedField::NestedStruct",
+ message: "Nested classes are not supported yet",
...
- })
+ }
}
);
Ok(())
@@ -1756,19 +1784,19 @@
fields: [
Field {
identifier: Some("first_field"), ...
- type_ : MappedType {
+ type_: Ok(MappedType {
rs_type : RsType { name : Some ("i32"), ...},
cc_type : CcType { name : Some ("int"), ...},
- }, ...
+ }), ...
offset: 0, ...
size: 32, ...
},
Field {
identifier: Some("second_field"), ...
- type_ : MappedType {
+ type_: Ok(MappedType {
rs_type : RsType { name : Some ("i32"), ...},
cc_type : CcType { name : Some ("int"), ...},
- }, ...
+ }), ...
offset: 32, ...
size: 32, ...
},
@@ -1799,19 +1827,19 @@
fields: [
Field {
identifier: Some("first_field"), ...
- type_ : MappedType {
+ type_: Ok(MappedType {
rs_type : RsType { name : Some ("i32"), ...},
cc_type : CcType { name : Some ("int"), ...},
- }, ...
+ }), ...
offset: 0, ...
size: 32, ...
},
Field {
identifier: Some("second_field"), ...
- type_ : MappedType {
+ type_: Ok(MappedType {
rs_type : RsType { name : Some ("i32"), ...},
cc_type : CcType { name : Some ("int"), ...},
- }, ...
+ }), ...
offset: 0, ...
size: 32, ...
},
@@ -2057,7 +2085,6 @@
let names = ir.unsupported_items().map(|i| i.name.as_str()).collect_vec();
assert_strings_dont_contain(names.as_slice(), "OuterStruct");
assert_strings_dont_contain(names.as_slice(), "NestedStructIsUnsupported");
- assert_strings_contain(names.as_slice(), "MyOtherStruct");
Ok(())
}
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index 05c1659..30418f5 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -964,14 +964,19 @@
}
}
-fn should_represent_field_as_opaque_blob(field: &Field) -> bool {
- // [[no_unique_address]] fields are replaced by an unaligned block of memory
- // which fills space up to the next field.
+/// Gets the type of `field` for layout purposes.
+///
+/// Note that `get_field_rs_type_for_layout` may return Err (for
+/// `is_no_unique_address` fields) even if `field.type_` is Ok.
+fn get_field_rs_type_for_layout(field: &Field) -> Result<&RsType, &str> {
+ // [[no_unique_address]] fields are replaced by a type-less, unaligned block of
+ // memory which fills space up to the next field.
// See: docs/struct_layout
- //
- // TODO(b/226580208): Fields of unsupported types should also be represented as
- // an opaque blob of bytes.
- field.is_no_unique_address
+ if field.is_no_unique_address {
+ return Err("`[[no_unique_address]]` attribute was present.");
+ }
+
+ field.type_.as_ref().map(|t| &t.rs_type).map_err(String::as_str)
}
/// Generates Rust source code for a given `Record` and associated assertions as
@@ -994,48 +999,61 @@
.iter()
.enumerate()
.map(|(field_index, field)| {
- let is_opaque_blob = should_represent_field_as_opaque_blob(field);
-
let ident = make_rs_field_ident(field, field_index);
- let doc_comment = generate_doc_comment(&field.doc_comment);
- let access = if field.access == AccessSpecifier::Public && !is_opaque_blob {
+ let doc_comment = match field.type_.as_ref() {
+ Ok(_) => generate_doc_comment(&field.doc_comment),
+ Err(msg) => {
+ let supplemental_text =
+ format!("Reason for representing this field as a blob of bytes:\n{}", msg);
+ let new_text = match field.doc_comment.as_ref() {
+ None => supplemental_text,
+ Some(old_text) => format!("{}\n\n{}", old_text, supplemental_text),
+ };
+ generate_doc_comment(&Some(new_text))
+ }
+ };
+ let access = if field.access == AccessSpecifier::Public
+ && get_field_rs_type_for_layout(field).is_ok()
+ {
quote! { pub }
} else {
quote! {}
};
- let field_type = if is_opaque_blob {
- let next_offset = if let Some(next) = record.fields.get(field_index + 1) {
- next.offset
- } else {
- record.size * 8
- };
- let width = Literal::usize_unsuffixed((next_offset - field.offset) / 8);
- quote! {[crate::rust_std::mem::MaybeUninit<u8>; #width]}
- } else {
- let mut formatted =
- format_rs_type(&field.type_.rs_type, ir).with_context(|| {
+ let field_type = match get_field_rs_type_for_layout(field) {
+ Err(_) => {
+ let next_offset = if let Some(next) = record.fields.get(field_index + 1) {
+ next.offset
+ } else {
+ record.size * 8
+ };
+ let width = Literal::usize_unsuffixed((next_offset - field.offset) / 8);
+ quote! {[crate::rust_std::mem::MaybeUninit<u8>; #width]}
+ }
+ Ok(rs_type) => {
+ let mut formatted = format_rs_type(&rs_type, ir).with_context(|| {
format!(
"Failed to format type for field {:?} on record {:?}",
field, record
)
})?;
- if should_implement_drop(record) || record.is_union {
- if needs_manually_drop(&field.type_.rs_type, ir)? {
- // TODO(b/212690698): Avoid (somewhat unergonomic) ManuallyDrop
- // if we can ask Rust to preserve field destruction order if the
- // destructor is the SpecialMemberDefinition::NontrivialMembers
- // case.
- formatted = quote! { crate::rust_std::mem::ManuallyDrop<#formatted> }
- } else {
- field_copy_trait_assertions.push(quote! {
- const _: () = {
- static_assertions::assert_impl_all!(#formatted: Copy);
- };
- });
- }
- };
- formatted
+ if should_implement_drop(record) || record.is_union {
+ if needs_manually_drop(rs_type, ir)? {
+ // TODO(b/212690698): Avoid (somewhat unergonomic) ManuallyDrop
+ // if we can ask Rust to preserve field destruction order if the
+ // destructor is the SpecialMemberDefinition::NontrivialMembers
+ // case.
+ formatted = quote! { crate::rust_std::mem::ManuallyDrop<#formatted> }
+ } else {
+ field_copy_trait_assertions.push(quote! {
+ const _: () = {
+ static_assertions::assert_impl_all!(#formatted: Copy);
+ };
+ });
+ }
+ };
+ formatted
+ }
};
// `is_opaque_blob` representation is always unaligned, even though the actual
@@ -1048,11 +1066,11 @@
};
let padding_size_in_bytes = match prev_field {
None => 0,
- // No padding should be needed if the type of the current field is present
+ // No padding should be needed if the type of the current field is known
// (i.e. if the current field is correctly aligned based on its original type).
- Some(_) if !is_opaque_blob => 0,
+ Some(_) if get_field_rs_type_for_layout(field).is_ok() => 0,
Some(prev_field) => {
- let current_offset = if should_represent_field_as_opaque_blob(prev_field) {
+ let current_offset = if get_field_rs_type_for_layout(prev_field).is_err() {
field.offset
} else {
prev_field.offset + prev_field.size
@@ -2270,16 +2288,20 @@
if field.access != AccessSpecifier::Public || !field.is_no_unique_address {
continue;
}
- fields.push(make_rs_ident(
- &field
- .identifier
- .as_ref()
- .expect("Unnamed fields can't be annotated with [[no_unique_address]]")
- .identifier,
- ));
- types.push(format_rs_type(&field.type_.rs_type, ir).with_context(|| {
- format!("Failed to format type for field {:?} on record {:?}", field, record)
- })?)
+ // Can't use `get_field_rs_type_for_layout` here, because we want to dig into
+ // no_unique_address fields, despite laying them out as opaque blobs of bytes.
+ if let Ok(rs_type) = field.type_.as_ref().map(|t| &t.rs_type) {
+ fields.push(make_rs_ident(
+ &field
+ .identifier
+ .as_ref()
+ .expect("Unnamed fields can't be annotated with [[no_unique_address]]")
+ .identifier,
+ ));
+ types.push(format_rs_type(rs_type, ir).with_context(|| {
+ format!("Failed to format type for field {:?} on record {:?}", field, record)
+ })?)
+ }
}
if fields.is_empty() {
@@ -2929,6 +2951,40 @@
}
#[test]
+ fn test_record_with_unsupported_field_type() -> Result<()> {
+ // Using a nested struct because it's currently not supported.
+ // But... any other unsupported type would also work for this test.
+ let ir = ir_from_cc(
+ r#"
+ struct StructWithUnsupportedField {
+ struct NestedStruct {
+ int nested_field;
+ };
+
+ // Doc comment for `my_field`.
+ NestedStruct my_field;
+ };
+ "#,
+ )?;
+ let rs_api = generate_bindings_tokens(&ir)?.rs_api;
+ assert_rs_matches!(
+ rs_api,
+ quote! {
+ #[repr(C, align(4))]
+ pub struct StructWithUnsupportedField {
+ #[doc = " Doc comment for `my_field`.\n \n Reason for representing this field as a blob of bytes:\n Unsupported type 'struct StructWithUnsupportedField::NestedStruct': No generated bindings found for 'NestedStruct'"]
+ my_field: [crate::rust_std::mem::MaybeUninit<u8>; 4],
+ }
+ ...
+ const _: () = assert!(
+ memoffset_unstable_const::offset_of!(
+ crate::StructWithUnsupportedField, my_field) * 8 == 0);
+ }
+ );
+ Ok(())
+ }
+
+ #[test]
fn test_struct_from_other_target() -> Result<()> {
let ir = ir_from_cc_dependency("// intentionally empty", "struct SomeStruct {};")?;
let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens(&ir)?;
diff --git a/rs_bindings_from_cc/test/golden/unsupported.h b/rs_bindings_from_cc/test/golden/unsupported.h
index fefc50a..c0db936 100644
--- a/rs_bindings_from_cc/test/golden/unsupported.h
+++ b/rs_bindings_from_cc/test/golden/unsupported.h
@@ -32,6 +32,9 @@
void NonStaticMemberFunction();
void StaticMemberFunction();
};
+
+ // Doc comment for an unsupported field.
+ NestedStruct nested_struct;
};
#endif // CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_UNSUPPORTED_H_
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 23e9b5d..6f17fbc 100644
--- a/rs_bindings_from_cc/test/golden/unsupported_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/unsupported_rs_api.rs
@@ -82,7 +82,11 @@
#[derive(Clone, Copy)]
#[repr(C)]
pub struct ContainingStruct {
- __non_field_data: [crate::rust_std::mem::MaybeUninit<u8>; 1],
+ /// Doc comment for an unsupported field.
+ ///
+ /// Reason for representing this field as a blob of bytes:
+ /// Unsupported type 'struct ContainingStruct::NestedStruct': No generated bindings found for 'NestedStruct'
+ nested_struct: [crate::rust_std::mem::MaybeUninit<u8>; 1],
}
forward_declare::unsafe_define!(
forward_declare::symbol!("ContainingStruct"),
@@ -168,3 +172,5 @@
const _: () = {
static_assertions::assert_not_impl_all!(crate::ContainingStruct: Drop);
};
+const _: () =
+ assert!(memoffset_unstable_const::offset_of!(crate::ContainingStruct, nested_struct) * 8 == 0);
diff --git a/rs_bindings_from_cc/test/golden/unsupported_rs_api_impl.cc b/rs_bindings_from_cc/test/golden/unsupported_rs_api_impl.cc
index de60a7d..6e998bb 100644
--- a/rs_bindings_from_cc/test/golden/unsupported_rs_api_impl.cc
+++ b/rs_bindings_from_cc/test/golden/unsupported_rs_api_impl.cc
@@ -48,5 +48,6 @@
static_assert(sizeof(class ContainingStruct) == 1);
static_assert(alignof(class ContainingStruct) == 1);
+static_assert(CRUBIT_OFFSET_OF(nested_struct, class ContainingStruct) * 8 == 0);
#pragma clang diagnostic pop