Explicit names for anonymous parameters: `fn foo(_: f64, _: f64)`.

In theory, the names could be omitted in *some* C++ translations (e.g.
`void foo(double, double)`), but they are required in other scenarios:

1. Forwarding an argument to a Rust function with a different name
   (e.g. `#[export_name = ...]` attribute and/or Rust-ABI functions).
   `return ::__crubit_internal::export_name(need_param_name_here)`.

2. Irrefutable, destructuring pattern matching - e.g.:
   `fn foo((x, y): (i32, i32)) {}`
   (this CL doesn't yet provide test coverage for this scenario,
   because types that support destructuring are not yet supported).

PiperOrigin-RevId: 486708734
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs
index 5f8fb68..73d623f 100644
--- a/cc_bindings_from_rs/bindings.rs
+++ b/cc_bindings_from_rs/bindings.rs
@@ -1101,6 +1101,33 @@
     }
 
     #[test]
+    fn test_format_def_fn_export_name_with_anonymous_parameter_names() {
+        let test_src = r#"
+                #[export_name = "export_name"]
+                pub extern "C" fn public_function(_: f64, _: f64) {}
+            "#;
+        test_format_def(test_src, "public_function", |result| {
+            let result = result.expect("Test expects success here");
+            assert!(result.includes.is_empty());
+            assert_cc_matches!(
+                result.api,
+                quote! {
+                    inline void public_function(double __param_0, double __param_1) {
+                        return ::__crubit_internal::export_name(__param_0, __param_1);
+                    }
+                }
+            );
+            assert_cc_matches!(
+                result.internals.expect("This test expects separate extern-C decl"),
+                quote! {
+                    extern "C" void export_name(double __param_0, double __param_1);
+                }
+            );
+        });
+    }
+
+
+    #[test]
     fn test_format_def_unsupported_fn_param_type() {
         let test_src = r#"
                 #[no_mangle]
diff --git a/common/code_gen_utils.rs b/common/code_gen_utils.rs
index 53d618a..e441062 100644
--- a/common/code_gen_utils.rs
+++ b/common/code_gen_utils.rs
@@ -17,6 +17,8 @@
 
 /// Formats a C++ identifier. Panics when `ident` is a C++ reserved keyword.
 pub fn format_cc_ident(ident: &str) -> Result<TokenStream> {
+    ensure!(!ident.is_empty(), "Empty string is not a valid C++ identifier");
+
     // C++ doesn't have an equivalent of
     // https://doc.rust-lang.org/rust-by-example/compatibility/raw_identifiers.html and therefore
     // an error is returned when `ident` is a C++ reserved keyword.
@@ -279,6 +281,13 @@
     }
 
     #[test]
+    fn test_format_cc_ident_empty() {
+        let err = format_cc_ident("").expect_err("This test expects an error");
+        let msg = err.to_string();
+        assert_eq!(msg, "Empty string is not a valid C++ identifier");
+    }
+
+    #[test]
     fn test_format_cc_ident_qualified_identifiers() {
         // https://en.cppreference.com/w/cpp/language/identifiers#Qualified_identifiers