Support reexported private symbols with `use`.
If a private symbol is reexported, we generate C++ bindings for the symbol in an internal namespace so that users are warned not to directly use that symbol. If we reexport the symbol with multiple `use`s, all the aliases represent the same type.
PiperOrigin-RevId: 671826238
Change-Id: Ib861969d19d463938198c2c5eea180bd475ea276
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs
index f7a77f7..939f71f 100644
--- a/cc_bindings_from_rs/bindings.rs
+++ b/cc_bindings_from_rs/bindings.rs
@@ -83,6 +83,8 @@
fn repr_attrs(&self, did: DefId) -> Rc<[rustc_attr::ReprAttr]>;
+ fn reexported_symbol_canonical_name_mapping(&self) -> HashMap<DefId, FullyQualifiedName>;
+
fn format_ty_for_cc(
&self,
ty: SugaredTy<'tcx>,
@@ -260,6 +262,7 @@
/// Represents the fully qualified name of a Rust item (e.g. of a `struct` or a
/// function).
+#[derive(Clone, Debug)]
struct FullyQualifiedName {
/// Name of the crate that defines the item.
/// For example, this would be `std` for `std::cmp::Ordering`.
@@ -268,20 +271,16 @@
/// Path to the module where the item is located.
/// For example, this would be `cmp` for `std::cmp::Ordering`.
/// The path may contain multiple modules - e.g. `foo::bar::baz`.
- mod_path: NamespaceQualifier,
+ rs_mod_path: NamespaceQualifier,
+ /// The C++ namespace to use for the symbol excluding the top-level
+ /// namespace.
+ cpp_ns_path: NamespaceQualifier,
- /// Name of the item.
+ /// Rust name of the item.
/// For example, this would be:
/// * `Some("Ordering")` for `std::cmp::Ordering`.
/// * `None` for `ItemKind::Use` - e.g.: `use submodule::*`
- name: Option<Symbol>,
-
- /// The fully-qualified C++ type to use for this, if this was originally a
- /// C++ type.
- ///
- /// For example, if a type has `#[__crubit::annotate(cpp_type="x::y")]`,
- /// then cpp_type will be `Some(x::y)`.
- cpp_type: Option<Symbol>,
+ rs_name: Option<Symbol>,
/// The C++ name to use for the symbol.
///
@@ -292,6 +291,13 @@
/// ```
/// will be generated as a C++ struct named `Bar` instead of `Foo`.
cpp_name: Option<Symbol>,
+
+ /// The fully-qualified C++ type to use for this, if this was originally a
+ /// C++ type.
+ ///
+ /// For example, if a type has `#[__crubit::annotate(cpp_type="x::y")]`,
+ /// then cpp_type will be `Some(x::y)`.
+ cpp_type: Option<Symbol>,
}
impl FullyQualifiedName {
@@ -299,7 +305,12 @@
///
/// May panic if `def_id` is an invalid id.
// TODO(b/259724276): This function's results should be memoized.
- fn new(tcx: TyCtxt, def_id: DefId) -> Self {
+ fn new(db: &dyn BindingsGenerator<'_>, def_id: DefId) -> Self {
+ if let Some(canonical_name) = db.reexported_symbol_canonical_name_mapping().get(&def_id) {
+ return canonical_name.clone();
+ }
+
+ let tcx = db.tcx();
let krate = tcx.crate_name(def_id.krate);
// Crash OK: these attributes are introduced by crubit itself, and "should
@@ -309,8 +320,8 @@
let mut full_path = tcx.def_path(def_id).data; // mod_path + name
let name = full_path.pop().expect("At least the item's name should be present");
- let name = name.data.get_opt_name();
- let cpp_name = attributes.cpp_name.map(|s| Symbol::intern(s.as_str())).or(name);
+ let rs_name = name.data.get_opt_name();
+ let cpp_name = attributes.cpp_name.map(|s| Symbol::intern(s.as_str())).or(rs_name);
let mod_path = NamespaceQualifier::new(
full_path
@@ -319,7 +330,14 @@
.map(|s| Rc::<str>::from(s.as_str())),
);
- Self { krate, mod_path, name, cpp_type, cpp_name }
+ Self {
+ krate,
+ rs_mod_path: mod_path.clone(),
+ cpp_ns_path: mod_path,
+ rs_name,
+ cpp_name,
+ cpp_type,
+ }
}
fn format_for_cc(&self) -> Result<TokenStream> {
@@ -328,22 +346,23 @@
return Ok(quote! {#path});
}
- let name = self.cpp_name.as_ref().unwrap_or_else(|| {
- self.name.as_ref().expect("`format_for_cc` can't be called on name-less item kinds")
- });
+ let name = self
+ .cpp_name
+ .as_ref()
+ .expect("`format_for_cc` can't be called on name-less item kinds");
let top_level_ns = format_cc_ident(self.krate.as_str())?;
- let ns_path = self.mod_path.format_for_cc()?;
+ let ns_path = self.cpp_ns_path.format_for_cc()?;
let name = format_cc_ident(name.as_str())?;
Ok(quote! { :: #top_level_ns :: #ns_path #name })
}
fn format_for_rs(&self) -> TokenStream {
let name =
- self.name.as_ref().expect("`format_for_rs` can't be called on name-less item kinds");
+ self.rs_name.as_ref().expect("`format_for_rs` can't be called on name-less item kinds");
let krate = make_rs_ident(self.krate.as_str());
- let mod_path = self.mod_path.format_for_rs();
+ let mod_path = self.rs_mod_path.format_for_rs();
let name = make_rs_ident(name.as_str());
quote! { :: #krate :: #mod_path #name }
}
@@ -477,6 +496,120 @@
Other,
}
+/// Computes a mapping from a `DefId` to a `FullyQualifiedName` for all
+/// not-directly-public symbols that are reexported by a `use` statement.
+// TODO(b/350772554): Don't generate bindings for ambiguous symbols.
+fn reexported_symbol_canonical_name_mapping(
+ db: &dyn BindingsGenerator<'_>,
+) -> HashMap<DefId, FullyQualifiedName> {
+ let tcx = db.tcx();
+ let mut name_map: HashMap<DefId, FullyQualifiedName> = HashMap::new();
+ let create_canonical_name = |name_map: &mut HashMap<DefId, FullyQualifiedName>,
+ alias_name: &str,
+ alias_local_def_id: LocalDefId,
+ aliased_entity_def_id: DefId|
+ -> Option<FullyQualifiedName> {
+ let rs_name = Symbol::intern(alias_name);
+ if let Some(canonical_name) = name_map.get(&aliased_entity_def_id) {
+ // We keep the lexicographically smallest name.
+ if canonical_name.rs_name.unwrap().as_str() < rs_name.as_str() {
+ return None;
+ }
+ }
+ let def_id = alias_local_def_id.to_def_id();
+ let tcx = db.tcx();
+
+ // We only handle local reexported private symbols for `pub use`.
+ if !tcx.effective_visibilities(()).is_directly_public(alias_local_def_id) // not pub use
+ || !aliased_entity_def_id.is_local() // symbols from other crates
+ || tcx.effective_visibilities(()).is_directly_public(aliased_entity_def_id.expect_local())
+ {
+ return None;
+ }
+ let item_name = tcx.opt_item_name(aliased_entity_def_id)?;
+ let krate = tcx.crate_name(def_id.krate);
+ let parent_def_key = tcx.def_key(def_id).parent?;
+ let parent_def_id = DefId::local(parent_def_key);
+
+ // If the parent is being aliased, we use its canonical name and we always
+ // process parents before their children.
+ let full_path_strs: Vec<Rc<str>> = if let Some(con_name) = name_map.get(&parent_def_id) {
+ con_name.rs_mod_path.0.clone()
+ } else {
+ let mut full_path = tcx.def_path(def_id).data; // mod_path + name
+ full_path.pop().expect("At least the use exists");
+ full_path
+ .into_iter()
+ .filter_map(|p| p.data.get_opt_name())
+ .map(|s| Rc::<str>::from(s.as_str()))
+ .collect()
+ };
+
+ let rs_mod_path = NamespaceQualifier::new(full_path_strs.clone());
+ let cpp_ns_path = NamespaceQualifier::new(
+ full_path_strs.into_iter().chain([Rc::from("__crubit_internal")]),
+ );
+ Some(FullyQualifiedName {
+ cpp_name: Some(item_name),
+ cpp_ns_path,
+ rs_name: Some(rs_name),
+ krate,
+ rs_mod_path,
+ cpp_type: None,
+ })
+ };
+
+ struct AliasInfo {
+ using_name: String,
+ local_def_id: LocalDefId,
+ type_def_id: DefId,
+ def_kind: DefKind,
+ }
+ let aliases =
+ tcx.hir()
+ .items()
+ .filter_map(|item_id| {
+ let local_def_id: LocalDefId = item_id.owner_id.def_id;
+ if let Item { ident, kind: ItemKind::Use(use_path, use_kind), .. } =
+ tcx.hir().expect_item(local_def_id)
+ {
+ // TODO(b/350772554): Preserve the errors.
+ collect_alias_from_use(db, ident.as_str(), use_path, use_kind).ok().map(
+ |aliases| {
+ aliases.into_iter().map(move |(using_name, type_def_id, def_kind)| {
+ AliasInfo { using_name, local_def_id, type_def_id, def_kind }
+ })
+ },
+ )
+ } else {
+ None
+ }
+ })
+ .flatten()
+ .collect::<Vec<AliasInfo>>();
+
+ // TODO(b/350772554): Support mod.
+ // We should process the aliases in the path order: mod -> struct ->
+ // function/etc. Otherwise, for example, the function will still use the
+ // private fully qualified name as it doesn't know the canonical struct name
+ // yet.
+ let (struct_like_aliases, other_aliases): (Vec<AliasInfo>, Vec<AliasInfo>) =
+ aliases.into_iter().partition(|AliasInfo { def_kind, .. }| {
+ matches!(*def_kind, DefKind::Struct | DefKind::Enum | DefKind::Union)
+ });
+ for AliasInfo { using_name, local_def_id, type_def_id, .. } in
+ struct_like_aliases.into_iter().chain(other_aliases.into_iter())
+ {
+ if let Some(canonical_name) =
+ create_canonical_name(&mut name_map, &using_name, local_def_id, type_def_id)
+ {
+ name_map.insert(type_def_id, canonical_name);
+ }
+ }
+
+ name_map
+}
+
fn format_pointer_or_reference_ty_for_cc<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
pointee: SugaredTy<'tcx>,
@@ -666,8 +799,8 @@
ty::TyKind::Adt(adt, substs) => {
ensure!(substs.len() == 0, "Generic types are not supported yet (b/259749095)");
ensure!(
- is_directly_public(tcx, adt.did()),
- "Not directly public type (re-exports are not supported yet - b/262052635)"
+ is_public_or_supported_export(db, adt.did()),
+ "Not a public or a supported reexported type (b/262052635)."
);
let def_id = adt.did();
@@ -693,7 +826,7 @@
format!("Failed to generate bindings for the definition of `{ty}`")
})?;
- CcSnippet { tokens: FullyQualifiedName::new(tcx, def_id).format_for_cc()?, prereqs }
+ CcSnippet { tokens: FullyQualifiedName::new(db, def_id).format_for_cc()?, prereqs }
}
ty::TyKind::RawPtr(pointee_mid, mutbl) => {
@@ -932,7 +1065,7 @@
/// than just `SomeStruct`.
//
// TODO(b/259724276): This function's results should be memoized.
-fn format_ty_for_rs(tcx: TyCtxt, ty: Ty) -> Result<TokenStream> {
+fn format_ty_for_rs(db: &dyn BindingsGenerator<'_>, ty: Ty) -> Result<TokenStream> {
Ok(match ty.kind() {
ty::TyKind::Bool
| ty::TyKind::Float(_)
@@ -954,14 +1087,14 @@
}
ty::TyKind::Adt(adt, substs) => {
ensure!(substs.len() == 0, "Generic types are not supported yet (b/259749095)");
- FullyQualifiedName::new(tcx, adt.did()).format_for_rs()
+ FullyQualifiedName::new(db, adt.did()).format_for_rs()
}
ty::TyKind::RawPtr(pointee_ty, mutbl) => {
let qualifier = match mutbl {
Mutability::Mut => quote! { mut },
Mutability::Not => quote! { const },
};
- let ty = format_ty_for_rs(tcx, *pointee_ty).with_context(|| {
+ let ty = format_ty_for_rs(db, *pointee_ty).with_context(|| {
format!("Failed to format the pointee of the pointer type `{ty}`")
})?;
quote! { * #qualifier #ty }
@@ -971,14 +1104,14 @@
Mutability::Mut => quote! { mut },
Mutability::Not => quote! {},
};
- let ty = format_ty_for_rs(tcx, *referent_ty).with_context(|| {
+ let ty = format_ty_for_rs(db, *referent_ty).with_context(|| {
format!("Failed to format the referent of the reference type `{ty}`")
})?;
let lifetime = format_region_as_rs_lifetime(region);
quote! { & #lifetime #mutability #ty }
}
ty::TyKind::Slice(slice_ty) => {
- let ty = format_ty_for_rs(tcx, *slice_ty).with_context(|| {
+ let ty = format_ty_for_rs(db, *slice_ty).with_context(|| {
format!("Failed to format the element type of the slice type `{ty}`")
})?;
quote! { [#ty] }
@@ -1144,12 +1277,13 @@
/// - `<::crate_name::some_module::SomeStruct as
/// ::core::default::Default>::default`
fn format_thunk_impl<'tcx>(
- tcx: TyCtxt<'tcx>,
+ db: &dyn BindingsGenerator<'tcx>,
fn_def_id: DefId,
sig: &ty::FnSig<'tcx>,
thunk_name: &str,
fully_qualified_fn_name: TokenStream,
) -> Result<TokenStream> {
+ let tcx = db.tcx();
let param_names_and_types: Vec<(Ident, Ty)> = {
let param_names = tcx.fn_arg_names(fn_def_id).iter().enumerate().map(|(i, ident)| {
if ident.as_str().is_empty() {
@@ -1167,7 +1301,7 @@
let mut thunk_params = param_names_and_types
.iter()
.map(|(param_name, ty)| {
- let rs_type = format_ty_for_rs(tcx, *ty)
+ let rs_type = format_ty_for_rs(db, *ty)
.with_context(|| format!("Error handling parameter `{param_name}`"))?;
Ok(if is_c_abi_compatible_by_value(*ty) {
quote! { #param_name: #rs_type }
@@ -1177,7 +1311,7 @@
})
.collect::<Result<Vec<_>>>()?;
- let mut thunk_ret_type = format_ty_for_rs(tcx, sig.output())?;
+ let mut thunk_ret_type = format_ty_for_rs(db, sig.output())?;
let mut thunk_body = {
let fn_args = param_names_and_types.iter().map(|(rs_name, ty)| {
if is_c_abi_compatible_by_value(*ty) {
@@ -1343,6 +1477,7 @@
def_kind: DefKind,
) -> Result<ApiSnippets> {
let tcx = db.tcx();
+
match def_kind {
DefKind::Fn => {
let mut prereqs;
@@ -1356,13 +1491,10 @@
} 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 fully_qualified_fn_name = FullyQualifiedName::new(db, def_id);
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())
+ format_cc_ident(fully_qualified_fn_name.cpp_name.unwrap().as_str())
.context("Error formatting function name")?;
let using_name = format_cc_ident(using_name).context("Error formatting using name")?;
@@ -1406,7 +1538,7 @@
let item_def_id = item_local_def_id.to_def_id();
let item_def_kind = tcx.def_kind(item_def_id);
- if !is_directly_public(tcx, item_def_id) {
+ if !is_exported(tcx, item_def_id) {
continue;
}
match item_def_kind {
@@ -1419,14 +1551,16 @@
items
}
-fn format_use(
+// Collect all the aliases (alias_name, underlying_type_def_id,
+// underlying_type_def_kind) created by the `use` statement. For example, `pub
+// use some_mod::*` will return all the free items that are exported.
+fn collect_alias_from_use(
db: &dyn BindingsGenerator<'_>,
using_name: &str,
use_path: &UsePath,
use_kind: &UseKind,
-) -> Result<ApiSnippets> {
+) -> Result<Vec<(String, DefId, DefKind)>> {
let tcx = db.tcx();
-
// TODO(b/350772554): Support multiple items with the same name in `use`
// statements.`
if use_path.res.len() != 1 {
@@ -1448,21 +1582,29 @@
);
}
};
- ensure!(
- is_directly_public(tcx, def_id),
- "Not directly public type (re-exports are not supported yet - b/262052635)"
- );
let mut aliases = vec![];
if def_kind == DefKind::Mod {
for (item_def_id, item_def_kind) in public_free_items_in_mod(db, def_id) {
- let item_using_name = format_cc_ident(tcx.item_name(item_def_id).as_str())
- .context("Error formatting using name")?;
- aliases.push((item_using_name.to_string(), item_def_id, item_def_kind));
+ if let Ok(item_using_name) = format_cc_ident(tcx.item_name(item_def_id).as_str())
+ .context("Error formatting using name")
+ {
+ aliases.push((item_using_name.to_string(), item_def_id, item_def_kind));
+ }
}
} else {
aliases.push((using_name.to_string(), def_id, def_kind));
}
+ Ok(aliases)
+}
+
+fn format_use(
+ db: &dyn BindingsGenerator<'_>,
+ using_name: &str,
+ use_path: &UsePath,
+ use_kind: &UseKind,
+) -> Result<ApiSnippets> {
+ let aliases = collect_alias_from_use(db, using_name, use_path, use_kind)?;
// TODO(b/350772554): Expose the errors. If any of the types in the `use`
// statement is not supported, we currently ignore it and discard the
// errors.
@@ -1605,8 +1747,8 @@
&& tcx.get_attr(def_id, rustc_span::symbol::sym::export_name).is_none());
let thunk_name = {
let symbol_name = if db.no_thunk_name_mangling() {
- FullyQualifiedName::new(tcx, def_id)
- .name
+ FullyQualifiedName::new(db, def_id)
+ .rs_name
.expect("Functions are assumed to always have a name")
.to_string()
} else {
@@ -1621,16 +1763,11 @@
}
};
- let fully_qualified_fn_name = FullyQualifiedName::new(tcx, def_id);
+ let fully_qualified_fn_name = FullyQualifiedName::new(db, def_id);
let unqualified_rust_fn_name =
- fully_qualified_fn_name.name.expect("Functions are assumed to always have a name");
- let main_api_fn_name = format_cc_ident(
- fully_qualified_fn_name
- .cpp_name
- .expect("Functions are assumed to always have a cpp name")
- .as_str(),
- )
- .context("Error formatting function name")?;
+ fully_qualified_fn_name.rs_name.expect("Functions are assumed to always have a name");
+ let main_api_fn_name = format_cc_ident(fully_qualified_fn_name.cpp_name.unwrap().as_str())
+ .context("Error formatting function name")?;
let mut main_api_prereqs = CcPrerequisites::default();
let main_api_ret_type =
@@ -1705,7 +1842,7 @@
Some(ty) => match ty.kind() {
ty::TyKind::Adt(adt, substs) => {
assert_eq!(0, substs.len(), "Callers should filter out generics");
- Some(FullyQualifiedName::new(tcx, adt.did()))
+ Some(FullyQualifiedName::new(db, adt.did()))
}
_ => panic!("Non-ADT `impl`s should be filtered by caller"),
},
@@ -1855,7 +1992,7 @@
quote! { #struct_name :: #fn_name }
}
};
- format_thunk_impl(tcx, def_id, &sig_mid, &thunk_name, fully_qualified_fn_name)?
+ format_thunk_impl(db, def_id, &sig_mid, &thunk_name, fully_qualified_fn_name)?
};
Ok(ApiSnippets { main_api, cc_details, rs_details })
}
@@ -1937,6 +2074,27 @@
}
}
+/// Like `TyCtxt::is_exported`, but works not only with `LocalDefId`, but
+/// also with `DefId`.
+fn is_exported(tcx: TyCtxt, def_id: DefId) -> bool {
+ match def_id.as_local() {
+ None => {
+ // This mimics the checks in `try_print_visible_def_path_recur` in
+ // `compiler/rustc_middle/src/ty/print/pretty.rs`.
+ let actual_parent = tcx.opt_parent(def_id);
+ let visible_parent = tcx.visible_parent_map(()).get(&def_id).copied();
+ actual_parent == visible_parent
+ }
+ Some(local_def_id) => tcx.effective_visibilities(()).is_exported(local_def_id),
+ }
+}
+
+fn is_public_or_supported_export(db: &dyn BindingsGenerator<'_>, def_id: DefId) -> bool {
+ is_directly_public(db.tcx(), def_id)
+ || (is_exported(db.tcx(), def_id)
+ && db.reexported_symbol_canonical_name_mapping().contains_key(&def_id))
+}
+
fn get_layout<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Result<Layout<'tcx>> {
let param_env = match ty.ty_adt_def() {
None => ty::ParamEnv::empty(),
@@ -1976,14 +2134,12 @@
let tcx = db.tcx();
let self_ty = tcx.type_of(def_id).instantiate_identity();
assert!(self_ty.is_adt());
- assert!(is_directly_public(tcx, def_id), "Caller should verify");
+ assert!(is_public_or_supported_export(db, def_id), "Caller should verify");
- let fully_qualified_name = FullyQualifiedName::new(tcx, def_id);
+ let fully_qualified_name = FullyQualifiedName::new(db, def_id);
let rs_fully_qualified_name = fully_qualified_name.format_for_rs();
- let cpp_name = format_cc_ident(
- fully_qualified_name.cpp_name.expect("Structs always have a name").as_str(),
- )
- .context("Error formatting item name")?;
+ let cpp_name = format_cc_ident(fully_qualified_name.cpp_name.unwrap().as_str())
+ .context("Error formatting item name")?;
// The check below ensures that `format_trait_thunks` will succeed for the
// `Drop`, `Default`, and/or `Clone` trait. Ideally we would directly check
@@ -2494,12 +2650,12 @@
} else {
let fully_qualified_fn_name = {
let fully_qualified_trait_name =
- FullyQualifiedName::new(tcx, trait_id).format_for_rs();
+ FullyQualifiedName::new(db, trait_id).format_for_rs();
let method_name = make_rs_ident(method.name.as_str());
quote! { <#struct_name as #fully_qualified_trait_name>::#method_name }
};
format_thunk_impl(
- tcx,
+ db,
method.def_id,
&sig_mid,
&thunk_name,
@@ -2857,14 +3013,14 @@
})
.filter_map(|impl_item_ref| {
let def_id = impl_item_ref.id.owner_id.def_id;
- if !tcx.effective_visibilities(()).is_directly_public(def_id) {
+ if !is_exported(db.tcx(), def_id.to_def_id()) {
return None;
}
let result = match impl_item_ref.kind {
AssocItemKind::Fn { .. } => {
let result = db.format_fn(def_id);
if result.is_ok() {
- let cpp_name = FullyQualifiedName::new(tcx, def_id.into())
+ let cpp_name = FullyQualifiedName::new(db, def_id.into())
.cpp_name
.unwrap()
.to_string();
@@ -3047,13 +3203,9 @@
/// Will panic if `def_id` is invalid (i.e. doesn't identify a HIR item).
fn format_item(db: &dyn BindingsGenerator<'_>, def_id: LocalDefId) -> Result<Option<ApiSnippets>> {
let tcx = db.tcx();
- // TODO(b/262052635): When adding support for re-exports we may need to change
- // `is_directly_public` below into `is_exported`. (OTOH such change *alone* is
- // undesirable, because it would mean exposing items from a private module.
- // Exposing a private module is undesirable, because it would mean that
- // changes of private implementation details of the crate could become
- // breaking changes for users of the generated C++ bindings.)
- if !tcx.effective_visibilities(()).is_directly_public(def_id) {
+
+ // TODO(b/350772554): Support `use` mod.
+ if !is_public_or_supported_export(db, def_id.to_def_id()) {
return Ok(None);
}
@@ -3263,7 +3415,7 @@
.chain(cc_details)
.map(|(local_def_id, tokens)| {
let ns_def_id = tcx.opt_parent(local_def_id.to_def_id());
- let mod_path = FullyQualifiedName::new(tcx, local_def_id.to_def_id()).mod_path;
+ let mod_path = FullyQualifiedName::new(db, local_def_id.to_def_id()).cpp_ns_path;
(ns_def_id, mod_path, tokens)
})
.collect_vec();
@@ -3938,49 +4090,58 @@
}
#[test]
- fn test_generated_bindings_reimports() {
+ fn test_format_item_reexport_private_type() {
let test_src = r#"
- #![allow(dead_code)]
- #![allow(unused_imports)]
- mod private_submodule1 {
- pub fn subfunction1() {}
- pub fn subfunction2() {}
- pub fn subfunction3() {}
+ #![allow(dead_code)]
+ mod test_mod {
+ pub struct ReExportedStruct{
+ pub field: i32
}
- mod private_submodule2 {
- pub fn subfunction8() {}
- pub fn subfunction9() {}
+ pub struct NotReExportedStruct{
+ pub field: i32
}
+ }
- // Public re-import.
- pub use private_submodule1::subfunction1;
-
- // Private re-import.
- use private_submodule1::subfunction2;
-
- // Re-import that renames.
- pub use private_submodule1::subfunction3 as public_function3;
-
- // Re-import of multiple items via glob.
- pub use private_submodule2::*;
+ pub use crate::test_mod::ReExportedStruct as Z;
+ pub use crate::test_mod::ReExportedStruct as X;
+ pub use crate::test_mod::ReExportedStruct as Y;
+ #[allow(unused_imports)]
+ use crate::test_mod::ReExportedStruct as PrivateUse;
"#;
+ test_format_item(test_src, "NotReExportedStruct", |result| {
+ let result = result.unwrap();
+ assert!(result.is_none());
+ });
+
+ test_format_item(test_src, "PrivateUse", |result| {
+ let result = result.unwrap();
+ assert!(result.is_none());
+ });
+
test_generated_bindings(test_src, |bindings| {
let bindings = bindings.unwrap();
-
- 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}: \
- Not directly public type (re-exports are not supported yet - b/262052635)"
- );
- assert_cc_matches!(
- bindings.h_body,
- quote! {
- __COMMENT__ #expected_comment_txt
+ assert_cc_matches!(
+ bindings.h_body,
+ quote! {
+ ...
+ namespace __crubit_internal {
+ ...
+ struct CRUBIT_INTERNAL_RUST_TYPE(":: rust_out :: X") alignas(4)
+ [[clang::trivial_abi]] ReExportedStruct final
+ ...
}
- );
- }
+ }
+ );
+
+ assert_rs_matches!(
+ bindings.rs_body,
+ quote! {
+ const _: () = assert!(::std::mem::size_of::<::rust_out::X>() == 4);
+ }
+ );
+
+ assert_rs_not_matches!(bindings.rs_body, quote! { ::rust_out::Y });
+ assert_rs_not_matches!(bindings.rs_body, quote! { ::rust_out::Z });
});
}
@@ -7722,10 +7883,7 @@
"#;
test_format_item(test_src, "TypeAlias", |result| {
let err = result.unwrap_err();
- assert_eq!(
- err,
- "Not directly public type (re-exports are not supported yet - b/262052635)"
- );
+ assert_eq!(err, "Not a public or a supported reexported type (b/262052635).");
});
}
@@ -8236,10 +8394,6 @@
),
("Option<i8>", "Generic types are not supported yet (b/259749095)"),
(
- "PublicReexportOfStruct",
- "Not directly public type (re-exports are not supported yet - b/262052635)",
- ),
- (
// This testcase is like `PublicReexportOfStruct`, but the private type and the
// re-export are in another crate. When authoring this test
// `core::alloc::LayoutError` was a public re-export of
@@ -8249,7 +8403,7 @@
// to test them via a test crate that we control (rather than testing via
// implementation details of the std crate).
"core::alloc::LayoutError",
- "Not directly public type (re-exports are not supported yet - b/262052635)",
+ "Not a public or a supported reexported type (b/262052635).",
),
(
"*const Option<i8>",
@@ -8278,12 +8432,6 @@
pub struct LifetimeGenericStruct<'a> {
pub reference: &'a u8,
}
-
- mod private_submodule {
- pub struct PublicStructInPrivateModule;
- }
- pub use private_submodule::PublicStructInPrivateModule
- as PublicReexportOfStruct;
};
test_ty(TypeLocation::FnParam, &testcases, preamble, |desc, tcx, ty, expected_msg| {
let db = bindings_db_for_tests(tcx);
@@ -8354,7 +8502,8 @@
}
};
test_ty(TypeLocation::FnParam, &testcases, preamble, |desc, tcx, ty, expected_tokens| {
- let actual_tokens = format_ty_for_rs(tcx, ty.mid()).unwrap().to_string();
+ let db = bindings_db_for_tests(tcx);
+ let actual_tokens = format_ty_for_rs(&db, ty.mid()).unwrap().to_string();
let expected_tokens = expected_tokens.parse::<TokenStream>().unwrap().to_string();
assert_eq!(actual_tokens, expected_tokens, "{desc}");
});
@@ -8390,8 +8539,9 @@
];
let preamble = quote! {};
test_ty(TypeLocation::FnParam, &testcases, preamble, |desc, tcx, ty, expected_err| {
+ let db = bindings_db_for_tests(tcx);
let anyhow_err =
- format_ty_for_rs(tcx, ty.mid()).expect_err(&format!("Expecting error for: {desc}"));
+ format_ty_for_rs(&db, ty.mid()).expect_err(&format!("Expecting error for: {desc}"));
let actual_err = format!("{anyhow_err:#}");
assert_eq!(&actual_err, *expected_err, "{desc}");
});
diff --git a/cc_bindings_from_rs/test/golden/uses.rs b/cc_bindings_from_rs/test/golden/uses.rs
index 2920f66..ba7c018 100644
--- a/cc_bindings_from_rs/test/golden/uses.rs
+++ b/cc_bindings_from_rs/test/golden/uses.rs
@@ -2,8 +2,8 @@
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#![allow(private_interfaces)]
#![allow(dead_code)]
-
pub mod test_use_glob {
pub fn f1() -> i32 {
42
@@ -27,3 +27,73 @@
}
pub use test_use_glob::*;
+
+mod private_module {
+ pub struct Bar {
+ i: i32,
+ }
+ pub struct Foo {
+ i: i32,
+ pub bar: Bar,
+ }
+
+ impl Foo {
+ pub fn create() -> Foo {
+ Foo { i: 0, bar: Bar { i: 0 } }
+ }
+
+ pub fn bar() -> Bar {
+ Bar { i: 0 }
+ }
+ }
+
+ fn private_fn() -> i32 {
+ 42
+ }
+
+ pub fn g1() -> i32 {
+ private_fn()
+ }
+
+ pub fn g2() -> i32 {
+ private_fn()
+ }
+}
+
+pub use private_module::*;
+
+// Unsupported case: `use` mod.
+/*
+mod another_private_mod {
+ pub mod another_private_mod_2 {
+ pub struct X {
+ pub field: i32,
+ }
+ }
+}
+
+pub use another_private_mod::another_private_mod_2;
+
+pub fn f3() -> another_private_mod_2::X {
+ another_private_mod_2::X { field: 42 }
+}
+*/
+
+// Unsupported case: export ambiguous name in glob.
+/*
+pub mod mod1 {
+ pub struct X {
+ pub field: i32,
+ }
+}
+
+pub mod mod2 {
+ pub struct X {
+ pub field: i32,
+ }
+}
+
+pub use mod1::*;
+pub use mod2::*;
+
+*/
diff --git a/cc_bindings_from_rs/test/golden/uses_cc_api.h b/cc_bindings_from_rs/test/golden/uses_cc_api.h
index bfb7c81..b807fbd 100644
--- a/cc_bindings_from_rs/test/golden/uses_cc_api.h
+++ b/cc_bindings_from_rs/test/golden/uses_cc_api.h
@@ -9,10 +9,12 @@
#pragma once
#include "support/internal/attribute_macros.h"
+#include "support/internal/return_value_slot.h"
#include <cstddef>
#include <cstdint>
#include <type_traits>
+#include <utility>
namespace uses_rust {
@@ -60,6 +62,94 @@
using ::uses_rust::test_use_glob::f2;
using X1 = ::uses_rust::test_use_glob::X1;
+namespace __crubit_internal {
+
+// Generated from:
+// cc_bindings_from_rs/test/golden/uses.rs;l=32
+struct CRUBIT_INTERNAL_RUST_TYPE(":: uses_rust :: Bar") alignas(4)
+ [[clang::trivial_abi]] Bar final {
+ public:
+ // `private_module::Bar` doesn't implement the `Default` trait
+ Bar() = delete;
+
+ // No custom `Drop` impl and no custom \"drop glue\" required
+ ~Bar() = default;
+ Bar(Bar&&) = default;
+ Bar& operator=(Bar&&) = default;
+
+ // `private_module::Bar` doesn't implement the `Clone` trait
+ Bar(const Bar&) = delete;
+ Bar& operator=(const Bar&) = delete;
+
+ private:
+ union {
+ // Generated from:
+ // cc_bindings_from_rs/test/golden/uses.rs;l=33
+ std::int32_t i;
+ };
+
+ private:
+ static void __crubit_field_offset_assertions();
+};
+
+// Generated from:
+// cc_bindings_from_rs/test/golden/uses.rs;l=35
+struct CRUBIT_INTERNAL_RUST_TYPE(":: uses_rust :: Foo") alignas(4)
+ [[clang::trivial_abi]] Foo final {
+ public:
+ // `private_module::Foo` doesn't implement the `Default` trait
+ Foo() = delete;
+
+ // No custom `Drop` impl and no custom \"drop glue\" required
+ ~Foo() = default;
+ Foo(Foo&&) = default;
+ Foo& operator=(Foo&&) = default;
+
+ // `private_module::Foo` doesn't implement the `Clone` trait
+ Foo(const Foo&) = delete;
+ Foo& operator=(const Foo&) = delete;
+
+ // Generated from:
+ // cc_bindings_from_rs/test/golden/uses.rs;l=41
+ static ::uses_rust::__crubit_internal::Foo create();
+
+ // Generated from:
+ // cc_bindings_from_rs/test/golden/uses.rs;l=45
+ static ::uses_rust::__crubit_internal::Bar bar();
+
+ private:
+ union {
+ // Generated from:
+ // cc_bindings_from_rs/test/golden/uses.rs;l=36
+ std::int32_t i;
+ };
+
+ public:
+ union {
+ // Generated from:
+ // cc_bindings_from_rs/test/golden/uses.rs;l=37
+ ::uses_rust::__crubit_internal::Bar bar_;
+ };
+
+ private:
+ static void __crubit_field_offset_assertions();
+};
+
+// Generated from:
+// cc_bindings_from_rs/test/golden/uses.rs;l=54
+std::int32_t g1();
+
+// Generated from:
+// cc_bindings_from_rs/test/golden/uses.rs;l=58
+std::int32_t g2();
+
+} // namespace __crubit_internal
+
+using Bar = ::uses_rust::__crubit_internal::Bar;
+using Foo = ::uses_rust::__crubit_internal::Foo;
+using ::uses_rust::__crubit_internal::g1;
+using ::uses_rust::__crubit_internal::g2;
+
namespace test_use_glob {
namespace __crubit_internal {
@@ -86,4 +176,62 @@
}
} // namespace test_use_glob
+namespace __crubit_internal {
+
+static_assert(
+ sizeof(Bar) == 4,
+ "Verify that ADT layout didn't change since this header got generated");
+static_assert(
+ alignof(Bar) == 4,
+ "Verify that ADT layout didn't change since this header got generated");
+static_assert(std::is_trivially_destructible_v<Bar>);
+static_assert(std::is_trivially_move_constructible_v<Bar>);
+static_assert(std::is_trivially_move_assignable_v<Bar>);
+inline void Bar::__crubit_field_offset_assertions() {
+ static_assert(0 == offsetof(Bar, i));
+}
+static_assert(
+ sizeof(Foo) == 8,
+ "Verify that ADT layout didn't change since this header got generated");
+static_assert(
+ alignof(Foo) == 4,
+ "Verify that ADT layout didn't change since this header got generated");
+static_assert(std::is_trivially_destructible_v<Foo>);
+static_assert(std::is_trivially_move_constructible_v<Foo>);
+static_assert(std::is_trivially_move_assignable_v<Foo>);
+namespace __crubit_internal {
+extern "C" void __crubit_thunk_create(
+ ::uses_rust::__crubit_internal::Foo* __ret_ptr);
+}
+inline ::uses_rust::__crubit_internal::Foo Foo::create() {
+ crubit::ReturnValueSlot<::uses_rust::__crubit_internal::Foo> __ret_slot;
+ __crubit_internal::__crubit_thunk_create(__ret_slot.Get());
+ return std::move(__ret_slot).AssumeInitAndTakeValue();
+}
+
+namespace __crubit_internal {
+extern "C" void __crubit_thunk_bar(
+ ::uses_rust::__crubit_internal::Bar* __ret_ptr);
+}
+inline ::uses_rust::__crubit_internal::Bar Foo::bar() {
+ crubit::ReturnValueSlot<::uses_rust::__crubit_internal::Bar> __ret_slot;
+ __crubit_internal::__crubit_thunk_bar(__ret_slot.Get());
+ return std::move(__ret_slot).AssumeInitAndTakeValue();
+}
+inline void Foo::__crubit_field_offset_assertions() {
+ static_assert(0 == offsetof(Foo, i));
+ static_assert(4 == offsetof(Foo, bar_));
+}
+namespace __crubit_internal {
+extern "C" std::int32_t __crubit_thunk_g1();
+}
+inline std::int32_t g1() { return __crubit_internal::__crubit_thunk_g1(); }
+
+namespace __crubit_internal {
+extern "C" std::int32_t __crubit_thunk_g2();
+}
+inline std::int32_t g2() { return __crubit_internal::__crubit_thunk_g2(); }
+
+} // namespace __crubit_internal
+
} // namespace uses_rust
diff --git a/cc_bindings_from_rs/test/golden/uses_cc_api_impl.rs b/cc_bindings_from_rs/test/golden/uses_cc_api_impl.rs
index 21f4845..5605457 100644
--- a/cc_bindings_from_rs/test/golden/uses_cc_api_impl.rs
+++ b/cc_bindings_from_rs/test/golden/uses_cc_api_impl.rs
@@ -17,3 +17,28 @@
}
const _: () = assert!(::std::mem::size_of::<::uses_rust::test_use_glob::X1>() == 4);
const _: () = assert!(::std::mem::align_of::<::uses_rust::test_use_glob::X1>() == 4);
+const _: () = assert!(::std::mem::size_of::<::uses_rust::Bar>() == 4);
+const _: () = assert!(::std::mem::align_of::<::uses_rust::Bar>() == 4);
+const _: () = assert!(::std::mem::size_of::<::uses_rust::Foo>() == 8);
+const _: () = assert!(::std::mem::align_of::<::uses_rust::Foo>() == 4);
+#[no_mangle]
+extern "C" fn __crubit_thunk_create(
+ __ret_slot: &mut ::core::mem::MaybeUninit<::uses_rust::Foo>,
+) -> () {
+ __ret_slot.write(::uses_rust::Foo::create());
+}
+#[no_mangle]
+extern "C" fn __crubit_thunk_bar(
+ __ret_slot: &mut ::core::mem::MaybeUninit<::uses_rust::Bar>,
+) -> () {
+ __ret_slot.write(::uses_rust::Foo::bar());
+}
+const _: () = assert!(::core::mem::offset_of!(::uses_rust::Foo, bar) == 4);
+#[no_mangle]
+extern "C" fn __crubit_thunk_g1() -> i32 {
+ ::uses_rust::g1()
+}
+#[no_mangle]
+extern "C" fn __crubit_thunk_g2() -> i32 {
+ ::uses_rust::g2()
+}
diff --git a/cc_bindings_from_rs/test/uses/uses.rs b/cc_bindings_from_rs/test/uses/uses.rs
index e22c827..000da3b 100644
--- a/cc_bindings_from_rs/test/uses/uses.rs
+++ b/cc_bindings_from_rs/test/uses/uses.rs
@@ -11,3 +11,23 @@
42
}
}
+
+mod private_mod {
+ pub struct ReexportedStruct {
+ pub field: i32,
+ }
+
+ impl ReexportedStruct {
+ pub fn create(field: i32) -> ReexportedStruct {
+ ReexportedStruct { field }
+ }
+ }
+
+ pub fn private_fn() -> i32 {
+ 42
+ }
+}
+
+pub use private_mod::private_fn;
+pub use private_mod::ReexportedStruct as ExportedStruct;
+pub use private_mod::ReexportedStruct as AliasOfExportedStruct;
diff --git a/cc_bindings_from_rs/test/uses/uses_test.cc b/cc_bindings_from_rs/test/uses/uses_test.cc
index f00f9ca..3bd919c 100644
--- a/cc_bindings_from_rs/test/uses/uses_test.cc
+++ b/cc_bindings_from_rs/test/uses/uses_test.cc
@@ -14,5 +14,15 @@
TEST(UsesTest, UsesExportsAsUsing) { EXPECT_EQ(uses::f(), 42); }
+TEST(UsesTest, ReexportPrivateStruct) {
+ constexpr int kField = 42;
+ uses::ExportedStruct x = uses::ExportedStruct::create(kField);
+ EXPECT_EQ(x.field, kField);
+
+ EXPECT_TRUE(
+ (std::is_same_v<uses::ExportedStruct, uses::AliasOfExportedStruct>));
+}
+
+TEST(UsesTest, ReexportPrivateFunction) { EXPECT_EQ(uses::private_fn(), 42); }
} // namespace
} // namespace crubit
\ No newline at end of file