Move `make_rs_ident` to the `common/code_gen_utils` crate.

PiperOrigin-RevId: 488667812
diff --git a/common/BUILD b/common/BUILD
index eeab30b..d9a0a35 100644
--- a/common/BUILD
+++ b/common/BUILD
@@ -31,6 +31,7 @@
         "@crate_index//:once_cell",
         "@crate_index//:proc-macro2",
         "@crate_index//:quote",
+        "@crate_index//:syn",
     ],
 )
 
diff --git a/common/code_gen_utils.rs b/common/code_gen_utils.rs
index e441062..2c9d878 100644
--- a/common/code_gen_utils.rs
+++ b/common/code_gen_utils.rs
@@ -4,8 +4,8 @@
 
 use anyhow::{anyhow, ensure, Result};
 use once_cell::sync::Lazy;
-use proc_macro2::TokenStream;
-use quote::{quote, ToTokens};
+use proc_macro2::{Ident, TokenStream};
+use quote::{format_ident, quote, ToTokens};
 use std::collections::{BTreeSet, HashSet};
 use std::rc::Rc;
 
@@ -15,7 +15,8 @@
 // - `make_rs_ident`
 // - `NamespaceQualifier`
 
-/// Formats a C++ identifier. Panics when `ident` is a C++ reserved keyword.
+/// Formats a C++ identifier. Returns an error when `ident` is a C++ reserved
+/// keyword or is an invalid identifier.
 pub fn format_cc_ident(ident: &str) -> Result<TokenStream> {
     ensure!(!ident.is_empty(), "Empty string is not a valid C++ identifier");
 
@@ -36,6 +37,20 @@
     )
 }
 
+/// Makes an 'Ident' to be used in the Rust source code. Escapes Rust keywords.
+/// Panics if `ident` is empty or is otherwise an invalid identifier.
+pub fn make_rs_ident(ident: &str) -> Ident {
+    // TODO(https://github.com/dtolnay/syn/pull/1098): Remove the hardcoded list once syn recognizes
+    // 2018 and 2021 keywords.
+    if ["async", "await", "try", "dyn"].contains(&ident) {
+        return format_ident!("r#{}", ident);
+    }
+    match syn::parse_str::<syn::Ident>(ident) {
+        Ok(_) => format_ident!("{}", ident),
+        Err(_) => format_ident!("r#{}", ident),
+    }
+}
+
 /// `CcInclude` represents a single `#include ...` directive in C++.
 #[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
 pub enum CcInclude {
@@ -212,7 +227,7 @@
 pub mod tests {
     use super::*;
     use quote::quote;
-    use token_stream_matchers::assert_cc_matches;
+    use token_stream_matchers::{assert_cc_matches, assert_rs_matches};
     use token_stream_printer::cc_tokens_to_formatted_string;
 
     #[test]
@@ -299,6 +314,36 @@
     }
 
     #[test]
+    fn test_make_rs_ident_basic() {
+        let id = make_rs_ident("foo");
+        assert_rs_matches!(quote! { #id }, quote! { foo });
+    }
+
+    #[test]
+    fn test_make_rs_ident_reserved_cc_keyword() {
+        let id = make_rs_ident("reinterpret_cast");
+        assert_rs_matches!(quote! { #id }, quote! { reinterpret_cast });
+    }
+
+    #[test]
+    fn test_make_rs_ident_reserved_rust_keyword() {
+        let id = make_rs_ident("impl");
+        assert_rs_matches!(quote! { #id }, quote! { r#impl });
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_make_rs_ident_unfinished_group() {
+        make_rs_ident("(foo"); // No closing `)`.
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_make_rs_ident_empty() {
+        make_rs_ident("");
+    }
+
+    #[test]
     fn test_cc_include_to_tokens_for_system_header() {
         let include = CcInclude::cstddef();
         assert_cc_matches!(
diff --git a/rs_bindings_from_cc/BUILD b/rs_bindings_from_cc/BUILD
index d41dbc0..308bf8b 100644
--- a/rs_bindings_from_cc/BUILD
+++ b/rs_bindings_from_cc/BUILD
@@ -309,7 +309,6 @@
         "@crate_index//:quote",
         "@crate_index//:serde",
         "@crate_index//:serde_json",
-        "@crate_index//:syn",
     ],
 )
 
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index b57c20c..f25d295 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
 use arc_anyhow::{Context, Result};
-use code_gen_utils::{format_cc_includes, CcInclude};
+use code_gen_utils::{format_cc_includes, make_rs_ident, CcInclude};
 use error_report::{anyhow, bail, ensure, ErrorReport, ErrorReporting, IgnoreErrors};
 use ffi_types::*;
 use ir::*;
@@ -2766,19 +2766,6 @@
     })
 }
 
-/// Makes an 'Ident' to be used in the Rust source code. Escapes Rust keywords.
-fn make_rs_ident(ident: &str) -> Ident {
-    // TODO(https://github.com/dtolnay/syn/pull/1098): Remove the hardcoded list once syn recognizes
-    // 2018 and 2021 keywords.
-    if ["async", "await", "try", "dyn"].contains(&ident) {
-        return format_ident!("r#{}", ident);
-    }
-    match syn::parse_str::<syn::Ident>(ident) {
-        Ok(_) => format_ident!("{}", ident),
-        Err(_) => format_ident!("r#{}", ident),
-    }
-}
-
 /// Formats a C++ identifier.  Panics if `ident` is a C++ reserved keyword.
 fn format_cc_ident(ident: &str) -> TokenStream {
     code_gen_utils::format_cc_ident(ident).expect("IR should only contain valid C++ identifiers")