| // 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() |
| ); |
| } |
| } |