Support use statement for functions and records.
PiperOrigin-RevId: 653413189
Change-Id: I47dd78ebd9acaeada17af109f7485a011259a248
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs
index 2a9d7fa..b4fe3b0 100644
--- a/cc_bindings_from_rs/bindings.rs
+++ b/cc_bindings_from_rs/bindings.rs
@@ -23,7 +23,8 @@
use proc_macro2::{Ident, Literal, TokenStream};
use quote::{format_ident, quote, ToTokens};
use rustc_attr::find_deprecation;
-use rustc_hir::{AssocItemKind, Item, ItemKind, Node, Safety};
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{AssocItemKind, Item, ItemKind, Node, Safety, UseKind, UsePath};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::dep_graph::DepContext;
use rustc_middle::mir::Mutability;
@@ -1078,6 +1079,91 @@
None
}
+fn format_use(
+ db: &dyn BindingsGenerator<'_>,
+ using_name: &str,
+ use_path: &UsePath,
+ use_kind: &UseKind,
+) -> Result<ApiSnippets> {
+ let tcx = db.tcx();
+
+ // TODO(b/350772554): Support multiple items with the same name in `use`
+ // statements.`
+ if use_path.res.len() != 1 {
+ bail!(
+ "use statements which resolve to multiple items with the same name are not supported yet"
+ );
+ }
+
+ match use_kind {
+ UseKind::Single => {}
+ // TODO(b/350772554): Implement `pub use foo::{x,y}` and `pub use foo::*`
+ UseKind::Glob | UseKind::ListStem => {
+ bail!("Unsupported use kind: {use_kind:?}");
+ }
+ };
+ let (def_kind, def_id) = match use_path.res[0] {
+ // TODO(b/350772554): Support PrimTy.
+ Res::Def(def_kind, def_id) => (def_kind, def_id),
+ _ => {
+ bail!(
+ "Unsupported use statement that refers to this type of the entity: {:#?}",
+ use_path.res[0]
+ );
+ }
+ };
+ ensure!(
+ is_directly_public(tcx, def_id),
+ "Not directly public type (re-exports are not supported yet - b/262052635)"
+ );
+
+ match def_kind {
+ DefKind::Fn => {
+ let mut prereqs;
+ // TODO(b/350772554): Support exporting private functions.
+ if let Some(local_id) = def_id.as_local() {
+ if let Ok(snippet) = db.format_fn(local_id) {
+ prereqs = snippet.main_api.prereqs;
+ } else {
+ bail!("Ignoring the use because the bindings for the target is not generated");
+ }
+ } else {
+ bail!("Unsupported checking for external function");
+ }
+ let fully_qualified_fn_name = FullyQualifiedName::new(tcx, def_id);
+ let unqualified_rust_fn_name =
+ fully_qualified_fn_name.name.expect("Functions are assumed to always have a name");
+ let formatted_fully_qualified_fn_name = fully_qualified_fn_name.format_for_cc()?;
+ let cpp_name = crubit_attr::get(tcx, def_id).unwrap().cpp_name;
+ let main_api_fn_name =
+ format_cc_ident(cpp_name.unwrap_or(unqualified_rust_fn_name).as_str())
+ .context("Error formatting function name")?;
+ let using_name = format_cc_ident(using_name).context("Error formatting using name")?;
+
+ prereqs.defs.insert(def_id.expect_local());
+ let tokens = if format!("{}", using_name) == format!("{}", main_api_fn_name) {
+ quote! {using #formatted_fully_qualified_fn_name;}
+ } else {
+ // TODO(b/350772554): Support function alias.
+ bail!("Unsupported function alias");
+ };
+ Ok(ApiSnippets {
+ main_api: CcSnippet { prereqs, tokens },
+ cc_details: CcSnippet::default(),
+ rs_details: quote! {},
+ })
+ }
+ DefKind::Struct | DefKind::Enum => {
+ let use_type = tcx.type_of(def_id).instantiate_identity();
+ create_type_alias(db, using_name, use_type)
+ }
+ _ => bail!(
+ "Unsupported use statement that refers to this type of the entity: {:#?}",
+ use_path.res
+ ),
+ }
+}
+
fn format_type_alias(
db: &dyn BindingsGenerator<'_>,
local_def_id: LocalDefId,
@@ -1085,19 +1171,23 @@
let tcx = db.tcx();
let def_id: DefId = local_def_id.to_def_id();
let alias_type = tcx.type_of(def_id).instantiate_identity();
- let alias_name = format_cc_ident(tcx.item_name(def_id).as_str())
- .context("Error formatting type alias name")?;
+ create_type_alias(db, tcx.item_name(def_id).as_str(), alias_type)
+}
+
+fn create_type_alias<'tcx>(
+ db: &dyn BindingsGenerator<'tcx>,
+ alias_name: &str,
+ alias_type: Ty<'tcx>,
+) -> Result<ApiSnippets> {
let cc_bindings = format_ty_for_cc(db, alias_type, TypeLocation::Other)?;
let mut main_api_prereqs = CcPrerequisites::default();
let actual_type_name = cc_bindings.into_tokens(&mut main_api_prereqs);
+ let alias_name = format_cc_ident(alias_name).context("Error formatting type alias name")?;
+ let tokens = quote! {using #alias_name = #actual_type_name;};
+
Ok(ApiSnippets {
- main_api: CcSnippet {
- prereqs: main_api_prereqs,
- tokens: quote! {
- using #alias_name = #actual_type_name;
- },
- },
+ main_api: CcSnippet { prereqs: main_api_prereqs, tokens },
cc_details: CcSnippet::default(),
rs_details: quote! {},
})
@@ -2526,6 +2616,9 @@
db.format_adt_core(def_id.to_def_id())
.map(|core| Some(format_adt(db, core))),
Item { kind: ItemKind::TyAlias(..), ..} => format_type_alias(db, def_id).map(Some),
+ Item { ident, kind: ItemKind::Use(use_path, use_kind), ..} => {
+ format_use(db, ident.as_str(), use_path, use_kind).map(Some)
+ },
Item { kind: ItemKind::Impl(_), .. } | // Handled by `format_adt`
Item { kind: ItemKind::Mod(_), .. } => // Handled by `format_crate`
Ok(None),
@@ -3420,12 +3513,12 @@
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
- let failures = vec![(1, 15), (3, 21), (4, 24)];
+ let failures = vec![(1, 15), (3, 21)];
for (use_number, line_number) in failures.into_iter() {
let expected_comment_txt = format!(
"Error generating bindings for `{{use#{use_number}}}` defined at \
<crubit_unittests.rs>;l={line_number}: \
- Unsupported rustc_hir::hir::ItemKind: `use` import"
+ Not directly public type (re-exports are not supported yet - b/262052635)"
);
assert_cc_matches!(
bindings.h_body,
@@ -6851,6 +6944,30 @@
}
#[test]
+ fn test_format_item_use_normal_type() {
+ let test_src = r#"
+ pub mod test_mod {
+ pub struct S{
+ pub field: i32
+ }
+ }
+
+ pub use test_mod::S as G;
+ "#;
+ test_format_item(test_src, "G", |result| {
+ let result = result.unwrap().unwrap();
+ let main_api = &result.main_api;
+ assert!(!main_api.prereqs.is_empty());
+ assert_cc_matches!(
+ main_api.tokens,
+ quote! {
+ using G = ::rust_out::test_mod::S;
+ }
+ );
+ });
+ }
+
+ #[test]
fn test_format_item_type_alias() {
let test_src = r#"
pub type TypeAlias = i32;
diff --git a/cc_bindings_from_rs/cc_bindings_from_rs.rs b/cc_bindings_from_rs/cc_bindings_from_rs.rs
index b95f326..967197c 100644
--- a/cc_bindings_from_rs/cc_bindings_from_rs.rs
+++ b/cc_bindings_from_rs/cc_bindings_from_rs.rs
@@ -336,11 +336,12 @@
assert!(error_report_out_path.exists());
let error_report = std::fs::read_to_string(&error_report_out_path)?;
let expected_error_report = r#"{
- "Unsupported rustc_hir::hir::ItemKind: {}": {
+ "Unsupported use statement that refers to this type of the entity: {:#?}": {
"count": 2,
- "sample_message": "Unsupported rustc_hir::hir::ItemKind: `use` import"
+ "sample_message": "Unsupported use statement that refers to this type of the entity: [\n Def(\n Mod,\n DefId(1:728 ~ std[56d5]::collections),\n ),\n]"
}
}"#;
+ println!("error_report: {}", error_report);
assert_eq!(expected_error_report, error_report);
Ok(())
}
diff --git a/cc_bindings_from_rs/test/uses/BUILD b/cc_bindings_from_rs/test/uses/BUILD
new file mode 100644
index 0000000..6841315
--- /dev/null
+++ b/cc_bindings_from_rs/test/uses/BUILD
@@ -0,0 +1,34 @@
+"""End-to-end tests of `cc_bindings_from_rs`, focusing on use statements."""
+
+load(
+ "@rules_rust//rust:defs.bzl",
+ "rust_library",
+)
+load(
+ "//cc_bindings_from_rs/bazel_support:cc_bindings_from_rust_rule.bzl",
+ "cc_bindings_from_rust",
+)
+load("//common:crubit_wrapper_macros_oss.bzl", "crubit_cc_test")
+
+package(default_applicable_licenses = ["//:license"])
+
+rust_library(
+ name = "uses",
+ testonly = 1,
+ srcs = ["uses.rs"],
+)
+
+cc_bindings_from_rust(
+ name = "uses_cc_api",
+ testonly = 1,
+ crate = ":uses",
+)
+
+crubit_cc_test(
+ name = "uses_test",
+ srcs = ["uses_test.cc"],
+ deps = [
+ ":uses_cc_api",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
diff --git a/cc_bindings_from_rs/test/uses/uses.rs b/cc_bindings_from_rs/test/uses/uses.rs
new file mode 100644
index 0000000..e22c827
--- /dev/null
+++ b/cc_bindings_from_rs/test/uses/uses.rs
@@ -0,0 +1,13 @@
+// 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
+
+// Put before the real definition to make sure that the generarated C++ bindings
+// is not affected by the order of the imports.
+pub use test_mod::f;
+
+pub mod test_mod {
+ pub fn f() -> i32 {
+ 42
+ }
+}
diff --git a/cc_bindings_from_rs/test/uses/uses_test.cc b/cc_bindings_from_rs/test/uses/uses_test.cc
new file mode 100644
index 0000000..f00f9ca
--- /dev/null
+++ b/cc_bindings_from_rs/test/uses/uses_test.cc
@@ -0,0 +1,18 @@
+// 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
+
+#include <cstdint>
+#include <type_traits>
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "cc_bindings_from_rs/test/uses/uses_cc_api.h"
+
+namespace crubit {
+namespace {
+
+TEST(UsesTest, UsesExportsAsUsing) { EXPECT_EQ(uses::f(), 42); }
+
+} // namespace
+} // namespace crubit
\ No newline at end of file
diff --git a/docs/rust/use_statements.md b/docs/rust/use_statements.md
new file mode 100644
index 0000000..61ba14e
--- /dev/null
+++ b/docs/rust/use_statements.md
@@ -0,0 +1,44 @@
+# C++ bindings for `use` statement
+
+Crubit supports `use` statement for functions and user-defined types. For the
+following Rust code:
+
+```rust
+pub use pub_module::pub_function;
+pub use pub_module::pub_struct;
+pub use pub_module::pub_struct2 as pub_struct3;
+```
+
+Crubit will generate the following bindings:
+
+```cpp
+using ::pub_module::pub_function;
+using ::pub_module::pub_struct;
+using pub_struct3 = ::pub_module::pub_struct2;
+```
+
+Currently, Crubit doesn't support the following cases:
+
+### `use` with multiple items or glob
+
+```rust
+pub use some_module::{a, b}; // Not supported yet.
+pub use some_module::*; // Not supported yet.`
+```
+
+### `use` a module
+
+```rust
+pub use some_module; // Not supported yet.
+```
+
+### `use` a non-directly public item
+
+```rust
+mod private_mod {
+ pub fn func() {}
+}
+
+pub use private_mod::func; // Not supported yet. `func` is not directly public
+ // because `private_mod` is private.
+```