use std::collections::HashSet;
use crate::conversion::parse::type_converter::Annotated;
use crate::{
conversion::{
api::{ApiDetail, ParseResults, TypeApiDetails, TypedefKind, UnanalyzedApi},
ConvertError,
},
types::make_ident,
types::Namespace,
types::TypeName,
};
use autocxx_parser::TypeConfig;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{parse_quote, Fields, Ident, Item, Type, TypePath, UseTree};
use super::{super::utilities::generate_utilities, type_converter::TypeConverter};
use super::parse_foreign_mod::ParseForeignMod;
pub(crate) struct ParseBindgen<'a> {
type_config: &'a TypeConfig,
results: ParseResults,
latest_virtual_this_type: Option<TypeName>,
}
struct ConvertErrorWithIdent(ConvertError, Option<Ident>);
impl std::fmt::Debug for ConvertErrorWithIdent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl<'a> ParseBindgen<'a> {
pub(crate) fn new(type_config: &'a TypeConfig) -> Self {
ParseBindgen {
type_config,
results: ParseResults {
apis: Vec::new(),
type_converter: TypeConverter::new(),
},
latest_virtual_this_type: None,
}
}
pub(crate) fn parse_items(
mut self,
items: Vec<Item>,
exclude_utilities: bool,
) -> Result<ParseResults, ConvertError> {
let items = Self::find_items_in_root(items)?;
if !exclude_utilities {
generate_utilities(&mut self.results.apis);
}
let root_ns = Namespace::new();
self.parse_mod_items(items, root_ns);
Ok(self.results)
}
fn find_items_in_root(items: Vec<Item>) -> Result<Vec<Item>, ConvertError> {
for item in items {
match item {
Item::Mod(root_mod) => {
assert!(root_mod.ident == "root");
if let Some((_, items)) = root_mod.content {
return Ok(items);
}
}
_ => return Err(ConvertError::UnexpectedOuterItem),
}
}
Ok(Vec::new())
}
fn parse_mod_items(&mut self, items: Vec<Item>, ns: Namespace) {
let mut mod_converter = ParseForeignMod::new(ns.clone());
for item in items {
let r = self.parse_item(item, &mut mod_converter, &ns);
match r {
Err(err) if err.0.is_ignorable() => {
eprintln!("Ignored item discovered whilst parsing: {}", err.0);
if let Some(id) = err.1 {
self.results.apis.push(UnanalyzedApi {
ns: ns.clone(),
id,
deps: HashSet::new(),
detail: ApiDetail::IgnoredItem,
})
}
}
Err(_) => r.unwrap(),
Ok(_) => {}
}
}
mod_converter.finished(&mut self.results.apis);
}
fn parse_item(
&mut self,
item: Item,
mod_converter: &mut ParseForeignMod,
ns: &Namespace,
) -> Result<(), ConvertErrorWithIdent> {
match item {
Item::ForeignMod(fm) => {
mod_converter
.convert_foreign_mod_items(fm.items, self.latest_virtual_this_type.clone());
Ok(())
}
Item::Struct(s) => {
if s.ident.to_string().ends_with("__bindgen_vtable") {
return Ok(());
}
let tyname = TypeName::new(ns, &s.ident.to_string());
let is_forward_declaration = Self::spot_forward_declaration(&s.fields);
self.parse_type(
tyname.clone(),
is_forward_declaration,
HashSet::new(),
Some(Item::Struct(s)),
);
self.latest_virtual_this_type = Some(tyname);
Ok(())
}
Item::Enum(e) => {
let tyname = TypeName::new(ns, &e.ident.to_string());
self.parse_type(tyname, false, HashSet::new(), Some(Item::Enum(e)));
Ok(())
}
Item::Impl(imp) => {
mod_converter.convert_impl_items(imp);
Ok(())
}
Item::Mod(itm) => {
if let Some((_, items)) = itm.content {
let new_ns = ns.push(itm.ident.to_string());
self.parse_mod_items(items, new_ns);
}
Ok(())
}
Item::Use(use_item) => {
let mut segs = Vec::new();
let mut tree = &use_item.tree;
loop {
match tree {
UseTree::Path(up) => {
segs.push(up.ident.clone());
tree = &up.tree;
}
UseTree::Name(un) if un.ident == "root" => break, UseTree::Rename(urn) => {
let old_id = &urn.ident;
let new_id = &urn.rename;
let new_tyname = TypeName::new(ns, &new_id.to_string());
if segs.remove(0) != "self" {
panic!("Path didn't start with self");
}
if segs.remove(0) != "super" {
panic!("Path didn't start with self::super");
}
let old_path: TypePath = parse_quote! {
#(#segs)::* :: #old_id
};
let old_tyname = TypeName::from_type_path(&old_path);
if new_tyname == old_tyname {
return Err(ConvertErrorWithIdent(
ConvertError::InfinitelyRecursiveTypedef(new_tyname),
Some(new_id.clone()),
));
}
self.results
.type_converter
.insert_typedef(new_tyname, Type::Path(old_path.clone()));
let mut deps = HashSet::new();
deps.insert(old_tyname);
self.results.apis.push(UnanalyzedApi {
id: new_id.clone(),
ns: ns.clone(),
deps,
detail: ApiDetail::Typedef {
payload: TypedefKind::Use(parse_quote! {
pub use #old_path as #new_id;
}),
},
});
break;
}
_ => panic!("Unexpected 'use' syntax encountered"),
}
}
Ok(())
}
Item::Const(const_item) => {
self.results.apis.push(UnanalyzedApi {
id: const_item.ident.clone(),
ns: ns.clone(),
deps: HashSet::new(),
detail: ApiDetail::Const { const_item },
});
Ok(())
}
Item::Type(mut ity) => {
let tyname = TypeName::new(ns, &ity.ident.to_string());
let type_conversion_results =
self.results.type_converter.convert_type(*ity.ty, ns, false);
match type_conversion_results {
Err(ConvertError::OpaqueTypeFound) => {
self.add_opaque_type(ity.ident, ns.clone());
Ok(())
}
Err(err) => Err(ConvertErrorWithIdent(err, Some(ity.ident))),
Ok(Annotated {
ty: syn::Type::Path(ref typ),
..
}) if TypeName::from_type_path(typ) == tyname => Err(ConvertErrorWithIdent(
ConvertError::InfinitelyRecursiveTypedef(tyname),
Some(ity.ident),
)),
Ok(mut final_type) => {
ity.ty = Box::new(final_type.ty.clone());
self.results
.type_converter
.insert_typedef(tyname, final_type.ty);
self.results.apis.append(&mut final_type.extra_apis);
self.results.apis.push(UnanalyzedApi {
id: ity.ident.clone(),
ns: ns.clone(),
deps: final_type.types_encountered,
detail: ApiDetail::Typedef {
payload: TypedefKind::Type(ity),
},
});
Ok(())
}
}
}
_ => Err(ConvertErrorWithIdent(
ConvertError::UnexpectedItemInMod,
None,
)),
}
}
fn spot_forward_declaration(s: &Fields) -> bool {
s.iter()
.filter_map(|f| f.ident.as_ref())
.any(|id| id == "_unused")
}
fn add_opaque_type(&mut self, id: Ident, ns: Namespace) {
self.results.apis.push(UnanalyzedApi {
id,
ns,
deps: HashSet::new(),
detail: ApiDetail::OpaqueTypedef,
});
}
fn parse_type(
&mut self,
tyname: TypeName,
is_forward_declaration: bool,
deps: HashSet<TypeName>,
bindgen_mod_item: Option<Item>,
) {
let final_ident = make_ident(tyname.get_final_ident());
if self.type_config.is_on_blocklist(&tyname.to_cpp_name()) {
return;
}
let tynamestring = tyname.to_cpp_name();
let mut for_extern_c_ts = if tyname.has_namespace() {
let ns_string = tyname
.ns_segment_iter()
.cloned()
.collect::<Vec<String>>()
.join("::");
quote! {
#[namespace = #ns_string]
}
} else {
TokenStream2::new()
};
let mut fulltypath: Vec<_> = ["bindgen", "root"].iter().map(make_ident).collect();
for_extern_c_ts.extend(quote! {
type #final_ident = super::bindgen::root::
});
for segment in tyname.ns_segment_iter() {
let id = make_ident(segment);
for_extern_c_ts.extend(quote! {
#id::
});
fulltypath.push(id);
}
for_extern_c_ts.extend(quote! {
#final_ident;
});
fulltypath.push(final_ident.clone());
let api = UnanalyzedApi {
ns: tyname.get_namespace().clone(),
id: final_ident.clone(),
deps,
detail: ApiDetail::Type {
ty_details: TypeApiDetails {
fulltypath,
final_ident,
tynamestring,
},
for_extern_c_ts,
is_forward_declaration,
bindgen_mod_item,
analysis: (),
},
};
self.results.apis.push(api);
self.results.type_converter.push(tyname);
}
}