Move NamespaceQualifier into `crubit/common/code_gen_utils.rs`.
This CL helps prepare for reusing NamespaceQualifier from
`crubit/cc_bindings_from_rs`. In addition to moving the code,
the CL had to make the following changes:
- `format_for_cc` has to return a `Result<...>`
(since `format_for_cc` in `code_gen_utils` is not infallible,
unlike the one in `rs_bindings_from_cc`)
- NamespaceQualifier::new is rs_bindings_from_cc/IR-specific
and therefore couldn't be moved into `common/code_gen_utils.rs`.
It was refactored into a separate function.
PiperOrigin-RevId: 490601254
diff --git a/common/code_gen_utils.rs b/common/code_gen_utils.rs
index 3405af6..78b151a 100644
--- a/common/code_gen_utils.rs
+++ b/common/code_gen_utils.rs
@@ -51,6 +51,29 @@
}
}
+/// Representation of `foo::bar::baz::` where each component is either the name
+/// of a C++ namespace, or the name of a Rust module.
+#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
+// TODO(b/258265044): Make the `Vec<String>` payload private + guarantee
+// additional invariants in an explicit, public `new` method. This will help to
+// catch some error conditions early (e.g. an empty path component may trigger a
+// panic in `make_rs_ident`; a reserved C++ keyword might trigger a late error
+// in `format_for_cc` / `format_cc_ident`).
+pub struct NamespaceQualifier(pub Vec<String>);
+
+impl NamespaceQualifier {
+ pub fn format_for_rs(&self) -> TokenStream {
+ let namespace_rs_idents = self.0.iter().map(|ns| make_rs_ident(ns));
+ quote! { #(#namespace_rs_idents::)* }
+ }
+
+ pub fn format_for_cc(&self) -> Result<TokenStream> {
+ let namespace_cc_idents =
+ self.0.iter().map(|ns| format_cc_ident(ns)).collect::<Result<Vec<_>>>()?;
+ Ok(quote! { #(#namespace_cc_idents::)* })
+ }
+}
+
/// `CcInclude` represents a single `#include ...` directive in C++.
#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum CcInclude {
@@ -410,4 +433,46 @@
"#
);
}
+
+ fn create_namespace_qualifier_for_tests(input: &[&str]) -> NamespaceQualifier {
+ NamespaceQualifier(input.into_iter().map(|s| s.to_string()).collect())
+ }
+
+ #[test]
+ fn test_namespace_qualifier_empty() {
+ let ns = create_namespace_qualifier_for_tests(&[]);
+ let actual_rs = ns.format_for_rs();
+ assert!(actual_rs.is_empty());
+ let actual_cc = ns.format_for_cc().unwrap();
+ assert!(actual_cc.is_empty());
+ }
+
+ #[test]
+ fn test_namespace_qualifier_basic() {
+ let ns = create_namespace_qualifier_for_tests(&["foo", "bar"]);
+ let actual_rs = ns.format_for_rs();
+ assert_rs_matches!(actual_rs, quote! { foo::bar:: });
+ let actual_cc = ns.format_for_cc().unwrap();
+ assert_cc_matches!(actual_cc, quote! { foo::bar:: });
+ }
+
+ #[test]
+ fn test_namespace_qualifier_reserved_cc_keyword() {
+ let ns = create_namespace_qualifier_for_tests(&["foo", "impl", "bar"]);
+ let actual_rs = ns.format_for_rs();
+ assert_rs_matches!(actual_rs, quote! { foo :: r#impl :: bar :: });
+ let actual_cc = ns.format_for_cc().unwrap();
+ assert_cc_matches!(actual_cc, quote! { foo::impl::bar:: });
+ }
+
+ #[test]
+ fn test_namespace_qualifier_reserved_rust_keyword() {
+ let ns = create_namespace_qualifier_for_tests(&["foo", "reinterpret_cast", "bar"]);
+ let actual_rs = ns.format_for_rs();
+ assert_rs_matches!(actual_rs, quote! { foo :: reinterpret_cast :: bar :: });
+ let cc_error = ns.format_for_cc().expect_err("This test expects an error");
+ let msg = cc_error.to_string();
+ assert!(msg.contains("`reinterpret_cast`"));
+ assert!(msg.contains("C++ reserved keyword"));
+ }
}