Fully implement "soft" bindings generation failures.
After this CL, you can disable bindings generation for a target (or even, in theory, an individual item), and it will not break the build if non-disabled items/targets use the disabled item in their public API. Instead:
1. Aliases which alias a disabled type will themselves be disabled, and not receive bindings.
2. Structs which reference a disabled type will replace it with an opaque blob of bytes in their layout.
3. Functions which reference a disabled type will not receive bindings.
The implementation shows, a little bit, why it's annoying to do this as a separate function to the side: we need to replicate the logic for generating _bindings_ for aliases inside the logic for testing if aliases are disabled, so as to avoid the cyclic dependency, per previous CL. Ah, well.
---
Quick note: one alternative design is to instead do feature checks in C++, and not emit them into the IR at all if they don't pass a feature check. That is simpler, but means writing more C++ code, and means we can't call Rust query functions. For example, if `rs_type_kind` fails for reasons not accounted for in C++, this allows aliases to fail gracefully. If the check were in C++, we'd continue to have a point of no return that leads to crashes.
However, more importantly, by doing this in Rust we are deliberately introducing more resiliency into `src_code_gen.rs`, and allowing it to succeed on more inputs without crashing entirely. In particular, doing it this way forced us to handle recoverable failures in TypeAlias. And doing it this way gave us the general mechanism with which to do it.
So overall I'm strongly in favor of this approach. We have had a bit of a mixed approach wrt error handling and when toc rash, and I think this CL starts painting a clearer picture by clearly delineating recoverable and non-recoverable errors.
### TODO:
Now that this is done, I'm finally unblocked on implementing and testing that template instantiation should be suppressed when the template definition is suppressed.
### Other changes
In order to make this work nicely, I ended up fixing a longstanding want-to-fix of showing nested causes in bindings error messages. That's just a matter of using `{:#}` instead of `{}` when formatting the error. So now, recoverable failures show the full cause chain, and in particular, we get nice cause chains like:
```
Failed to format type of parameter 0: Missing required features on //test:dependency: [//third_party/crubit:experimental]
```
Also, this now formats the error reason for `[[no_unique_address]]` fields, which previously were omitted due to slightly different branching code.
PiperOrigin-RevId: 524106254
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index 245d576..bbea229 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -714,7 +714,7 @@
Self::new(ir, item, message.into(), None)
}
pub fn new_with_cause(ir: &IR, item: &impl GenericItem, cause: Error) -> Self {
- Self::new(ir, item, cause.to_string().into(), Some(cause))
+ Self::new(ir, item, format!("{cause:#}").into(), Some(cause))
}
pub fn message(&self) -> &str {
diff --git a/rs_bindings_from_cc/ir_testing.rs b/rs_bindings_from_cc/ir_testing.rs
index 087e24b..4816b3a 100644
--- a/rs_bindings_from_cc/ir_testing.rs
+++ b/rs_bindings_from_cc/ir_testing.rs
@@ -42,6 +42,7 @@
/// `make_ir_from_items` and `ir_from_cc_dependency`.
fn update_test_ir(ir: &mut IR) {
*ir.target_crubit_features_mut(&ir.current_target().clone()) = *TESTING_FEATURES;
+ *ir.target_crubit_features_mut(&ir::BazelLabel(DEPENDENCY_TARGET.into())) = *TESTING_FEATURES;
}
/// Create a testing `IR` instance from given items, using mock values for other
@@ -148,7 +149,9 @@
ir_from_cc("")?,
quote! {
crubit_features: hash_map!{
+ ...
BazelLabel("//test:testing_target"): CrubitFeaturesIR(FlagSet(Supported|Experimental))
+ ...
}
}
);
@@ -160,7 +163,9 @@
make_ir_from_items([])?,
quote! {
crubit_features: hash_map!{
+ ...
BazelLabel("//test:testing_target"): CrubitFeaturesIR(FlagSet(Supported|Experimental))
+ ...
}
}
);
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index a2199ae..40abda4 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -1221,10 +1221,10 @@
let mut param_types = func
.params
.iter()
- .map(|p| {
- db.rs_type_kind(p.type_.rs_type.clone()).with_context(|| {
- format!("Failed to process type of parameter {:?} on {:?}", p, func)
- })
+ .enumerate()
+ .map(|(i, p)| {
+ db.rs_type_kind(p.type_.rs_type.clone())
+ .with_context(|| format!("Failed to format type of parameter {i}"))
})
.collect::<Result<Vec<_>>>()?;
@@ -1238,7 +1238,7 @@
let mut return_type = db
.rs_type_kind(func.return_type.rs_type.clone())
- .with_context(|| format!("Failed to format return type for {:?}", &func))?;
+ .with_context(|| "Failed to format return type")?;
return_type.check_by_value()?;
let param_idents =
func.params.iter().map(|p| make_rs_ident(&p.identifier.identifier)).collect_vec();
@@ -1988,9 +1988,8 @@
///
/// For non-Copy union fields, failing to use `ManuallyDrop<T>` would
/// additionally cause a compile-time error until https://github.com/rust-lang/rust/issues/55149 is stabilized.
-fn needs_manually_drop(db: &Database, ty: ir::RsType) -> Result<bool> {
- let ty_implements_copy = db.rs_type_kind(ty)?.implements_copy();
- Ok(!ty_implements_copy)
+fn needs_manually_drop(ty: &RsTypeKind) -> bool {
+ !ty.implements_copy()
}
fn namespace_qualifier_of_item(item_id: ItemId, ir: &IR) -> Result<NamespaceQualifier> {
@@ -2033,17 +2032,19 @@
/// 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> {
+/// Note that `get_field_rs_type_kind_for_layout` may return Err (for
+/// `is_no_unique_address` fields) even if `rs_type_kind` returns Ok.
+fn get_field_rs_type_kind_for_layout(db: &Database, field: &Field) -> Result<RsTypeKind> {
// [[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
if field.is_no_unique_address {
- return Err("`[[no_unique_address]]` attribute was present.");
+ bail!("`[[no_unique_address]]` attribute was present.");
}
-
- field.type_.as_ref().map(|t| &t.rs_type).map_err(String::as_str)
+ match &field.type_ {
+ Ok(t) => db.rs_type_kind(t.rs_type.clone()),
+ Err(e) => Err(anyhow!("{e}")),
+ }
}
/// Returns the type of a type-less, unaligned block of memory that can hold a
@@ -2081,7 +2082,7 @@
// We retain the end offset of fields only if we have a matching Rust type
// to represent them. Otherwise we'll fill up all the space to the next field.
// See: docs/struct_layout
- match get_field_rs_type_for_layout(field) {
+ match get_field_rs_type_kind_for_layout(db, field) {
// Regular field
Ok(_rs_type) => Some(field.offset + field.size),
// Opaque field
@@ -2149,7 +2150,8 @@
//
// We also don't need padding if we're in a union.
let padding_size_in_bits = if record.is_union()
- || (field.is_some() && get_field_rs_type_for_layout(field.unwrap()).is_ok())
+ || (field.is_some()
+ && get_field_rs_type_kind_for_layout(db, field.unwrap()).is_ok())
{
0
} else {
@@ -2178,15 +2180,18 @@
let field = field.unwrap();
let ident = make_rs_field_ident(field, field_index);
- let doc_comment = match field.type_.as_ref() {
+ let field_rs_type_kind = get_field_rs_type_kind_for_layout(db, field);
+ let doc_comment = match &field_rs_type_kind {
Ok(_) => generate_doc_comment(
field.doc_comment.as_deref(),
None,
db.generate_source_loc_doc_comment(),
),
Err(msg) => {
- let supplemental_text =
- format!("Reason for representing this field as a blob of bytes:\n{}", msg);
+ let supplemental_text = format!(
+ "Reason for representing this field as a blob of bytes:\n{:#}",
+ msg
+ );
let new_text = match &field.doc_comment {
None => supplemental_text,
Some(old_text) => format!("{}\n\n{}", old_text.as_ref(), supplemental_text),
@@ -2198,26 +2203,18 @@
)
}
};
- let access = if field.access == AccessSpecifier::Public
- && get_field_rs_type_for_layout(field).is_ok()
- {
+ let access = if field.access == AccessSpecifier::Public && field_rs_type_kind.is_ok() {
quote! { pub }
} else {
quote! { pub(crate) }
};
- let field_type = match get_field_rs_type_for_layout(field) {
+ let field_type = match field_rs_type_kind {
Err(_) => bit_padding(end - field.offset),
- Ok(rs_type) => {
- let type_kind = db.rs_type_kind(rs_type.clone()).with_context(|| {
- format!(
- "Failed to format type for field {:?} on record {:?}",
- field, record
- )
- })?;
+ Ok(type_kind) => {
let mut formatted = quote! {#type_kind};
if should_implement_drop(record) || record.is_union() {
- if needs_manually_drop(db, rs_type.clone())? {
+ if needs_manually_drop(&type_kind) {
// TODO(b/212690698): Avoid (somewhat unergonomic) ManuallyDrop
// if we can ask Rust to preserve field destruction order if the
// destructor is the SpecialMemberFunc::NontrivialMembers
@@ -2804,6 +2801,10 @@
missing_features: flagset::FlagSet<ir::CrubitFeature>,
target: BazelLabel,
},
+ DependencyFailed {
+ context: Rc<str>,
+ error: Error,
+ },
}
#[must_use]
@@ -2824,6 +2825,13 @@
// Function bindings aren't guaranteed, because they don't _need_ to be guaranteed. We
// choose not to generate code which relies on functions existing in other TUs.
Item::Func(..) => HasBindings::Maybe,
+ Item::TypeAlias(alias) => match db.rs_type_kind(alias.underlying_type.rs_type.clone()) {
+ Ok(_) => HasBindings::Yes,
+ Err(error) => HasBindings::No(NoBindingsReason::DependencyFailed {
+ context: alias.debug_name(&ir),
+ error,
+ }),
+ },
_ => HasBindings::Yes,
}
}
@@ -2836,6 +2844,9 @@
missing_features.into_iter().map(|feature| feature.aspect_hint()).collect();
anyhow!("Missing required features on {target}: [{}]", feature_strings.join(", "))
}
+ NoBindingsReason::DependencyFailed { context, error } => error.context(format!(
+ "Can't generate bindings for {context} due to missing bindings for its dependency"
+ )),
}
}
}
@@ -3593,7 +3604,20 @@
"Type arguments on records nor type aliases are not yet supported: {:?}",
ty
);
- match ir.item_for_type(&ty)? {
+ let item = ir.item_for_type(&ty)?;
+ match has_bindings(db, item) {
+ HasBindings::Yes => {}
+ HasBindings::Maybe => {
+ bail!(
+ "Type {} may or may not exist, and cannot be used.",
+ item.debug_name(&ir)
+ );
+ }
+ HasBindings::No(reason) => {
+ return Err(reason.into());
+ }
+ }
+ match item {
Item::IncompleteRecord(incomplete_record) => RsTypeKind::IncompleteRecord {
incomplete_record: incomplete_record.clone(),
crate_path: Rc::new(CratePath::new(
@@ -3865,8 +3889,9 @@
if field.access != AccessSpecifier::Public || !field.is_no_unique_address {
continue;
}
- // 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.
+ // Can't use `get_field_rs_type_kind_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.clone()) {
fields.push(make_rs_ident(
&field
@@ -5545,6 +5570,7 @@
quote! {
#[repr(C)]
pub struct Derived {
+ ...
__non_field_data: [::core::mem::MaybeUninit<u8>; 1],
}
}
@@ -5621,7 +5647,9 @@
quote! {
#[repr(C, align(8))]
pub struct Struct {
+ ...
pub(crate) field1: [::core::mem::MaybeUninit<u8>; 8],
+ ...
pub(crate) field2: [::core::mem::MaybeUninit<u8>; 2],
pub z: i16,
}
@@ -5663,7 +5691,9 @@
quote! {
#[repr(C, align(8))]
pub struct Struct {
+ ...
pub(crate) field1: [::core::mem::MaybeUninit<u8>; 8],
+ ...
pub(crate) field2: [::core::mem::MaybeUninit<u8>; 8],
}
}
@@ -5688,6 +5718,7 @@
quote! {
#[repr(C, align(4))]
pub struct Struct {
+ ...
pub(crate) field: [::core::mem::MaybeUninit<u8>; 0],
pub x: i32,
}
@@ -5712,6 +5743,7 @@
quote! {
#[repr(C)]
pub struct Struct {
+ ...
pub(crate) field: [::core::mem::MaybeUninit<u8>; 1],
}
}
@@ -6200,6 +6232,7 @@
#[derive(Clone, Copy)]
#[repr(C)]
pub struct EmptyStruct {
+ ...
__non_field_data: [::core::mem::MaybeUninit<u8>; 1],
}
}
@@ -6231,6 +6264,7 @@
#[derive(Clone, Copy)]
#[repr(C)]
pub union EmptyUnion {
+ ...
__non_field_data: [::core::mem::MaybeUninit<u8>; 1],
}
}
@@ -8972,6 +9006,62 @@
}
#[test]
+ fn test_default_crubit_features_disabled_dependency_function_parameter() -> Result<()> {
+ for dependency in ["struct NotPresent {};", "using NotPresent = int;"] {
+ let mut ir = ir_from_cc_dependency("void Func(NotPresent);", dependency)?;
+ ir.target_crubit_features_mut(&ir::BazelLabel("//test:dependency".into())).clear();
+ let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens(ir)?;
+ assert_rs_not_matches!(rs_api, quote! {Func});
+ assert_cc_not_matches!(rs_api_impl, quote! {Func});
+ let expected = "\
+ Generated from: google3/ir_from_cc_virtual_header.h;l=3\n\
+ Error while generating bindings for item 'Func':\n\
+ Failed to format type of parameter 0: Missing required features on //test:dependency: [//:experimental]\
+ ";
+ assert_rs_matches!(rs_api, quote! { __COMMENT__ #expected});
+ }
+ Ok(())
+ }
+
+ #[test]
+ fn test_default_crubit_features_disabled_dependency_function_return_type() -> Result<()> {
+ for dependency in ["struct NotPresent {};", "using NotPresent = int;"] {
+ let mut ir = ir_from_cc_dependency("NotPresent Func();", dependency)?;
+ ir.target_crubit_features_mut(&ir::BazelLabel("//test:dependency".into())).clear();
+ let BindingsTokens { rs_api, rs_api_impl } = generate_bindings_tokens(ir)?;
+ assert_rs_not_matches!(rs_api, quote! {Func});
+ assert_cc_not_matches!(rs_api_impl, quote! {Func});
+ let expected = "\
+ Generated from: google3/ir_from_cc_virtual_header.h;l=3\n\
+ Error while generating bindings for item 'Func':\n\
+ Failed to format return type: Missing required features on //test:dependency: [//:experimental]\
+ ";
+ assert_rs_matches!(rs_api, quote! { __COMMENT__ #expected});
+ }
+ Ok(())
+ }
+
+ #[test]
+ fn test_default_crubit_features_disabled_dependency_struct() -> Result<()> {
+ for dependency in ["struct NotPresent {signed char x;};", "using NotPresent = signed char;"]
+ {
+ let mut ir = ir_from_cc_dependency("struct Present {NotPresent field;};", dependency)?;
+ ir.target_crubit_features_mut(&ir::BazelLabel("//test:dependency".into())).clear();
+ let BindingsTokens { rs_api, rs_api_impl: _ } = generate_bindings_tokens(ir)?;
+ assert_rs_matches!(
+ rs_api,
+ quote! {
+ pub struct Present {
+ ...
+ pub(crate) field: [::core::mem::MaybeUninit<u8>; 1],
+ }
+ }
+ );
+ }
+ Ok(())
+ }
+
+ #[test]
fn test_rstypekind_format_as_self_param_rvalue_reference() -> Result<()> {
let type_args: &[RsTypeKind] = &[];
let referent = Rc::new(RsTypeKind::Other { name: "T".into(), type_args: type_args.into() });
diff --git a/rs_bindings_from_cc/test/crubit_features/BUILD b/rs_bindings_from_cc/test/crubit_features/BUILD
index ce06e66..5be6c1f 100644
--- a/rs_bindings_from_cc/test/crubit_features/BUILD
+++ b/rs_bindings_from_cc/test/crubit_features/BUILD
@@ -12,14 +12,29 @@
)
crubit_test_cc_library(
+ name = "alias_enabled",
+ hdrs = ["alias_enabled.h"],
+ deps = [":definition_disabled"],
+)
+
+crubit_test_cc_library(
name = "definition_enabled",
hdrs = ["definition_enabled.h"],
)
+crubit_test_cc_library(
+ name = "alias_disabled",
+ hdrs = ["alias_disabled.h"],
+ aspect_hints = [], # deliberately disable Crubit.
+ deps = [":definition_enabled"],
+)
+
rust_test(
name = "test",
srcs = ["test.rs"],
cc_deps = [
+ ":alias_disabled",
+ ":alias_enabled",
":definition_disabled",
":definition_enabled",
],
diff --git a/rs_bindings_from_cc/test/crubit_features/alias_disabled.h b/rs_bindings_from_cc/test/crubit_features/alias_disabled.h
new file mode 100644
index 0000000..4c9091e
--- /dev/null
+++ b/rs_bindings_from_cc/test/crubit_features/alias_disabled.h
@@ -0,0 +1,13 @@
+// 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_CRUBIT_FEATURES_ALIAS_DISABLED_H_
+#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_CRUBIT_FEATURES_ALIAS_DISABLED_H_
+
+#include "rs_bindings_from_cc/test/crubit_features/definition_enabled.h"
+
+using AliasedEnabledStruct = EnabledStruct;
+using AliasedEnabledTemplate = EnabledTemplate<int>;
+
+#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_CRUBIT_FEATURES_ALIAS_DISABLED_H_
diff --git a/rs_bindings_from_cc/test/crubit_features/alias_enabled.h b/rs_bindings_from_cc/test/crubit_features/alias_enabled.h
new file mode 100644
index 0000000..ae79bcb
--- /dev/null
+++ b/rs_bindings_from_cc/test/crubit_features/alias_enabled.h
@@ -0,0 +1,13 @@
+// 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_CRUBIT_FEATURES_ALIAS_ENABLED_H_
+#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_CRUBIT_FEATURES_ALIAS_ENABLED_H_
+
+#include "rs_bindings_from_cc/test/crubit_features/definition_disabled.h"
+
+using AliasedDisabledStruct = DisabledStruct;
+using AliasedDisabledTemplate = DisabledTemplate<int>;
+
+#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_CRUBIT_FEATURES_ALIAS_ENABLED_H_
diff --git a/rs_bindings_from_cc/test/crubit_features/definition_disabled.h b/rs_bindings_from_cc/test/crubit_features/definition_disabled.h
index a673cbf..d25709f 100644
--- a/rs_bindings_from_cc/test/crubit_features/definition_disabled.h
+++ b/rs_bindings_from_cc/test/crubit_features/definition_disabled.h
@@ -9,4 +9,9 @@
unsigned char x;
};
+template <typename T>
+struct DisabledTemplate {
+ T x;
+};
+
#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_CRUBIT_FEATURES_DEFINITION_H_
diff --git a/rs_bindings_from_cc/test/crubit_features/definition_enabled.h b/rs_bindings_from_cc/test/crubit_features/definition_enabled.h
index bb3b976..4e1e957 100644
--- a/rs_bindings_from_cc/test/crubit_features/definition_enabled.h
+++ b/rs_bindings_from_cc/test/crubit_features/definition_enabled.h
@@ -9,4 +9,9 @@
unsigned char x;
};
+template <typename T>
+struct EnabledTemplate {
+ T x;
+};
+
#endif // THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_CRUBIT_FEATURES_DEFINITION_ENABLED_H_
diff --git a/rs_bindings_from_cc/test/crubit_features/test.rs b/rs_bindings_from_cc/test/crubit_features/test.rs
index 1078cf3..ce690f1 100644
--- a/rs_bindings_from_cc/test/crubit_features/test.rs
+++ b/rs_bindings_from_cc/test/crubit_features/test.rs
@@ -20,3 +20,54 @@
);
}
}
+
+mod aliases {
+ use super::*;
+ /// This test will fail if aliases expose a struct whose bindings were
+ /// disabled.
+ #[test]
+ fn aliases_dont_expose_disabled_structs() {
+ assert!(
+ !type_exists!(alias_enabled::AliasedDisabledStruct),
+ "AliasedDisabledStruct was exported by `alias_enabled`, even though `DisabledStruct` disabled bindings."
+ );
+ }
+
+ /// This test will fail if aliases expose a template whose bindings were
+ /// disabled.
+ ///
+ /// This is subtly different from the non-template case, because template
+ /// _instantiation_ actually occurs in this crate. Template _instantiation_
+ /// in other headers should respect the template _definition_ and its
+ /// API promises.
+ #[test]
+ #[ignore] // TODO(b/266727458): implement this
+ fn aliases_dont_expose_disabled_templates() {
+ assert!(
+ !type_exists!(alias_enabled::AliasedDisabledTemplate),
+ "AliasedDisabledTemplate was exported by `alias_enabled`, even though `DisabledTemplate` disabled bindings."
+ );
+ }
+
+ /// This test will fail if aliases produce bindings on targets whose
+ /// bindings were disabled, where the alias was to an enabled target.
+ ///
+ /// While Crubit _was_ enabled for the definition, the usage site also needs
+ /// to consent to people depending on the type _via_ the using library,
+ /// since that implies a maintenance burden.
+ #[test]
+ fn disabled_struct_aliases_arent_exposed() {
+ assert!(
+ !type_exists!(alias_disabled::AliasedEnabledStruct),
+ "AliasedEnabledStruct was exported by `alias_disabled`, even though that build target disabled bindings."
+ );
+ }
+
+ #[test]
+ fn disabled_template_aliases_arent_exposed() {
+ assert!(
+ !type_exists!(alias_disabled::AliasedEnabledTemplate),
+ "AliasedEnabledTemplate was exported by `alias_disabled`, even though that build target disabled bindings."
+ );
+ }
+}
diff --git a/rs_bindings_from_cc/test/golden/bitfields_rs_api.rs b/rs_bindings_from_cc/test/golden/bitfields_rs_api.rs
index 3ca6560..3d9c73d 100644
--- a/rs_bindings_from_cc/test/golden/bitfields_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/bitfields_rs_api.rs
@@ -33,6 +33,8 @@
pub f5: i32,
// f6 : 23 bits
__bitfields4: [::core::mem::MaybeUninit<u8>; 3],
+ /// Reason for representing this field as a blob of bytes:
+ /// `[[no_unique_address]]` attribute was present.
pub(crate) f7: [::core::mem::MaybeUninit<u8>; 1],
// f8 : 2 bits
__bitfields6: [::core::mem::MaybeUninit<u8>; 1],
diff --git a/rs_bindings_from_cc/test/golden/no_unique_address_rs_api.rs b/rs_bindings_from_cc/test/golden/no_unique_address_rs_api.rs
index 6fc92fa..3cd33ce 100644
--- a/rs_bindings_from_cc/test/golden/no_unique_address_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/no_unique_address_rs_api.rs
@@ -30,7 +30,12 @@
pub struct Struct {
/// Nobody would ever use a no_unique_address int/char field, this is just
/// enough to test that the transmute is correct.
+ ///
+ /// Reason for representing this field as a blob of bytes:
+ /// `[[no_unique_address]]` attribute was present.
pub(crate) field1: [::core::mem::MaybeUninit<u8>; 4],
+ /// Reason for representing this field as a blob of bytes:
+ /// `[[no_unique_address]]` attribute was present.
pub(crate) field2: [::core::mem::MaybeUninit<u8>; 4],
}
forward_declare::unsafe_define!(forward_declare::symbol!("Struct"), crate::Struct);
@@ -106,6 +111,9 @@
pub field1: u8,
__padding1: [::core::mem::MaybeUninit<u8>; 3],
/// size: 4, alignment: 4 => offset: 4
+ ///
+ /// Reason for representing this field as a blob of bytes:
+ /// `[[no_unique_address]]` attribute was present.
pub(crate) field2: [::core::mem::MaybeUninit<u8>; 4],
}
forward_declare::unsafe_define!(
@@ -262,6 +270,8 @@
#[repr(C, align(4))]
pub struct FieldInTailPadding {
__non_field_data: [::core::mem::MaybeUninit<u8>; 0],
+ /// Reason for representing this field as a blob of bytes:
+ /// `[[no_unique_address]]` attribute was present.
pub(crate) inner_struct: [::core::mem::MaybeUninit<u8>; 5],
/// offset: 5 (dsize of `s`).
pub char_in_tail_padding_of_prev_field: u8,