blob: cfc213b2dbfe82ced3db9a8955c41e3939b05f1f [file] [log] [blame]
// 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 anyhow::{anyhow, bail, Context, Result};
use proc_macro2::{Literal, TokenStream};
use quote::{ToTokens, TokenStreamExt};
use serde::Deserialize;
use std::collections::hash_map::{Entry, HashMap};
use std::convert::TryFrom;
use std::fmt;
use std::io::Read;
/// Deserialize `IR` from JSON given as a reader.
pub fn deserialize_ir<R: Read>(reader: R) -> Result<IR> {
let flat_ir = serde_json::from_reader(reader)?;
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(
items: Vec<Item>,
used_headers: Vec<HeaderName>,
current_target: BazelLabel,
top_level_item_ids: Vec<ItemId>,
) -> Result<IR> {
make_ir(FlatIR { used_headers, current_target, items, top_level_item_ids })
}
fn make_ir(flat_ir: FlatIR) -> Result<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) {
bail!("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 { lifetime_params, .. }) => lifetime_params,
Item::Func(Func { lifetime_params, .. }) => lifetime_params,
_ => continue,
};
for lifetime in lifetime_params {
match lifetimes.entry(lifetime.id) {
Entry::Occupied(occupied) => {
bail!(
"Duplicate use of lifetime ID {:?} 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);
});
Ok(IR {
flat_ir,
item_id_to_item_idx,
lifetimes,
namespace_id_to_number_of_reopened_namespaces,
reopened_namespace_id_to_idx,
})
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct HeaderName {
pub name: String,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Deserialize)]
#[serde(transparent)]
pub struct LifetimeId(pub i32);
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct LifetimeName {
pub name: String,
pub id: LifetimeId,
}
impl ToTokens for LifetimeName {
fn to_tokens(&self, tokens: &mut TokenStream) {
let lifetime =
syn::Lifetime::new(&format!("'{}", self.name), proc_macro2::Span::call_site());
lifetime.to_tokens(tokens)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct RsType {
pub name: Option<String>,
pub lifetime_args: Vec<LifetimeId>,
pub type_args: Vec<RsType>,
pub decl_id: Option<ItemId>,
}
impl RsType {
pub fn is_unit_type(&self) -> bool {
self.name.as_deref() == Some("()")
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct CcType {
pub name: Option<String>,
pub is_const: bool,
pub type_args: Vec<CcType>,
pub decl_id: Option<ItemId>,
}
impl CcType {
pub fn is_void(&self) -> bool {
self.name.as_deref() == Some("void")
}
}
pub trait TypeWithDeclId {
fn decl_id(&self) -> Option<ItemId>;
}
impl TypeWithDeclId for RsType {
fn decl_id(&self) -> Option<ItemId> {
self.decl_id
}
}
impl TypeWithDeclId for CcType {
fn decl_id(&self) -> Option<ItemId> {
self.decl_id
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct MappedType {
pub rs_type: RsType,
pub cc_type: CcType,
}
#[derive(PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Identifier {
pub identifier: String,
}
impl fmt::Debug for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&format!("\"{}\"", &self.identifier))
}
}
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Deserialize)]
pub struct IntegerConstant {
pub is_negative: bool,
pub wrapped_value: u64,
}
impl ToTokens for IntegerConstant {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.append(if self.is_negative {
Literal::i64_unsuffixed(self.wrapped_value as i64)
} else {
Literal::u64_unsuffixed(self.wrapped_value)
})
}
}
#[derive(PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Operator {
pub name: String,
}
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 fmt::Debug for Operator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&format!("\"{}\"", &self.cc_name()))
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Deserialize)]
#[serde(transparent)]
pub struct ItemId(usize);
impl ItemId {
pub fn new_for_testing(value: usize) -> Self {
Self(value)
}
}
impl ToTokens for ItemId {
fn to_tokens(&self, tokens: &mut TokenStream) {
proc_macro2::Literal::usize_unsuffixed(self.0).to_tokens(tokens)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(transparent)]
pub struct BazelLabel(pub String);
impl BazelLabel {
pub fn target_name(&self) -> &str {
match self.0.split_once(':') {
Some((_package, target_name)) => target_name,
None => panic!("Unsupported label format {:?}", self.0),
}
}
}
impl<T: Into<String>> From<T> for BazelLabel {
fn from(label: T) -> Self {
Self(label.into())
}
}
#[derive(PartialEq, Eq, Hash, Clone, Deserialize)]
pub enum UnqualifiedIdentifier {
Identifier(Identifier),
Operator(Operator),
Constructor,
Destructor,
}
impl UnqualifiedIdentifier {
pub fn identifier_as_str(&self) -> Option<&str> {
match self {
UnqualifiedIdentifier::Identifier(identifier) => Some(identifier.identifier.as_str()),
_ => None,
}
}
}
impl fmt::Debug for UnqualifiedIdentifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UnqualifiedIdentifier::Identifier(identifier) => fmt::Debug::fmt(identifier, f),
UnqualifiedIdentifier::Operator(op) => fmt::Debug::fmt(op, f),
UnqualifiedIdentifier::Constructor => f.write_str("Constructor"),
UnqualifiedIdentifier::Destructor => f.write_str("Destructor"),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub enum ReferenceQualification {
LValue,
RValue,
Unqualified,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct InstanceMethodMetadata {
pub reference: ReferenceQualification,
pub is_const: bool,
pub is_virtual: bool,
/// If the member function was a constructor with an `explicit` specifier.
pub is_explicit_ctor: bool,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct MemberFuncMetadata {
pub record_id: ItemId,
pub instance_method_metadata: Option<InstanceMethodMetadata>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct FuncParam {
#[serde(rename(deserialize = "type"))]
pub type_: MappedType,
pub identifier: Identifier,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Func {
pub name: UnqualifiedIdentifier,
pub owning_target: BazelLabel,
pub mangled_name: String,
pub doc_comment: Option<String>,
pub return_type: MappedType,
pub params: Vec<FuncParam>,
pub lifetime_params: Vec<LifetimeName>,
pub is_inline: bool,
pub member_func_metadata: Option<MemberFuncMetadata>,
pub has_c_calling_convention: bool,
pub is_member_or_descendant_of_class_template: bool,
pub source_loc: SourceLoc,
pub id: ItemId,
pub enclosing_namespace_id: Option<ItemId>,
}
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)]
pub struct Field {
pub identifier: Option<Identifier>,
pub doc_comment: Option<String>,
#[serde(rename(deserialize = "type"))]
pub type_: Result<MappedType, String>,
pub access: AccessSpecifier,
pub offset: usize,
pub size: usize,
pub is_no_unique_address: bool,
pub is_bitfield: bool,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub enum SpecialMemberFunc {
Trivial,
NontrivialMembers,
NontrivialUserDefined,
Unavailable,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct BaseClass {
pub base_record_id: ItemId,
pub offset: Option<i64>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct IncompleteRecord {
pub cc_name: String,
pub id: ItemId,
pub owning_target: BazelLabel,
pub enclosing_namespace_id: Option<ItemId>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Record {
pub rs_name: String,
pub cc_name: String,
pub id: ItemId,
pub owning_target: BazelLabel,
pub doc_comment: Option<String>,
pub unambiguous_public_bases: Vec<BaseClass>,
pub fields: Vec<Field>,
pub lifetime_params: Vec<LifetimeName>,
pub size: usize,
pub alignment: usize,
pub is_derived_class: bool,
pub override_alignment: bool,
pub copy_constructor: SpecialMemberFunc,
pub move_constructor: SpecialMemberFunc,
pub destructor: SpecialMemberFunc,
pub is_trivial_abi: bool,
pub is_inheritable: bool,
pub is_union: bool,
pub is_aggregate: bool,
pub child_item_ids: Vec<ItemId>,
pub enclosing_namespace_id: Option<ItemId>,
}
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>`.
///
/// Conditions:
///
/// 1. It is trivially relocatable, and thus can be passed by value and have
/// its memory directly mutated by Rust using memcpy-like
/// assignment/swap.
///
/// 2. It cannot overlap with any other objects. In particular, it cannot be
/// inherited from, as inheritance allows for the tail padding to be
/// reused by other objects.
///
/// (In future versions, we could also include types which are POD for
/// the purpose of layout, but this is less predictable to C++ users,
/// and ABI-specific.)
///
/// We are assuming, for the moment, that no object is stored in a
/// `[[no_unique_address]]` variable. Much like packed structs and
/// the like, users of `[[no_unique_address]]` must be very careful
/// when passing mutable references to Rust.
///
/// Described in more detail at: docs/unpin
///
/// TODO(b/200067242): Actually force mut references to !is_unpin to be
/// Pin<&mut T>.
pub fn is_unpin(&self) -> bool {
self.is_trivial_abi && !self.is_inheritable
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Enum {
pub identifier: Identifier,
pub id: ItemId,
pub owning_target: BazelLabel,
pub underlying_type: MappedType,
pub enumerators: Vec<Enumerator>,
pub enclosing_namespace_id: Option<ItemId>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Enumerator {
pub identifier: Identifier,
pub value: IntegerConstant,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct TypeAlias {
pub identifier: Identifier,
pub id: ItemId,
pub owning_target: BazelLabel,
pub doc_comment: Option<String>,
pub underlying_type: MappedType,
pub enclosing_namespace_id: Option<ItemId>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct SourceLoc {
pub filename: String,
pub line: u64,
pub column: u64,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct UnsupportedItem {
pub name: String,
pub message: String,
pub source_loc: SourceLoc,
pub id: ItemId,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Comment {
pub text: String,
pub id: ItemId,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub struct Namespace {
pub name: Identifier,
pub id: ItemId,
pub canonical_namespace_id: ItemId,
pub owning_target: BazelLabel,
#[serde(default)]
pub child_item_ids: Vec<ItemId>,
pub enclosing_namespace_id: Option<ItemId>,
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
pub enum Item {
Func(Func),
IncompleteRecord(IncompleteRecord),
Record(Record),
Enum(Enum),
TypeAlias(TypeAlias),
UnsupportedItem(UnsupportedItem),
Comment(Comment),
Namespace(Namespace),
}
impl Item {
fn id(&self) -> ItemId {
match self {
Item::Func(func) => func.id,
Item::IncompleteRecord(record) => record.id,
Item::Record(record) => record.id,
Item::Enum(enum_) => enum_.id,
Item::TypeAlias(type_alias) => type_alias.id,
Item::UnsupportedItem(unsupported) => unsupported.id,
Item::Comment(comment) => comment.id,
Item::Namespace(namespace) => namespace.id,
}
}
pub fn enclosing_namespace_id(&self) -> Option<ItemId> {
match self {
Item::Record(record) => record.enclosing_namespace_id,
Item::IncompleteRecord(record) => record.enclosing_namespace_id,
Item::Enum(enum_) => enum_.enclosing_namespace_id,
Item::Func(func) => func.enclosing_namespace_id,
Item::Namespace(namespace) => namespace.enclosing_namespace_id,
Item::TypeAlias(type_alias) => type_alias.enclosing_namespace_id,
Item::Comment(_) => None,
Item::UnsupportedItem(_) => None,
}
}
}
impl From<Func> for Item {
fn from(func: Func) -> Item {
Item::Func(func)
}
}
impl<'a> TryFrom<&'a Item> for &'a Func {
type Error = anyhow::Error;
fn try_from(value: &'a Item) -> Result<Self, Self::Error> {
if let Item::Func(f) = value { Ok(f) } else { anyhow::bail!("Not a Func: {:#?}", value) }
}
}
impl From<Record> for Item {
fn from(record: Record) -> Item {
Item::Record(record)
}
}
impl<'a> TryFrom<&'a Item> for &'a Record {
type Error = anyhow::Error;
fn try_from(value: &'a Item) -> Result<Self, Self::Error> {
if let Item::Record(r) = value {
Ok(r)
} else {
anyhow::bail!("Not a Record: {:#?}", value)
}
}
}
impl From<UnsupportedItem> for Item {
fn from(unsupported: UnsupportedItem) -> Item {
Item::UnsupportedItem(unsupported)
}
}
impl<'a> TryFrom<&'a Item> for &'a UnsupportedItem {
type Error = anyhow::Error;
fn try_from(value: &'a Item) -> Result<Self, Self::Error> {
if let Item::UnsupportedItem(u) = value {
Ok(u)
} else {
anyhow::bail!("Not an UnsupportedItem: {:#?}", value)
}
}
}
impl From<Comment> for Item {
fn from(comment: Comment) -> Item {
Item::Comment(comment)
}
}
impl<'a> TryFrom<&'a Item> for &'a Comment {
type Error = anyhow::Error;
fn try_from(value: &'a Item) -> Result<Self, Self::Error> {
if let Item::Comment(c) = value {
Ok(c)
} else {
anyhow::bail!("Not a Comment: {:#?}", value)
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize)]
#[serde(rename(deserialize = "IR"))]
struct FlatIR {
#[serde(default)]
used_headers: Vec<HeaderName>,
current_target: BazelLabel,
#[serde(default)]
items: Vec<Item>,
#[serde(default)]
top_level_item_ids: Vec<ItemId>,
}
/// 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>,
}
impl IR {
pub fn items(&self) -> impl Iterator<Item = &Item> {
self.flat_ir.items.iter()
}
pub fn top_level_item_ids(&self) -> impl Iterator<Item = &ItemId> {
self.flat_ir.top_level_item_ids.iter()
}
pub fn items_mut(&mut self) -> impl Iterator<Item = &mut Item> {
self.flat_ir.items.iter_mut()
}
pub fn take_items(self) -> Vec<Item> {
self.flat_ir.items
}
pub fn used_headers(&self) -> impl Iterator<Item = &HeaderName> {
self.flat_ir.used_headers.iter()
}
pub fn functions(&self) -> impl Iterator<Item = &Func> {
self.items().filter_map(|item| match item {
Item::Func(func) => Some(func),
_ => None,
})
}
pub fn records(&self) -> impl Iterator<Item = &Record> {
self.items().filter_map(|item| match item {
Item::Record(func) => Some(func),
_ => None,
})
}
pub fn unsupported_items(&self) -> impl Iterator<Item = &UnsupportedItem> {
self.items().filter_map(|item| match item {
Item::UnsupportedItem(unsupported_item) => Some(unsupported_item),
_ => None,
})
}
pub fn comments(&self) -> impl Iterator<Item = &Comment> {
self.items().filter_map(|item| match item {
Item::Comment(comment) => Some(comment),
_ => None,
})
}
pub fn namespaces(&self) -> impl Iterator<Item = &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 + std::fmt::Debug,
{
if let Some(decl_id) = ty.decl_id() {
self.find_untyped_decl(decl_id)
.with_context(|| format!("Failed to retrieve item for type {:?}", ty))
} else {
bail!("Type {:?} does not have an associated item.", ty)
}
}
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).and_then(|decl| {
decl.try_into().map_err(|_| {
anyhow!("DeclId {:?} doesn't refer to a {}", decl_id, std::any::type_name::<T>())
})
})
}
fn find_untyped_decl(&self, decl_id: ItemId) -> Result<&Item> {
let idx = *self
.item_id_to_item_idx
.get(&decl_id)
.with_context(|| format!("Couldn't find decl_id {:?} in the IR.", decl_id))?;
self.flat_ir.items.get(idx).with_context(|| format!("Couldn't find an item at idx {}", idx))
}
// 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()
}
pub fn current_target(&self) -> &BazelLabel {
&self.flat_ir.current_target
}
// Returns whether `target` is the target that corresponds to the C++
// standard library.
pub fn is_stdlib_target(&self, target: &BazelLabel) -> bool {
// TODO(hlopko): Make this be a pointer comparison, now it's comparing string
// values.
// TODO(b/208377928): We don't yet have an actual target for the standard
// library, so instead we're just testing against the "virtual target" that
// AstVisitor::GetOwningTarget() returns if it can't find the header in the
// header-to-target map.
// Once we do have an actual target for the standard library, we may need to
// query `self` to find out what it is, so we have a `self` parameter on this
// method even though we currently don't use it.
target.0 == "//:virtual_clang_resource_dir_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 `Record` defining `func`, or `None` if `func` is not a
/// member function.
///
/// If `Func` is a member function, but its `Record` is somehow not in
/// `self`, returns an error.
pub fn record_for_member_func<'a>(&self, func: &'a Func) -> Result<Option<&Record>> {
if let Some(meta) = func.member_func_metadata.as_ref() {
Ok(Some(
self.find_decl(meta.record_id)
.context("Failed to retrieve Record for MemberFuncMetadata")?,
))
} else {
Ok(None)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
use token_stream_matchers::assert_rs_matches;
#[test]
fn test_identifier_debug_print() {
assert_eq!(format!("{:?}", Identifier { identifier: "hello".to_string() }), "\"hello\"");
}
#[test]
fn test_unqualified_identifier_debug_print() {
assert_eq!(
format!(
"{:?}",
UnqualifiedIdentifier::Identifier(Identifier { identifier: "hello".to_string() })
),
"\"hello\""
);
assert_eq!(format!("{:?}", UnqualifiedIdentifier::Constructor), "Constructor");
assert_eq!(format!("{:?}", UnqualifiedIdentifier::Destructor), "Destructor");
}
#[test]
fn test_used_headers() {
let input = r#"
{
"used_headers": [{ "name": "foo/bar.h" }],
"current_target": "//foo:bar"
}
"#;
let ir = deserialize_ir(input.as_bytes()).unwrap();
let expected = FlatIR {
used_headers: vec![HeaderName { name: "foo/bar.h".to_string() }],
current_target: "//foo:bar".into(),
top_level_item_ids: vec![],
items: vec![],
};
assert_eq!(ir.flat_ir, expected);
}
#[test]
fn test_lifetime_name_to_tokens() {
let lifetime = LifetimeName { name: "name".to_string(), id: LifetimeId(42) };
assert_rs_matches!(quote! { #lifetime }, quote! { 'name });
}
}