Support unnamed fields.
This is not a very important case, but it does occur (e.g., in `<bits/timex.h>`).
The reason to support this is to get rid of one of the remaining cases where we fail to import fields. If we could eliminate that fully, the overall importer setup could be simplified.
PiperOrigin-RevId: 448246613
diff --git a/rs_bindings_from_cc/importers/cxx_record.cc b/rs_bindings_from_cc/importers/cxx_record.cc
index bebcdac..5347ac1 100644
--- a/rs_bindings_from_cc/importers/cxx_record.cc
+++ b/rs_bindings_from_cc/importers/cxx_record.cc
@@ -132,13 +132,13 @@
std::optional<Identifier> field_name =
ictx_.GetTranslatedIdentifier(field_decl);
- if (!field_name.has_value()) {
- return absl::UnimplementedError(
- absl::Substitute("Cannot translate name for field '$0'",
- field_decl->getNameAsString()));
- }
+ CRUBIT_CHECK(
+ field_name ||
+ !field_decl->hasAttr<clang::NoUniqueAddressAttr>() &&
+ "Unnamed fields can't be annotated with [[no_unique_address]]");
fields.push_back(
- {.identifier = *std::move(field_name),
+ {.identifier = field_name ? *std::move(field_name)
+ : llvm::Optional<Identifier>(llvm::None),
.doc_comment = ictx_.GetComment(field_decl),
.type = *type,
.access = TranslateAccessSpecifier(access),
diff --git a/rs_bindings_from_cc/ir.h b/rs_bindings_from_cc/ir.h
index 00d7e92..b0c35e3 100644
--- a/rs_bindings_from_cc/ir.h
+++ b/rs_bindings_from_cc/ir.h
@@ -476,7 +476,7 @@
struct Field {
llvm::json::Value ToJson() const;
- Identifier identifier;
+ llvm::Optional<Identifier> identifier;
llvm::Optional<std::string> doc_comment;
MappedType type;
AccessSpecifier access;
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index 217f939..e91e099 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -305,7 +305,7 @@
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Field {
- pub identifier: Identifier,
+ pub identifier: Option<Identifier>,
pub doc_comment: Option<String>,
#[serde(rename(deserialize = "type"))]
pub type_: MappedType,
diff --git a/rs_bindings_from_cc/ir_from_cc_test.rs b/rs_bindings_from_cc/ir_from_cc_test.rs
index d1cf765..842e1ed 100644
--- a/rs_bindings_from_cc/ir_from_cc_test.rs
+++ b/rs_bindings_from_cc/ir_from_cc_test.rs
@@ -187,19 +187,19 @@
rs_name: "SomeStruct", ...
fields: [
Field {
- identifier: "default_access_int" ...
+ identifier: Some("default_access_int") ...
access: Public ...
},
Field {
- identifier: "public_int" ...
+ identifier: Some("public_int") ...
access: Public ...
},
Field {
- identifier: "protected_int" ...
+ identifier: Some("protected_int") ...
access: Protected ...
},
Field {
- identifier: "private_int" ...
+ identifier: Some("private_int") ...
access: Private ...
},
] ...
@@ -213,7 +213,7 @@
rs_name: "SomeClass", ...
fields: [
Field {
- identifier: "default_access_int" ...
+ identifier: Some("default_access_int") ...
access: Private ...
}
] ...
@@ -223,6 +223,31 @@
}
#[test]
+fn test_unnamed_fields() {
+ let ir = ir_from_cc(
+ r#"
+ struct WithUnnamedFields {
+ int foo;
+ int :32;
+ };"#,
+ )
+ .unwrap();
+
+ assert_ir_matches!(
+ ir,
+ quote! {
+ Record {
+ rs_name: "WithUnnamedFields", ...
+ fields: [
+ Field { identifier: Some("foo") ... },
+ Field { identifier: None ... },
+ ] ...
+ }
+ }
+ );
+}
+
+#[test]
fn test_record_private_member_functions_not_present() {
let ir = ir_from_cc(
"
@@ -332,7 +357,7 @@
ir,
quote! {
Field {
- identifier: "ptr" ...
+ identifier: Some("ptr") ...
type_: MappedType {
rs_type: RsType {
name: Some("*mut") ...
@@ -826,7 +851,7 @@
doc_comment: None,
unambiguous_public_bases: [],
fields: [Field {
- identifier: "derived_field", ...
+ identifier: Some("derived_field"), ...
offset: 32, ...
}], ...
size: 8,
@@ -960,7 +985,7 @@
cc_name: "SomeStruct" ...
fields: [
Field {
- identifier: "first_field", ...
+ identifier: Some("first_field"), ...
type_ : MappedType {
rs_type : RsType { name : Some ("i32"), ...},
cc_type : CcType { name : Some ("int"), ...},
@@ -968,7 +993,7 @@
offset: 0, ...
},
Field {
- identifier: "second_field", ...
+ identifier: Some("second_field"), ...
type_ : MappedType {
rs_type : RsType { name : Some ("i32"), ...},
cc_type : CcType { name : Some ("int"), ...},
@@ -1001,7 +1026,7 @@
cc_name: "SomeUnion" ...
fields: [
Field {
- identifier: "first_field", ...
+ identifier: Some("first_field"), ...
type_ : MappedType {
rs_type : RsType { name : Some ("i32"), ...},
cc_type : CcType { name : Some ("int"), ...},
@@ -1009,7 +1034,7 @@
offset: 0, ...
},
Field {
- identifier: "second_field", ...
+ identifier: Some("second_field"), ...
type_ : MappedType {
rs_type : RsType { name : Some ("i32"), ...},
cc_type : CcType { name : Some ("int"), ...},
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index a982c39..ee99178 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -958,8 +958,16 @@
};
let doc_comment = generate_doc_comment(&record.doc_comment);
- let field_idents =
- record.fields.iter().map(|f| make_rs_ident(&f.identifier.identifier)).collect_vec();
+ let field_idents = record
+ .fields
+ .iter()
+ .enumerate()
+ .map(|(i, f)| {
+ make_rs_ident(
+ f.identifier.as_ref().map_or(&format!("__unnamed_field{}", i), |id| &id.identifier),
+ )
+ })
+ .collect_vec();
let field_doc_coments =
record.fields.iter().map(|f| generate_doc_comment(&f.doc_comment)).collect_vec();
@@ -2172,15 +2180,18 @@
let alignment = Literal::usize_unsuffixed(record.alignment);
let tag_kind = tag_kind(record);
let field_assertions =
- 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.
- quote! {
- static_assert(CRUBIT_OFFSET_OF(#field_ident, #tag_kind #namespace_qualifier #record_ident) * 8 == #offset);
+ record.fields.iter()
+ .filter(|f| f.access == AccessSpecifier::Public && f.identifier.is_some())
+ .map(|field| {
+ let field_ident = format_cc_ident(&field.identifier.as_ref().unwrap().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.
+ quote! {
+ static_assert(CRUBIT_OFFSET_OF(#field_ident, #tag_kind #namespace_qualifier #record_ident) * 8 == #offset);
+ }
}
- });
+ );
Ok(quote! {
static_assert(sizeof(#tag_kind #namespace_qualifier #record_ident) == #size);
static_assert(alignof(#tag_kind #namespace_qualifier #record_ident) == #alignment);
@@ -2196,7 +2207,8 @@
if field.access != AccessSpecifier::Public || !field.is_no_unique_address {
continue;
}
- fields.push(make_rs_ident(&field.identifier.identifier));
+ 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)
})?)
diff --git a/rs_bindings_from_cc/test/golden/unnamed_fields.h b/rs_bindings_from_cc/test/golden/unnamed_fields.h
new file mode 100644
index 0000000..f94b713
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/unnamed_fields.h
@@ -0,0 +1,16 @@
+// 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 CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_UNNAMED_FIELDS_H_
+#define CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_UNNAMED_FIELDS_H_
+
+struct WithUnnamedFields {
+ int foo;
+ int : 32;
+ int bar;
+ int : 3;
+ int baz;
+};
+
+#endif // CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_UNNAMED_FIELDS_H_
diff --git a/rs_bindings_from_cc/test/golden/unnamed_fields_rs_api.rs b/rs_bindings_from_cc/test/golden/unnamed_fields_rs_api.rs
new file mode 100644
index 0000000..2f48f5c
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/unnamed_fields_rs_api.rs
@@ -0,0 +1,70 @@
+// 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
+
+// Automatically @generated Rust bindings for C++ target
+// //rs_bindings_from_cc/test/golden:unnamed_fields_cc
+#![rustfmt::skip]
+#![feature(const_ptr_offset_from, custom_inner_attributes, negative_impls)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+use ::std as rust_std;
+use memoffset_unstable_const::offset_of;
+
+// 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
+
+#[repr(C)]
+pub struct WithUnnamedFields {
+ pub foo: i32,
+ pub __unnamed_field1: i32,
+ pub bar: i32,
+ pub __unnamed_field3: i32,
+ pub baz: i32,
+}
+forward_declare::unsafe_define!(
+ forward_declare::symbol!("WithUnnamedFields"),
+ crate::WithUnnamedFields
+);
+
+impl !Unpin for WithUnnamedFields {}
+
+// rs_bindings_from_cc/test/golden/unnamed_fields.h;l=8
+// Error while generating bindings for item 'WithUnnamedFields::WithUnnamedFields':
+// Unsafe constructors (e.g. with no elided or explicit lifetimes) are intentionally not supported
+
+// rs_bindings_from_cc/test/golden/unnamed_fields.h;l=8
+// Error while generating bindings for item 'WithUnnamedFields::WithUnnamedFields':
+// Unsafe constructors (e.g. with no elided or explicit lifetimes) are intentionally not supported
+
+// rs_bindings_from_cc/test/golden/unnamed_fields.h;l=8
+// Error while generating bindings for item 'WithUnnamedFields::WithUnnamedFields':
+// Parameter #0 is not supported: Unsupported type 'struct WithUnnamedFields &&': Unsupported type: && without lifetime
+
+// rs_bindings_from_cc/test/golden/unnamed_fields.h;l=8
+// Error while generating bindings for item 'WithUnnamedFields::operator=':
+// Bindings for this kind of operator are not supported
+
+// rs_bindings_from_cc/test/golden/unnamed_fields.h;l=8
+// Error while generating bindings for item 'WithUnnamedFields::operator=':
+// Parameter #0 is not supported: Unsupported type 'struct WithUnnamedFields &&': Unsupported type: && without lifetime
+
+// CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_UNNAMED_FIELDS_H_
+
+const _: () = assert!(rust_std::mem::size_of::<Option<&i32>>() == rust_std::mem::size_of::<&i32>());
+
+const _: () = assert!(rust_std::mem::size_of::<crate::WithUnnamedFields>() == 20usize);
+const _: () = assert!(rust_std::mem::align_of::<crate::WithUnnamedFields>() == 4usize);
+const _: () = {
+ static_assertions::assert_not_impl_all!(crate::WithUnnamedFields: Copy);
+};
+const _: () = {
+ static_assertions::assert_not_impl_all!(crate::WithUnnamedFields: Drop);
+};
+const _: () = assert!(offset_of!(crate::WithUnnamedFields, foo) * 8 == 0usize);
+const _: () = assert!(offset_of!(crate::WithUnnamedFields, __unnamed_field1) * 8 == 32usize);
+const _: () = assert!(offset_of!(crate::WithUnnamedFields, bar) * 8 == 64usize);
+const _: () = assert!(offset_of!(crate::WithUnnamedFields, __unnamed_field3) * 8 == 96usize);
+const _: () = assert!(offset_of!(crate::WithUnnamedFields, baz) * 8 == 128usize);
diff --git a/rs_bindings_from_cc/test/golden/unnamed_fields_rs_api_impl.cc b/rs_bindings_from_cc/test/golden/unnamed_fields_rs_api_impl.cc
new file mode 100644
index 0000000..7009dbf
--- /dev/null
+++ b/rs_bindings_from_cc/test/golden/unnamed_fields_rs_api_impl.cc
@@ -0,0 +1,38 @@
+// 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 <cstddef>
+#include <memory>
+
+#include "rs_bindings_from_cc/support/cxx20_backports.h"
+#include "rs_bindings_from_cc/support/offsetof.h"
+#include "rs_bindings_from_cc/test/golden/unnamed_fields.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wthread-safety-analysis"
+extern "C" void __rust_thunk___ZN17WithUnnamedFieldsC1Ev(
+ class WithUnnamedFields* __this) {
+ crubit::construct_at(std::forward<decltype(__this)>(__this));
+}
+extern "C" void __rust_thunk___ZN17WithUnnamedFieldsC1ERKS_(
+ class WithUnnamedFields* __this, const class WithUnnamedFields& __param_0) {
+ crubit::construct_at(std::forward<decltype(__this)>(__this),
+ std::forward<decltype(__param_0)>(__param_0));
+}
+extern "C" void __rust_thunk___ZN17WithUnnamedFieldsD1Ev(
+ class WithUnnamedFields* __this) {
+ std::destroy_at(std::forward<decltype(__this)>(__this));
+}
+extern "C" class WithUnnamedFields& __rust_thunk___ZN17WithUnnamedFieldsaSERKS_(
+ class WithUnnamedFields* __this, const class WithUnnamedFields& __param_0) {
+ return __this->operator=(std::forward<decltype(__param_0)>(__param_0));
+}
+
+static_assert(sizeof(class WithUnnamedFields) == 20);
+static_assert(alignof(class WithUnnamedFields) == 4);
+static_assert(CRUBIT_OFFSET_OF(foo, class WithUnnamedFields) * 8 == 0);
+static_assert(CRUBIT_OFFSET_OF(bar, class WithUnnamedFields) * 8 == 64);
+static_assert(CRUBIT_OFFSET_OF(baz, class WithUnnamedFields) * 8 == 128);
+
+#pragma clang diagnostic pop