Make payload of `NamespaceQualifier` private.

This CL prevents direct construction of `NamespaceQualifier` and forces
using explicit `code_gen_utils` APIs to construct a
`NamespaceQualifier`.  This is a step toward a future where the provided
construction API performs *early* verification of the input (e.g.
panicking or returning an error early when the inputs cannot be
formatted as C++ namespace identifiers or Rust module names).

This CL refactors places that used to depend on a public payload
of `NamespaceQualifier` in the following way:

* Construction of `NamespaceQualifier` now goes through an explicit
  `NamespaceQualifier::new` API.

* `CratePath` has been refactored to avoid having to rip-open and
  deconstruct a `NamespaceQualifier`.

PiperOrigin-RevId: 499270950
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs
index 7a6ece2..8c976f6 100644
--- a/cc_bindings_from_rs/bindings.rs
+++ b/cc_bindings_from_rs/bindings.rs
@@ -22,6 +22,7 @@
 use rustc_target::spec::PanicStrategy;
 use std::collections::{BTreeSet, HashMap, HashSet};
 use std::ops::AddAssign;
+use std::rc::Rc;
 
 pub struct GeneratedBindings {
     pub h_body: TokenStream,
@@ -211,8 +212,9 @@
         let name = full_path.pop().expect("At least the item's name should be present");
         let name = get_symbol(name);
 
-        let mod_path = full_path.into_iter().map(get_symbol).map(|s| s.as_str().into()).collect();
-        let mod_path = NamespaceQualifier(mod_path);
+        let mod_path = NamespaceQualifier::new(
+            full_path.into_iter().map(get_symbol).map(|s| Rc::<str>::from(s.as_str())),
+        );
 
         Self { krate, mod_path, name }
     }
diff --git a/common/code_gen_utils.rs b/common/code_gen_utils.rs
index cc81138..3f38d94 100644
--- a/common/code_gen_utils.rs
+++ b/common/code_gen_utils.rs
@@ -45,22 +45,30 @@
     }
 }
 
-/// Representation of `foo::bar::baz::` where each component is either the name
+/// 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<Rc<str>>);
+pub struct NamespaceQualifier(Vec<Rc<str>>);
 
 impl NamespaceQualifier {
+    /// Constructs a new `NamespaceQualifier` from a sequence of names.
+    pub fn new<T: Into<Rc<str>>>(iter: impl IntoIterator<Item = T>) -> Self {
+        // TODO(b/258265044): Catch most (all if possible) error conditions early.  For
+        // example:
+        // - Panic early if any strings are empty, or are not Rust identifiers
+        // - Report an error early if any strings are C++ reserved keywords
+        // This may make `format_for_cc`, `format_with_cc_body`, and
+        // `format_namespace_bound_cc_tokens` infallible.
+        Self(iter.into_iter().map(Into::into).collect())
+    }
+
+    /// Returns `foo::bar::baz::` (escaping Rust keywords as needed).
     pub fn format_for_rs(&self) -> TokenStream {
         let namespace_rs_idents = self.0.iter().map(|ns| make_rs_ident(ns));
         quote! { #(#namespace_rs_idents::)* }
     }
 
+    /// Returns `foo::bar::baz::` (reporting errors for C++ keywords).
     pub fn format_for_cc(&self) -> Result<TokenStream> {
         let namespace_cc_idents = self.cc_idents()?;
         Ok(quote! { #(#namespace_cc_idents::)* })
@@ -497,13 +505,9 @@
         );
     }
 
-    fn create_namespace_qualifier_for_tests(input: &[&str]) -> NamespaceQualifier {
-        NamespaceQualifier(input.into_iter().map(|&s| s.into()).collect())
-    }
-
     #[test]
     fn test_namespace_qualifier_empty() {
-        let ns = create_namespace_qualifier_for_tests(&[]);
+        let ns = NamespaceQualifier::new::<&str>([]);
         let actual_rs = ns.format_for_rs();
         assert!(actual_rs.is_empty());
         let actual_cc = ns.format_for_cc().unwrap();
@@ -512,7 +516,7 @@
 
     #[test]
     fn test_namespace_qualifier_basic() {
-        let ns = create_namespace_qualifier_for_tests(&["foo", "bar"]);
+        let ns = NamespaceQualifier::new(["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();
@@ -521,7 +525,7 @@
 
     #[test]
     fn test_namespace_qualifier_reserved_cc_keyword() {
-        let ns = create_namespace_qualifier_for_tests(&["foo", "impl", "bar"]);
+        let ns = NamespaceQualifier::new(["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();
@@ -530,7 +534,7 @@
 
     #[test]
     fn test_namespace_qualifier_reserved_rust_keyword() {
-        let ns = create_namespace_qualifier_for_tests(&["foo", "reinterpret_cast", "bar"]);
+        let ns = NamespaceQualifier::new(["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().unwrap_err();
@@ -541,7 +545,7 @@
 
     #[test]
     fn test_namespace_qualifier_format_with_cc_body_top_level_namespace() {
-        let ns = create_namespace_qualifier_for_tests(&[]);
+        let ns = NamespaceQualifier::new::<&str>([]);
         assert_cc_matches!(
             ns.format_with_cc_body(quote! { cc body goes here }).unwrap(),
             quote! { cc body goes here },
@@ -550,7 +554,7 @@
 
     #[test]
     fn test_namespace_qualifier_format_with_cc_body_nested_namespace() {
-        let ns = create_namespace_qualifier_for_tests(&["foo", "bar", "baz"]);
+        let ns = NamespaceQualifier::new(["foo", "bar", "baz"]);
         assert_cc_matches!(
             ns.format_with_cc_body(quote! { cc body goes here }).unwrap(),
             quote! {
@@ -563,10 +567,10 @@
 
     #[test]
     fn test_format_namespace_bound_cc_tokens() {
-        let top_level = create_namespace_qualifier_for_tests(&[]);
-        let m1 = create_namespace_qualifier_for_tests(&["m1"]);
-        let m2 = create_namespace_qualifier_for_tests(&["m2"]);
-        let input = vec![
+        let top_level = NamespaceQualifier::new::<&str>([]);
+        let m1 = NamespaceQualifier::new(["m1"]);
+        let m2 = NamespaceQualifier::new(["m2"]);
+        let input = [
             (top_level.clone(), quote! { void f0a(); }),
             (m1.clone(), quote! { void f1a(); }),
             (m1.clone(), quote! { void f1b(); }),
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index 9b2cc88..efc6f34 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -872,8 +872,8 @@
         }
     }
 
-    pub fn crate_root_path(&self) -> Option<&str> {
-        self.flat_ir.crate_root_path.as_deref()
+    pub fn crate_root_path(&self) -> Option<Rc<str>> {
+        self.flat_ir.crate_root_path.clone()
     }
 }
 
@@ -934,6 +934,6 @@
         }
         "#;
         let ir = deserialize_ir(input.as_bytes()).unwrap();
-        assert_eq!(ir.crate_root_path(), Some("__cc_template_instantiations_rs_api"));
+        assert_eq!(ir.crate_root_path().as_deref(), Some("__cc_template_instantiations_rs_api"));
     }
 }
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index f50f5ac..169d376 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -1905,7 +1905,7 @@
             }
         }
     }
-    Ok(NamespaceQualifier(namespaces.into_iter().rev().collect_vec()))
+    Ok(NamespaceQualifier::new(namespaces.into_iter().rev()))
 }
 
 /// Generates Rust source code for a given incomplete record declaration.
@@ -2849,46 +2849,33 @@
 /// Qualified path from the root of the crate to the module containing the type.
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct CratePath {
-    idents: Vec<String>,
+    /// `Some("other_crate")` or `None` for paths within the current crate.
+    crate_ident: Option<Ident>,
+
+    crate_root_path: NamespaceQualifier,
+    namespace_qualifier: NamespaceQualifier,
 }
 
 impl CratePath {
     fn new(
         ir: &IR,
-        namespace_qualifier: &NamespaceQualifier,
+        namespace_qualifier: NamespaceQualifier,
         crate_ident: Option<Ident>,
     ) -> CratePath {
-        let namespace_qualifiers = &namespace_qualifier.0;
-        let crate_ident = match crate_ident {
-            Some(ci) => ci.to_string(),
-            None => "crate".to_string(),
-        };
-        let idents = std::iter::once(crate_ident)
-            .chain(ir.crate_root_path().into_iter().map(ToOwned::to_owned))
-            .chain(namespace_qualifiers.iter().map(|s| s.to_string()))
-            .collect();
-        CratePath { idents }
+        let crate_root_path = NamespaceQualifier::new(ir.crate_root_path());
+        CratePath { crate_ident, crate_root_path, namespace_qualifier }
     }
 }
 
 impl ToTokens for CratePath {
     fn to_tokens(&self, tokens: &mut TokenStream) {
-        use quote::TokenStreamExt;
-        for (i, ident) in self.idents.iter().enumerate() {
-            if i > 0 {
-                // Double colon `::`
-                tokens.append(proc_macro2::Punct::new(':', proc_macro2::Spacing::Joint));
-                tokens.append(proc_macro2::Punct::new(':', proc_macro2::Spacing::Alone));
-                tokens.append(make_rs_ident(ident))
-            } else {
-                tokens.append(if ident == "crate" {
-                    // leading token `crate` should be interpreted as a keyword, not escaped.
-                    format_ident!("{}", ident)
-                } else {
-                    make_rs_ident(ident)
-                })
-            }
-        }
+        let crate_ident = match self.crate_ident.as_ref() {
+            None => quote! { crate },
+            Some(ident) => quote! { #ident },
+        };
+        let crate_root_path = self.crate_root_path.format_for_rs();
+        let namespace_qualifier = self.namespace_qualifier.format_for_rs();
+        quote! { #crate_ident :: #crate_root_path #namespace_qualifier }.to_tokens(tokens)
     }
 }
 
@@ -2939,7 +2926,7 @@
     pub fn new_record(record: Rc<Record>, ir: &IR) -> Result<Self> {
         let crate_path = Rc::new(CratePath::new(
             ir,
-            &namespace_qualifier_of_item(record.id, ir)?,
+            namespace_qualifier_of_item(record.id, ir)?,
             rs_imported_crate_name(&record.owning_target, ir),
         ));
         Ok(RsTypeKind::Record { record, crate_path })
@@ -3171,15 +3158,15 @@
             }
             RsTypeKind::IncompleteRecord { incomplete_record, crate_path } => {
                 let record_ident = make_rs_ident(incomplete_record.rs_name.as_ref());
-                quote! { #crate_path :: #record_ident }
+                quote! { #crate_path #record_ident }
             }
             RsTypeKind::Record { record, crate_path } => {
                 let ident = make_rs_ident(record.rs_name.as_ref());
-                quote! { #crate_path :: #ident }
+                quote! { #crate_path #ident }
             }
             RsTypeKind::TypeAlias { type_alias, crate_path, .. } => {
                 let ident = make_rs_ident(&type_alias.identifier.identifier);
-                quote! { #crate_path :: #ident }
+                quote! { #crate_path #ident }
             }
             // This doesn't affect void in function return values, as those are special-cased to be
             // omitted.
@@ -3275,7 +3262,7 @@
                     incomplete_record: incomplete_record.clone(),
                     crate_path: Rc::new(CratePath::new(
                         &ir,
-                        &namespace_qualifier_of_item(incomplete_record.id, &ir)?,
+                        namespace_qualifier_of_item(incomplete_record.id, &ir)?,
                         rs_imported_crate_name(&incomplete_record.owning_target, &ir),
                     )),
                 },
@@ -3290,7 +3277,7 @@
                             type_alias: type_alias.clone(),
                             crate_path: Rc::new(CratePath::new(
                                 &ir,
-                                &namespace_qualifier_of_item(type_alias.id, &ir)?,
+                                namespace_qualifier_of_item(type_alias.id, &ir)?,
                                 rs_imported_crate_name(&type_alias.owning_target, &ir),
                             )),
                             underlying_type: Rc::new(
@@ -3569,11 +3556,9 @@
 }
 
 fn crate_root_path_tokens(ir: &IR) -> TokenStream {
-    if let Some(crate_root_path) = ir.crate_root_path() {
-        let crate_root_path = make_rs_ident(crate_root_path);
-        quote! {crate::#crate_root_path}
-    } else {
-        quote! {crate}
+    match ir.crate_root_path().as_deref().map(make_rs_ident) {
+        None => quote! { crate },
+        Some(crate_root_path) => quote! { crate :: #crate_root_path },
     }
 }