use crate::additional_cpp_generator::AdditionalNeed;
use crate::byvalue_checker::ByValueChecker;
use crate::known_types::KNOWN_TYPES;
use crate::TypeName;
use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree};
use std::collections::HashSet;
use syn::punctuated::Punctuated;
use syn::Token;
use syn::{
parse_quote, AngleBracketedGenericArguments, Attribute, FnArg, ForeignItem, ForeignItemFn,
GenericArgument, Ident, Item, ItemForeignMod, ItemMod, PatType, Path, PathArguments,
PathSegment, ReturnType, Type, TypePath, TypePtr, TypeReference,
};
#[derive(Debug)]
pub enum ConvertError {
NoContent,
UnsafePODType(String),
UnknownForeignItem,
}
pub(crate) struct BridgeConversion {
pub items: Vec<Item>,
pub additional_cpp_needs: Vec<AdditionalNeed>,
}
pub(crate) struct BridgeConverter {
include_list: Vec<String>,
pod_requests: Vec<TypeName>,
class_names_discovered: HashSet<TypeName>,
byvalue_checker: ByValueChecker,
}
struct TypeAliasResults {
for_extern_c: ForeignItem,
for_bridge: Item,
for_anywhere: Item,
}
impl<'a> BridgeConverter {
pub fn new(include_list: Vec<String>, pod_requests: Vec<TypeName>) -> Self {
Self {
include_list,
class_names_discovered: HashSet::new(),
byvalue_checker: ByValueChecker::new(),
pod_requests,
}
}
fn find_nested_pod_types(&mut self, items: &[Item]) -> Result<(), ConvertError> {
for item in items {
if let Item::Struct(s) = item {
self.byvalue_checker.ingest_struct(s);
}
}
self.byvalue_checker
.satisfy_requests(self.pod_requests.clone())
.map_err(ConvertError::UnsafePODType)
}
fn generate_type_alias(&self, tyname: &TypeName, should_be_pod: bool) -> TypeAliasResults {
let tyident = tyname.to_ident();
let kind_item: Ident = Ident::new(
if should_be_pod { "Trivial" } else { "Opaque" },
Span::call_site(),
);
let tynamestring = tyname.to_cxx_name();
let mut for_extern_c_ts = TokenStream2::new();
for_extern_c_ts.extend(
[
TokenTree::Ident(Ident::new("type", Span::call_site())),
TokenTree::Ident(tyident.clone()),
TokenTree::Punct(proc_macro2::Punct::new('=', proc_macro2::Spacing::Alone)),
TokenTree::Ident(Ident::new("super", Span::call_site())),
TokenTree::Punct(proc_macro2::Punct::new(':', proc_macro2::Spacing::Joint)),
TokenTree::Punct(proc_macro2::Punct::new(':', proc_macro2::Spacing::Joint)),
TokenTree::Ident(Ident::new("bindgen", Span::call_site())),
TokenTree::Punct(proc_macro2::Punct::new(':', proc_macro2::Spacing::Joint)),
TokenTree::Punct(proc_macro2::Punct::new(':', proc_macro2::Spacing::Joint)),
TokenTree::Ident(tyident.clone()),
TokenTree::Punct(proc_macro2::Punct::new(';', proc_macro2::Spacing::Alone)),
]
.to_vec(),
);
TypeAliasResults {
for_extern_c: ForeignItem::Verbatim(for_extern_c_ts),
for_bridge: Item::Impl(parse_quote! {
impl UniquePtr<#tyident> {}
}),
for_anywhere: Item::Impl(parse_quote! {
unsafe impl cxx::ExternType for bindgen::#tyident {
type Id = cxx::type_id!(#tynamestring);
type Kind = cxx::kind::#kind_item;
}
}),
}
}
pub(crate) fn convert(
&mut self,
bindings: ItemMod,
extra_inclusion: Option<&str>,
) -> Result<BridgeConversion, ConvertError> {
match bindings.content {
None => Err(ConvertError::NoContent),
Some((brace, items)) => {
self.find_nested_pod_types(&items)?;
let mut all_items: Vec<Item> = Vec::new();
let mut bindgen_mod = ItemMod {
attrs: bindings.attrs,
vis: bindings.vis,
ident: bindings.ident,
mod_token: bindings.mod_token,
content: Some((brace, Vec::new())),
semi: bindings.semi,
};
let mut bridge_items = Vec::new();
let mut extern_c_mod = None;
let mut full_include_list = self.include_list.clone();
if let Some(extra_inclusion) = extra_inclusion {
full_include_list.push(extra_inclusion.to_string());
}
let mut extern_c_mod_items: Vec<ForeignItem> = full_include_list
.iter()
.map(|inc| {
ForeignItem::Macro(parse_quote! {
include!(#inc);
})
})
.collect();
let mut additional_cpp_needs = Vec::new();
let mut types_found = Vec::new();
let mut bindgen_items = Vec::new();
for item in items {
match item {
Item::ForeignMod(fm) => {
if extern_c_mod.is_none() {
extern_c_mod = Some(ItemForeignMod {
attrs: fm.attrs,
abi: fm.abi,
brace_token: fm.brace_token,
items: Vec::new(),
});
}
extern_c_mod
.as_mut()
.unwrap()
.items
.extend(self.convert_foreign_mod_items(&types_found, fm.items)?);
}
Item::Struct(s) => {
let tyident = s.ident.clone();
let tyname = TypeName::from_ident(&tyident);
types_found.push(tyname.clone());
self.class_names_discovered.insert(tyname.clone());
let should_be_pod = self.byvalue_checker.is_pod(&tyname);
let type_alias = self.generate_type_alias(&tyname, should_be_pod);
bridge_items.push(type_alias.for_bridge);
extern_c_mod_items.push(type_alias.for_extern_c);
bindgen_mod
.content
.as_mut()
.unwrap()
.1
.push(Item::Struct(self.convert_struct(s)));
all_items.push(type_alias.for_anywhere);
}
Item::Enum(e) => {
let tyident = e.ident.clone();
let tyname = TypeName::from_ident(&tyident);
types_found.push(tyname.clone());
let type_alias = self.generate_type_alias(&tyname, true);
bridge_items.push(type_alias.for_bridge);
extern_c_mod_items.push(type_alias.for_extern_c);
bindgen_mod.content.as_mut().unwrap().1.push(Item::Enum(e));
all_items.push(type_alias.for_anywhere);
}
Item::Impl(i) => {
if let Some(ty) = self.type_to_typename(&i.self_ty) {
for item in i.items {
match item {
syn::ImplItem::Method(m) if m.sig.ident == "new" => {
let constructor_args = m
.sig
.inputs
.iter()
.filter_map(|x| match x {
FnArg::Typed(ty) => {
self.type_to_typename(&ty.ty)
}
FnArg::Receiver(_) => None,
})
.collect::<Vec<TypeName>>();
additional_cpp_needs.push(AdditionalNeed::MakeUnique(
ty.clone(),
constructor_args.clone(),
));
}
_ => {}
}
}
}
}
_ => {
all_items.push(item);
}
}
}
let mut extern_c_mod = match extern_c_mod {
None => ItemForeignMod {
attrs: Vec::new(),
abi: syn::Abi {
extern_token: Token),
name: Some(syn::LitStr::new("C", Span::call_site())),
},
brace_token: syn::token::Brace {
span: Span::call_site(),
},
items: Vec::new(),
},
Some(md) => md,
};
extern_c_mod.items.append(&mut extern_c_mod_items);
bridge_items.push(Item::ForeignMod(extern_c_mod));
bindgen_mod
.content
.as_mut()
.unwrap()
.1
.append(&mut bindgen_items);
all_items.push(Item::Mod(bindgen_mod));
let mut bridge_mod: ItemMod = parse_quote! {
#[cxx::bridge]
pub mod cxxbridge {
}
};
bridge_mod
.content
.as_mut()
.unwrap()
.1
.append(&mut bridge_items);
all_items.push(Item::Mod(bridge_mod));
Ok(BridgeConversion {
items: all_items,
additional_cpp_needs,
})
}
}
}
fn type_to_typename(&self, ty: &Type) -> Option<TypeName> {
match ty {
Type::Path(pn) => Some(TypeName::from_type_path(pn)),
_ => None,
}
}
fn convert_foreign_mod_items(
&self,
encountered_types: &[TypeName],
foreign_mod_items: Vec<ForeignItem>,
) -> Result<Vec<ForeignItem>, ConvertError> {
let mut new_items = Vec::new();
for i in foreign_mod_items {
match i {
ForeignItem::Fn(f) => {
let maybe_foreign_item = self.convert_foreign_fn(encountered_types, f)?;
if let Some(foreign_item) = maybe_foreign_item {
new_items.push(ForeignItem::Fn(foreign_item));
}
}
_ => return Err(ConvertError::UnknownForeignItem),
}
}
Ok(new_items)
}
fn convert_foreign_fn(
&self,
encountered_types: &[TypeName],
fun: ForeignItemFn,
) -> Result<Option<ForeignItemFn>, ConvertError> {
let mut s = fun.sig.clone();
let old_name = s.ident.to_string();
for ty in encountered_types {
let constructor_name = format!("{}_{}", ty, ty);
if old_name == constructor_name {
return Ok(None);
}
}
s.output = self.convert_return_type(s.output);
let (new_params, any_this): (Punctuated<_, _>, Vec<_>) = fun
.sig
.inputs
.into_iter()
.map(|i| self.convert_fn_arg(i))
.unzip();
s.inputs = new_params;
let is_a_method = any_this.iter().any(|b| *b);
if is_a_method {
for cn in &self.class_names_discovered {
if old_name.starts_with(&cn.0) {
s.ident = Ident::new(&old_name[cn.0.len() + 1..], s.ident.span());
break;
}
}
}
Ok(Some(ForeignItemFn {
attrs: self.strip_attr(fun.attrs, "link_name"),
vis: fun.vis,
sig: s,
semi_token: fun.semi_token,
}))
}
fn strip_attr(&self, attrs: Vec<Attribute>, to_strip: &str) -> Vec<Attribute> {
attrs
.into_iter()
.filter(|a| {
let i = a.path.get_ident();
!matches!(i, Some(i2) if *i2 == to_strip)
})
.collect::<Vec<Attribute>>()
}
fn convert_fn_arg(&self, arg: FnArg) -> (FnArg, bool) {
match arg {
FnArg::Typed(pt) => {
let mut found_this = false;
let old_pat = *pt.pat;
let new_pat = match old_pat {
syn::Pat::Ident(pp) if pp.ident == "this" => {
found_this = true;
syn::Pat::Ident(syn::PatIdent {
attrs: pp.attrs,
by_ref: pp.by_ref,
mutability: pp.mutability,
subpat: pp.subpat,
ident: Ident::new("self", pp.ident.span()),
})
}
_ => old_pat,
};
(
FnArg::Typed(PatType {
attrs: pt.attrs,
pat: Box::new(new_pat),
colon_token: pt.colon_token,
ty: self.convert_boxed_type(pt.ty),
}),
found_this,
)
}
_ => (arg, false),
}
}
fn convert_return_type(&self, rt: ReturnType) -> ReturnType {
match rt {
ReturnType::Default => ReturnType::Default,
ReturnType::Type(rarrow, typebox) => {
ReturnType::Type(rarrow, self.convert_boxed_type(typebox))
}
}
}
fn convert_boxed_type(&self, ty: Box<Type>) -> Box<Type> {
Box::new(self.convert_type(*ty))
}
fn convert_type(&self, ty: Type) -> Type {
match ty {
Type::Path(p) => Type::Path(self.convert_type_path(p)),
Type::Reference(r) => Type::Reference(TypeReference {
and_token: r.and_token,
lifetime: r.lifetime,
mutability: r.mutability,
elem: self.convert_boxed_type(r.elem),
}),
Type::Ptr(ptr) => Type::Reference(self.convert_ptr_to_reference(ptr)),
_ => ty,
}
}
fn convert_ptr_to_reference(&self, ptr: TypePtr) -> TypeReference {
TypeReference {
and_token: Token),
lifetime: None,
mutability: ptr.mutability,
elem: self.convert_boxed_type(ptr.elem),
}
}
fn convert_type_path(&self, typ: TypePath) -> TypePath {
let p = typ.path;
let new_p = Path {
leading_colon: p.leading_colon,
segments: p
.segments
.into_iter()
.map(|s| {
let old_ident = TypeName::from_ident(&s.ident);
let args = match s.arguments {
PathArguments::AngleBracketed(ab) => {
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
colon2_token: ab.colon2_token,
lt_token: ab.lt_token,
gt_token: ab.gt_token,
args: self.convert_punctuated(ab.args),
})
}
_ => s.arguments,
};
let ident = match crate::known_types::KNOWN_TYPES
.get(&old_ident)
.and_then(|x| x.cxx_replacement.as_ref())
{
None => s.ident,
Some(replacement) => replacement.to_ident(),
};
PathSegment {
ident,
arguments: args,
}
})
.collect(),
};
TypePath {
qself: typ.qself,
path: new_p,
}
}
fn convert_punctuated<P>(
&self,
pun: Punctuated<GenericArgument, P>,
) -> Punctuated<GenericArgument, P>
where
P: Default,
{
let mut new_pun = Punctuated::new();
for arg in pun.into_iter() {
new_pun.push(match arg {
GenericArgument::Type(t) => GenericArgument::Type(self.convert_type(t)),
_ => arg,
});
}
new_pun
}
fn convert_struct(&self, mut strct: syn::ItemStruct) -> syn::ItemStruct {
for f in &mut strct.fields {
self.convert_struct_field_type(&mut f.ty);
}
strct
}
fn convert_struct_field_type(&self, ty: &mut Type) {
match ty {
Type::Path(ty) => {
for (type_name, type_details) in KNOWN_TYPES.iter() {
if ty.path.is_ident(&type_name.to_string()) {
if let Some(replacement) = &type_details.cxx_replacement {
let replacement = replacement.to_ident();
ty.path = parse_quote! {
cxx:: #replacement
};
}
}
}
}
_ => {}
}
}
}