Skip to content

Commit bd8e3a8

Browse files
Googlercopybara-github
authored andcommitted
Use leading_colons_for_cpp_type feature flag and refactor NamespaceQualifier
> AI Disclaimer: the code and description are AI generated, but I have reviewed them so readers need not worry about AI slop. This change uses the new Crubit feature flag, `leading_colons_for_cpp_type`. When enabled, it ensures that generated `cpp_type` annotations in Rust bindings have a leading `::` (e.g., `CRUBIT_ANNOTATE: cpp_type=::Foo`). This change required some refactoring that spans both `rs_bindings_from_cc` and `cc_bindings_from_rs`. Here is a summary and justification for those changes: 1. **Refactor of `NamespaceQualifier`**: * **What**: Added a `use_leading_colons` field to `NamespaceQualifier` and updated `NamespaceQualifier::new` to accept this boolean explicitly. Centralized the logic for prepending `::` in `format_for_cc`. * **Why**: Instead of manually handling string manipulation at every annotation generation site, centralizing it in `NamespaceQualifier` is less error-prone. Forcing the argument in `new` ensures that we explicitly consider the feature flag at every call site rather than defaulting to `false`. 2. **Refactor of `crate_features` in `cc_bindings_from_rs`**: * **What**: Moved the `crate_features` free function from `generate_bindings/lib.rs` to be a method on `BindingsGenerator` in `database/db.rs`. * **Why**: To support the feature flag in `cc_bindings_from_rs` (specifically in `fully_qualified_name.rs` where `ExportedPath` is converted to `NamespaceQualifier`), we needed access to crate features. `fully_qualified_name.rs` belongs to the `database` crate and could not access the free function in `lib.rs`. Moving it to `BindingsGenerator` resolved this dependency issue and is architecturally cleaner, making feature lookup part of the database query. 3. **Cleanup and Inlining**: * **What**: Inlined the remaining free function `crate_features` in `lib.rs` and updated all call sites in `lib.rs` and `generate_struct_and_union.rs` to use `db.crate_features` directly. 4. **Test consolidation**: * **What**: Updated existing tests for `NamespaceQualifier` to use `true` for the feature flag instead of adding duplicate tests for both boolean states. PiperOrigin-RevId: 900832916
1 parent 5be7af4 commit bd8e3a8

File tree

48 files changed

+450
-364
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+450
-364
lines changed

cc_bindings_from_rs/generate_bindings/database/db.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,18 @@ memoized::query_group! {
337337
) -> Option<Result<RsStdEnumTemplateSpecialization<'tcx>>>;
338338
}
339339
}
340+
341+
impl<'tcx> BindingsGenerator<'tcx> {
342+
pub fn crate_features(
343+
&self,
344+
krate: CrateNum,
345+
) -> flagset::FlagSet<crubit_feature::CrubitFeature> {
346+
let crate_features = self.crate_name_to_features();
347+
let features = if krate == self.source_crate_num() {
348+
crate_features.get("self")
349+
} else {
350+
crate_features.get(self.tcx().crate_name(krate).as_str())
351+
};
352+
features.copied().unwrap_or_else(|| self.default_features())
353+
}
354+
}

cc_bindings_from_rs/generate_bindings/database/fully_qualified_name.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,18 @@ pub fn rename_clang_builtin_macros(ns: Rc<str>) -> Rc<str> {
175175
ns
176176
}
177177

178-
impl From<&ExportedPath> for NamespaceQualifier {
179-
fn from(this: &ExportedPath) -> Self {
178+
impl ExportedPath {
179+
pub fn to_namespace_qualifier(&self, db: &BindingsGenerator) -> NamespaceQualifier {
180+
let features = db.crate_features(self.krate);
181+
let use_leading_colons =
182+
features.contains(crubit_feature::CrubitFeature::LeadingColonsForCppType);
180183
NamespaceQualifier::new(
181-
this.path
184+
self.path
182185
.iter()
183186
.map(|s| Rc::<str>::from(s.as_str()))
184187
.map(rename_clang_builtin_macros)
185188
.collect::<Vec<_>>(),
189+
use_leading_colons,
186190
)
187191
}
188192
}

cc_bindings_from_rs/generate_bindings/generate_bindings_test.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2204,9 +2204,9 @@ fn test_format_item_generate_bindings_for_top_level_type_alias() {
22042204
fn test_format_namespace_bound_cc_tokens() {
22052205
run_compiler_for_testing("", |tcx| {
22062206
let db = bindings_db_for_tests(tcx);
2207-
let top_level = NamespaceQualifier::new::<&str>([]);
2208-
let m1 = NamespaceQualifier::new(["m1"]);
2209-
let m2 = NamespaceQualifier::new(["m2"]);
2207+
let top_level = NamespaceQualifier::new::<&str>([], true);
2208+
let m1 = NamespaceQualifier::new(["m1"], true);
2209+
let m2 = NamespaceQualifier::new(["m2"], true);
22102210
let input = [
22112211
(None, top_level.clone(), quote! { void f0a(); }),
22122212
(None, m1.clone(), quote! { void f1a(); }),

cc_bindings_from_rs/generate_bindings/generate_struct_and_union.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@ use crate::generate_function::{
1414
format_variant_ctor_cc_name, generate_thunk_call, Param, ThunkSelfParameter,
1515
};
1616
use crate::{
17-
crate_features, generate_const, generate_deprecated_tag, generate_must_use_tag,
18-
generate_trait_thunks, generate_unsupported_def, get_layout, get_scalar_int_type,
19-
get_tag_size_with_padding, is_bridged_type, is_copy, BridgedBuiltin, RsSnippet, SortedByDef,
20-
TraitThunks,
17+
generate_const, generate_deprecated_tag, generate_must_use_tag, generate_trait_thunks,
18+
generate_unsupported_def, get_layout, get_scalar_int_type, get_tag_size_with_padding,
19+
is_bridged_type, is_copy, BridgedBuiltin, RsSnippet, SortedByDef, TraitThunks,
2120
};
2221
use arc_anyhow::{Context, Result};
2322
use code_gen_utils::{expect_format_cc_type_name, make_rs_ident, CcInclude};
@@ -339,7 +338,7 @@ pub(crate) fn generate_associated_item<'tcx>(
339338
}
340339
};
341340
let result = result.and_then(|snippet| {
342-
snippet.resolve_feature_requirements(crate_features(db, db.source_crate_num()))
341+
snippet.resolve_feature_requirements(db.crate_features(db.source_crate_num()))
343342
});
344343
match result {
345344
Err(err) => {
@@ -1461,10 +1460,9 @@ pub(crate) fn generate_fields<'tcx>(
14611460
size,
14621461
cpp_type: db
14631462
.format_ty_for_cc(ty, TypeLocation::Other)?
1464-
.resolve_feature_requirements(crate_features(
1465-
db,
1466-
db.source_crate_num(),
1467-
))?,
1463+
.resolve_feature_requirements(
1464+
db.crate_features(db.source_crate_num()),
1465+
)?,
14681466
})
14691467
});
14701468
let name = field_def.ident(tcx).to_string();

cc_bindings_from_rs/generate_bindings/lib.rs

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,8 @@ pub fn generate_bindings(db: &BindingsGenerator) -> Result<BindingsTokens> {
244244
let source_crate_num = db.source_crate_num();
245245
let crate_name = tcx.crate_name(source_crate_num);
246246
let crubit_features = {
247-
let mut crubit_features: Vec<&str> = crate_features(db, source_crate_num)
247+
let mut crubit_features: Vec<&str> = db
248+
.crate_features(source_crate_num)
248249
.into_iter()
249250
.map(|feature| feature.short_name())
250251
.collect();
@@ -336,19 +337,6 @@ pub fn generate_bindings(db: &BindingsGenerator) -> Result<BindingsTokens> {
336337
Ok(BindingsTokens { cc_api, cc_api_impl })
337338
}
338339

339-
pub(crate) fn crate_features(
340-
db: &BindingsGenerator,
341-
krate: CrateNum,
342-
) -> flagset::FlagSet<crubit_feature::CrubitFeature> {
343-
let crate_features = db.crate_name_to_features();
344-
let features = if krate == db.source_crate_num() {
345-
crate_features.get("self")
346-
} else {
347-
crate_features.get(db.tcx().crate_name(krate).as_str())
348-
};
349-
features.copied().unwrap_or_else(|| db.default_features())
350-
}
351-
352340
fn check_feature_enabled_on_self_and_all_deps(
353341
db: &BindingsGenerator,
354342
feature: FineGrainedFeature,
@@ -705,9 +693,15 @@ fn symbol_canonical_name(db: &BindingsGenerator<'_>, def_id: DefId) -> Option<Fu
705693
}
706694
}
707695

708-
let rs_mod_path = NamespaceQualifier::new(full_path_strs.clone());
709-
let cpp_ns_path =
710-
NamespaceQualifier::new(full_path_strs.into_iter().map(rename_clang_builtin_macros));
696+
let features = db.crate_features(krate_num);
697+
let use_leading_colons =
698+
features.contains(crubit_feature::CrubitFeature::LeadingColonsForCppType);
699+
700+
let rs_mod_path = NamespaceQualifier::new(full_path_strs.clone(), use_leading_colons);
701+
let cpp_ns_path = NamespaceQualifier::new(
702+
full_path_strs.into_iter().map(rename_clang_builtin_macros),
703+
use_leading_colons,
704+
);
711705
let cpp_top_level_ns = format_top_level_ns_for_crate(db, krate_num);
712706
Some(FullyQualifiedName {
713707
krate,
@@ -1661,7 +1655,7 @@ fn generate_item_impl<'tcx>(
16611655
};
16621656

16631657
if let Ok(Some(item)) = item {
1664-
Ok(Some(item.resolve_feature_requirements(crate_features(db, db.source_crate_num()))?))
1658+
Ok(Some(item.resolve_feature_requirements(db.crate_features(db.source_crate_num()))?))
16651659
} else {
16661660
item
16671661
}
@@ -1937,7 +1931,7 @@ fn generate_crate(db: &BindingsGenerator) -> Result<BindingsTokens> {
19371931
// `format_namespace_bound_cc_tokens`, so we want to carry it alongside our alias
19381932
// even though we technically no longer need it.
19391933
def_id,
1940-
NamespaceQualifier::from(alias),
1934+
alias.to_namespace_qualifier(db),
19411935
using_snippets.into_tokens(&mut cc_details_prereqs),
19421936
));
19431937
}
@@ -2096,26 +2090,45 @@ fn generate_crate(db: &BindingsGenerator) -> Result<BindingsTokens> {
20962090
.into_iter()
20972091
.chain(ordered_main_apis)
20982092
.map(|(node, tokens)| match node {
2099-
Node::Def(def_id) => (
2100-
tcx.opt_parent(def_id),
2101-
NamespaceQualifier::new(cpp_top_level_ns.iter().cloned().chain({
2102-
db.symbol_canonical_name(def_id)
2103-
.unwrap_or_else(|| {
2104-
panic!("Exported item {def_id:?} should have a canonical name")
2105-
})
2106-
.cpp_ns_path
2107-
.namespaces
2108-
})),
2109-
tokens,
2110-
),
2093+
Node::Def(def_id) => {
2094+
let features = db.crate_features(def_id.krate);
2095+
let use_leading_colons =
2096+
features.contains(crubit_feature::CrubitFeature::LeadingColonsForCppType);
2097+
(
2098+
tcx.opt_parent(def_id),
2099+
NamespaceQualifier::new(
2100+
cpp_top_level_ns.iter().cloned().chain({
2101+
db.symbol_canonical_name(def_id)
2102+
.unwrap_or_else(|| {
2103+
panic!(
2104+
"Exported item {def_id:?} should have a canonical name"
2105+
)
2106+
})
2107+
.cpp_ns_path
2108+
.namespaces
2109+
}),
2110+
use_leading_colons,
2111+
),
2112+
tokens,
2113+
)
2114+
}
21112115
// Specializations always live in the top-level namespace.
2112-
Node::Specialization(_) => (None, NamespaceQualifier::new::<Rc<str>>([]), tokens),
2116+
Node::Specialization(_) => {
2117+
let use_leading_colons = db
2118+
.crate_features(db.source_crate_num())
2119+
.contains(crubit_feature::CrubitFeature::LeadingColonsForCppType);
2120+
(None, NamespaceQualifier::new::<Rc<str>>([], use_leading_colons), tokens)
2121+
}
21132122
})
21142123
.chain(cc_details.into_iter().map(|details| {
2124+
let use_leading_colons = db
2125+
.crate_features(details.def_id.krate)
2126+
.contains(crubit_feature::CrubitFeature::LeadingColonsForCppType);
21152127
(
21162128
tcx.opt_parent(details.def_id),
21172129
NamespaceQualifier::new(
21182130
cpp_top_level_ns.iter().cloned().chain(details.namespace.namespaces),
2131+
use_leading_colons,
21192132
),
21202133
details.tokens,
21212134
)

common/code_gen_utils.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -348,17 +348,27 @@ pub struct NamespaceQualifier {
348348
pub namespaces: Vec<Rc<str>>,
349349
// Outer to innermost. Paired as (rs_name, cc_name)
350350
pub nested_records: Vec<(Rc<str>, Rc<str>)>,
351+
/// Whether to prepend `::` when formatting for C++.
352+
// TODO(b/502939407): Remove this field (it will always be true).
353+
pub use_leading_colons: bool,
351354
}
352355

353356
impl NamespaceQualifier {
354357
/// Constructs a new `NamespaceQualifier` from a sequence of names.
355-
pub fn new<T: Into<Rc<str>>>(iter: impl IntoIterator<Item = T>) -> Self {
358+
pub fn new<T: Into<Rc<str>>>(
359+
iter: impl IntoIterator<Item = T>,
360+
use_leading_colons: bool,
361+
) -> Self {
356362
// TODO(b/258265044): Catch most (all if possible) error conditions early. For
357363
// example:
358364
// - Panic early if any strings are empty, or are not Rust identifiers
359365
// - Report an error early if any strings are C++ reserved keywords
360366
// This may make `format_for_cc` and `format_namespace_bound_cc_tokens` infallible.
361-
Self { namespaces: iter.into_iter().map(Into::into).collect(), nested_records: vec![] }
367+
Self {
368+
namespaces: iter.into_iter().map(Into::into).collect(),
369+
nested_records: vec![],
370+
use_leading_colons,
371+
}
362372
}
363373

364374
pub fn is_empty(&self) -> bool {
@@ -387,7 +397,11 @@ impl NamespaceQualifier {
387397

388398
/// Returns `foo::bar::baz::` (reporting errors for C++ keywords).
389399
pub fn format_for_cc(&self) -> Result<TokenStream> {
390-
let mut path = quote! {};
400+
let mut path = if self.use_leading_colons {
401+
quote! { :: }
402+
} else {
403+
quote! {}
404+
};
391405
for namespace in &self.namespaces {
392406
let namespace = format_cc_ident(namespace)?;
393407
path.extend(quote! { #namespace :: });
@@ -745,34 +759,34 @@ pub mod tests {
745759

746760
#[gtest]
747761
fn test_namespace_qualifier_empty() {
748-
let ns = NamespaceQualifier::new::<&str>([]);
762+
let ns = NamespaceQualifier::new::<&str>([], true);
749763
let actual_rs = ns.format_for_rs();
750764
assert!(actual_rs.is_empty());
751765
let actual_cc = ns.format_for_cc().unwrap();
752-
assert!(actual_cc.is_empty());
766+
assert_cc_matches!(actual_cc, quote! { :: });
753767
}
754768

755769
#[gtest]
756770
fn test_namespace_qualifier_basic() {
757-
let ns = NamespaceQualifier::new(["foo", "bar"]);
771+
let ns = NamespaceQualifier::new(["foo", "bar"], true);
758772
let actual_rs = ns.format_for_rs();
759773
assert_rs_matches!(actual_rs, quote! { foo::bar:: });
760774
let actual_cc = ns.format_for_cc().unwrap();
761-
assert_cc_matches!(actual_cc, quote! { foo::bar:: });
775+
assert_cc_matches!(actual_cc, quote! { :: foo::bar:: });
762776
}
763777

764778
#[gtest]
765779
fn test_namespace_qualifier_reserved_cc_keyword() {
766-
let ns = NamespaceQualifier::new(["foo", "impl", "bar"]);
780+
let ns = NamespaceQualifier::new(["foo", "impl", "bar"], true);
767781
let actual_rs = ns.format_for_rs();
768782
assert_rs_matches!(actual_rs, quote! { foo :: r#impl :: bar :: });
769783
let actual_cc = ns.format_for_cc().unwrap();
770-
assert_cc_matches!(actual_cc, quote! { foo::impl::bar:: });
784+
assert_cc_matches!(actual_cc, quote! { :: foo::impl::bar:: });
771785
}
772786

773787
#[gtest]
774788
fn test_namespace_qualifier_reserved_rust_keyword() {
775-
let ns = NamespaceQualifier::new(["foo", "reinterpret_cast", "bar"]);
789+
let ns = NamespaceQualifier::new(["foo", "reinterpret_cast", "bar"], true);
776790
let actual_rs = ns.format_for_rs();
777791
assert_rs_matches!(actual_rs, quote! { foo :: reinterpret_cast :: bar :: });
778792
let cc_error = ns.format_for_cc().unwrap_err();

rs_bindings_from_cc/generate_bindings/database/db.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,17 @@ impl<'db> BindingsGenerator<'db> {
480480
}
481481
namespaces.reverse();
482482
nested_records.reverse();
483-
code_gen_utils::NamespaceQualifier { namespaces, nested_records }
483+
let use_leading_colons =
484+
if let Some(target) = self.find_untyped_decl(item_id).owning_target() {
485+
self.ir()
486+
.target_crubit_features(&target)
487+
.contains(crubit_feature::CrubitFeature::LeadingColonsForCppType)
488+
} else {
489+
// We default to true here because the final change will always be to add `::` to
490+
// the beginning of the type.
491+
true
492+
};
493+
code_gen_utils::NamespaceQualifier { namespaces, nested_records, use_leading_colons }
484494
}
485495

486496
/// Returns the name of the snake-cased module that exposes the given record's nested items.

rs_bindings_from_cc/generate_bindings/database/rs_snippet.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,10 @@ impl CratePath {
169169
namespace_qualifier: NamespaceQualifier,
170170
crate_ident: Option<Ident>,
171171
) -> CratePath {
172-
let crate_root_path = NamespaceQualifier::new(ir.crate_root_path());
172+
let use_leading_colons = ir
173+
.target_crubit_features(ir.current_target())
174+
.contains(CrubitFeature::LeadingColonsForCppType);
175+
let crate_root_path = NamespaceQualifier::new(ir.crate_root_path(), use_leading_colons);
173176
CratePath { crate_ident, crate_root_path, namespace_qualifier }
174177
}
175178
}
@@ -2173,7 +2176,11 @@ mod tests {
21732176
}
21742177

21752178
fn make_crate_path() -> Rc<CratePath> {
2176-
let ns = NamespaceQualifier { namespaces: vec![], nested_records: vec![] };
2179+
let ns = NamespaceQualifier {
2180+
namespaces: vec![],
2181+
nested_records: vec![],
2182+
use_leading_colons: true,
2183+
};
21772184
Rc::new(CratePath {
21782185
crate_ident: None,
21792186
crate_root_path: ns.clone(),

rs_bindings_from_cc/generate_bindings/generate_bindings_test.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,24 @@ fn test_rust_keywords_are_escaped_in_rs_api_file() -> Result<()> {
724724
Ok(())
725725
}
726726

727+
#[gtest]
728+
fn test_leading_colons_for_cpp_type() -> Result<()> {
729+
let mut ir = ir_from_cc("struct S {};")?;
730+
let target = ir.current_target().clone();
731+
let features = ir.target_crubit_features(&target);
732+
*ir.target_crubit_features_mut(&target) =
733+
features | crubit_feature::CrubitFeature::LeadingColonsForCppType;
734+
let rs_api = generate_bindings_tokens_for_test(ir)?.rs_api;
735+
assert_rs_matches!(
736+
rs_api,
737+
quote! {
738+
#[doc = "CRUBIT_ANNOTATE: cpp_type=:: S"]
739+
pub struct S
740+
}
741+
);
742+
Ok(())
743+
}
744+
727745
#[gtest]
728746
fn test_rust_keywords_are_not_escaped_in_rs_api_impl_file() -> Result<()> {
729747
let ir = ir_from_cc("struct type { int dyn; };")?;

rs_bindings_from_cc/generate_bindings/generate_function_thunk.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,11 @@ fn generate_function_assertion_for_identifier(
270270
id: &Identifier,
271271
) -> Result<ThunkImpl> {
272272
let fn_ident = format_cc_ident(&id.identifier)?;
273-
let path_to_func = db.namespace_qualifier(func).format_for_cc()?;
274-
let implementation_function = quote! { :: #path_to_func #fn_ident };
273+
let mut namespace_qualifier = db.namespace_qualifier(func);
274+
// Keep goldens the same.
275+
namespace_qualifier.use_leading_colons = true;
276+
let path_to_func = namespace_qualifier.format_for_cc()?;
277+
let implementation_function = quote! { #path_to_func #fn_ident };
275278
let method_qualification;
276279
let member_function_prefix;
277280
let func_params;

0 commit comments

Comments
 (0)