Implement error reporting for the bindings generator.
This feature aggregates nonfatal errors encountered while generating bindings and writes a JSON report to a file. It can be activated by passing `--//path/to/crubit/bazel_support:generate_error_report` to a Bazel build that generates bindings. The error report is named `${cc_library}_rust_api_error_report.json` and appears in the output directory alongside the generated bindings.
PiperOrigin-RevId: 481263077
diff --git a/common/arc_anyhow.rs b/common/arc_anyhow.rs
index 159ec68..7ed7be9 100644
--- a/common/arc_anyhow.rs
+++ b/common/arc_anyhow.rs
@@ -54,6 +54,13 @@
{
self.into_anyhow().context(context).into()
}
+
+ pub fn downcast_ref<E>(&self) -> Option<&E>
+ where
+ E: Display + Debug + Send + Sync + 'static,
+ {
+ self.0.downcast_ref()
+ }
}
impl PartialEq for Error {
diff --git a/rs_bindings_from_cc/BUILD b/rs_bindings_from_cc/BUILD
index 9b79269..43a7068 100644
--- a/rs_bindings_from_cc/BUILD
+++ b/rs_bindings_from_cc/BUILD
@@ -290,6 +290,7 @@
deps = [
"//common:arc_anyhow",
"@crate_index//:itertools",
+ "@crate_index//:once_cell",
"@crate_index//:proc-macro2",
"@crate_index//:quote",
"@crate_index//:serde",
@@ -382,6 +383,7 @@
name = "src_code_gen_impl",
srcs = ["src_code_gen.rs"],
deps = [
+ ":error_report",
":ir",
"//common:arc_anyhow",
"//common:code_gen_utils",
@@ -411,6 +413,25 @@
],
)
+rust_library(
+ name = "error_report",
+ srcs = ["error_report.rs"],
+ deps = [
+ "//common:arc_anyhow",
+ "@crate_index//:anyhow",
+ "@crate_index//:serde",
+ "@crate_index//:serde_json",
+ ],
+)
+
+rust_test(
+ name = "error_report_test",
+ crate = ":error_report",
+ deps = [
+ "@crate_index//:serde_json",
+ ],
+)
+
cc_library(
name = "ast_convert",
srcs = ["ast_convert.cc"],
diff --git a/rs_bindings_from_cc/bazel_support/BUILD b/rs_bindings_from_cc/bazel_support/BUILD
index 2477435..0f69d18 100644
--- a/rs_bindings_from_cc/bazel_support/BUILD
+++ b/rs_bindings_from_cc/bazel_support/BUILD
@@ -164,3 +164,9 @@
deps_for_bindings(
name = "empty_deps",
)
+
+bool_flag(
+ name = "generate_error_report",
+ build_setting_default = False,
+ visibility = ["//visibility:public"],
+)
diff --git a/rs_bindings_from_cc/bazel_support/generate_bindings.bzl b/rs_bindings_from_cc/bazel_support/generate_bindings.bzl
index eee086c..8ecb82d 100644
--- a/rs_bindings_from_cc/bazel_support/generate_bindings.bzl
+++ b/rs_bindings_from_cc/bazel_support/generate_bindings.bzl
@@ -9,6 +9,7 @@
"""
load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")
+load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
def _get_hdrs_command_line(hdrs):
return ["--public_headers=" + ",".join([x.path for x in hdrs])]
@@ -43,11 +44,34 @@
extra_rs_srcs: A list of extra source files to add.
Returns:
- tuple(cc_output, rs_output): The generated source files.
+ tuple(cc_output, rs_output, namespaces_output, error_report_output): The generated source files.
"""
cc_output = ctx.actions.declare_file(ctx.label.name + "_rust_api_impl.cc")
rs_output = ctx.actions.declare_file(ctx.label.name + "_rust_api.rs")
namespaces_output = ctx.actions.declare_file(ctx.label.name + "_namespaces.json")
+ error_report_output = None
+
+ rs_bindings_from_cc_flags = [
+ "--stderrthreshold=2",
+ "--rs_out",
+ rs_output.path,
+ "--cc_out",
+ cc_output.path,
+ "--namespaces_out",
+ namespaces_output.path,
+ "--crubit_support_path",
+ "rs_bindings_from_cc/support",
+ "--rustfmt_exe_path",
+ "third_party/unsupported_toolchains/rust/toolchains/nightly/bin/rustfmt",
+ "--rustfmt_config_path",
+ "nowhere/rustfmt.toml",
+ ]
+ if ctx.attr._generate_error_report[BuildSettingInfo].value:
+ error_report_output = ctx.actions.declare_file(ctx.label.name + "_rust_api_error_report.json")
+ rs_bindings_from_cc_flags += [
+ "--error_report_out",
+ error_report_output.path,
+ ]
variables = cc_common.create_compile_variables(
feature_configuration = feature_configuration,
@@ -73,21 +97,7 @@
preprocessor_defines = compilation_context.defines,
variables_extension = {
"rs_bindings_from_cc_tool": ctx.executable._generator.path,
- "rs_bindings_from_cc_flags": [
- "--stderrthreshold=2",
- "--rs_out",
- rs_output.path,
- "--cc_out",
- cc_output.path,
- "--namespaces_out",
- namespaces_output.path,
- "--crubit_support_path",
- "rs_bindings_from_cc/support",
- "--rustfmt_exe_path",
- "third_party/unsupported_toolchains/rust/toolchains/nightly/bin/rustfmt",
- "--rustfmt_config_path",
- "nowhere/rustfmt.toml",
- ] + _get_hdrs_command_line(public_hdrs) + _get_extra_rs_srcs_command_line(extra_rs_srcs),
+ "rs_bindings_from_cc_flags": rs_bindings_from_cc_flags + _get_hdrs_command_line(public_hdrs) + _get_extra_rs_srcs_command_line(extra_rs_srcs),
"targets_and_headers": targets_and_headers,
},
)
@@ -109,7 +119,7 @@
] + ctx.files._rustfmt_cfg + extra_rs_srcs,
transitive = [action_inputs],
),
- additional_outputs = [rs_output, namespaces_output],
+ additional_outputs = [x for x in [rs_output, namespaces_output, error_report_output] if x != None],
variables = variables,
)
- return (cc_output, rs_output, namespaces_output)
+ return (cc_output, rs_output, namespaces_output, error_report_output)
diff --git a/rs_bindings_from_cc/bazel_support/rust_bindings_from_cc_utils.bzl b/rs_bindings_from_cc/bazel_support/rust_bindings_from_cc_utils.bzl
index 77efc12..13983dc 100644
--- a/rs_bindings_from_cc/bazel_support/rust_bindings_from_cc_utils.bzl
+++ b/rs_bindings_from_cc/bazel_support/rust_bindings_from_cc_utils.bzl
@@ -59,7 +59,7 @@
unsupported_features = ctx.disabled_features + ["module_maps"],
)
- cc_output, rs_output, namespaces_output = generate_bindings(
+ cc_output, rs_output, namespaces_output, error_report_output = generate_bindings(
ctx = ctx,
attr = attr,
cc_toolchain = cc_toolchain,
@@ -111,7 +111,7 @@
rust_file = rs_output,
namespaces_file = namespaces_output,
),
- OutputGroupInfo(out = depset([cc_output, rs_output, namespaces_output])),
+ OutputGroupInfo(out = depset([x for x in [cc_output, rs_output, namespaces_output, error_report_output] if x != None])),
]
bindings_attrs = {
@@ -158,4 +158,7 @@
"_builtin_hdrs": attr.label(
default = "//rs_bindings_from_cc:builtin_headers",
),
+ "_generate_error_report": attr.label(
+ default = "//rs_bindings_from_cc/bazel_support:generate_error_report",
+ ),
}
diff --git a/rs_bindings_from_cc/cmdline.cc b/rs_bindings_from_cc/cmdline.cc
index 461ccaf..0cef578 100644
--- a/rs_bindings_from_cc/cmdline.cc
+++ b/rs_bindings_from_cc/cmdline.cc
@@ -63,6 +63,8 @@
ABSL_FLAG(std::string, namespaces_out, "",
"(optional) output path for the JSON file containing the target's"
"namespace hierarchy.");
+ABSL_FLAG(std::string, error_report_out, "",
+ "(optional) output path for the JSON error report");
namespace crubit {
@@ -92,7 +94,8 @@
absl::GetFlag(FLAGS_targets_and_headers),
absl::GetFlag(FLAGS_extra_rs_srcs),
absl::GetFlag(FLAGS_srcs_to_scan_for_instantiations),
- absl::GetFlag(FLAGS_instantiations_out));
+ absl::GetFlag(FLAGS_instantiations_out),
+ absl::GetFlag(FLAGS_error_report_out));
}
absl::StatusOr<Cmdline> Cmdline::CreateFromArgs(
@@ -102,7 +105,7 @@
bool do_nothing, std::vector<std::string> public_headers,
std::string targets_and_headers_str, std::vector<std::string> extra_rs_srcs,
std::vector<std::string> srcs_to_scan_for_instantiations,
- std::string instantiations_out) {
+ std::string instantiations_out, std::string error_report_out) {
Cmdline cmdline;
if (rs_out.empty()) {
@@ -149,6 +152,7 @@
cmdline.instantiations_out_ = std::move(instantiations_out);
cmdline.srcs_to_scan_for_instantiations_ =
std::move(srcs_to_scan_for_instantiations);
+ cmdline.error_report_out_ = std::move(error_report_out);
if (targets_and_headers_str.empty()) {
return absl::InvalidArgumentError("please specify --targets_and_headers");
diff --git a/rs_bindings_from_cc/cmdline.h b/rs_bindings_from_cc/cmdline.h
index 7b6282d..af89c97 100644
--- a/rs_bindings_from_cc/cmdline.h
+++ b/rs_bindings_from_cc/cmdline.h
@@ -32,14 +32,14 @@
std::string targets_and_headers_str,
std::vector<std::string> extra_rs_sources,
std::vector<std::string> srcs_to_scan_for_instantiations,
- std::string instantiations_out) {
+ std::string instantiations_out, std::string error_report_out) {
return CreateFromArgs(
std::move(cc_out), std::move(rs_out), std::move(ir_out),
std::move(namespaces_out), std::move(crubit_support_path),
std::move(rustfmt_exe_path), std::move(rustfmt_config_path), do_nothing,
std::move(public_headers), std::move(targets_and_headers_str),
std::move(extra_rs_sources), std::move(srcs_to_scan_for_instantiations),
- std::move(instantiations_out));
+ std::move(instantiations_out), std::move(error_report_out));
}
Cmdline(const Cmdline&) = delete;
@@ -55,6 +55,7 @@
absl::string_view rustfmt_exe_path() const { return rustfmt_exe_path_; }
absl::string_view rustfmt_config_path() const { return rustfmt_config_path_; }
absl::string_view instantiations_out() const { return instantiations_out_; }
+ absl::string_view error_report_out() const { return error_report_out_; }
bool do_nothing() const { return do_nothing_; }
const std::vector<HeaderName>& public_headers() const {
@@ -87,7 +88,7 @@
std::string targets_and_headers_str,
std::vector<std::string> extra_rs_sources,
std::vector<std::string> srcs_to_scan_for_instantiations,
- std::string instantiations_out);
+ std::string instantiations_out, std::string error_report_out);
absl::StatusOr<BazelLabel> FindHeader(const HeaderName& header) const;
@@ -97,6 +98,7 @@
std::string crubit_support_path_;
std::string rustfmt_exe_path_;
std::string rustfmt_config_path_;
+ std::string error_report_out_;
bool do_nothing_ = true;
BazelLabel current_target_;
diff --git a/rs_bindings_from_cc/cmdline_test.cc b/rs_bindings_from_cc/cmdline_test.cc
index c891f12..4bc7752 100644
--- a/rs_bindings_from_cc/cmdline_test.cc
+++ b/rs_bindings_from_cc/cmdline_test.cc
@@ -33,7 +33,8 @@
std::move(targets_and_headers),
/* extra_rs_srcs= */ {},
/* srcs_to_scan_for_instantiations= */ {},
- /*instantiations_out=*/"");
+ /* instantiations_out= */ "",
+ /* error_report_out= */ "");
}
} // namespace
@@ -46,7 +47,8 @@
"rustfmt_exe_path", "rustfmt_config_path",
/* do_nothing= */ false, {"h1"},
R"([{"t": "t1", "h": ["h1", "h2"]}])", {"extra_file.rs"},
- {"scan_for_instantiations.rs"}, "instantiations_out"));
+ {"scan_for_instantiations.rs"}, "instantiations_out",
+ "error_report_out"));
EXPECT_EQ(cmdline.cc_out(), "cc_out");
EXPECT_EQ(cmdline.rs_out(), "rs_out");
EXPECT_EQ(cmdline.ir_out(), "ir_out");
@@ -55,6 +57,7 @@
EXPECT_EQ(cmdline.rustfmt_exe_path(), "rustfmt_exe_path");
EXPECT_EQ(cmdline.rustfmt_config_path(), "rustfmt_config_path");
EXPECT_EQ(cmdline.instantiations_out(), "instantiations_out");
+ EXPECT_EQ(cmdline.error_report_out(), "error_report_out");
EXPECT_EQ(cmdline.do_nothing(), false);
EXPECT_EQ(cmdline.current_target().value(), "t1");
EXPECT_THAT(cmdline.public_headers(), ElementsAre(HeaderName("h1")));
@@ -205,7 +208,7 @@
"rustfmt_exe_path", "rustfmt_config_path",
/* do_nothing= */ false, {"a.h"}, std::string(kTargetsAndHeaders),
/* extra_rs_srcs= */ {}, {"lib.rs"},
- /* instantiations_out= */ "")),
+ /* instantiations_out= */ "", "error_report_out")),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr(
@@ -223,7 +226,8 @@
"rustfmt_exe_path", "rustfmt_config_path",
/* do_nothing= */ false, {"a.h"}, std::string(kTargetsAndHeaders),
/* extra_rs_srcs= */ {},
- /* srcs_to_scan_for_instantiations= */ {}, "instantiations_out"),
+ /* srcs_to_scan_for_instantiations= */ {}, "instantiations_out",
+ "error_report_out"),
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr(
@@ -242,7 +246,7 @@
/* do_nothing= */ false, {"a.h"}, std::string(kTargetsAndHeaders),
/* extra_rs_srcs= */ {},
/* srcs_to_scan_for_instantiations= */ {},
- /* instantiations_out= */ ""),
+ /* instantiations_out= */ "", "error_report_out"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("please specify --cc_out")));
}
@@ -258,7 +262,7 @@
/* do_nothing= */ false, {"a.h"}, std::string(kTargetsAndHeaders),
/* extra_rs_srcs= */ {},
/* srcs_to_scan_for_instantiations= */ {},
- /* instantiations_out= */ ""),
+ /* instantiations_out= */ "", "error_report_out"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("please specify --rs_out")));
}
@@ -273,7 +277,7 @@
/* do_nothing= */ false, {"a.h"}, std::string(kTargetsAndHeaders),
/* extra_rs_srcs= */ {},
/* srcs_to_scan_for_instantiations= */ {},
- /* instantiations_out= */ ""));
+ /* instantiations_out= */ "", "error_report_out"));
}
TEST(CmdlineTest, RustfmtExePathEmpty) {
@@ -287,7 +291,7 @@
/* do_nothing= */ false, {"a.h"}, std::string(kTargetsAndHeaders),
/* extra_rs_srcs= */ {},
/* srcs_to_scan_for_instantiations= */ {},
- /* instantiations_out= */ ""),
+ /* instantiations_out= */ "", "error_report_out"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("please specify --rustfmt_exe_path")));
}
diff --git a/rs_bindings_from_cc/error_report.rs b/rs_bindings_from_cc/error_report.rs
new file mode 100644
index 0000000..2386d32
--- /dev/null
+++ b/rs_bindings_from_cc/error_report.rs
@@ -0,0 +1,359 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+use std::borrow::Cow;
+use std::collections::BTreeMap;
+
+use serde::Serialize;
+
+#[doc(hidden)]
+pub mod macro_internal {
+ use std::borrow::Cow;
+ use std::fmt::{self, Arguments, Display, Formatter};
+
+ pub use anyhow;
+ pub use arc_anyhow;
+ pub use std::format_args;
+
+ /// An error that stores its format string as well as the formatted message.
+ #[derive(Debug, Clone)]
+ pub struct AttributedError {
+ pub fmt: Cow<'static, str>,
+ pub message: Cow<'static, str>,
+ }
+
+ impl AttributedError {
+ pub fn new_static(fmt: &'static str, args: Arguments) -> arc_anyhow::Error {
+ arc_anyhow::Error::from(anyhow::Error::from(match args.as_str() {
+ // This format string has no parameters.
+ Some(s) => Self { fmt: Cow::Borrowed(s), message: Cow::Borrowed(s) },
+ // This format string has parameters and must be formatted.
+ None => Self { fmt: Cow::Borrowed(fmt), message: Cow::Owned(fmt::format(args)) },
+ }))
+ }
+
+ pub fn new_dynamic(err: impl Display) -> arc_anyhow::Error {
+ // Use the whole error as the format string. This is preferable to
+ // grouping all dynamic errors under the "{}" format string.
+ let message = format!("{}", err);
+ arc_anyhow::Error::from(anyhow::Error::from(Self {
+ fmt: Cow::Owned(message.clone()),
+ message: Cow::Owned(message),
+ }))
+ }
+ }
+
+ impl Display for AttributedError {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "{}", self.message)
+ }
+ }
+
+ impl std::error::Error for AttributedError {}
+}
+
+use crate::macro_internal::AttributedError;
+
+/// Evaluates to an [`arc_anyhow::Error`].
+///
+/// Otherwise similar to [`anyhow::anyhow`].
+#[macro_export]
+macro_rules! anyhow {
+ ($fmt:literal $(,)?) => {
+ $crate::macro_internal::AttributedError::new_static(
+ $fmt,
+ $crate::macro_internal::format_args!($fmt),
+ )
+ };
+ ($err:expr $(,)?) => {
+ $crate::macro_internal::AttributedError::new_dynamic($err)
+ };
+ ($fmt:expr, $($arg:tt)*) => {
+ $crate::macro_internal::AttributedError::new_static(
+ $fmt,
+ $crate::macro_internal::format_args!($fmt, $($arg)*),
+ )
+ };
+}
+
+/// Returns a [`Result::Err`] containing an [`arc_anyhow::Error`].
+///
+/// Otherwise similar to [`anyhow::bail`].
+#[macro_export]
+macro_rules! bail {
+ ($fmt:literal $(,)?) => {
+ return Err($crate::anyhow!($fmt))
+ };
+ ($err:expr $(,)?) => {
+ return Err($crate::anyhow!($err))
+ };
+ ($fmt:expr, $($arg:tt)*) => {
+ return Err($crate::anyhow!($fmt, $($arg)*))
+ };
+}
+
+/// Returns a [`Result::Err`] containing an [`arc_anyhow::Error`] if the given
+/// condition evaluates to false.
+///
+/// Otherwise similar to [`anyhow::ensure`].
+#[macro_export]
+macro_rules! ensure {
+ ($cond:expr, $fmt:literal $(,)?) => {
+ if !$cond { bail!($fmt); }
+ };
+ ($cond:expr, $err:expr $(,)?) => {
+ if !$cond { bail!($err); }
+ };
+ ($cond:expr, $fmt:expr, $($arg:tt)*) => {
+ if !$cond { bail!($fmt, $($arg)*); }
+ };
+}
+
+pub trait ErrorReporting {
+ fn insert(&mut self, error: &arc_anyhow::Error);
+ fn serialize_to_vec(&self) -> anyhow::Result<Vec<u8>>;
+}
+
+/// A null [`ErrorReporting`] strategy.
+pub struct IgnoreErrors;
+
+impl ErrorReporting for IgnoreErrors {
+ fn insert(&mut self, _error: &arc_anyhow::Error) {}
+
+ fn serialize_to_vec(&self) -> anyhow::Result<Vec<u8>> {
+ Ok(vec![])
+ }
+}
+
+/// An aggregate of zero or more errors.
+#[derive(Default, Serialize)]
+pub struct ErrorReport {
+ #[serde(flatten)]
+ map: BTreeMap<Cow<'static, str>, ErrorReportEntry>,
+}
+
+impl ErrorReport {
+ pub fn new() -> Self {
+ Self::default()
+ }
+}
+
+impl ErrorReporting for ErrorReport {
+ fn insert(&mut self, error: &arc_anyhow::Error) {
+ if let Some(error) = error.downcast_ref::<AttributedError>() {
+ let sample_message = if error.message != error.fmt { &*error.message } else { "" };
+ self.map.entry(error.fmt.clone()).or_default().add(Cow::Borrowed(sample_message));
+ } else {
+ self.map.entry(Cow::Borrowed("{}")).or_default().add(Cow::Owned(format!("{error}")));
+ }
+ }
+
+ fn serialize_to_vec(&self) -> anyhow::Result<Vec<u8>> {
+ Ok(serde_json::to_vec(self)?)
+ }
+}
+
+#[derive(Default, Serialize)]
+struct ErrorReportEntry {
+ count: u64,
+ #[serde(skip_serializing_if = "String::is_empty")]
+ sample_message: String,
+}
+
+impl ErrorReportEntry {
+ fn add(&mut self, message: Cow<str>) {
+ if self.count == 0 {
+ self.sample_message = message.into_owned();
+ }
+ self.count += 1;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::borrow::Cow;
+
+ #[test]
+ fn anyhow_1arg_static_plain() {
+ let arc_err = anyhow!("abc");
+ let err: &AttributedError = arc_err.downcast_ref().unwrap();
+ assert!(matches!(err.fmt, Cow::Borrowed(_)));
+ assert_eq!(err.fmt, "abc");
+ assert!(matches!(err.message, Cow::Borrowed(_)));
+ assert_eq!(err.message, "abc");
+ }
+
+ #[test]
+ fn anyhow_1arg_static_fmt() {
+ let some_var = "def";
+ let arc_err = anyhow!("abc{some_var}");
+ let err: &AttributedError = arc_err.downcast_ref().unwrap();
+ assert!(matches!(err.fmt, Cow::Borrowed(_)));
+ assert_eq!(err.fmt, "abc{some_var}");
+ assert!(matches!(err.message, Cow::Owned(_)));
+ assert_eq!(err.message, "abcdef");
+ }
+
+ #[test]
+ fn anyhow_1arg_dynamic() {
+ let arc_err = anyhow!(format!("abc{}", "def"));
+ let err: &AttributedError = arc_err.downcast_ref().unwrap();
+ assert!(matches!(err.fmt, Cow::Owned(_)));
+ assert_eq!(err.fmt, "abcdef");
+ assert!(matches!(err.message, Cow::Owned(_)));
+ assert_eq!(err.message, "abcdef");
+ }
+
+ #[test]
+ fn anyhow_2arg() {
+ let arc_err = anyhow!("abc{}", "def");
+ let err: &AttributedError = arc_err.downcast_ref().unwrap();
+ assert!(matches!(err.fmt, Cow::Borrowed(_)));
+ assert_eq!(err.fmt, "abc{}");
+ assert!(matches!(err.message, Cow::Owned(_)));
+ assert_eq!(err.message, "abcdef");
+ }
+
+ #[test]
+ fn bail_1arg_static_plain() {
+ let arc_err = (|| -> arc_anyhow::Result<()> { bail!("abc") })().unwrap_err();
+ let err: &AttributedError = arc_err.downcast_ref().unwrap();
+ assert!(matches!(err.fmt, Cow::Borrowed(_)));
+ assert_eq!(err.fmt, "abc");
+ assert!(matches!(err.message, Cow::Borrowed(_)));
+ assert_eq!(err.message, "abc");
+ }
+
+ #[test]
+ fn bail_1arg_static_fmt() {
+ let some_var = "def";
+ let arc_err = (|| -> arc_anyhow::Result<()> { bail!("abc{some_var}") })().unwrap_err();
+ let err: &AttributedError = arc_err.downcast_ref().unwrap();
+ assert!(matches!(err.fmt, Cow::Borrowed(_)));
+ assert_eq!(err.fmt, "abc{some_var}");
+ assert!(matches!(err.message, Cow::Owned(_)));
+ assert_eq!(err.message, "abcdef");
+ }
+
+ #[test]
+ fn bail_1arg_dynamic() {
+ let arc_err =
+ (|| -> arc_anyhow::Result<()> { bail!(format!("abc{}", "def")) })().unwrap_err();
+ let err: &AttributedError = arc_err.downcast_ref().unwrap();
+ assert!(matches!(err.fmt, Cow::Owned(_)));
+ assert_eq!(err.fmt, "abcdef");
+ assert!(matches!(err.message, Cow::Owned(_)));
+ assert_eq!(err.message, "abcdef");
+ }
+
+ #[test]
+ fn bail_2arg() {
+ let arc_err = (|| -> arc_anyhow::Result<()> { bail!("abc{}", "def") })().unwrap_err();
+ let err: &AttributedError = arc_err.downcast_ref().unwrap();
+ assert!(matches!(err.fmt, Cow::Borrowed(_)));
+ assert_eq!(err.fmt, "abc{}");
+ assert!(matches!(err.message, Cow::Owned(_)));
+ assert_eq!(err.message, "abcdef");
+ }
+
+ #[test]
+ fn ensure_pass() {
+ let f = || {
+ ensure!(true, "unused message");
+ Ok(())
+ };
+ f().unwrap();
+ }
+
+ #[test]
+ fn ensure_fail_1arg_static_plain() {
+ let arc_err = (|| {
+ ensure!(false, "abc");
+ Ok(())
+ })()
+ .unwrap_err();
+ let err: &AttributedError = arc_err.downcast_ref().unwrap();
+ assert!(matches!(err.fmt, Cow::Borrowed(_)));
+ assert_eq!(err.fmt, "abc");
+ assert!(matches!(err.message, Cow::Borrowed(_)));
+ assert_eq!(err.message, "abc");
+ }
+
+ #[test]
+ fn ensure_fail_1arg_static_fmt() {
+ let some_var = "def";
+ let arc_err = (|| {
+ ensure!(false, "abc{some_var}");
+ Ok(())
+ })()
+ .unwrap_err();
+ let err: &AttributedError = arc_err.downcast_ref().unwrap();
+ assert!(matches!(err.fmt, Cow::Borrowed(_)));
+ assert_eq!(err.fmt, "abc{some_var}");
+ assert!(matches!(err.message, Cow::Owned(_)));
+ assert_eq!(err.message, "abcdef");
+ }
+
+ #[test]
+ fn ensure_fail_1arg_dynamic() {
+ let arc_err = (|| {
+ ensure!(false, format!("abc{}", "def"));
+ Ok(())
+ })()
+ .unwrap_err();
+ let err: &AttributedError = arc_err.downcast_ref().unwrap();
+ assert!(matches!(err.fmt, Cow::Owned(_)));
+ assert_eq!(err.fmt, "abcdef");
+ assert!(matches!(err.message, Cow::Owned(_)));
+ assert_eq!(err.message, "abcdef");
+ }
+
+ #[test]
+ fn ensure_fail_2arg() {
+ let arc_err = (|| {
+ ensure!(false, "abc{}", "def");
+ Ok(())
+ })()
+ .unwrap_err();
+ let err: &AttributedError = arc_err.downcast_ref().unwrap();
+ assert!(matches!(err.fmt, Cow::Borrowed(_)));
+ assert_eq!(err.fmt, "abc{}");
+ assert!(matches!(err.message, Cow::Owned(_)));
+ assert_eq!(err.message, "abcdef");
+ }
+
+ #[test]
+ fn error_report() {
+ let mut report = ErrorReport::new();
+ report.insert(&anyhow!("abc{}", "def"));
+ report.insert(&anyhow!("abc{}", "123"));
+ report.insert(&anyhow!("error code: {}", 65535));
+ report.insert(&anyhow!("no parameters"));
+ report.insert(&anyhow!("no parameters"));
+ report.insert(&anyhow!("no parameters"));
+ report.insert(&anyhow::Error::msg("not attributed").into());
+
+ assert_eq!(
+ serde_json::to_string_pretty(&report).unwrap(),
+ r#"{
+ "abc{}": {
+ "count": 2,
+ "sample_message": "abcdef"
+ },
+ "error code: {}": {
+ "count": 1,
+ "sample_message": "error code: 65535"
+ },
+ "no parameters": {
+ "count": 3
+ },
+ "{}": {
+ "count": 1,
+ "sample_message": "not attributed"
+ }
+}"#,
+ );
+ }
+}
diff --git a/rs_bindings_from_cc/generate_bindings_and_metadata.cc b/rs_bindings_from_cc/generate_bindings_and_metadata.cc
index 37d5a3a..7f0d126 100644
--- a/rs_bindings_from_cc/generate_bindings_and_metadata.cc
+++ b/rs_bindings_from_cc/generate_bindings_and_metadata.cc
@@ -73,10 +73,12 @@
cmdline.headers_to_targets(), cmdline.extra_rs_srcs(),
clang_args_view, requested_instantiations));
- CRUBIT_ASSIGN_OR_RETURN(Bindings bindings,
- GenerateBindings(ir, cmdline.crubit_support_path(),
- cmdline.rustfmt_exe_path(),
- cmdline.rustfmt_config_path()));
+ bool generate_error_report = !cmdline.error_report_out().empty();
+ CRUBIT_ASSIGN_OR_RETURN(
+ Bindings bindings,
+ GenerateBindings(ir, cmdline.crubit_support_path(),
+ cmdline.rustfmt_exe_path(),
+ cmdline.rustfmt_config_path(), generate_error_report));
absl::flat_hash_map<std::string, std::string> instantiations;
std::optional<const Namespace*> ns =
@@ -97,6 +99,7 @@
.rs_api_impl = bindings.rs_api_impl,
.namespaces = std::move(top_level_namespaces),
.instantiations = std::move(instantiations),
+ .error_report = bindings.error_report,
};
}
diff --git a/rs_bindings_from_cc/generate_bindings_and_metadata.h b/rs_bindings_from_cc/generate_bindings_and_metadata.h
index 2c56c83..6b30073 100644
--- a/rs_bindings_from_cc/generate_bindings_and_metadata.h
+++ b/rs_bindings_from_cc/generate_bindings_and_metadata.h
@@ -28,6 +28,8 @@
// C++ class templates explicitly instantiated in this TU and their Rust
// struct name.
absl::flat_hash_map<std::string, std::string> instantiations;
+ // A JSON error report, if requested.
+ std::string error_report;
};
// Returns `BindingsAndMetadata` as requested by the user on the command line.
diff --git a/rs_bindings_from_cc/generate_bindings_and_metadata_test.cc b/rs_bindings_from_cc/generate_bindings_and_metadata_test.cc
index 6decefc..4cb60e3 100644
--- a/rs_bindings_from_cc/generate_bindings_and_metadata_test.cc
+++ b/rs_bindings_from_cc/generate_bindings_and_metadata_test.cc
@@ -42,7 +42,8 @@
/* public_headers= */ {"a.h"}, std::string(kTargetsAndHeaders),
/* extra_rs_srcs= */ {},
/* srcs_to_scan_for_instantiations= */ {},
- /* instantiations_out= */ ""));
+ /* instantiations_out= */ "",
+ /* error_report_out= */ ""));
ASSERT_OK_AND_ASSIGN(
BindingsAndMetadata result,
@@ -52,6 +53,7 @@
ASSERT_EQ(result.ir.used_headers.size(), 1);
ASSERT_EQ(result.ir.used_headers.front().IncludePath(), "a.h");
+ ASSERT_EQ(result.error_report, "");
// Check that IR items have the proper owning target set.
auto item = result.ir.get_items_if<Namespace>().front();
@@ -71,7 +73,8 @@
/* public_headers= */ {"a.h"}, std::string(kTargetsAndHeaders),
/* extra_rs_srcs= */ {},
/* srcs_to_scan_for_instantiations= */ {},
- /* instantiations_out= */ ""));
+ /* instantiations_out= */ "",
+ /* error_report_out= */ ""));
ASSERT_OK_AND_ASSIGN(
BindingsAndMetadata result,
@@ -100,7 +103,7 @@
{"a.h"}, std::string(kTargetsAndHeaders),
/* extra_rs_srcs= */ {},
/* srcs_to_scan_for_instantiations= */ {a_rs_path},
- "instantiations_out"));
+ "instantiations_out", /* error_report_out= */ ""));
CRUBIT_ASSIGN_OR_RETURN(
BindingsAndMetadata result,
@@ -267,7 +270,7 @@
/* public_headers= */ {"a.h"}, std::string(kTargetsAndHeaders),
/* extra_rs_srcs= */ {},
/* srcs_to_scan_for_instantiations= */ {},
- /* instantiations_out= */ ""));
+ /* instantiations_out= */ "", /* error_report_out= */ ""));
ASSERT_OK_AND_ASSIGN(BindingsAndMetadata result,
GenerateBindingsAndMetadata(
cmdline, DefaultClangArgs(),
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index 3cb475a..4e6d2ba 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -6,13 +6,15 @@
//! `rs_bindings_from_cc/ir.h` for more
//! information.
-use arc_anyhow::{anyhow, bail, Context, Result};
+use arc_anyhow::{anyhow, bail, Context, Error, Result};
+use once_cell::unsync::OnceCell;
use proc_macro2::{Literal, TokenStream};
use quote::{quote, ToTokens, TokenStreamExt};
use serde::Deserialize;
use std::collections::hash_map::{Entry, HashMap};
use std::convert::TryFrom;
-use std::fmt;
+use std::fmt::{self, Debug, Formatter};
+use std::hash::{Hash, Hasher};
use std::io::Read;
use std::rc::Rc;
@@ -499,12 +501,60 @@
pub column: u64,
}
+/// A wrapper type that does not contribute to equality or hashing. All
+/// instances are equal.
+#[derive(Clone, Copy, Default)]
+struct IgnoredField<T>(T);
+
+impl<T> Debug for IgnoredField<T> {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "_")
+ }
+}
+
+impl<T> PartialEq for IgnoredField<T> {
+ fn eq(&self, _other: &Self) -> bool {
+ true
+ }
+}
+
+impl<T> Eq for IgnoredField<T> {}
+
+impl<T> Hash for IgnoredField<T> {
+ fn hash<H: Hasher>(&self, _state: &mut H) {}
+}
+
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct UnsupportedItem {
pub name: String,
- pub message: String,
+ message: String,
pub source_loc: SourceLoc,
pub id: ItemId,
+ #[serde(skip)]
+ cause: IgnoredField<OnceCell<Error>>,
+}
+
+impl UnsupportedItem {
+ pub fn new_with_message(
+ name: String,
+ message: String,
+ source_loc: SourceLoc,
+ id: ItemId,
+ ) -> Self {
+ Self { name, message, source_loc, id, cause: Default::default() }
+ }
+
+ pub fn new_with_cause(name: String, cause: Error, source_loc: SourceLoc, id: ItemId) -> Self {
+ Self { name, message: cause.to_string(), source_loc, id, cause: IgnoredField(cause.into()) }
+ }
+
+ pub fn message(&self) -> &str {
+ &self.message
+ }
+
+ pub fn cause(&self) -> &Error {
+ self.cause.0.get_or_init(|| anyhow!(self.message.clone()))
+ }
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
@@ -581,7 +631,7 @@
}
impl<'a> TryFrom<&'a Item> for &'a Rc<Func> {
- type Error = arc_anyhow::Error;
+ type Error = Error;
fn try_from(value: &'a Item) -> Result<Self, Self::Error> {
if let Item::Func(f) = value { Ok(f) } else { bail!("Not a Func: {:#?}", value) }
}
@@ -594,7 +644,7 @@
}
impl<'a> TryFrom<&'a Item> for &'a Rc<Record> {
- type Error = arc_anyhow::Error;
+ type Error = Error;
fn try_from(value: &'a Item) -> Result<Self, Self::Error> {
if let Item::Record(r) = value { Ok(r) } else { bail!("Not a Record: {:#?}", value) }
}
@@ -607,7 +657,7 @@
}
impl<'a> TryFrom<&'a Item> for &'a Rc<UnsupportedItem> {
- type Error = arc_anyhow::Error;
+ type Error = Error;
fn try_from(value: &'a Item) -> Result<Self, Self::Error> {
if let Item::UnsupportedItem(u) = value {
Ok(u)
@@ -624,7 +674,7 @@
}
impl<'a> TryFrom<&'a Item> for &'a Rc<Comment> {
- type Error = arc_anyhow::Error;
+ type Error = Error;
fn try_from(value: &'a Item) -> Result<Self, Self::Error> {
if let Item::Comment(c) = value { Ok(c) } else { bail!("Not a Comment: {:#?}", value) }
}
@@ -709,7 +759,7 @@
pub fn item_for_type<T>(&self, ty: &T) -> Result<&Item>
where
- T: TypeWithDeclId + std::fmt::Debug,
+ T: TypeWithDeclId + Debug,
{
if let Some(decl_id) = ty.decl_id() {
self.find_untyped_decl(decl_id)
diff --git a/rs_bindings_from_cc/ir_from_cc_test.rs b/rs_bindings_from_cc/ir_from_cc_test.rs
index 1c2a97d..521ada9 100644
--- a/rs_bindings_from_cc/ir_from_cc_test.rs
+++ b/rs_bindings_from_cc/ir_from_cc_test.rs
@@ -2903,7 +2903,7 @@
#[test]
fn test_volatile_is_unsupported() {
let ir = ir_from_cc("volatile int* foo();").unwrap();
- let f = ir.unsupported_items().find(|i| i.message.contains("volatile")).unwrap();
+ let f = ir.unsupported_items().find(|i| i.message().contains("volatile")).unwrap();
assert_eq!("foo", f.name);
}
diff --git a/rs_bindings_from_cc/rs_bindings_from_cc.cc b/rs_bindings_from_cc/rs_bindings_from_cc.cc
index dabf607..b6fd721 100644
--- a/rs_bindings_from_cc/rs_bindings_from_cc.cc
+++ b/rs_bindings_from_cc/rs_bindings_from_cc.cc
@@ -82,6 +82,11 @@
crubit::NamespacesAsJson(bindings_and_metadata.namespaces)));
}
+ if (!cmdline.error_report_out().empty()) {
+ CRUBIT_RETURN_IF_ERROR(SetFileContents(cmdline.error_report_out(),
+ bindings_and_metadata.error_report));
+ }
+
return absl::OkStatus();
}
diff --git a/rs_bindings_from_cc/src_code_gen.cc b/rs_bindings_from_cc/src_code_gen.cc
index 9ead1e9..a4f23e5 100644
--- a/rs_bindings_from_cc/src_code_gen.cc
+++ b/rs_bindings_from_cc/src_code_gen.cc
@@ -18,13 +18,15 @@
struct FfiBindings {
FfiU8SliceBox rs_api;
FfiU8SliceBox rs_api_impl;
+ FfiU8SliceBox error_report;
};
// This function is implemented in Rust.
extern "C" FfiBindings GenerateBindingsImpl(FfiU8Slice json,
FfiU8Slice crubit_support_path,
FfiU8Slice rustfmt_exe_path,
- FfiU8Slice rustfmt_config_path);
+ FfiU8Slice rustfmt_config_path,
+ bool generate_error_report);
// Creates `Bindings` instance from copied data from `ffi_bindings`.
static absl::StatusOr<Bindings> MakeBindingsFromFfiBindings(
@@ -33,9 +35,11 @@
const FfiU8SliceBox& rs_api = ffi_bindings.rs_api;
const FfiU8SliceBox& rs_api_impl = ffi_bindings.rs_api_impl;
+ const FfiU8SliceBox& error_report = ffi_bindings.error_report;
bindings.rs_api = std::string(rs_api.ptr, rs_api.size);
bindings.rs_api_impl = std::string(rs_api_impl.ptr, rs_api_impl.size);
+ bindings.error_report = std::string(error_report.ptr, error_report.size);
return bindings;
}
@@ -43,16 +47,20 @@
static void FreeFfiBindings(FfiBindings ffi_bindings) {
FreeFfiU8SliceBox(ffi_bindings.rs_api);
FreeFfiU8SliceBox(ffi_bindings.rs_api_impl);
+ FreeFfiU8SliceBox(ffi_bindings.error_report);
}
-absl::StatusOr<Bindings> GenerateBindings(
- const IR& ir, absl::string_view crubit_support_path,
- absl::string_view rustfmt_exe_path, absl::string_view rustfmt_config_path) {
+absl::StatusOr<Bindings> GenerateBindings(const IR& ir,
+ absl::string_view crubit_support_path,
+ absl::string_view rustfmt_exe_path,
+ absl::string_view rustfmt_config_path,
+ bool generate_error_report) {
std::string json = llvm::formatv("{0}", ir.ToJson());
FfiBindings ffi_bindings = GenerateBindingsImpl(
MakeFfiU8Slice(json), MakeFfiU8Slice(crubit_support_path),
- MakeFfiU8Slice(rustfmt_exe_path), MakeFfiU8Slice(rustfmt_config_path));
+ MakeFfiU8Slice(rustfmt_exe_path), MakeFfiU8Slice(rustfmt_config_path),
+ generate_error_report);
CRUBIT_ASSIGN_OR_RETURN(Bindings bindings,
MakeBindingsFromFfiBindings(ffi_bindings));
FreeFfiBindings(ffi_bindings);
diff --git a/rs_bindings_from_cc/src_code_gen.h b/rs_bindings_from_cc/src_code_gen.h
index 6382eea..ee77113 100644
--- a/rs_bindings_from_cc/src_code_gen.h
+++ b/rs_bindings_from_cc/src_code_gen.h
@@ -19,12 +19,16 @@
std::string rs_api;
// C++ source code.
std::string rs_api_impl;
+ // Optional JSON error report.
+ std::string error_report;
};
// Generates bindings from the given `IR`.
-absl::StatusOr<Bindings> GenerateBindings(
- const IR& ir, absl::string_view crubit_support_path,
- absl::string_view rustfmt_exe_path, absl::string_view rustfmt_config_path);
+absl::StatusOr<Bindings> GenerateBindings(const IR& ir,
+ absl::string_view crubit_support_path,
+ absl::string_view rustfmt_exe_path,
+ absl::string_view rustfmt_config_path,
+ bool generate_error_report);
} // namespace crubit
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index 3982cc9..caf41ac 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -2,7 +2,8 @@
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-use arc_anyhow::{anyhow, bail, ensure, Context, Result};
+use arc_anyhow::{Context, Result};
+use error_report::{anyhow, bail, ensure, ErrorReport, ErrorReporting, IgnoreErrors};
use ffi_types::*;
use ir::*;
use itertools::Itertools;
@@ -26,6 +27,7 @@
pub struct FfiBindings {
rs_api: FfiU8SliceBox,
rs_api_impl: FfiU8SliceBox,
+ error_report: FfiU8SliceBox,
}
/// Deserializes IR from `json` and generates bindings source code.
@@ -57,6 +59,7 @@
crubit_support_path: FfiU8Slice,
rustfmt_exe_path: FfiU8Slice,
rustfmt_config_path: FfiU8Slice,
+ generate_error_report: bool,
) -> FfiBindings {
let json: &[u8] = json.as_slice();
let crubit_support_path: &str = std::str::from_utf8(crubit_support_path.as_slice()).unwrap();
@@ -66,14 +69,31 @@
std::str::from_utf8(rustfmt_config_path.as_slice()).unwrap().into();
catch_unwind(|| {
// It is ok to abort here.
- let Bindings { rs_api, rs_api_impl } =
- generate_bindings(json, crubit_support_path, &rustfmt_exe_path, &rustfmt_config_path)
- .unwrap();
+ let mut error_report;
+ let mut ignore_errors;
+ let errors: &mut dyn ErrorReporting = if generate_error_report {
+ error_report = ErrorReport::new();
+ &mut error_report
+ } else {
+ ignore_errors = IgnoreErrors;
+ &mut ignore_errors
+ };
+ let Bindings { rs_api, rs_api_impl } = generate_bindings(
+ json,
+ crubit_support_path,
+ &rustfmt_exe_path,
+ &rustfmt_config_path,
+ errors,
+ )
+ .unwrap();
FfiBindings {
rs_api: FfiU8SliceBox::from_boxed_slice(rs_api.into_bytes().into_boxed_slice()),
rs_api_impl: FfiU8SliceBox::from_boxed_slice(
rs_api_impl.into_bytes().into_boxed_slice(),
),
+ error_report: FfiU8SliceBox::from_boxed_slice(
+ errors.serialize_to_vec().unwrap().into_boxed_slice(),
+ ),
}
})
.unwrap_or_else(|_| process::abort())
@@ -126,11 +146,12 @@
crubit_support_path: &str,
rustfmt_exe_path: &OsStr,
rustfmt_config_path: &OsStr,
+ errors: &mut dyn ErrorReporting,
) -> Result<Bindings> {
let ir = Rc::new(deserialize_ir(json)?);
let BindingsTokens { rs_api, rs_api_impl } =
- generate_bindings_tokens(ir.clone(), crubit_support_path)?;
+ generate_bindings_tokens(ir.clone(), crubit_support_path, errors)?;
let rs_api = {
let rustfmt_config = RustfmtConfig::new(rustfmt_exe_path, rustfmt_config_path);
rs_tokens_to_formatted_string(rs_api, &rustfmt_config)?
@@ -348,22 +369,22 @@
}
fn make_unsupported_fn(func: &Func, ir: &IR, message: impl ToString) -> Result<UnsupportedItem> {
- Ok(UnsupportedItem {
- name: cxx_function_name(func, ir)?,
- message: message.to_string(),
- source_loc: func.source_loc.clone(),
- id: func.id,
- })
+ Ok(UnsupportedItem::new_with_message(
+ cxx_function_name(func, ir)?,
+ message.to_string(),
+ func.source_loc.clone(),
+ func.id,
+ ))
}
fn make_unsupported_nested_type_alias(type_alias: &TypeAlias) -> Result<UnsupportedItem> {
- Ok(UnsupportedItem {
+ Ok(UnsupportedItem::new_with_message(
// TODO(jeanpierreda): It would be nice to include the enclosing record name here too.
- name: type_alias.identifier.identifier.to_string(),
- message: "Typedefs nested in classes are not supported yet".to_string(),
- source_loc: type_alias.source_loc.clone(),
- id: type_alias.id,
- })
+ type_alias.identifier.identifier.to_string(),
+ "Typedefs nested in classes are not supported yet".to_string(),
+ type_alias.source_loc.clone(),
+ type_alias.id,
+ ))
}
/// The name of a one-function trait, with extra entries for
@@ -1249,7 +1270,11 @@
*trait_generic_params = lifetimes
.iter()
.filter_map(|lifetime| {
- if trait_lifetimes.contains(lifetime) { Some(quote! {#lifetime}) } else { None }
+ if trait_lifetimes.contains(lifetime) {
+ Some(quote! {#lifetime})
+ } else {
+ None
+ }
})
.collect();
} else {
@@ -1789,7 +1814,11 @@
/// Generates Rust source code for a given `Record` and associated assertions as
/// a tuple.
-fn generate_record(db: &Database, record: &Rc<Record>) -> Result<GeneratedItem> {
+fn generate_record(
+ db: &Database,
+ record: &Rc<Record>,
+ errors: &mut dyn ErrorReporting,
+) -> Result<GeneratedItem> {
let ir = db.ir();
let ident = make_rs_ident(&record.rs_name);
let namespace_qualifier = NamespaceQualifier::new(record.id, &ir)?.format_for_rs();
@@ -2075,7 +2104,7 @@
let item = ir.find_decl(*id).with_context(|| {
format!("Failed to look up `record.child_item_ids` for {:?}", record)
})?;
- generate_item(db, item)
+ generate_item(db, item, errors)
})
.collect::<Result<Vec<_>>>()?;
@@ -2249,7 +2278,12 @@
}
/// Generates Rust source code for a given `UnsupportedItem`.
-fn generate_unsupported(item: &UnsupportedItem) -> Result<TokenStream> {
+fn generate_unsupported(
+ item: &UnsupportedItem,
+ errors: &mut dyn ErrorReporting,
+) -> Result<TokenStream> {
+ errors.insert(item.cause());
+
let location = if item.source_loc.filename.is_empty() {
"<unknown location>".to_string()
} else {
@@ -2261,7 +2295,9 @@
};
let message = format!(
"{}\nError while generating bindings for item '{}':\n{}",
- &location, &item.name, &item.message
+ &location,
+ &item.name,
+ item.message()
);
Ok(quote! { __COMMENT__ #message })
}
@@ -2272,7 +2308,11 @@
Ok(quote! { __COMMENT__ #text })
}
-fn generate_namespace(db: &Database, namespace: &Namespace) -> Result<GeneratedItem> {
+fn generate_namespace(
+ db: &Database,
+ namespace: &Namespace,
+ errors: &mut dyn ErrorReporting,
+) -> Result<GeneratedItem> {
let ir = db.ir();
let mut items = vec![];
let mut thunks = vec![];
@@ -2285,7 +2325,7 @@
let item = ir.find_decl(*item_id).with_context(|| {
format!("Failed to look up namespace.child_item_ids for {:?}", namespace)
})?;
- let generated = generate_item(db, item)?;
+ let generated = generate_item(db, item, errors)?;
items.push(generated.item);
if !generated.thunks.is_empty() {
thunks.push(generated.thunks);
@@ -2361,13 +2401,20 @@
has_record: bool,
}
-fn generate_item(db: &Database, item: &Item) -> Result<GeneratedItem> {
+fn generate_item(
+ db: &Database,
+ item: &Item,
+ errors: &mut dyn ErrorReporting,
+) -> Result<GeneratedItem> {
let ir = db.ir();
let overloaded_funcs = db.overloaded_funcs();
let generated_item = match item {
Item::Func(func) => match db.generate_func(func.clone()) {
Err(e) => GeneratedItem {
- item: generate_unsupported(&make_unsupported_fn(func, &ir, format!("{e}"))?)?,
+ item: generate_unsupported(
+ &make_unsupported_fn(func, &ir, format!("{e}"))?,
+ errors,
+ )?,
..Default::default()
},
Ok(None) => GeneratedItem::default(),
@@ -2375,11 +2422,14 @@
let (api_func, thunk, function_id) = &*f;
if overloaded_funcs.contains(function_id) {
GeneratedItem {
- item: generate_unsupported(&make_unsupported_fn(
- func,
- &ir,
- "Cannot generate bindings for overloaded function",
- )?)?,
+ item: generate_unsupported(
+ &make_unsupported_fn(
+ func,
+ &ir,
+ "Cannot generate bindings for overloaded function",
+ )?,
+ errors,
+ )?,
..Default::default()
}
} else {
@@ -2412,7 +2462,7 @@
{
GeneratedItem::default()
} else {
- generate_record(db, record)?
+ generate_record(db, record, errors)?
}
}
Item::Enum(enum_) => {
@@ -2432,7 +2482,10 @@
} else if type_alias.enclosing_record_id.is_some() {
// TODO(b/200067824): support nested type aliases.
GeneratedItem {
- item: generate_unsupported(&make_unsupported_nested_type_alias(type_alias)?)?,
+ item: generate_unsupported(
+ &make_unsupported_nested_type_alias(type_alias)?,
+ errors,
+ )?,
..Default::default()
}
} else {
@@ -2440,12 +2493,12 @@
}
}
Item::UnsupportedItem(unsupported) => {
- GeneratedItem { item: generate_unsupported(unsupported)?, ..Default::default() }
+ GeneratedItem { item: generate_unsupported(unsupported, errors)?, ..Default::default() }
}
Item::Comment(comment) => {
GeneratedItem { item: generate_comment(comment)?, ..Default::default() }
}
- Item::Namespace(namespace) => generate_namespace(db, namespace)?,
+ Item::Namespace(namespace) => generate_namespace(db, namespace, errors)?,
Item::UseMod(use_mod) => {
let UseMod { path, mod_name, .. } = &**use_mod;
let mod_name = make_rs_ident(&mod_name.identifier);
@@ -2482,7 +2535,11 @@
// Returns the Rust code implementing bindings, plus any auxiliary C++ code
// needed to support it.
-fn generate_bindings_tokens(ir: Rc<IR>, crubit_support_path: &str) -> Result<BindingsTokens> {
+fn generate_bindings_tokens(
+ ir: Rc<IR>,
+ crubit_support_path: &str,
+ errors: &mut dyn ErrorReporting,
+) -> Result<BindingsTokens> {
let mut db = Database::default();
db.set_ir(ir.clone());
@@ -2511,7 +2568,7 @@
for top_level_item_id in ir.top_level_item_ids() {
let item =
ir.find_decl(*top_level_item_id).context("Failed to look up ir.top_level_item_ids")?;
- let generated = generate_item(&db, item)?;
+ let generated = generate_item(&db, item, errors)?;
items.push(generated.item);
if !generated.thunks.is_empty() {
thunks.push(generated.thunks);
@@ -3668,7 +3725,7 @@
use token_stream_printer::rs_tokens_to_formatted_string_for_tests;
fn generate_bindings_tokens(ir: Rc<IR>) -> Result<BindingsTokens> {
- super::generate_bindings_tokens(ir, "crubit/rs_bindings_support")
+ super::generate_bindings_tokens(ir, "crubit/rs_bindings_support", &mut IgnoreErrors)
}
fn db_from_cc(cc_src: &str) -> Result<Database> {