blob: 1d90fc39dd7a7e793efc948c7a00e105b36d1357 [file] [log] [blame] [edit]
// 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
//! Types and deserialization logic for IR. See docs in
//! `rs_bindings_from_cc/ir.h` for more
//! information.
use arc_anyhow::{anyhow, bail, Context, Error, Result};
use code_gen_utils::{make_rs_ident, NamespaceQualifier};
use crubit_feature::CrubitFeature;
use proc_macro2::{Ident, TokenStream};
use quote::{quote, ToTokens};
use serde::Deserialize;
use std::cell::OnceCell;
use std::cmp::Ordering;
use std::collections::hash_map::{Entry, HashMap};
use std::collections::BTreeMap;
use std::fmt::{self, Debug, Display, Formatter};
use std::hash::{Hash, Hasher};
use std::rc::Rc;
/// Common data about all items.
pub trait GenericItem {
fn id(&self) -> ItemId;
/// The Bazel target which owns the bindings for this item.
fn owning_target(&self) -> Option<BazelLabel>;
/// The name of the item, readable by programmers.
///
/// For example, `void Foo();` should have name `Foo`.
fn debug_name(&self, ir: &IR) -> Rc<str>;
/// If this item is unsupported by Crubit, we may generate
/// markers in the Rust bindings to indicate that the item is not
/// supported. This function returns the kind of unsupported item
/// in order to generate such markers in the proper namespace
/// (type, function, module).
fn unsupported_kind(&self) -> UnsupportedItemKind;
/// The recorded source location, or None if none is present.
fn source_loc(&self) -> Option<Rc<str>>;
/// A human-readable list of unknown attributes, or None if all attributes
/// were understood.
fn unknown_attr(&self) -> Option<Rc<str>>;
/// Whether failure to generate binding should be treated as a hard error (`CRUBIT_MUST_BIND`).
fn must_bind(&self) -> bool;
}
impl<T> GenericItem for Rc<T>
where
T: GenericItem + ?Sized,
{
fn id(&self) -> ItemId {
(**self).id()
}
fn owning_target(&self) -> Option<BazelLabel> {
(**self).owning_target()
}
fn debug_name(&self, ir: &IR) -> Rc<str> {
(**self).debug_name(ir)
}
fn unsupported_kind(&self) -> UnsupportedItemKind {
(**self).unsupported_kind()
}
fn source_loc(&self) -> Option<Rc<str>> {
(**self).source_loc()
}
fn unknown_attr(&self) -> Option<Rc<str>> {
(**self).unknown_attr()
}
fn must_bind(&self) -> bool {
(**self).must_bind()
}
}
/// Deserialize `IR` from JSON bytes.
pub fn deserialize_ir(bytes: &[u8]) -> Result<IR> {
let flat_ir = serde_json::from_slice(bytes)?;
Ok(make_ir(flat_ir))
}
/// Create a testing `IR` instance from given parts. This function does not use
/// any mock values.
pub fn make_ir_from_parts<CrubitFeatures>(
items: Vec<Item>,
public_headers: Vec<HeaderName>,
current_target: BazelLabel,
top_level_item_ids: BTreeMap<BazelLabel, Vec<ItemId>>,
crate_root_path: Option<Rc<str>>,
crubit_features: BTreeMap<BazelLabel, CrubitFeatures>,
) -> IR
where
CrubitFeatures: Into<flagset::FlagSet<CrubitFeature>>,
{
make_ir(FlatIR {
public_headers,
current_target,
items,
top_level_item_ids,
crate_root_path,
crubit_features: crubit_features
.into_iter()
.map(|(label, features)| {
(label, crubit_feature::SerializedCrubitFeatures(features.into()))
})
.collect(),
})
}
fn make_ir(flat_ir: FlatIR) -> IR {
let mut used_decl_ids = HashMap::new();
for item in &flat_ir.items {
if let Some(existing_decl) = used_decl_ids.insert(item.id(), item) {
panic!("Duplicate decl_id found in {:?} and {:?}", existing_decl, item);
}
}
let item_id_to_item_idx = flat_ir
.items
.iter()
.enumerate()
.map(|(idx, item)| (item.id(), idx))
.collect::<HashMap<_, _>>();
let mut lifetimes: HashMap<LifetimeId, LifetimeName> = HashMap::new();
for item in &flat_ir.items {
let lifetime_params = match item {
Item::Record(record) => &record.lifetime_params,
Item::Func(func) => &func.lifetime_params,
_ => continue,
};
for lifetime in lifetime_params {
match lifetimes.entry(lifetime.id) {
Entry::Occupied(occupied) => {
panic!(
"Duplicate use of lifetime ID {:?} in item {item:?} for names: '{}, '{}",
lifetime.id,
&occupied.get().name,
&lifetime.name
)
}
Entry::Vacant(vacant) => {
vacant.insert(lifetime.clone());
}
}
}
}
let mut namespace_id_to_number_of_reopened_namespaces = HashMap::new();
let mut reopened_namespace_id_to_idx = HashMap::new();
flat_ir
.items
.iter()
.filter_map(|item| match item {
Item::Namespace(ns) if ns.owning_target == flat_ir.current_target => {
Some((ns.canonical_namespace_id, ns.id))
}
_ => None,
})
.for_each(|(canonical_id, id)| {
let current_count =
*namespace_id_to_number_of_reopened_namespaces.entry(canonical_id).or_insert(0);
reopened_namespace_id_to_idx.insert(id, current_count);
namespace_id_to_number_of_reopened_namespaces.insert(canonical_id, current_count + 1);
});
let mut function_name_to_functions = HashMap::<UnqualifiedIdentifier, Vec<Rc<Func>>>::new();
flat_ir
.items
.iter()
.filter_map(|item| match item {
Item::Func(func) => Some(func),
_ => None,
})
.for_each(|f| {
function_name_to_functions.entry(f.rs_name.clone()).or_default().push(f.clone());
});
IR {
flat_ir,
item_id_to_item_idx,
lifetimes,
namespace_id_to_number_of_reopened_namespaces,
reopened_namespace_id_to_idx,
function_name_to_functions,
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct HeaderName {
pub name: Rc<str>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(transparent)]
pub struct LifetimeId(pub i32);
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LifetimeName {
pub name: Rc<str>,
pub id: LifetimeId,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CcType {
pub variant: CcTypeVariant,
pub is_const: bool,
pub unknown_attr: Rc<str>,
}
impl CcType {
pub fn is_unit_type(&self) -> bool {
matches!(&self.variant, CcTypeVariant::Primitive(Primitive::Void))
}
}
impl From<&Record> for CcType {
fn from(record: &Record) -> Self {
CcType {
variant: CcTypeVariant::Decl(record.id),
is_const: false,
unknown_attr: Rc::default(),
}
}
}
impl From<&TypeAlias> for CcType {
fn from(alias: &TypeAlias) -> Self {
CcType {
variant: CcTypeVariant::Decl(alias.id),
is_const: false,
unknown_attr: Rc::default(),
}
}
}
impl From<&ExistingRustType> for CcType {
fn from(existing_rust_type: &ExistingRustType) -> Self {
CcType {
variant: CcTypeVariant::Decl(existing_rust_type.id),
is_const: false,
unknown_attr: Rc::default(),
}
}
}
#[derive(Copy, Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum PointerTypeKind {
LValueRef,
RValueRef,
Nullable,
NonNull,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PointerType {
pub kind: PointerTypeKind,
pub lifetime: Option<LifetimeId>,
pub pointee_type: Rc<CcType>,
}
/// Generates an enum type that implements `Deserialize`, which parses the stringified contents of
/// the braces, and `ToTokens`, which quotes the contents of the braces.
macro_rules! define_typed_tokens_enum {
{$(#[$ty_attr:meta])* $vis:vis enum $Type:ident {$($(#[$variant_attr:meta])* $Variant:ident = {$($cpp_spelling:tt)+},)+}} => {
$(#[$ty_attr])*
$vis enum $Type {
$(
$(#[$variant_attr])*
$Variant,
)+
}
// #[serde(rename(deserialize = stringify!(...)))] doesn't work, so manual impl.
impl<'de> serde::Deserialize<'de> for $Type {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct Visitor;
impl serde::de::Visitor<'_> for Visitor {
type Value = $Type;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
const S: &str = concat!(
"one of: ",
$(
"\"",
stringify!($($cpp_spelling)+),
"\", "
),+
);
formatter.write_str(&S[..S.len() - ", ".len()])
}
fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<Self::Value, E> {
match s {
$(
stringify!($($cpp_spelling)+) => Ok($Type::$Variant),
)+
_ => Err(serde::de::Error::invalid_value(serde::de::Unexpected::Str(s), &self))
}
}
}
deserializer.deserialize_str(Visitor)
}
}
impl ToTokens for $Type {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
$(
Self::$Variant => quote! { $($cpp_spelling)+ }.to_tokens(tokens),
)+
}
}
}
}
}
define_typed_tokens_enum! {
#[derive(Copy, Debug, PartialEq, Eq, Hash, Clone)]
pub enum Primitive {
Bool = {bool},
Void = {void},
Float = {float},
Double = {double},
Char = {char},
SignedChar = {signed char},
UnsignedChar = {unsigned char},
Short = {short},
Int = {int},
Long = {long},
LongLong = {long long},
UnsignedShort = {unsigned short},
UnsignedInt = {unsigned int},
UnsignedLong = {unsigned long},
UnsignedLongLong = {unsigned long long},
Char16T = {char16_t},
Char32T = {char32_t},
PtrdiffT = {ptrdiff_t},
IntptrT = {intptr_t},
SizeT = {size_t},
UintptrT = {uintptr_t},
StdPtrdiffT = {std::ptrdiff_t},
StdIntptrT = {std::intptr_t},
StdSizeT = {std::size_t},
StdUintptrT = {std::uintptr_t},
Int8T = {int8_t},
Int16T = {int16_t},
Int32T = {int32_t},
Int64T = {int64_t},
StdInt8T = {std::int8_t},
StdInt16T = {std::int16_t},
StdInt32T = {std::int32_t},
StdInt64T = {std::int64_t},
Uint8T = {uint8_t},
Uint16T = {uint16_t},
Uint32T = {uint32_t},
Uint64T = {uint64_t},
StdUint8T = {std::uint8_t},
StdUint16T = {std::uint16_t},
StdUint32T = {std::uint32_t},
StdUint64T = {std::uint64_t},
}
}
define_typed_tokens_enum! {
/// The C++ calling convention of a function.
#[derive(Copy, Debug, PartialEq, Eq, Hash, Clone)]
pub enum CcCallingConv {
C = {cdecl},
X86FastCall = {fastcall},
X86VectorCall = {vectorcall},
X86ThisCall = {thiscall},
X86StdCall = {stdcall},
Win64 = {ms_abi},
}
}
impl CcCallingConv {
/// Converts clang::CallingConv enum [1] into an equivalent Rust Abi [2, 3, 4].
/// [1]
/// https://github.com/llvm/llvm-project/blob/c6a3225bb03b6afc2b63fbf13db3c100406b32ce/clang/include/clang/Basic/Specifiers.h#L262-L283
/// [2] https://doc.rust-lang.org/reference/types/function-pointer.html
/// [3]
/// https://doc.rust-lang.org/reference/items/functions.html#extern-function-qualifier
/// [4]
/// https://github.com/rust-lang/rust/blob/b27ccbc7e1e6a04d749e244a3c13f72ca38e80e7/compiler/rustc_target/src/spec/abi.rs#L49
pub fn rs_extern_abi(self) -> &'static str {
match self {
CcCallingConv::C => {
// https://doc.rust-lang.org/reference/items/external-blocks.html#abi says
// that:
// - `extern "C"` [...] whatever the default your C compiler supports.
// - `extern "cdecl"` -- The default for x86_32 C code.
//
// We don't support C++ exceptions and therefore we use "C" (rather than
// "C-unwind") - we have no need for unwinding across the FFI boundary -
// e.g. from C++ into Rust frames (or vice versa).
"C"
}
CcCallingConv::X86FastCall => {
// https://doc.rust-lang.org/reference/items/external-blocks.html#abi says
// that the fastcall ABI -- corresponds to MSVC's __fastcall and GCC and
// clang's __attribute__((fastcall)).
"fastcall"
}
CcCallingConv::X86VectorCall => {
// https://doc.rust-lang.org/reference/items/external-blocks.html#abi says
// that the vectorcall ABI -- corresponds to MSVC's __vectorcall and
// clang's __attribute__((vectorcall)).
"vectorcall"
}
CcCallingConv::X86ThisCall => {
// We don't support C++ exceptions and therefore we use "thiscall" (rather
// than "thiscall-unwind") - we have no need for unwinding across the FFI
// boundary - e.g. from C++ into Rust frames (or vice versa).
"thiscall"
}
CcCallingConv::X86StdCall => {
// https://doc.rust-lang.org/reference/items/external-blocks.html#abi says
// extern "stdcall" -- The default for the Win32 API on x86_32.
//
// We don't support C++ exceptions and therefore we use "stdcall" (rather
// than "stdcall-unwind") - we have no need for unwinding across the FFI
// boundary - e.g. from C++ into Rust frames (or vice versa).
"stdcall"
}
CcCallingConv::Win64 => {
// https://doc.rust-lang.org/reference/items/external-blocks.html#abi says
// extern "win64" -- The default for C code on x86_64 Windows.
"win64"
}
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum CcTypeVariant {
Primitive(Primitive),
Pointer(PointerType),
FuncPointer {
non_null: bool,
call_conv: CcCallingConv,
/// The parameter types, followed by the return type.
param_and_return_types: Rc<[CcType]>,
},
Decl(ItemId),
}
impl CcTypeVariant {
pub fn as_pointer(&self) -> Option<&PointerType> {
match &self {
CcTypeVariant::Pointer(pointer) => Some(pointer),
_ => None,
}
}
}
impl CcTypeVariant {
pub fn as_primitive(&self) -> Option<Primitive> {
match &self {
CcTypeVariant::Primitive(primitive) => Some(*primitive),
_ => None,
}
}
}
pub trait TypeWithDeclId {
fn decl_id(&self) -> Option<ItemId>;
}
impl TypeWithDeclId for CcType {
fn decl_id(&self) -> Option<ItemId> {
match &self.variant {
CcTypeVariant::Decl(id) => Some(*id),
_ => None,
}
}
}
#[derive(PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Identifier {
pub identifier: Rc<str>,
}
impl Identifier {
pub fn as_str(&self) -> &str {
&self.identifier
}
}
impl Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.identifier)
}
}
impl Debug for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\"{}\"", self.identifier)
}
}
impl PartialEq<str> for Identifier {
fn eq(&self, other: &str) -> bool {
self.identifier.as_ref() == other
}
}
impl PartialEq<&str> for Identifier {
fn eq(&self, other: &&str) -> bool {
self.eq(*other)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct IntegerConstant {
pub is_negative: bool,
pub wrapped_value: u64,
}
#[derive(PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Operator {
pub name: Rc<str>,
}
impl Operator {
pub fn cc_name(&self) -> String {
let separator = match self.name.chars().next() {
Some(c) if c.is_alphabetic() => " ",
_ => "",
};
format!("operator{separator}{name}", separator = separator, name = self.name)
}
}
impl Debug for Operator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\"{}\"", self.cc_name())
}
}
#[derive(PartialEq, Eq, Hash, Clone, Copy, Deserialize)]
#[serde(transparent)]
pub struct ItemId(usize);
impl Debug for ItemId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ItemId({:#x})", self.0)
}
}
impl ItemId {
pub const fn new_for_testing(value: usize) -> Self {
Self(value)
}
}
impl ToTokens for ItemId {
fn to_tokens(&self, tokens: &mut TokenStream) {
use std::str::FromStr;
proc_macro2::Literal::from_str(&format!("{:#x}", self.0)).unwrap().to_tokens(tokens)
}
}
/// A Bazel label, e.g. `//foo:bar`.
#[derive(Debug, Eq, Clone, Deserialize)]
#[serde(transparent)]
pub struct BazelLabel(pub Rc<str>);
impl BazelLabel {
/// Returns the target name. E.g. `bar` for `//foo:bar`.
pub fn target_name(&self) -> &str {
if let Some((_package, target_name)) = self.0.split_once(':') {
return target_name;
}
if let Some((_, last_package_component)) = self.0.rsplit_once('/') {
return last_package_component;
}
&self.0
}
fn package_name(&self) -> &str {
self.0.rsplit_once(':').unwrap_or((&self.0, "")).0
}
fn last_package_component(&self) -> &str {
self.package_name().rsplit_once('/').unwrap_or(("", "")).1
}
// TODO(b/216587072): Remove this hacky escaping and use the import! macro once
// available.
// For now, use the simple escaping scheme of mapping all invalid characters
// to underscore, instead of the one similar to `convert_to_cc_identifier`, so
// that the escaped target name doesn't become longer (rustc currently produces
// .o artifacts that repeat the target name twice, which can easily cause
// the path length of artifacts to exceed the limit of the file system.)
pub fn target_name_escaped(&self) -> String {
let mut target_name = self.target_name().to_owned();
if target_name == "core" {
target_name = "core_".to_owned() + self.last_package_component();
} else if target_name.starts_with(char::is_numeric) {
target_name.insert(0, 'n');
}
target_name.replace(|c: char| !c.is_ascii_alphanumeric(), "_")
}
// Returns the bazel label as a valid C++ identifier, with a leading underscore.
// Non-alphanumeric characters are escaped as `_xx`, where `xx` is the the byte
// as hexadecimal.
//
// For instance, `//foo` becomes `__2f_2ffoo`.
pub fn convert_to_cc_identifier(&self) -> String {
use std::fmt::Write;
let mut result = "_".to_string();
result.reserve_exact(self.0.len().checked_mul(2).unwrap_or(self.0.len()));
// This is yet another escaping scheme... :-/ Compare this with
// https://github.com/bazelbuild/rules_rust/blob/1f2e6231de29d8fad8d21486f0d16403632700bf/rust/private/utils.bzl#L459-L586
for b in self.0.bytes() {
if (b as char).is_ascii_alphanumeric() {
result.push(b as char);
} else {
write!(result, "_{b:02x}").unwrap();
}
}
result.shrink_to_fit();
#[cfg(debug_assertions)]
for c in result.chars() {
debug_assert!(
c.is_ascii_alphanumeric() || c == '_',
"invalid result identifier: {result:?}"
);
}
result
}
/// Private helper function for simplifying PartialEq, PartialOrd, and Hash implementations
/// to ensure that //foo:bar and //foo/bar:bar are considered identical.
fn components(&self) -> (&str, &str) {
(self.target_name(), self.package_name())
}
}
impl PartialEq for BazelLabel {
fn eq(&self, other: &Self) -> bool {
self.components() == other.components()
}
}
impl PartialOrd for BazelLabel {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for BazelLabel {
fn cmp(&self, other: &Self) -> Ordering {
self.components().cmp(&other.components())
}
}
impl Hash for BazelLabel {
fn hash<H: Hasher>(&self, state: &mut H) {
self.components().hash(state);
}
}
impl<T: Into<String>> From<T> for BazelLabel {
fn from(label: T) -> Self {
Self(label.into().into())
}
}
impl Display for BazelLabel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// If this isn't actually a known bazel target, stringify for humans as the filename.
if let Some(s) = self.0.strip_prefix("//_unknown_target:") {
write!(f, "{}", s)
} else {
write!(f, "{}", &*self.0)
}
}
}
#[derive(PartialEq, Eq, Hash, Clone, Deserialize)]
pub enum UnqualifiedIdentifier {
Identifier(Identifier),
Operator(Operator),
Constructor,
Destructor,
}
impl UnqualifiedIdentifier {
pub fn is_constructor(&self) -> bool {
matches!(self, UnqualifiedIdentifier::Constructor)
}
pub fn is_destructor(&self) -> bool {
matches!(self, UnqualifiedIdentifier::Destructor)
}
pub fn as_identifier(&self) -> Option<&Identifier> {
match self {
UnqualifiedIdentifier::Identifier(identifier) => Some(identifier),
_ => None,
}
}
pub fn as_operator(&self) -> Option<&Operator> {
match self {
UnqualifiedIdentifier::Operator(op) => Some(op),
_ => None,
}
}
pub fn identifier_as_str(&self) -> Option<&str> {
self.as_identifier().map(|id| id.identifier.as_ref())
}
}
impl Debug for UnqualifiedIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UnqualifiedIdentifier::Identifier(identifier) => Debug::fmt(identifier, f),
UnqualifiedIdentifier::Operator(op) => Debug::fmt(op, f),
UnqualifiedIdentifier::Constructor => f.write_str("Constructor"),
UnqualifiedIdentifier::Destructor => f.write_str("Destructor"),
}
}
}
impl PartialEq<str> for UnqualifiedIdentifier {
fn eq(&self, other: &str) -> bool {
if let UnqualifiedIdentifier::Identifier(identifier) = self {
&*identifier.identifier == other
} else {
false
}
}
}
impl PartialEq<&str> for UnqualifiedIdentifier {
fn eq(&self, other: &&str) -> bool {
self.eq(*other)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize)]
pub enum ReferenceQualification {
LValue,
RValue,
Unqualified,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct InstanceMethodMetadata {
pub reference: ReferenceQualification,
pub is_const: bool,
pub is_virtual: bool,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct MemberFuncMetadata {
pub record_id: ItemId,
pub instance_method_metadata: Option<InstanceMethodMetadata>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct FuncParam {
#[serde(rename(deserialize = "type"))]
pub type_: CcType,
pub identifier: Identifier,
/// A human-readable list of attributes that Crubit doesn't understand.
///
/// Because attributes can change the behavior or semantics of function
/// parameters in ways that may affect interop, we default-closed and
/// do not expose functions with unknown attributes.
///
/// One notable example is `lifetimebound`, which we might expect to map
/// to Rust lifetimes.
pub unknown_attr: Option<Rc<str>>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum SafetyAnnotation {
DisableUnsafe,
Unsafe,
Unannotated,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Func {
pub cc_name: UnqualifiedIdentifier,
pub rs_name: UnqualifiedIdentifier,
pub owning_target: BazelLabel,
pub mangled_name: Rc<str>,
pub doc_comment: Option<Rc<str>>,
pub return_type: CcType,
pub params: Vec<FuncParam>,
/// For tests and internal use only.
///
/// Prefer to reconstruct the lifetime params from the parameter types, as
/// needed. This allows new parameters and lifetimes to be added that were
/// not originally part of the IR.
pub lifetime_params: Vec<LifetimeName>,
pub is_inline: bool,
pub member_func_metadata: Option<MemberFuncMetadata>,
pub is_extern_c: bool,
pub is_noreturn: bool,
pub is_variadic: bool,
pub is_consteval: bool,
/// The `[[nodiscard("...")]]` string. If `[[nodiscard]]`, then the empty
/// string is used.
pub nodiscard: Option<Rc<str>>,
/// The `[[deprecated("...")]]` string. If `[[deprecated]]`, then the empty
/// string is used.
pub deprecated: Option<Rc<str>>,
/// A human-readable list of attributes that Crubit doesn't understand.
///
/// Because attributes can change the behavior or semantics of functions in
/// fairly significant ways, and in ways that may affect interop, we
/// default-closed and do not expose functions with unknown attributes.
pub unknown_attr: Option<Rc<str>>,
pub has_c_calling_convention: bool,
pub is_member_or_descendant_of_class_template: bool,
pub safety_annotation: SafetyAnnotation,
pub source_loc: Rc<str>,
pub id: ItemId,
pub enclosing_item_id: Option<ItemId>,
/// If this function was declared as a `friend` inside of a record
/// definition, this ItemId refers to the record containing the `friend`
/// function declaration.
///
/// The record pointed to by `ItemId` must then be ADL-visible in order to
/// invoke this function.
pub adl_enclosing_record: Option<ItemId>,
pub must_bind: bool,
}
impl GenericItem for Func {
fn id(&self) -> ItemId {
self.id
}
fn owning_target(&self) -> Option<BazelLabel> {
Some(self.owning_target.clone())
}
fn debug_name(&self, ir: &IR) -> Rc<str> {
let record: Option<Rc<str>> = ir.record_for_member_func(self).map(|r| r.debug_name(ir));
let record: Option<&str> = record.as_deref();
let func_name = match &self.rs_name {
UnqualifiedIdentifier::Identifier(id) => id.identifier.to_string(),
UnqualifiedIdentifier::Operator(op) => op.cc_name(),
UnqualifiedIdentifier::Destructor => {
format!("~{}", record.expect("destructor must be associated with a record"))
}
UnqualifiedIdentifier::Constructor => {
record.expect("constructor must be associated with a record").to_string()
}
};
if let Some(record_name) = record {
format!("{}::{}", record_name, func_name).into()
} else {
func_name.into()
}
}
fn unsupported_kind(&self) -> UnsupportedItemKind {
if self.cc_name == UnqualifiedIdentifier::Constructor {
UnsupportedItemKind::Constructor
} else {
UnsupportedItemKind::Func
}
}
fn source_loc(&self) -> Option<Rc<str>> {
Some(self.source_loc.clone())
}
fn unknown_attr(&self) -> Option<Rc<str>> {
self.unknown_attr.clone()
}
fn must_bind(&self) -> bool {
self.must_bind
}
}
impl Func {
pub fn is_instance_method(&self) -> bool {
self.member_func_metadata
.as_ref()
.filter(|meta| meta.instance_method_metadata.is_some())
.is_some()
}
}
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Deserialize)]
pub enum AccessSpecifier {
Public,
Protected,
Private,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Field {
pub rust_identifier: Option<Identifier>,
pub cpp_identifier: Option<Identifier>,
pub doc_comment: Option<Rc<str>>,
#[serde(rename(deserialize = "type"))]
pub type_: Result<CcType, String>,
pub access: AccessSpecifier,
pub offset: usize,
pub size: usize,
/// A human-readable list of attributes that Crubit doesn't understand.
pub unknown_attr: Result<Option<Rc<str>>, String>,
pub is_no_unique_address: bool,
pub is_bitfield: bool,
// TODO(kinuko): Consider removing this, it is a duplicate of the same information
// in `Record`.
pub is_inheritable: bool,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub enum SpecialMemberFunc {
Trivial,
NontrivialMembers,
NontrivialUserDefined,
Unavailable,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BaseClass {
pub base_record_id: ItemId,
pub offset: Option<i64>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct IncompleteRecord {
pub cc_name: Identifier,
pub rs_name: Identifier,
pub id: ItemId,
pub owning_target: BazelLabel,
/// A human-readable list of attributes that Crubit doesn't understand.
///
/// Because attributes can change the behavior or semantics of types in
/// fairly significant ways, and in ways that may affect interop, we
/// default-closed and do not expose functions with unknown attributes.
pub unknown_attr: Option<Rc<str>>,
pub record_type: RecordType,
pub enclosing_item_id: Option<ItemId>,
pub must_bind: bool,
}
impl GenericItem for IncompleteRecord {
fn id(&self) -> ItemId {
self.id
}
fn owning_target(&self) -> Option<BazelLabel> {
Some(self.owning_target.clone())
}
fn debug_name(&self, _: &IR) -> Rc<str> {
self.cc_name.identifier.clone()
}
fn unsupported_kind(&self) -> UnsupportedItemKind {
self.record_type.unsupported_item_kind()
}
fn source_loc(&self) -> Option<Rc<str>> {
None
}
fn unknown_attr(&self) -> Option<Rc<str>> {
self.unknown_attr.clone()
}
fn must_bind(&self) -> bool {
self.must_bind
}
}
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Deserialize)]
pub enum RecordType {
Struct,
Union,
Class,
}
impl RecordType {
fn unsupported_item_kind(&self) -> UnsupportedItemKind {
match self {
RecordType::Struct => UnsupportedItemKind::Struct,
RecordType::Union => UnsupportedItemKind::Union,
RecordType::Class => UnsupportedItemKind::Class,
}
}
}
impl ToTokens for RecordType {
fn to_tokens(&self, tokens: &mut TokenStream) {
let tag = match self {
RecordType::Struct => quote! { struct },
RecordType::Union => quote! { union },
RecordType::Class => quote! { class },
};
tag.to_tokens(tokens)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SizeAlign {
pub size: usize,
pub alignment: usize,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum BridgeType {
BridgeVoidConverters {
rust_name: Rc<str>,
rust_to_cpp_converter: Rc<str>,
cpp_to_rust_converter: Rc<str>,
},
ProtoMessageBridge {
rust_name: Rc<str>,
abi_rust: Rc<str>,
abi_cpp: Rc<str>,
},
Bridge {
rust_name: Rc<str>,
abi_rust: Rc<str>,
abi_cpp: Rc<str>,
},
StdOptional(CcType),
StdPair(CcType, CcType),
StdString,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct TemplateArg {
#[serde(rename(deserialize = "type"))]
pub type_: Result<CcType, String>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct TemplateSpecialization {
/// Is this a `std::string_view`?
pub is_string_view: bool,
/// Is this a `std::wstring_view`?
pub is_wstring_view: bool,
/// The target containing the template definition
pub defining_target: BazelLabel,
pub template_name: Rc<str>,
pub template_args: Vec<TemplateArg>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub enum TraitImplPolarity {
Negative,
None,
Positive,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TraitDerives {
// <internal link> start
pub clone: TraitImplPolarity,
pub copy: TraitImplPolarity,
pub debug: TraitImplPolarity,
// <internal link> end
pub send: bool,
pub sync: bool,
pub custom: Vec<Rc<str>>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Record {
pub rs_name: Identifier,
pub cc_name: Identifier,
/// Mangled record names are used to 1) provide valid Rust identifiers for
/// C++ template specializations, and 2) help build unique names for virtual
/// upcast thunks.
pub mangled_cc_name: Rc<str>,
pub id: ItemId,
pub owning_target: BazelLabel,
pub template_specialization: Option<TemplateSpecialization>,
/// A human-readable list of attributes that Crubit doesn't understand.
///
/// Because attributes can change the behavior or semantics of types in
/// fairly significant ways, and in ways that may affect interop, we
/// default-closed and do not expose functions with unknown attributes.
pub unknown_attr: Option<Rc<str>>,
pub doc_comment: Option<Rc<str>>,
pub bridge_type: Option<BridgeType>,
pub source_loc: Rc<str>,
pub unambiguous_public_bases: Vec<BaseClass>,
pub fields: Vec<Field>,
pub lifetime_params: Vec<LifetimeName>,
pub size_align: SizeAlign,
pub trait_derives: TraitDerives,
pub is_derived_class: bool,
pub override_alignment: bool,
pub is_unsafe_type: bool,
pub copy_constructor: SpecialMemberFunc,
pub move_constructor: SpecialMemberFunc,
pub destructor: SpecialMemberFunc,
pub is_trivial_abi: bool,
pub is_inheritable: bool,
pub is_abstract: bool,
/// The `[[nodiscard("...")]]` string. If `[[nodiscard]]`, then the empty
/// string is used.
pub nodiscard: Option<Rc<str>>,
pub record_type: RecordType,
pub is_aggregate: bool,
pub is_anon_record_with_typedef: bool,
pub child_item_ids: Vec<ItemId>,
pub enclosing_item_id: Option<ItemId>,
pub must_bind: bool,
}
impl GenericItem for Record {
fn id(&self) -> ItemId {
self.id
}
fn owning_target(&self) -> Option<BazelLabel> {
Some(self.owning_target.clone())
}
fn debug_name(&self, _: &IR) -> Rc<str> {
self.cc_name.identifier.clone()
}
fn unsupported_kind(&self) -> UnsupportedItemKind {
self.record_type.unsupported_item_kind()
}
fn source_loc(&self) -> Option<Rc<str>> {
Some(self.source_loc.clone())
}
fn unknown_attr(&self) -> Option<Rc<str>> {
self.unknown_attr.clone()
}
fn must_bind(&self) -> bool {
self.must_bind
}
}
impl Record {
/// Whether this type has Rust-like object semantics for mutating
/// assignment, and can be passed by mut reference as a result.
///
/// If a type `T` is mut reference safe, it can be possed as a `&mut T`
/// safely. Otherwise, mutable references must use `Pin<&mut T>`.
///
/// In C++, this is called "trivially relocatable". Such types can be passed
/// by value and have their memory directly mutated by Rust using
/// memcpy-like assignment/swap.
///
/// Described in more detail at: docs/unpin
pub fn is_unpin(&self) -> bool {
self.is_trivial_abi
}
pub fn is_union(&self) -> bool {
match self.record_type {
RecordType::Union => true,
RecordType::Struct | RecordType::Class => false,
}
}
/// Returns a `TokenStream` containing the C++ tag kind for this record.
///
/// This is the `struct`, `union`, or `class` keyword, or nothing if this
/// is an anonymous record with a typedef.
pub fn cc_tag_kind(&self) -> TokenStream {
if self.is_anon_record_with_typedef {
quote! {}
} else {
self.record_type.into_token_stream()
}
}
pub fn should_implement_drop(&self) -> bool {
match self.destructor {
// TODO(b/202258760): Only omit destructor if `Copy` is specified.
SpecialMemberFunc::Trivial => false,
// TODO(b/212690698): Avoid calling into the C++ destructor (e.g. let
// Rust drive `drop`-ing) to avoid (somewhat unergonomic) ManuallyDrop
// if we can ask Rust to preserve C++ field destruction order in
// NontrivialMembers case.
SpecialMemberFunc::NontrivialMembers => true,
// The `impl Drop` for NontrivialUserDefined needs to call into the
// user-defined destructor on C++ side.
SpecialMemberFunc::NontrivialUserDefined => true,
// TODO(b/213516512): Today the IR doesn't contain Func entries for
// deleted functions/destructors/etc. But, maybe we should generate
// `impl Drop` in this case? With `unreachable!`? With
// `std::mem::forget`?
SpecialMemberFunc::Unavailable => false,
}
}
/// Whether this is a template instantiation that is generally available.
///
/// We special-case a handful of template specializations, such as `std::string_view`,
/// AKA `basic_string_view<char>`, even though we do not make `basic_string_view` itself
/// available to all.
pub fn is_disallowed_template_instantiation(self: &Record) -> bool {
self.template_specialization
.as_ref()
.is_some_and(|ts| !ts.is_string_view && !ts.is_wstring_view)
}
pub fn defining_target(&self) -> Option<&BazelLabel> {
self.template_specialization.as_ref().map(|ts| &ts.defining_target)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct GlobalVar {
pub cc_name: Identifier,
pub rs_name: Identifier,
pub id: ItemId,
pub owning_target: BazelLabel,
pub source_loc: Rc<str>,
/// A human-readable list of attributes that Crubit doesn't understand.
pub unknown_attr: Option<Rc<str>>,
pub enclosing_item_id: Option<ItemId>,
pub mangled_name: Option<Rc<str>>,
#[serde(rename(deserialize = "type"))]
pub type_: CcType,
pub must_bind: bool,
}
impl GenericItem for GlobalVar {
fn id(&self) -> ItemId {
self.id
}
fn owning_target(&self) -> Option<BazelLabel> {
Some(self.owning_target.clone())
}
fn debug_name(&self, _: &IR) -> Rc<str> {
self.rs_name.identifier.clone()
}
fn unsupported_kind(&self) -> UnsupportedItemKind {
UnsupportedItemKind::GlobalVar
}
fn source_loc(&self) -> Option<Rc<str>> {
Some(self.source_loc.clone())
}
fn unknown_attr(&self) -> Option<Rc<str>> {
self.unknown_attr.clone()
}
fn must_bind(&self) -> bool {
self.must_bind
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Enum {
pub cc_name: Identifier,
pub rs_name: Identifier,
pub id: ItemId,
pub owning_target: BazelLabel,
pub source_loc: Rc<str>,
pub underlying_type: CcType,
/// The enumerators. If None, this is a forward-declared (opaque) enum.
///
/// That is, the difference between `enum X : int {};` and `enum X : int;`
/// is that the former has `Some(vec![])` for the enumerators, while the
/// latter has `None`.
pub enumerators: Option<Vec<Enumerator>>,
/// A human-readable list of attributes that Crubit doesn't understand.
pub unknown_attr: Option<Rc<str>>,
pub enclosing_item_id: Option<ItemId>,
pub must_bind: bool,
}
impl GenericItem for Enum {
fn id(&self) -> ItemId {
self.id
}
fn owning_target(&self) -> Option<BazelLabel> {
Some(self.owning_target.clone())
}
fn debug_name(&self, _: &IR) -> Rc<str> {
self.rs_name.identifier.clone()
}
fn unsupported_kind(&self) -> UnsupportedItemKind {
UnsupportedItemKind::Enum
}
fn source_loc(&self) -> Option<Rc<str>> {
Some(self.source_loc.clone())
}
fn unknown_attr(&self) -> Option<Rc<str>> {
self.unknown_attr.clone()
}
fn must_bind(&self) -> bool {
self.must_bind
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Enumerator {
pub identifier: Identifier,
pub value: IntegerConstant,
/// A human-readable list of attributes that Crubit doesn't understand.
pub unknown_attr: Option<Rc<str>>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TypeAlias {
pub cc_name: Identifier,
pub rs_name: Identifier,
pub id: ItemId,
pub owning_target: BazelLabel,
pub doc_comment: Option<Rc<str>>,
/// A human-readable list of attributes that Crubit doesn't understand.
pub unknown_attr: Option<Rc<str>>,
pub underlying_type: CcType,
pub source_loc: Rc<str>,
pub enclosing_item_id: Option<ItemId>,
pub must_bind: bool,
}
impl GenericItem for TypeAlias {
fn id(&self) -> ItemId {
self.id
}
fn owning_target(&self) -> Option<BazelLabel> {
Some(self.owning_target.clone())
}
fn debug_name(&self, _: &IR) -> Rc<str> {
self.rs_name.identifier.clone()
}
fn unsupported_kind(&self) -> UnsupportedItemKind {
UnsupportedItemKind::TypeAlias
}
fn source_loc(&self) -> Option<Rc<str>> {
Some(self.source_loc.clone())
}
fn unknown_attr(&self) -> Option<Rc<str>> {
self.unknown_attr.clone()
}
fn must_bind(&self) -> bool {
self.must_bind
}
}
impl Display for TypeAlias {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({}, {})", self.rs_name, self.owning_target, self.source_loc)
}
}
/// 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, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct FormattedError {
pub fmt: Rc<str>,
pub message: Rc<str>,
}
/// Kind is used to indicate which item would cannot be wrapped.
/// Need to be synced with UnsupportedItem::Kind in ir.h.
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum UnsupportedItemKind {
Func,
GlobalVar,
Struct,
Union,
Class,
Enum,
TypeAlias,
Namespace,
Constructor,
// Represents: Comment, Type Map (crubit_internal_rust_type),
// Use Mod, Hard Error in c++.
Other,
}
impl UnsupportedItemKind {
fn str(&self) -> &'static str {
match self {
UnsupportedItemKind::Func => "function",
UnsupportedItemKind::GlobalVar => "global variable",
UnsupportedItemKind::Struct => "struct",
UnsupportedItemKind::Union => "union",
UnsupportedItemKind::Class => "class",
UnsupportedItemKind::Enum => "enum",
UnsupportedItemKind::TypeAlias => "type alias",
UnsupportedItemKind::Namespace => "namespace",
UnsupportedItemKind::Constructor => "constructor",
UnsupportedItemKind::Other => "item",
}
}
}
impl Display for UnsupportedItemKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.str())
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UnsupportedItemPath {
pub ident: UnqualifiedIdentifier,
pub enclosing_item_id: Option<ItemId>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UnsupportedItem {
pub name: Rc<str>,
pub kind: UnsupportedItemKind,
pub path: Option<UnsupportedItemPath>,
errors: Vec<Rc<FormattedError>>,
pub source_loc: Option<Rc<str>>,
pub id: ItemId,
pub must_bind: bool,
/// Stores either one natively generated [`arc_anyhow::Error`] or the
/// memoized result of converting `errors`.
#[serde(skip)]
cause: IgnoredField<OnceCell<Vec<Error>>>,
}
impl GenericItem for UnsupportedItem {
fn id(&self) -> ItemId {
self.id
}
fn owning_target(&self) -> Option<BazelLabel> {
None
}
fn debug_name(&self, _: &IR) -> Rc<str> {
self.name.clone()
}
fn unsupported_kind(&self) -> UnsupportedItemKind {
self.kind
}
fn source_loc(&self) -> Option<Rc<str>> {
self.source_loc.clone()
}
fn unknown_attr(&self) -> Option<Rc<str>> {
None
}
fn must_bind(&self) -> bool {
self.must_bind
}
}
impl UnsupportedItem {
fn new(
ir: &IR,
item: &impl GenericItem,
path: Option<UnsupportedItemPath>,
error: Option<Rc<FormattedError>>,
cause: Option<Error>,
must_bind: bool,
) -> Self {
Self {
name: item.debug_name(ir),
errors: error.into_iter().collect(),
kind: item.unsupported_kind(),
path,
source_loc: item.source_loc(),
id: item.id(),
cause: IgnoredField(cause.map(|e| OnceCell::from(vec![e])).unwrap_or_default()),
must_bind,
}
}
pub fn new_with_static_message(
ir: &IR,
item: &impl GenericItem,
path: Option<UnsupportedItemPath>,
message: &'static str,
) -> Self {
Self::new(
ir,
item,
path,
Some(Rc::new(FormattedError { fmt: message.into(), message: message.into() })),
None,
item.must_bind(),
)
}
pub fn new_with_cause(
ir: &IR,
item: &impl GenericItem,
path: Option<UnsupportedItemPath>,
cause: Error,
) -> Self {
Self::new(ir, item, path, None, Some(cause), item.must_bind())
}
pub fn errors(&self) -> &[Error] {
self.cause.0.get_or_init(|| {
self.errors
.iter()
.map(|e| {
error_report::FormattedError {
fmt: e.fmt.to_string().into(),
message: e.message.to_string().into(),
}
.into()
})
.collect()
})
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Comment {
pub text: Rc<str>,
pub id: ItemId,
pub must_bind: bool,
}
impl GenericItem for Comment {
fn id(&self) -> ItemId {
self.id
}
fn owning_target(&self) -> Option<BazelLabel> {
None
}
fn debug_name(&self, _: &IR) -> Rc<str> {
"comment".into()
}
fn unsupported_kind(&self) -> UnsupportedItemKind {
UnsupportedItemKind::Other
}
fn source_loc(&self) -> Option<Rc<str>> {
None
}
fn unknown_attr(&self) -> Option<Rc<str>> {
None
}
fn must_bind(&self) -> bool {
self.must_bind
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Namespace {
pub cc_name: Identifier,
pub rs_name: Identifier,
pub id: ItemId,
pub canonical_namespace_id: ItemId,
/// A human-readable list of attributes that Crubit doesn't understand.
pub unknown_attr: Option<Rc<str>>,
pub owning_target: BazelLabel,
#[serde(default)]
pub child_item_ids: Vec<ItemId>,
pub enclosing_item_id: Option<ItemId>,
pub is_inline: bool,
pub must_bind: bool,
}
impl GenericItem for Namespace {
fn id(&self) -> ItemId {
self.id
}
fn owning_target(&self) -> Option<BazelLabel> {
Some(self.owning_target.clone())
}
fn debug_name(&self, _: &IR) -> Rc<str> {
self.rs_name.to_string().into()
}
fn unsupported_kind(&self) -> UnsupportedItemKind {
UnsupportedItemKind::Namespace
}
fn source_loc(&self) -> Option<Rc<str>> {
None
}
fn unknown_attr(&self) -> Option<Rc<str>> {
self.unknown_attr.clone()
}
fn must_bind(&self) -> bool {
self.must_bind
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct UseMod {
pub path: Rc<str>,
pub mod_name: Identifier,
pub id: ItemId,
pub must_bind: bool,
}
impl GenericItem for UseMod {
fn id(&self) -> ItemId {
self.id
}
fn owning_target(&self) -> Option<BazelLabel> {
None
}
fn debug_name(&self, _: &IR) -> Rc<str> {
format!("[internal] use mod {}::* = {}", self.mod_name, self.path).into()
}
fn unsupported_kind(&self) -> UnsupportedItemKind {
UnsupportedItemKind::Other
}
fn source_loc(&self) -> Option<Rc<str>> {
None
}
fn unknown_attr(&self) -> Option<Rc<str>> {
None
}
fn must_bind(&self) -> bool {
self.must_bind
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ExistingRustType {
pub rs_name: Rc<str>,
pub cc_name: Rc<str>,
pub type_parameters: Vec<CcType>,
pub owning_target: BazelLabel,
pub size_align: Option<SizeAlign>,
pub is_same_abi: bool,
pub id: ItemId,
pub must_bind: bool,
}
impl GenericItem for ExistingRustType {
fn id(&self) -> ItemId {
self.id
}
fn owning_target(&self) -> Option<BazelLabel> {
Some(self.owning_target.clone())
}
fn debug_name(&self, _: &IR) -> Rc<str> {
self.cc_name.clone()
}
fn unsupported_kind(&self) -> UnsupportedItemKind {
UnsupportedItemKind::Other
}
fn source_loc(&self) -> Option<Rc<str>> {
None
}
fn unknown_attr(&self) -> Option<Rc<str>> {
None
}
fn must_bind(&self) -> bool {
self.must_bind
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub enum Item {
Func(Rc<Func>),
IncompleteRecord(Rc<IncompleteRecord>),
Record(Rc<Record>),
Enum(Rc<Enum>),
GlobalVar(Rc<GlobalVar>),
TypeAlias(Rc<TypeAlias>),
UnsupportedItem(Rc<UnsupportedItem>),
Comment(Rc<Comment>),
Namespace(Rc<Namespace>),
UseMod(Rc<UseMod>),
ExistingRustType(Rc<ExistingRustType>),
}
macro_rules! forward_item {
(match $item:ident { _($item_name:ident) => $expr:expr $(,)? }) => {
match $item {
Item::Func($item_name) => $expr,
Item::IncompleteRecord($item_name) => $expr,
Item::Record($item_name) => $expr,
Item::Enum($item_name) => $expr,
Item::GlobalVar($item_name) => $expr,
Item::TypeAlias($item_name) => $expr,
Item::UnsupportedItem($item_name) => $expr,
Item::Comment($item_name) => $expr,
Item::Namespace($item_name) => $expr,
Item::UseMod($item_name) => $expr,
Item::ExistingRustType($item_name) => $expr,
}
};
}
impl GenericItem for Item {
fn id(&self) -> ItemId {
forward_item! {
match self {
_(x) => x.id()
}
}
}
fn owning_target(&self) -> Option<BazelLabel> {
forward_item! {
match self {
_(x) => x.owning_target()
}
}
}
fn debug_name(&self, ir: &IR) -> Rc<str> {
forward_item! {
match self {
_(x) => x.debug_name(ir)
}
}
}
fn unsupported_kind(&self) -> UnsupportedItemKind {
forward_item! {
match self {
_(x) => x.unsupported_kind()
}
}
}
fn source_loc(&self) -> Option<Rc<str>> {
forward_item! {
match self {
_(x) => x.source_loc()
}
}
}
fn unknown_attr(&self) -> Option<Rc<str>> {
forward_item! {
match self {
_(x) => x.unknown_attr()
}
}
}
fn must_bind(&self) -> bool {
forward_item! {
match self {
_(x) => x.must_bind()
}
}
}
}
impl Item {
pub fn enclosing_item_id(&self) -> Option<ItemId> {
match self {
Item::Record(record) => record.enclosing_item_id,
Item::IncompleteRecord(record) => record.enclosing_item_id,
Item::Enum(enum_) => enum_.enclosing_item_id,
Item::GlobalVar(type_) => type_.enclosing_item_id,
Item::Func(func) => func.enclosing_item_id,
Item::Namespace(namespace) => namespace.enclosing_item_id,
Item::TypeAlias(type_alias) => type_alias.enclosing_item_id,
Item::Comment(..) => None,
Item::UnsupportedItem(unsupported) => {
unsupported.path.as_ref().and_then(|p| p.enclosing_item_id)
}
Item::UseMod(..) => None,
Item::ExistingRustType(..) => None,
}
}
/// Returns the target that this was defined in, if it was defined somewhere
/// other than `owning_target()`.
pub fn defining_target(&self) -> Option<&BazelLabel> {
match self {
Item::Record(record) => record.defining_target(),
_ => None,
}
}
/// Returns true if this corresponds to the definition of a new name for a
/// type.
pub fn is_type_definition(&self) -> bool {
match self {
Item::Func(_) => false,
Item::IncompleteRecord(_) => true,
Item::Record(_) => true,
Item::Enum(_) => true,
Item::GlobalVar(_) => false,
Item::TypeAlias(_) => true,
Item::UnsupportedItem(_) => false,
Item::Comment(_) => false,
Item::Namespace(_) => false,
Item::UseMod(_) => false,
Item::ExistingRustType(_) => false,
}
}
/// Returns the C++ identifier for this item, if it has one.
pub fn cc_name_as_str(&self) -> Option<Rc<str>> {
match self {
Item::Func(func) => match &func.cc_name {
UnqualifiedIdentifier::Identifier(identifier) => {
Some(identifier.identifier.clone())
}
_ => None,
},
Item::IncompleteRecord(incomplete_record) => {
Some(incomplete_record.cc_name.identifier.clone())
}
Item::Record(record) => Some(record.cc_name.identifier.clone()),
Item::Enum(enum_) => Some(enum_.cc_name.identifier.clone()),
Item::GlobalVar(global_var) => Some(global_var.cc_name.identifier.clone()),
Item::TypeAlias(type_alias) => Some(type_alias.cc_name.identifier.clone()),
Item::Namespace(namespace) => Some(namespace.cc_name.identifier.clone()),
Item::UnsupportedItem(_) => None,
Item::Comment(_) => None,
Item::UseMod(_) => None,
Item::ExistingRustType(existing_rust_type) => Some(existing_rust_type.cc_name.clone()),
}
}
/// If this item is a child item of a Record, returns true if it should be
/// placed in a nested module.
pub fn place_in_nested_module_if_nested_in_record(&self) -> bool {
match self {
Item::IncompleteRecord(_)
| Item::Record(_)
| Item::GlobalVar(_)
| Item::TypeAlias(_)
| Item::Enum(_)
| Item::UseMod(_)
| Item::ExistingRustType(_) => true,
Item::Func(_) | Item::UnsupportedItem(_) | Item::Comment(_) => false,
Item::Namespace(_) => unreachable!("Found a namespace that's opened inside of a record. This is not valid C++, so this is a bug."),
}
}
}
impl From<Func> for Item {
fn from(func: Func) -> Item {
Item::Func(Rc::new(func))
}
}
impl<'a> TryFrom<&'a Item> for &'a Rc<Func> {
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)
}
}
}
impl From<Record> for Item {
fn from(record: Record) -> Item {
Item::Record(Rc::new(record))
}
}
impl<'a> TryFrom<&'a Item> for &'a Rc<Record> {
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)
}
}
}
impl From<UnsupportedItem> for Item {
fn from(unsupported: UnsupportedItem) -> Item {
Item::UnsupportedItem(Rc::new(unsupported))
}
}
impl<'a> TryFrom<&'a Item> for &'a Rc<UnsupportedItem> {
type Error = Error;
fn try_from(value: &'a Item) -> Result<Self, Self::Error> {
if let Item::UnsupportedItem(u) = value {
Ok(u)
} else {
bail!("Not an UnsupportedItem: {:#?}", value)
}
}
}
impl From<Comment> for Item {
fn from(comment: Comment) -> Item {
Item::Comment(Rc::new(comment))
}
}
impl<'a> TryFrom<&'a Item> for &'a Rc<Comment> {
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)
}
}
}
impl From<Namespace> for Item {
fn from(ns: Namespace) -> Item {
Item::Namespace(Rc::new(ns))
}
}
impl<'a> TryFrom<&'a Item> for &'a Rc<Namespace> {
type Error = Error;
fn try_from(value: &'a Item) -> Result<Self, Self::Error> {
if let Item::Namespace(c) = value {
Ok(c)
} else {
bail!("Not a Namespace: {:#?}", value)
}
}
}
#[derive(PartialEq, Eq, Clone, Deserialize)]
#[serde(deny_unknown_fields, rename(deserialize = "IR"))]
struct FlatIR {
#[serde(default)]
public_headers: Vec<HeaderName>,
current_target: BazelLabel,
#[serde(default)]
items: Vec<Item>,
#[serde(default)]
top_level_item_ids: BTreeMap<BazelLabel, Vec<ItemId>>,
#[serde(default)]
crate_root_path: Option<Rc<str>>,
#[serde(default)]
crubit_features: BTreeMap<BazelLabel, crubit_feature::SerializedCrubitFeatures>,
}
/// A custom debug impl that wraps the HashMap in rustfmt-friendly notation.
///
/// See b/272530008.
impl Debug for FlatIR {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// BTreeMap has consistent ordering, unlike HashMap, so it's reasonable to rely on a
// consistent Debug output.
struct DebugBTreeMap<T>(pub T);
impl<K: Debug, V: Debug> Debug for DebugBTreeMap<&BTreeMap<K, V>> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// prefix the map with `map!` so that the output can be fed to rustfmt.
// The end result is something like `map! { k1: v1, k2: v2 }`, which reads well.
write!(f, "map!")?;
Debug::fmt(&self.0, f)
}
}
// exhaustive-match so we don't forget to add fields to Debug when we add to
// FlatIR.
let FlatIR {
public_headers,
current_target,
items,
top_level_item_ids,
crate_root_path,
crubit_features,
} = self;
f.debug_struct("FlatIR")
.field("public_headers", public_headers)
.field("current_target", current_target)
.field("items", items)
.field("top_level_item_ids", &DebugBTreeMap(top_level_item_ids))
.field("crate_root_path", crate_root_path)
.field("crubit_features", &DebugBTreeMap(crubit_features))
.finish()
}
}
/// Struct providing the necessary information about the API of a C++ target to
/// enable generation of Rust bindings source code (both `rs_api.rs` and
/// `rs_api_impl.cc` files).
#[derive(PartialEq, Debug)]
pub struct IR {
flat_ir: FlatIR,
// A map from a `decl_id` to an index of an `Item` in the `flat_ir.items` vec.
item_id_to_item_idx: HashMap<ItemId, usize>,
lifetimes: HashMap<LifetimeId, LifetimeName>,
namespace_id_to_number_of_reopened_namespaces: HashMap<ItemId, usize>,
reopened_namespace_id_to_idx: HashMap<ItemId, usize>,
function_name_to_functions: HashMap<UnqualifiedIdentifier, Vec<Rc<Func>>>,
}
impl IR {
pub fn items(&self) -> impl Iterator<Item = &Item> {
self.flat_ir.items.iter()
}
pub fn top_level_item_ids_in_target(&self, target: &BazelLabel) -> &[ItemId] {
&self.flat_ir.top_level_item_ids[target]
}
pub fn top_level_item_ids(&self) -> &[ItemId] {
self.top_level_item_ids_in_target(self.current_target())
}
pub fn items_mut(&mut self) -> impl Iterator<Item = &mut Item> {
self.flat_ir.items.iter_mut()
}
pub fn public_headers(&self) -> impl Iterator<Item = &HeaderName> {
self.flat_ir.public_headers.iter()
}
pub fn functions(&self) -> impl Iterator<Item = &Rc<Func>> {
self.items().filter_map(|item| match item {
Item::Func(func) => Some(func),
_ => None,
})
}
pub fn type_aliases(&self) -> impl Iterator<Item = &Rc<TypeAlias>> {
self.items().filter_map(|item| match item {
Item::TypeAlias(type_alias) => Some(type_alias),
_ => None,
})
}
pub fn records(&self) -> impl Iterator<Item = &Rc<Record>> {
self.items().filter_map(|item| match item {
Item::Record(record) => Some(record),
_ => None,
})
}
pub fn unsupported_items(&self) -> impl Iterator<Item = &Rc<UnsupportedItem>> {
self.items().filter_map(|item| match item {
Item::UnsupportedItem(unsupported_item) => Some(unsupported_item),
_ => None,
})
}
pub fn comments(&self) -> impl Iterator<Item = &Rc<Comment>> {
self.items().filter_map(|item| match item {
Item::Comment(comment) => Some(comment),
_ => None,
})
}
pub fn namespaces(&self) -> impl Iterator<Item = &Rc<Namespace>> {
self.items().filter_map(|item| match item {
Item::Namespace(ns) => Some(ns),
_ => None,
})
}
pub fn item_for_type<T>(&self, ty: &T) -> Result<&Item>
where
T: TypeWithDeclId + Debug,
{
if let Some(decl_id) = ty.decl_id() {
Ok(self.find_untyped_decl(decl_id))
} else {
bail!("Type {:?} does not have an associated item.", ty)
}
}
#[track_caller]
pub fn find_decl<'a, T>(&'a self, decl_id: ItemId) -> Result<&'a T>
where
&'a T: TryFrom<&'a Item>,
{
self.find_untyped_decl(decl_id).try_into().map_err(|_| {
anyhow!("DeclId {:?} doesn't refer to a {}", decl_id, std::any::type_name::<T>())
})
}
#[track_caller]
pub fn find_untyped_decl(&self, decl_id: ItemId) -> &Item {
let Some(idx) = self.item_id_to_item_idx.get(&decl_id) else {
panic!("Couldn't find decl_id {:?} in the IR:\n{:#?}", decl_id, self.flat_ir)
};
let Some(item) = self.flat_ir.items.get(*idx) else {
panic!("Couldn't find an item at idx {} in IR:\n{:#?}", idx, self.flat_ir)
};
item
}
/// Returns whether `target` is the current target.
pub fn is_current_target(&self, target: &BazelLabel) -> bool {
// TODO(hlopko): Make this be a pointer comparison, now it's comparing string
// values.
*target == *self.current_target()
}
/// Returns the Crubit features enabled for the given `target`.
#[must_use]
pub fn target_crubit_features(&self, target: &BazelLabel) -> flagset::FlagSet<CrubitFeature> {
self.flat_ir.crubit_features.get(target).cloned().unwrap_or_default().0
}
/// Returns a mutable reference to the Crubit features enabled for the given
/// `target`.
///
/// Since IR is generally only held immutably, this is only useful for
/// testing.
#[must_use]
pub fn target_crubit_features_mut(
&mut self,
target: &BazelLabel,
) -> &mut flagset::FlagSet<CrubitFeature> {
// TODO(jeanpierreda): migrate to raw_entry_mut when stable.
// (target is taken by reference exactly because ideally this function would use
// the raw entry API.)
&mut self.flat_ir.crubit_features.entry(target.clone()).or_default().0
}
pub fn current_target(&self) -> &BazelLabel {
&self.flat_ir.current_target
}
// Returns the standard Debug print string for the `flat_ir`. The reason why we
// don't use the debug print of `Self` is that `Self` contains HashMaps, and
// their debug print produces content that is not valid Rust code.
// `token_stream_matchers` (hacky) implementation parses the debug print and
// chokes on HashMaps. Therefore this method.
//
// Used for `token_stream_matchers`, do not use for anything else.
pub fn flat_ir_debug_print(&self) -> String {
format!("{:?}", self.flat_ir)
}
pub fn get_lifetime(&self, lifetime_id: LifetimeId) -> Option<&LifetimeName> {
self.lifetimes.get(&lifetime_id)
}
pub fn get_reopened_namespace_idx(&self, id: ItemId) -> Result<usize> {
Ok(*self.reopened_namespace_id_to_idx.get(&id).with_context(|| {
format!("Could not find the reopened namespace index for namespace {:?}.", id)
})?)
}
pub fn is_last_reopened_namespace(&self, id: ItemId, canonical_id: ItemId) -> Result<bool> {
let idx = self.get_reopened_namespace_idx(id)?;
let last_item_idx = self
.namespace_id_to_number_of_reopened_namespaces
.get(&canonical_id)
.with_context(|| {
format!(
"Could not find number of reopened namespaces for namespace {:?}.",
canonical_id
)
})? - 1;
Ok(idx == last_item_idx)
}
/// Returns the `Item` defining `func`, or `None` if `func` is not a
/// member function.
///
/// Note that even if `func` is a member function, the associated record
/// might not be a Record IR Item (e.g. it has its type changed via
/// crubit_internal_rust_type).
pub fn record_for_member_func(&self, func: &Func) -> Option<&Item> {
if let Some(meta) = func.member_func_metadata.as_ref() {
Some(self.find_untyped_decl(meta.record_id))
} else {
None
}
}
pub fn crate_root_path(&self) -> Option<Rc<str>> {
self.flat_ir.crate_root_path.clone()
}
pub fn crate_root_path_tokens(&self) -> TokenStream {
match self.crate_root_path().as_deref().map(make_rs_ident) {
None => quote! { crate },
Some(crate_root_path) => quote! { crate :: #crate_root_path },
}
}
pub fn get_functions_by_name(
&self,
function_name: &UnqualifiedIdentifier,
) -> impl Iterator<Item = &Rc<Func>> {
self.function_name_to_functions.get(function_name).map_or([].iter(), |v| v.iter())
}
pub fn namespace_qualifier(&self, item: &impl GenericItem) -> NamespaceQualifier {
self.namespace_qualifier_from_id(item.id())
}
#[track_caller]
fn namespace_qualifier_from_id(&self, item: ItemId) -> NamespaceQualifier {
let mut namespaces = vec![];
let mut nested_records = vec![];
let mut enclosing_item_id = self.find_untyped_decl(item).enclosing_item_id();
while let Some(parent_id) = enclosing_item_id {
match self.find_untyped_decl(parent_id) {
Item::Namespace(ns) => {
namespaces.push(ns.rs_name.identifier.clone());
enclosing_item_id = ns.enclosing_item_id;
}
Item::Record(parent_record) => {
assert!(
namespaces.is_empty(),
"Record was listed as the enclosing item for a namespace, this is a bug."
);
nested_records.push((
parent_record.rs_name.identifier.clone(),
parent_record.cc_name.identifier.clone(),
));
enclosing_item_id = parent_record.enclosing_item_id;
}
_ => panic!("Expected namespace or parent record, found enclosing item {item:?}"),
}
}
namespaces.reverse();
nested_records.reverse();
NamespaceQualifier { namespaces, nested_records }
}
}
// TODO(jeanpierreda): This should probably be a method on IR accepting a GenericItem,
// and returning the crate name, or similar.
/// Returns Some(crate_ident) if this is an imported crate.
pub fn rs_imported_crate_name(owning_target: &BazelLabel, ir: &IR) -> Option<Ident> {
if ir.is_current_target(owning_target) {
None
} else {
let owning_crate = make_rs_ident(&owning_target.target_name_escaped());
Some(owning_crate)
}
}
#[cfg(test)]
mod tests {
use super::*;
use googletest::prelude::*;
#[gtest]
fn test_identifier_debug_print() {
assert_eq!(format!("{:?}", Identifier { identifier: "hello".into() }), "\"hello\"");
}
#[gtest]
fn test_unqualified_identifier_debug_print() {
assert_eq!(
format!(
"{:?}",
UnqualifiedIdentifier::Identifier(Identifier { identifier: "hello".into() })
),
"\"hello\""
);
assert_eq!(format!("{:?}", UnqualifiedIdentifier::Constructor), "Constructor");
assert_eq!(format!("{:?}", UnqualifiedIdentifier::Destructor), "Destructor");
}
#[gtest]
fn test_used_headers() {
let input = r#"
{
"public_headers": [{ "name": "foo/bar.h" }],
"current_target": "//foo:bar"
}
"#;
let ir = deserialize_ir(input.as_bytes()).unwrap();
let expected = FlatIR {
public_headers: vec![HeaderName { name: "foo/bar.h".into() }],
current_target: "//foo:bar".into(),
top_level_item_ids: BTreeMap::new(),
items: vec![],
crate_root_path: None,
crubit_features: Default::default(),
};
assert_eq!(ir.flat_ir, expected);
}
#[gtest]
fn test_empty_crate_root_path() {
let input = "{ \"current_target\": \"//foo:bar\" }";
let ir = deserialize_ir(input.as_bytes()).unwrap();
assert_eq!(ir.crate_root_path(), None);
}
#[gtest]
fn test_crate_root_path() {
let input = r#"
{
"crate_root_path": "__cc_template_instantiations_rs_api",
"current_target": "//foo:bar"
}
"#;
let ir = deserialize_ir(input.as_bytes()).unwrap();
assert_eq!(ir.crate_root_path().as_deref(), Some("__cc_template_instantiations_rs_api"));
}
#[gtest]
fn test_bazel_label_target() {
let label: BazelLabel = "//foo:bar".into();
assert_eq!(label.target_name(), "bar");
}
#[gtest]
fn test_bazel_label_target_dotless() {
let label: BazelLabel = "//foo".into();
assert_eq!(label.target_name(), "foo");
}
#[gtest]
fn test_bazel_label_implicit_target_equals_explicit_target() {
let implicit: BazelLabel = "//foo".into();
let explicit: BazelLabel = "//foo:foo".into();
assert_eq!(implicit, explicit);
}
#[gtest]
fn test_bazel_label_dotless_slashless() {
let label: BazelLabel = "foo".into();
assert_eq!(label.target_name(), "foo");
}
/// These are not labels, but there is an unambiguous interpretation of
/// what their target should be that lets us keep going.
#[gtest]
fn test_bazel_label_empty_target() {
for s in ["foo:", "foo/", ""] {
let label: BazelLabel = s.into();
assert_eq!(label.target_name(), "", "label={s:?}");
}
}
#[gtest]
fn test_bazel_label_escape_target_name_with_relative_label() {
let label: BazelLabel = "foo".into();
assert_eq!(label.target_name_escaped(), "foo");
}
#[gtest]
fn test_bazel_label_escape_target_name_with_invalid_characters() {
let label: BazelLabel = "//:!./%-@^#$&()*-+,;<=>?[]{|}~".into();
assert_eq!(label.target_name_escaped(), "___________________________");
}
#[gtest]
fn test_bazel_label_escape_target_name_core() {
let label: BazelLabel = "//foo~:core".into();
assert_eq!(label.target_name_escaped(), "core_foo_");
}
#[gtest]
fn test_bazel_label_escape_target_name_with_no_target_name() {
let label: BazelLabel = "//foo/bar~".into();
assert_eq!(label.target_name_escaped(), "bar_");
}
#[gtest]
fn test_bazel_label_escape_target_name_with_no_package_name() {
let label: BazelLabel = "//:foo~".into();
assert_eq!(label.target_name_escaped(), "foo_");
}
#[gtest]
fn test_bazel_label_escape_target_name_core_with_no_package_name_with_no_target_name() {
let label: BazelLabel = "core".into();
assert_eq!(label.target_name_escaped(), "core_");
}
#[gtest]
fn test_bazel_label_escape_target_name_starting_with_digit() {
let label: BazelLabel = "12345".into();
assert_eq!(label.target_name_escaped(), "n12345");
}
#[gtest]
fn test_bazel_to_cc_identifier_empty() {
assert_eq!(BazelLabel::from("").convert_to_cc_identifier(), "_");
}
#[gtest]
fn test_bazel_to_cc_identifier_alphanumeric_not_transformed() {
assert_eq!(BazelLabel::from("abc").convert_to_cc_identifier(), "_abc");
assert_eq!(BazelLabel::from("foo123").convert_to_cc_identifier(), "_foo123");
assert_eq!(BazelLabel::from("123foo").convert_to_cc_identifier(), "_123foo");
}
#[gtest]
fn test_bazel_to_cc_identifier_simple_targets() {
assert_eq!(
BazelLabel::from("//foo/bar:baz_abc").convert_to_cc_identifier(),
"__2f_2ffoo_2fbar_3abaz_5fabc"
);
}
#[gtest]
fn test_bazel_to_cc_identifier_conflict() {
assert_ne!(
BazelLabel::from("//foo_bar:baz").convert_to_cc_identifier(),
BazelLabel::from("//foo/bar:baz").convert_to_cc_identifier()
);
}
}