Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion rs_bindings_from_cc/generate_bindings/database/code_snippet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ pub fn missing_feature_descriptions(db: &BindingsGenerator, item: &Item) -> Resu
if let Some(unknown_attr) = &param.unknown_attr {
missing_features.push(format!(
"crubit.rs/errors/unknown_attribute: param {param} has unknown attribute(s): {unknown_attr}",
param = &param.identifier.identifier
param = param.identifier.identifier
));
}
}
Expand Down Expand Up @@ -536,6 +536,7 @@ pub fn generated_items_to_tokens<'db>(
incomplete_definition,
upcast_impls,
display_impl,
debug_impl,
no_unique_address_accessors,
items,
nested_items,
Expand Down Expand Up @@ -680,6 +681,7 @@ pub fn generated_items_to_tokens<'db>(
#sync_impl
#cxx_impl
#display_impl
#debug_impl

#incomplete_definition

Expand Down Expand Up @@ -1030,6 +1032,7 @@ pub struct Record {
pub incomplete_definition: Option<TokenStream>,
pub upcast_impls: Vec<Result<UpcastImpl, String>>,
pub display_impl: Option<DisplayImpl>,
pub debug_impl: Option<DebugImpl>,
pub no_unique_address_accessors: Vec<NoUniqueAddressAccessor>,
pub items: Vec<ItemId>,
pub nested_items: Vec<ItemId>,
Expand Down Expand Up @@ -1092,6 +1095,47 @@ impl ToTokens for DisplayImpl {
}
}

#[derive(Clone, Debug)]
pub struct DebugField {
pub name: String,
pub expr: TokenStream,
}

#[derive(Clone, Debug)]
pub struct DebugImpl {
pub ident: Ident,
pub stubbed_lifetime_params: TokenStream,
pub fields: Vec<DebugField>,
pub is_exhaustive: bool,
}

impl ToTokens for DebugImpl {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self { ident, stubbed_lifetime_params, fields, is_exhaustive } = self;
let type_name_str = ident.to_string();
let field_calls = fields.iter().map(|f| {
let name = &f.name;
let expr = &f.expr;
quote! { .field(#name, #expr) }
});
let finish_call = if *is_exhaustive {
quote! { .finish() }
} else {
quote! { .finish_non_exhaustive() }
};
quote! {
impl ::core::fmt::Debug for #ident #stubbed_lifetime_params {
fn fmt(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
formatter.debug_struct(#type_name_str)
#(#field_calls)*
#finish_call
}
}
}
.to_tokens(tokens);
}
}

#[derive(Clone, Debug, PartialEq)]
pub struct DocCommentAttr(pub Rc<str>);

Expand Down Expand Up @@ -1259,6 +1303,7 @@ pub enum FieldDefinition {
visibility: Visibility,
ident: Ident,
field_type: FieldType,
is_debug_formattable: bool,
},
}

Expand Down Expand Up @@ -1288,6 +1333,7 @@ impl ToTokens for FieldDefinition {
visibility,
ident,
field_type,
is_debug_formattable: _,
} => {
let padding_field = padding.map(|padding| {
let padding_name =
Expand Down
99 changes: 93 additions & 6 deletions rs_bindings_from_cc/generate_bindings/generate_struct_and_union.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,22 @@ fn make_rs_field_ident(field: &Field, field_index: usize) -> Ident {
}
}

fn is_debug_formattable(db: &BindingsGenerator, ty: &ir::CcType) -> bool {
match ty.variant() {
ir::CcTypeVariant::Primitive(_)
| ir::CcTypeVariant::Pointer(_)
| ir::CcTypeVariant::FuncPointer { .. } => true,
ir::CcTypeVariant::Decl { id, .. } => match db.find_decl::<ir::Item>(*id) {
Ok(ir::Item::Record(rec)) => rec.impl_debug,
Ok(ir::Item::Enum(_)) => true,
Ok(ir::Item::TypeAlias(alias)) => is_debug_formattable(db, &alias.underlying_type),
Ok(ir::Item::ExistingRustType(existing_type)) => existing_type.impl_debug,
_ => false,
},
ir::CcTypeVariant::Error(_) => false,
}
}

/// Gets the type of `field` for layout purposes.
///
/// Note that `get_field_rs_type_kind_for_layout` may return Err even if
Expand Down Expand Up @@ -376,6 +392,8 @@ fn field_definition(
visibility,
ident,
field_type,
is_debug_formattable: field.cpp_identifier.is_some()
&& is_debug_formattable(db, &field.type_),
})
}

Expand Down Expand Up @@ -639,11 +657,13 @@ pub fn generate_record(db: &BindingsGenerator, record: Rc<Record>) -> Result<Api
lifetime_params =
record.lifetime_inputs.iter().map(|id| make_rs_lifetime_ident(id)).collect();
}
let mut upcastable_bases = vec![];
if crubit_features.contains(crubit_feature::CrubitFeature::Experimental) {
let (new_upcast_impls, thunks, thunk_impls) = cc_struct_upcast_impl(db, &record, ir)?;
upcast_impls = new_upcast_impls;
api_snippets.thunks.extend(thunks);
api_snippets.cc_details.extend(thunk_impls);
let implementation: UpcastImplementation = cc_struct_upcast_impl(db, &record, ir)?;
upcast_impls = implementation.upcast_impls;
upcastable_bases = implementation.upcastable_bases;
api_snippets.thunks.extend(implementation.thunks);
api_snippets.cc_details.extend(implementation.thunk_impls);
}
if record.overloads_operator_delete
&& !record.has_private_or_deleted_operator_delete
Expand Down Expand Up @@ -678,6 +698,63 @@ pub fn generate_record(db: &BindingsGenerator, record: Rc<Record>) -> Result<Api
} else {
None
};
let debug_impl = if record.impl_debug && !record.is_raw_string_view() {
let fields: Vec<database::code_snippet::DebugField> = if record.is_union() {
vec![]
} else {
let bases = upcastable_bases.iter().filter(|base_record| base_record.impl_debug).map(
|base_record| {
let base_type = db.rs_type_kind(base_record.as_ref().into())?;
let base_name = base_type.to_token_stream(db);
Ok(database::code_snippet::DebugField {
name: "".to_string(),
expr: quote! { ::oops::Upcast::<&#base_name>::upcast(self) },
})
},
);

let fields = field_definitions.iter().filter_map(|field_def| {
let FieldDefinition::Field {
ident,
field_type: FieldType::Type { .. },
is_debug_formattable: true,
..
} = field_def
else {
return None;
};
let field_name_str = ident.to_string();
let clean_field_name_str = field_name_str.trim_start_matches("r#");
Some(database::code_snippet::DebugField {
name: clean_field_name_str.to_string(),
expr: quote! { &self.#ident },
})
});

bases.chain(fields.map(Ok)).collect::<Result<_>>()?
};

let fields_exhaustive = field_definitions.iter().all(|field_def| {
matches!(
field_def,
FieldDefinition::Field {
field_type: FieldType::Type { .. },
is_debug_formattable: true,
..
}
)
});

Some(database::code_snippet::DebugImpl {
ident: ident.clone(),
stubbed_lifetime_params: stubbed_lifetime_params.clone(),
fields,
is_exhaustive: !record.is_union() && !record.is_derived_class && fields_exhaustive,
})
} else {
None
};

let no_unique_address_accessors =
if crubit_features.contains(crubit_feature::CrubitFeature::Experimental) {
cc_struct_no_unique_address_impl(db, &record)?
Expand Down Expand Up @@ -760,6 +837,7 @@ pub fn generate_record(db: &BindingsGenerator, record: Rc<Record>) -> Result<Api
incomplete_definition,
upcast_impls,
display_impl,
debug_impl,
no_unique_address_accessors,
items,
nested_items,
Expand Down Expand Up @@ -974,16 +1052,24 @@ fn cc_struct_no_unique_address_impl(

type UpcastImplResult = Result<UpcastImpl, String>;

struct UpcastImplementation {
upcast_impls: Vec<UpcastImplResult>,
thunks: Vec<Thunk>,
thunk_impls: Vec<ThunkImpl>,
upcastable_bases: Vec<Rc<Record>>,
}

/// Returns the implementation of base class conversions, for converting a type
/// to its unambiguous public base classes.
fn cc_struct_upcast_impl(
db: &BindingsGenerator,
record: &Rc<Record>,
ir: &IR,
) -> Result<(Vec<UpcastImplResult>, Vec<Thunk>, Vec<ThunkImpl>)> {
) -> Result<UpcastImplementation> {
let mut thunks = vec![];
let mut thunk_impls = vec![];
let mut upcast_impls = vec![];
let mut upcastable_bases = vec![];
let derived_name = db.rs_type_kind(record.as_ref().into())?.to_token_stream(db);
for base in &record.unambiguous_public_bases {
let base_record: &Rc<Record> = db
Expand Down Expand Up @@ -1040,9 +1126,10 @@ fn cc_struct_upcast_impl(
derived_name: derived_name.clone(),
body,
}));
upcastable_bases.push(base_record.clone());
}

Ok((upcast_impls, thunks, thunk_impls))
Ok(UpcastImplementation { upcast_impls, thunks, thunk_impls, upcastable_bases })
}

fn cc_struct_operator_delete_impl(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use generate_struct_and_union::generate_derives;
use googletest::{expect_that, gtest, matchers::contains_substring};
use ir_testing::with_lifetime_macros;
use multiplatform_ir_testing::{
ir_from_assumed_lifetimes_cc, ir_from_cc, ir_from_cc_dependency, ir_record,
ir_from_assumed_lifetimes_cc, ir_from_cc, ir_from_cc_dependency, ir_from_record_impl_debug_cc,
ir_record,
};
use proc_macro2::TokenStream;
use quote::quote;
Expand Down Expand Up @@ -1990,3 +1991,33 @@ fn test_non_thread_safe_struct_has_negative_send_sync() -> Result<()> {

Ok(())
}

#[gtest]
fn test_impl_debug() -> Result<()> {
let ir = ir_from_record_impl_debug_cc("struct S { int x; };")?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_matches!(
rs_api,
quote! {
impl ::core::fmt::Debug for S {
...
}
}
);
Ok(())
}

#[gtest]
fn test_crubit_override_debug_false() -> Result<()> {
let ir = ir_from_record_impl_debug_cc(
r#"struct [[clang::annotate("crubit_override_debug", false)]] S { int x; };"#,
)?;
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
assert_rs_not_matches!(
rs_api,
quote! {
impl ::core::fmt::Debug for S { ... }
}
);
Ok(())
}
10 changes: 10 additions & 0 deletions rs_bindings_from_cc/generate_bindings/multiplatform_ir_testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ pub fn ir_from_cc_dependency(header: &str, dep_header: &str) -> Result<IR> {
)
}

pub fn ir_from_record_impl_debug_cc(header: &str) -> Result<IR> {
ir_testing::ir_from_cc_dependency(
test_platform(),
header,
"// empty header",
Some("record_impl_debug"),
/*kythe_annotations=*/ false,
)
}

pub fn ir_record(name: &str) -> Record {
ir_testing::ir_record(test_platform(), name)
}
Expand Down
15 changes: 15 additions & 0 deletions rs_bindings_from_cc/importer_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,21 @@ TEST(ImporterTest, ImplDebugOverrideTrueIsTrue) {
ElementsAre(Pointee(AllOf(RsNameIs("S"), ImplDebug()))));
}

TEST(ImporterTest, ImplDebugDeriveDebugDeprecated) {
ASSERT_OK_AND_ASSIGN(
const IR ir, IrFromCcWithRecordImplDebug(R"cc(
struct [[clang::annotate("crubit_internal_trait_derive", "Debug")]] S {
};
)cc"));
EXPECT_THAT(ir.get_items_if<Record>(), IsEmpty());
EXPECT_THAT(ir.get_items_if<UnsupportedItem>(),
ElementsAre(Pointee(AllOf(
UnsupportedItemNameIs("S"),
Field(&UnsupportedItem::errors,
ElementsAre(Property(&FormattedError::message,
HasSubstr("deprecated"))))))));
}

TEST(ImporterTest, ImplDebugUnexpectedArgsMissing) {
ASSERT_OK_AND_ASSIGN(const IR ir, IrFromCcWithRecordImplDebug(R"cc(
struct [[clang::annotate("crubit_override_debug")]] S {
Expand Down
6 changes: 6 additions & 0 deletions rs_bindings_from_cc/importers/cxx_record.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,12 @@ std::optional<IR::Item> CXXRecordDeclImporter::Import(

bool impl_debug = false;
if (record_impl_debug_enabled) {
if (trait_derives->debug != TraitImplPolarity::kNone) {
return unsupported(FormattedError::Static(
"derive(Debug) is deprecated when record_impl_debug is enabled. "
"Debug is implemented by default; use CRUBIT_OVERRIDE_DEBUG(false) "
"to opt out."));
}
absl::StatusOr<std::optional<bool>> override_debug =
ictx_.GetCrubitOverrideDebugAnnotation(*record_decl);
if (!override_debug.ok()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ pub struct __CcTemplateInstN4absl13flat_hash_mapIimLi42EEE {
}
impl !Send for __CcTemplateInstN4absl13flat_hash_mapIimLi42EEE {}
impl !Sync for __CcTemplateInstN4absl13flat_hash_mapIimLi42EEE {}
impl ::core::fmt::Debug for __CcTemplateInstN4absl13flat_hash_mapIimLi42EEE {
fn fmt(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
formatter.debug_struct("__CcTemplateInstN4absl13flat_hash_mapIimLi42EEE").finish()
}
}
forward_declare::unsafe_define!(
forward_declare::symbol!(":: absl :: flat_hash_map < int , unsigned long , 42 >"),
crate::__CcTemplateInstN4absl13flat_hash_mapIimLi42EEE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ impl ::core::fmt::Display for SV<'_> {
}
}
}
impl ::core::fmt::Debug for SV<'_> {
fn fmt(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
formatter.debug_struct("SV").finish()
}
}
forward_declare::unsafe_define!(forward_declare::symbol!(":: SV"), crate::SV<'_>);

impl<'a> Default for SV<'a> {
Expand Down
3 changes: 1 addition & 2 deletions rs_bindings_from_cc/test/bridging/composable_bridging_lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ std::pair<std::optional<int>, std::optional<std::pair<float, Vec3<float>>>>
MakeStuff();

// Not a bridge type!
struct [[clang::annotate("crubit_internal_trait_derive", "Debug", "PartialEq")]]
Stuff {
struct [[clang::annotate("crubit_internal_trait_derive", "PartialEq")]] Stuff {
int i;
float f;
};
Expand Down
4 changes: 2 additions & 2 deletions rs_bindings_from_cc/test/crate_derive/crate_derive.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
#ifndef THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_CRATE_DERIVE_CRATE_DERIVE_H_
#define THIRD_PARTY_CRUBIT_RS_BINDINGS_FROM_CC_TEST_CRATE_DERIVE_CRATE_DERIVE_H_

struct [[clang::annotate("crubit_internal_trait_derive", "!Clone",
"Debug")]] StructWithDerives {
struct [[clang::annotate("crubit_internal_trait_derive", "!Clone")]]
StructWithDerives {
int x;
};

Expand Down
Loading