Return an error instead of panicking in `format_cc_ident`.
This will help to gracefully fail when `cc_bindings_from_rs`
needs to format names that are not valid C++ identifiers.
PiperOrigin-RevId: 479331168
diff --git a/common/BUILD b/common/BUILD
index 667cb93..9cce4ab 100644
--- a/common/BUILD
+++ b/common/BUILD
@@ -27,6 +27,7 @@
name = "code_gen_utils",
srcs = ["code_gen_utils.rs"],
deps = [
+ "@crate_index//:anyhow",
"@crate_index//:once_cell",
"@crate_index//:proc-macro2",
],
diff --git a/common/code_gen_utils.rs b/common/code_gen_utils.rs
index 8f05d74..2031372 100644
--- a/common/code_gen_utils.rs
+++ b/common/code_gen_utils.rs
@@ -2,6 +2,7 @@
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+use anyhow::{anyhow, ensure, Result};
use once_cell::sync::Lazy;
use proc_macro2::TokenStream;
use std::collections::HashSet;
@@ -13,17 +14,23 @@
// - `NamespaceQualifier`
/// Formats a C++ identifier. Panics when `ident` is a C++ reserved keyword.
-pub fn format_cc_ident(ident: &str) -> TokenStream {
+pub fn format_cc_ident(ident: &str) -> Result<TokenStream> {
// C++ doesn't have an equivalent of
// https://doc.rust-lang.org/rust-by-example/compatibility/raw_identifiers.html and therefore
- // we panic when `ident` is a C++ reserved keyword.
- assert!(
+ // an error is returned when `ident` is a C++ reserved keyword.
+ ensure!(
!RESERVED_CC_KEYWORDS.contains(ident),
- "The following reserved keyword can't be used as a C++ identifier: {}",
+ "`{}` is a C++ reserved keyword and \
+ can't be used as a C++ identifier in the generated bindings",
ident
);
- ident.parse().unwrap()
+ ident.parse().map_err(
+ // Explicitly mapping the error via `anyhow!`, because `LexError` is not `Sync`
+ // (required for `anyhow::Error` to implement `From<LexError>`) and
+ // therefore we can't just use `?`.
+ |lex_error| anyhow!("Can't format `{ident}` as a C++ identifier: {lex_error}"),
+ )
}
static RESERVED_CC_KEYWORDS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
@@ -139,17 +146,77 @@
#[test]
fn test_format_cc_ident_basic() {
- assert_cc_matches!(format_cc_ident("foo"), quote! { foo });
+ assert_cc_matches!(
+ format_cc_ident("foo").expect("No errors expected in this test"),
+ quote! { foo }
+ );
}
#[test]
fn test_format_cc_ident_reserved_rust_keyword() {
- assert_cc_matches!(format_cc_ident("impl"), quote! { impl });
+ assert_cc_matches!(
+ format_cc_ident("impl").expect("No errors expected in this test"),
+ quote! { impl }
+ );
}
#[test]
- #[should_panic(expected = "can't be used as a C++ identifier: reinterpret_cast")]
fn test_format_cc_ident_reserved_cc_keyword() {
- format_cc_ident("reinterpret_cast");
+ let err = format_cc_ident("reinterpret_cast").expect_err("This test expects an error");
+ let msg = err.to_string();
+ assert!(msg.contains("`reinterpret_cast`"));
+ assert!(msg.contains("C++ reserved keyword"));
+ }
+
+ #[test]
+ fn test_format_cc_ident_unfinished_group() {
+ let err = format_cc_ident("(foo") // No closing `)`.
+ .expect_err("This test expects an error");
+ let msg = err.to_string();
+ assert!(msg.contains("Can't format `(foo` as a C++ identifier"));
+ assert!(msg.contains("cannot parse"));
+ }
+
+ #[test]
+ fn test_format_cc_ident_unqualified_identifiers() {
+ // https://en.cppreference.com/w/cpp/language/identifiers#Unqualified_identifiers
+
+ // These may appear in `IR::Func::name`.
+ assert_cc_matches!(
+ format_cc_ident("operator==").expect("No errors expected in this test"),
+ quote! { operator== }
+ );
+ assert_cc_matches!(
+ format_cc_ident("operator new").expect("No errors expected in this test"),
+ quote! { operator new }
+ );
+
+ // This may appear in `IR::Record::cc_name` (although in practice these will
+ // be namespace-qualified most of the time).
+ assert_cc_matches!(
+ format_cc_ident("MyTemplate<int>").expect("No errors expected in this test"),
+ quote! { MyTemplate<int> }
+ );
+
+ // These forms of unqualified identifiers are not used by Crubit in practice,
+ assert_cc_matches!(
+ format_cc_ident("~MyClass").expect("No errors expected in this test"),
+ quote! { ~MyClass }
+ );
+ assert_cc_matches!(
+ format_cc_ident(r#" operator "" _km "#).expect("No errors expected in this test"),
+ quote! { operator "" _km }
+ );
+ }
+
+ #[test]
+ fn test_format_cc_ident_qualified_identifiers() {
+ // https://en.cppreference.com/w/cpp/language/identifiers#Qualified_identifiers
+
+ // This may appear in `IR::Record::cc_name`.
+ assert_cc_matches!(
+ format_cc_ident("std::vector<int>").expect("No errors expected in this test"),
+ quote! { std::vector<int> }
+ );
}
}
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index faea7e1..8afe0d8 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -3,7 +3,6 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
use arc_anyhow::{anyhow, bail, ensure, Context, Result};
-use code_gen_utils::format_cc_ident;
use ffi_types::*;
use ir::*;
use itertools::Itertools;
@@ -2466,6 +2465,11 @@
}
}
+/// 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")
+}
+
/// Returns Some(crate_ident) if this is an imported crate.
fn rs_imported_crate_name(owning_target: &BazelLabel, ir: &IR) -> Option<Ident> {
if ir.is_current_target(owning_target) || ir.is_stdlib_target(owning_target) {