Merge `api` and `cc_impl` fields of `BindingsSnippet`.
Before this CL, (optional) thunk declarations would go into `cc_impl`
and API functions would go into the `api` field of `BindingsSnippet`.
`cc_impl` would be emitted in a separate `__crubit_internal` namespace
that would go before `api` in the generated `...cc_api.h` file. This
scheme aesthetically separates implementation details (thunk
declarations in the `__crubit_internal` namespace) from the real API.
Unfortunately, this scheme will break down in the future - once
`struct`s can be used as function parameters, because `stucts`
definitions are part of the API, but thunk declarations still need to
refer to them. Therefore, after this CL, `cc_impl` and `api` fields of
`BindingsSnippet` are merged into a single field.
This CL also performs additional refactorings in the related code:
- The `api` field is renamed to `cc`
- The `rs_impl` field is renamed to `rs`
- `BindingsSnippet` is renamed to `MixedSnippet` (since now it contains
`cc` and `rs` fields - a mix of C++ and Rust).
- Instead of having a separate `include` and `cc` fields, we A)
remove the `include`, B) change the type of the `cc` field from
`TokenStream` to `CcSnippet` (which internally already contains
`includes` and a `TokenStream`).
PiperOrigin-RevId: 489588797
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs
index f6d940e..fa6d2a0 100644
--- a/cc_bindings_from_rs/bindings.rs
+++ b/cc_bindings_from_rs/bindings.rs
@@ -78,7 +78,7 @@
Ok(query_context.peek_mut().enter(f))
}
-#[derive(Debug)]
+#[derive(Debug, Default)]
struct CcSnippet {
snippet: TokenStream,
@@ -284,42 +284,18 @@
})
}
-#[derive(Debug)]
-struct BindingsSnippet {
- /// `#include`s that go at the top of the generated `..._cc_api.h` file.
- includes: BTreeSet<CcInclude>,
-
- /// Public API of the bindings in the generated `..._cc_api.h` file.
- api: TokenStream,
-
- /// Internal implementation details for `..._cc_api.h` file (e.g.
- /// declarations of Rust thunks).
- cc_impl: Option<TokenStream>,
-
- /// Rust implementation of the bindings:
- /// - Definitions of FFI-friendly `extern "C"` thunks that C++ can call
- /// into.
- /// - Static / `const` assrtions about layout of Rust structs
- rs_impl: Option<TokenStream>,
+#[derive(Debug, Default)]
+struct MixedSnippet {
+ cc: CcSnippet,
+ rs: Option<TokenStream>,
}
-impl BindingsSnippet {
- fn new() -> Self {
- Self { includes: BTreeSet::new(), api: quote! {}, cc_impl: None, rs_impl: None }
- }
-}
-
-impl AddAssign for BindingsSnippet {
+impl AddAssign for MixedSnippet {
fn add_assign(&mut self, rhs: Self) {
- let Self {
- includes: mut rhs_includes,
- api: rhs_api,
- cc_impl: rhs_cc_impl,
- rs_impl: rhs_rs_impl,
- } = rhs;
+ let Self { cc: mut rhs_cc, rs: rhs_rs } = rhs;
- self.includes.append(&mut rhs_includes);
- self.api.extend(rhs_api);
+ self.cc.includes.append(&mut rhs_cc.includes);
+ self.cc.snippet.extend(rhs_cc.snippet);
fn concat_optional_tokens(
lhs: Option<TokenStream>,
@@ -335,13 +311,12 @@
}
}
}
- self.cc_impl = concat_optional_tokens(self.cc_impl.take(), rhs_cc_impl);
- self.rs_impl = concat_optional_tokens(self.rs_impl.take(), rhs_rs_impl);
+ self.rs = concat_optional_tokens(self.rs.take(), rhs_rs);
}
}
-impl Add for BindingsSnippet {
- type Output = BindingsSnippet;
+impl Add for MixedSnippet {
+ type Output = MixedSnippet;
fn add(mut self, rhs: Self) -> Self {
self += rhs;
@@ -349,9 +324,9 @@
}
}
-impl Sum for BindingsSnippet {
+impl Sum for MixedSnippet {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
- iter.fold(BindingsSnippet::new(), Add::add)
+ iter.fold(Default::default(), Add::add)
}
}
@@ -362,7 +337,7 @@
/// - doesn't identify a function,
/// - has generic parameters of any kind - lifetime parameters (see also
/// b/258235219), type parameters, or const parameters.
-fn format_fn(tcx: TyCtxt, def_id: LocalDefId) -> Result<BindingsSnippet> {
+fn format_fn(tcx: TyCtxt, def_id: LocalDefId) -> Result<MixedSnippet> {
let def_id: DefId = def_id.to_def_id(); // Convert LocalDefId to DefId.
let item_name = tcx.item_name(def_id);
@@ -422,9 +397,7 @@
};
let mut includes = BTreeSet::new();
- let api: TokenStream;
- let cc_impl: Option<TokenStream>;
- {
+ let cc_snippet = {
let ret_type = format_ret_ty_for_cc(sig.output())
.context("Error formatting function return type")?
.into_tokens(&mut includes);
@@ -450,30 +423,29 @@
})
.collect::<Result<Vec<_>>>()?;
if item_name.as_str() == symbol_name.name {
- api = quote! {
+ quote! {
extern "C" #ret_type #fn_name (
#( #arg_types #arg_names ),*
);
- };
- cc_impl = None;
+ }
} else {
let exported_name =
format_cc_ident(symbol_name.name).context("Error formatting exported name")?;
- api = quote! {
+ quote! {
+ namespace __crubit_internal {
+ extern "C" #ret_type #exported_name (
+ #( #arg_types #arg_names ),*
+ );
+ } // namespace __crubit_internal
inline #ret_type #fn_name (
#( #arg_types #arg_names ),* ) {
- return :: __crubit_internal :: #exported_name( #( #arg_names ),* );
+ return __crubit_internal :: #exported_name( #( #arg_names ),* );
}
- };
- cc_impl = Some(quote! {
- extern "C" #ret_type #exported_name (
- #( #arg_types #arg_names ),*
- );
- });
+ }
}
- }
+ };
- let rs_impl = if !needs_thunk {
+ let rs_snippet = if !needs_thunk {
None
} else {
let crate_name = make_rs_ident(tcx.crate_name(LOCAL_CRATE).as_str());
@@ -503,14 +475,14 @@
}
})
};
- Ok(BindingsSnippet { includes, api, cc_impl, rs_impl })
+ Ok(MixedSnippet { cc: CcSnippet { includes, snippet: cc_snippet }, rs: rs_snippet })
}
/// Formats a Rust item idenfied by `def_id`.
///
/// Will panic if `def_id` is invalid (i.e. doesn't identify a Rust node or
/// item).
-fn format_def(tcx: TyCtxt, def_id: LocalDefId) -> Result<BindingsSnippet> {
+fn format_def(tcx: TyCtxt, def_id: LocalDefId) -> Result<MixedSnippet> {
match tcx.hir().get_by_def_id(def_id) {
Node::Item(item) => match item {
Item { kind: ItemKind::Fn(_, generics, _) |
@@ -538,7 +510,7 @@
tcx: TyCtxt,
local_def_id: LocalDefId,
err: anyhow::Error,
-) -> BindingsSnippet {
+) -> MixedSnippet {
let span = tcx.sess().source_map().span_to_embeddable_string(tcx.def_span(local_def_id));
let name = tcx.def_path_str(local_def_id.to_def_id());
@@ -547,12 +519,12 @@
let msg = format!("Error generating bindings for `{name}` defined at {span}: {err:#}");
let comment = quote! { __NEWLINE__ __NEWLINE__ __COMMENT__ #msg __NEWLINE__ };
- BindingsSnippet { api: comment, ..BindingsSnippet::new() }
+ MixedSnippet { cc: CcSnippet { snippet: comment, ..Default::default() }, rs: None }
}
/// Formats all public items from the Rust crate being compiled.
fn format_crate(tcx: TyCtxt) -> Result<GeneratedBindings> {
- let snippets: BindingsSnippet = tcx
+ let MixedSnippet{ cc, rs } = tcx
.hir()
.items()
.filter_map(|item_id| {
@@ -568,44 +540,30 @@
})
.sum();
- let includes = format_cc_includes(&snippets.includes);
- let api = {
+ let h_body = {
// TODO(b/254690602): Decide whether using `#crate_name` as the name of the
// top-level namespace is okay (e.g. investigate if this name is globally
// unique + ergonomic).
let crate_name = format_cc_ident(tcx.crate_name(LOCAL_CRATE).as_str())?;
- let api_body = &snippets.api;
+
+ let CcSnippet { includes, snippet } = cc;
+ let includes = format_cc_includes(&includes);
quote! {
+ #includes __NEWLINE__
namespace #crate_name {
- #api_body
+ #snippet
}
}
};
- let cc_impl = {
- match snippets.cc_impl {
- None => quote! {},
- Some(details_body) => quote! {
- namespace __crubit_internal {
- #details_body
- }
- __NEWLINE__
- },
- }
- };
- let h_body = quote! {
- #includes __NEWLINE__
- #cc_impl
- #api
- };
- let rs_body = snippets.rs_impl.unwrap_or_else(|| quote! {});
+ let rs_body = rs.unwrap_or_else(|| quote! {});
Ok(GeneratedBindings { h_body, rs_body })
}
#[cfg(test)]
pub mod tests {
use super::{
- format_def, format_ret_ty_for_cc, format_ty_for_cc, format_ty_for_rs, BindingsSnippet,
- GeneratedBindings,
+ format_def, format_ret_ty_for_cc, format_ty_for_cc, format_ty_for_rs, GeneratedBindings,
+ MixedSnippet,
};
use anyhow::Result;
@@ -730,8 +688,8 @@
#[test]
fn test_generated_bindings_fn_export_name() {
- // Coverage of how `BindingsSnippet::cc_impl` are propagated when there are no
- // `BindingsSnippet::rs_impl` (e.g. no Rust thunks are needed).
+ // Coverage of how `MixedSnippet::cc` are propagated when there is no
+ // `MixedSnippet::rs` (e.g. no Rust thunks are needed).
let test_src = r#"
#[export_name = "export_name"]
pub extern "C" fn public_function(x: f64, y: f64) -> f64 { x + y }
@@ -741,12 +699,12 @@
assert_cc_matches!(
bindings.h_body,
quote! {
- namespace __crubit_internal {
- extern "C" double export_name(double x, double y);
- }
namespace rust_out {
+ namespace __crubit_internal {
+ extern "C" double export_name(double x, double y);
+ }
inline double public_function(double x, double y) {
- return ::__crubit_internal::export_name(x, y);
+ return __crubit_internal::export_name(x, y);
}
}
}
@@ -865,11 +823,10 @@
"#;
test_format_def(test_src, "public_function", |result| {
let result = result.expect("Test expects success here");
- assert!(result.includes.is_empty());
- assert!(result.cc_impl.is_none());
- assert!(result.rs_impl.is_none());
+ assert!(result.cc.includes.is_empty());
+ assert!(result.rs.is_none());
assert_cc_matches!(
- result.api,
+ result.cc.snippet,
quote! {
extern "C" void public_function();
}
@@ -891,11 +848,10 @@
"#;
test_format_def(test_src, "explicit_unit_return_type", |result| {
let result = result.expect("Test expects success here");
- assert!(result.includes.is_empty());
- assert!(result.cc_impl.is_none());
- assert!(result.rs_impl.is_none());
+ assert!(result.cc.includes.is_empty());
+ assert!(result.rs.is_none());
assert_cc_matches!(
- result.api,
+ result.cc.snippet,
quote! {
extern "C" void explicit_unit_return_type();
}
@@ -917,11 +873,10 @@
// TODO(b/254507801): Expect `crubit::Never` instead (see the bug for more
// details).
let result = result.expect("Test expects success here");
- assert!(result.includes.is_empty());
- assert!(result.cc_impl.is_none());
- assert!(result.rs_impl.is_none());
+ assert!(result.cc.includes.is_empty());
+ assert!(result.rs.is_none());
assert_cc_matches!(
- result.api,
+ result.cc.snippet,
quote! {
extern "C" void never_returning_function();
}
@@ -940,22 +895,19 @@
"#;
test_format_def(test_src, "public_function", |result| {
let result = result.expect("Test expects success here");
- assert!(result.includes.is_empty());
- assert!(result.rs_impl.is_none());
+ assert!(result.cc.includes.is_empty());
+ assert!(result.rs.is_none());
assert_cc_matches!(
- result.api,
+ result.cc.snippet,
quote! {
+ namespace __crubit_internal {
+ extern "C" double ...(double x, double y);
+ }
inline double public_function(double x, double y) {
return ...(x, y);
}
}
);
- assert_cc_matches!(
- result.cc_impl.expect("This test expects separate extern-C decl"),
- quote! {
- extern "C" double ...(double x, double y);
- }
- );
});
}
@@ -967,20 +919,17 @@
"#;
test_format_def(test_src, "public_function", |result| {
let result = result.expect("Test expects success here");
- assert!(result.includes.is_empty());
- assert!(result.rs_impl.is_none());
+ assert!(result.cc.includes.is_empty());
+ assert!(result.rs.is_none());
assert_cc_matches!(
- result.api,
+ result.cc.snippet,
quote! {
- inline double public_function(double x, double y) {
- return ::__crubit_internal::export_name(x, y);
+ namespace __crubit_internal {
+ extern "C" double export_name(double x, double y);
}
- }
- );
- assert_cc_matches!(
- result.cc_impl.expect("This test expects separate extern-C decl"),
- quote! {
- extern "C" double export_name(double x, double y);
+ inline double public_function(double x, double y) {
+ return __crubit_internal::export_name(x, y);
+ }
}
);
});
@@ -1017,26 +966,22 @@
// TODO(b/254095787): Update test expectations below once `const fn` from Rust
// is translated into a `consteval` C++ function.
let result = result.expect("Test expects success here");
- assert!(!result.includes.is_empty());
- assert!(result.cc_impl.is_some());
+ assert!(!result.cc.includes.is_empty());
let thunk_name = quote!{ __crubit_thunk__RNvCsgl0yn9Ytt4z_8rust_out3foo };
assert_cc_matches!(
- result.cc_impl.unwrap(),
+ result.cc.snippet,
quote! {
- extern "C" std::int32_t #thunk_name( std::int32_t i);
- }
- );
- assert_cc_matches!(
- result.api,
- quote! {
+ namespace __crubit_internal {
+ extern "C" std::int32_t #thunk_name( std::int32_t i);
+ }
inline std::int32_t foo(std::int32_t i) {
- return ::__crubit_internal::#thunk_name(i);
+ return __crubit_internal::#thunk_name(i);
}
}
);
- assert!(result.rs_impl.is_some());
+ assert!(result.rs.is_some());
assert_rs_matches!(
- result.rs_impl.unwrap(),
+ result.rs.unwrap(),
quote! {
#[no_mangle]
extern "C"
@@ -1059,11 +1004,10 @@
"#;
test_format_def(test_src, "may_throw", |result| {
let result = result.expect("Test expects success here");
- assert!(result.includes.is_empty());
- assert!(result.cc_impl.is_none());
- assert!(result.rs_impl.is_none());
+ assert!(result.cc.includes.is_empty());
+ assert!(result.rs.is_none());
assert_cc_matches!(
- result.api,
+ result.cc.snippet,
quote! {
extern "C" void may_throw();
}
@@ -1086,11 +1030,10 @@
"#;
test_format_def(test_src, "type_aliased_return", |result| {
let result = result.expect("Test expects success here");
- assert!(result.includes.is_empty());
- assert!(result.cc_impl.is_none());
- assert!(result.rs_impl.is_none());
+ assert!(result.cc.includes.is_empty());
+ assert!(result.rs.is_none());
assert_cc_matches!(
- result.api,
+ result.cc.snippet,
quote! {
extern "C" double type_aliased_return();
}
@@ -1232,26 +1175,22 @@
"#;
test_format_def(test_src, "add", |result| {
let result = result.expect("Test expects success here");
- assert!(result.includes.is_empty());
- assert!(result.cc_impl.is_some());
+ assert!(result.cc.includes.is_empty());
let thunk_name = quote! { __crubit_thunk__RNvCsgl0yn9Ytt4z_8rust_out3add };
assert_cc_matches!(
- result.cc_impl.unwrap(),
+ result.cc.snippet,
quote! {
- extern "C" double #thunk_name(double x, double y);
- }
- );
- assert_cc_matches!(
- result.api,
- quote! {
+ namespace __crubit_internal {
+ extern "C" double #thunk_name(double x, double y);
+ }
inline double add(double x, double y) {
- return ::__crubit_internal::#thunk_name(x, y);
+ return __crubit_internal::#thunk_name(x, y);
}
}
);
- assert!(result.rs_impl.is_some());
+ assert!(result.rs.is_some());
assert_rs_matches!(
- result.rs_impl.unwrap(),
+ result.rs.unwrap(),
quote! {
#[no_mangle]
extern "C"
@@ -1285,26 +1224,22 @@
"#;
test_format_def(test_src, "add", |result| {
let result = result.expect("Test expects success here");
- assert!(result.includes.is_empty());
- assert!(result.cc_impl.is_some());
+ assert!(result.cc.includes.is_empty());
let thunk_name = quote! { __crubit_thunk__RNvCsgl0yn9Ytt4z_8rust_out3add };
assert_cc_matches!(
- result.cc_impl.unwrap(),
+ result.cc.snippet,
quote! {
- extern "C" double #thunk_name(double x, double y);
- }
- );
- assert_cc_matches!(
- result.api,
- quote! {
+ namespace __crubit_internal {
+ extern "C" double #thunk_name(double x, double y);
+ }
inline double add(double x, double y) {
- return ::__crubit_internal::#thunk_name(x, y);
+ return __crubit_internal::#thunk_name(x, y);
}
}
);
- assert!(result.rs_impl.is_some());
+ assert!(result.rs.is_some());
assert_rs_matches!(
- result.rs_impl.unwrap(),
+ result.rs.unwrap(),
quote! {
#[no_mangle]
extern "C"
@@ -1340,11 +1275,10 @@
"#;
test_format_def(test_src, "foo", |result| {
let result = result.expect("Test expects success here");
- assert!(result.includes.is_empty());
- assert!(result.cc_impl.is_none());
- assert!(result.rs_impl.is_none());
+ assert!(result.cc.includes.is_empty());
+ assert!(result.rs.is_none());
assert_cc_matches!(
- result.api,
+ result.cc.snippet,
quote! {
extern "C" void foo(bool b, double f);
}
@@ -1361,11 +1295,10 @@
"#;
test_format_def(test_src, "some_function", |result| {
let result = result.expect("Test expects success here");
- assert!(result.includes.is_empty());
- assert!(result.cc_impl.is_none());
- assert!(result.rs_impl.is_none());
+ assert!(result.cc.includes.is_empty());
+ assert!(result.rs.is_none());
assert_cc_matches!(
- result.api,
+ result.cc.snippet,
quote! {
extern "C" void some_function(double __param_0);
}
@@ -1380,28 +1313,24 @@
"#;
test_format_def(test_src, "foo", |result| {
let result = result.expect("Test expects success here");
- assert!(result.includes.is_empty());
- assert!(result.cc_impl.is_some());
+ assert!(result.cc.includes.is_empty());
assert_cc_matches!(
- result.cc_impl.unwrap(),
+ result.cc.snippet,
quote! {
- extern "C" void ...(
- double __param_0, double __param_1);
- }
- );
- assert_cc_matches!(
- result.api,
- quote! {
+ namespace __crubit_internal {
+ extern "C" void ...(
+ double __param_0, double __param_1);
+ }
inline void foo(double __param_0, double __param_1) {
- return ::__crubit_internal
+ return __crubit_internal
::...(
__param_0, __param_1);
}
}
);
- assert!(result.rs_impl.is_some());
+ assert!(result.rs.is_some());
assert_rs_matches!(
- result.rs_impl.unwrap(),
+ result.rs.unwrap(),
quote! {
#[no_mangle]
extern "C" fn ...(__param_0: f64, __param_1: f64) -> () {
@@ -1767,7 +1696,7 @@
/// result from `format_def`.)
fn test_format_def<F, T>(source: &str, name: &str, test_function: F) -> T
where
- F: FnOnce(Result<BindingsSnippet, String>) -> T + Send,
+ F: FnOnce(Result<MixedSnippet, String>) -> T + Send,
T: Send,
{
run_compiler(source, |tcx| {
diff --git a/cc_bindings_from_rs/cc_bindings_from_rs.rs b/cc_bindings_from_rs/cc_bindings_from_rs.rs
index 692260b..91b8c86 100644
--- a/cc_bindings_from_rs/cc_bindings_from_rs.rs
+++ b/cc_bindings_from_rs/cc_bindings_from_rs.rs
@@ -358,13 +358,13 @@
#pragma once
+namespace test_crate {
namespace __crubit_internal {
extern "C" void
__crubit_thunk__RNvCslKXfKXPWofF_10test_crate15public_function();
}
-namespace test_crate {
inline void public_function() {
- return ::__crubit_internal::
+ return __crubit_internal::
__crubit_thunk__RNvCslKXfKXPWofF_10test_crate15public_function();
}
} // namespace test_crate"#