blob: 17ede681b9b6f1a1717c0d5f21733603fe644e86 [file]
// 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
extern crate rustc_span;
use crate::db::BindingsGenerator;
use arc_anyhow::Result;
use code_gen_utils::{format_cc_type_name, make_rs_ident, NamespaceQualifier};
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use rustc_span::def_id::DefId;
use rustc_span::symbol::Symbol;
use std::rc::Rc;
/// Represents the fully qualified name of a Rust item (e.g. of a `struct` or a
/// function).
#[derive(Clone, Debug, PartialEq)]
pub struct FullyQualifiedName {
/// Name of the crate that defines the item.
/// For example, this would be `std` for `std::cmp::Ordering`.
pub krate: Symbol,
/// Configurable top-level namespace of the C++ bindings.
/// For example, this would be `::foo` for `foo::bar::baz::qux`.
pub cpp_top_level_ns: Rc<[Symbol]>,
/// 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`.
pub rs_mod_path: NamespaceQualifier,
/// The C++ namespace to use for the symbol excluding the top-level
/// namespace.
pub cpp_ns_path: NamespaceQualifier,
/// Rust name of the item.
/// For example, this would be:
/// * `Some("Ordering")` for `std::cmp::Ordering`.
/// * `None` for `ItemKind::Use` - e.g.: `use submodule::*`
pub rs_name: Option<Symbol>,
/// The C++ name to use for the symbol.
///
/// For example, the following struct
/// ```
/// #[crubit_annotate::cpp_layout_equivalent(cpp_name="Bar")]
/// struct Foo { ... }
/// ```
/// will be generated as a C++ struct named `Bar` instead of `Foo`.
pub 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_layout_equivalent(cpp_type="x::y")]`,
/// then cpp_type will be `Some(x::y)`.
pub cpp_type: Option<Symbol>,
}
fn format_ns_path_for_cc(
db: &dyn BindingsGenerator<'_>,
ns: &NamespaceQualifier,
) -> Result<TokenStream> {
let idents =
ns.parts().map(|s| db.format_cc_ident(Symbol::intern(s))).collect::<Result<Vec<_>>>()?;
Ok(quote! { #(#idents::)* })
}
impl FullyQualifiedName {
/// Computes a `FullyQualifiedName` for `def_id`.
///
/// May panic if `def_id` is an invalid id.
// TODO(b/259724276): This function's results should be memoized.
pub 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);
let cpp_top_level_ns = db.format_top_level_ns_for_crate(def_id.krate);
let attributes = crubit_attr::get_attrs(tcx, def_id)
.expect("these attributes should never be malformed because they are introduced by crubit itself");
let cpp_type = attributes.cpp_type;
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 rs_name = name.data.get_opt_name();
let cpp_name = attributes.cpp_name.map(|s| Symbol::intern(s.as_str())).or_else(|| {
// If the rs_name is going to be used for the cpp_name, then we need to unkeyword it.
// This prevents silly Rust names like "reinterpret_cast" from trying to be named
// "reinterpret_cast" in C++, which would be an error.
// If the user has opted in to one of these reserved names by setting cpp_name, however,
// we should _not_ implicitly change it, and should instead given them an error.
// Hence, this unkeywording behavior only happens in the case where we implicitly
// delegate to the Rust name.
rs_name.map(|rs_name| {
Symbol::intern(code_gen_utils::unkeyword_cpp_ident(rs_name.as_str()).as_ref())
})
});
let mod_path = NamespaceQualifier::new(
full_path
.into_iter()
.filter_map(|p| p.data.get_opt_name())
.map(|s| Rc::<str>::from(s.as_str())),
);
Self {
krate,
cpp_top_level_ns,
rs_mod_path: mod_path.clone(),
cpp_ns_path: mod_path,
rs_name,
cpp_name,
cpp_type,
}
}
pub fn format_for_cc(&self, db: &dyn BindingsGenerator<'_>) -> Result<TokenStream> {
if let Some(path) = self.cpp_type {
let path = format_cc_type_name(path.as_str())?;
return Ok(path);
}
let name = self.cpp_name.expect("`format_for_cc` can't be called on name-less item kinds");
let cpp_top_level_ns = self
.cpp_top_level_ns
.iter()
.map(|ns| db.format_cc_ident(*ns))
.collect::<Result<Vec<_>>>()?;
let ns_path = format_ns_path_for_cc(db, &self.cpp_ns_path)?;
let name = format_cc_type_name(name.as_str())?;
Ok(quote! { :: #(#cpp_top_level_ns::)* #ns_path #name })
}
pub fn format_for_rs(&self) -> TokenStream {
let iter = self.rs_name_parts();
quote! { #( ::#iter )* }
}
/// Returns an iterator of parts to spell out the Rust name.
pub fn rs_name_parts(&self) -> impl Iterator<Item = Ident> + use<'_> {
let krate = make_rs_ident(self.krate.as_str());
let mod_path = self.rs_mod_path.parts_with_snake_case_record_names();
let name =
self.rs_name.as_ref().expect("`format_for_rs` can't be called on name-less item kinds");
let name = make_rs_ident(name.as_str());
std::iter::once(krate).chain(mod_path).chain(std::iter::once(name))
}
}