mod bridge_name_tracker;
pub(crate) mod function_wrapper;
mod overload_tracker;
mod rust_name_tracker;
use crate::{
conversion::{
convert_error::ConvertErrorWithContext, convert_error::ErrorContext,
error_reporter::add_api_or_report_error,
},
known_types::known_types,
};
use std::collections::{HashMap, HashSet};
use autocxx_parser::{TypeConfig, UnsafePolicy};
use function_wrapper::{FunctionWrapper, FunctionWrapperPayload, TypeConversionPolicy};
use proc_macro2::Span;
use syn::{
parse_quote, punctuated::Punctuated, FnArg, ForeignItemFn, Ident, LitStr, Pat, ReturnType,
Type, TypePtr, Visibility,
};
use crate::{
conversion::{
api::{Api, ApiAnalysis, ApiDetail, FuncToConvert, TypeKind, UnanalyzedApi},
codegen_cpp::AdditionalNeed,
parse::type_converter::TypeConverter,
ConvertError,
},
types::{make_ident, validate_ident_ok_for_cxx, Namespace, QualifiedName},
};
use self::{
bridge_name_tracker::BridgeNameTracker, overload_tracker::OverloadTracker,
rust_name_tracker::RustNameTracker,
};
use super::pod::PodAnalysis;
pub(crate) enum MethodKind {
Normal,
Constructor,
Static,
Virtual,
PureVirtual,
}
pub(crate) enum FnKind {
Function,
Method(QualifiedName, MethodKind),
}
pub(crate) enum RustRenameStrategy {
None,
RenameUsingRustAttr,
RenameInOutputMod(Ident),
}
pub(crate) struct FnAnalysisBody {
pub(crate) cxxbridge_name: Ident,
pub(crate) rust_name: String,
pub(crate) rust_rename_strategy: RustRenameStrategy,
pub(crate) params: Punctuated<FnArg, syn::Token![,]>,
pub(crate) kind: FnKind,
pub(crate) ret_type: ReturnType,
pub(crate) param_details: Vec<ArgumentAnalysis>,
pub(crate) cpp_call_name: String,
pub(crate) requires_unsafe: bool,
pub(crate) vis: Visibility,
pub(crate) cpp_wrapper: Option<AdditionalNeed>,
}
pub(crate) struct ArgumentAnalysis {
pub(crate) conversion: TypeConversionPolicy,
pub(crate) name: Pat,
pub(crate) self_type: Option<QualifiedName>,
was_reference: bool,
deps: HashSet<QualifiedName>,
is_virtual: bool,
requires_unsafe: bool,
}
struct ReturnTypeAnalysis {
rt: ReturnType,
conversion: Option<TypeConversionPolicy>,
was_reference: bool,
deps: HashSet<QualifiedName>,
}
pub(crate) struct FnAnalysis;
impl ApiAnalysis for FnAnalysis {
type TypeAnalysis = TypeKind;
type FunAnalysis = FnAnalysisBody;
}
pub(crate) struct FnAnalyzer<'a> {
unsafe_policy: UnsafePolicy,
rust_name_tracker: RustNameTracker,
extra_apis: Vec<UnanalyzedApi>,
type_converter: &'a mut TypeConverter<'a>,
bridge_name_tracker: BridgeNameTracker,
pod_safe_types: HashSet<QualifiedName>,
type_config: &'a TypeConfig,
incomplete_types: HashSet<QualifiedName>,
overload_trackers_by_mod: HashMap<Namespace, OverloadTracker>,
generate_utilities: bool,
}
struct FnAnalysisResult(FnAnalysisBody, Ident, HashSet<QualifiedName>);
impl<'a> FnAnalyzer<'a> {
pub(crate) fn analyze_functions(
apis: Vec<Api<PodAnalysis>>,
unsafe_policy: UnsafePolicy,
type_converter: &'a mut TypeConverter<'a>,
type_database: &'a TypeConfig,
) -> Vec<Api<FnAnalysis>> {
let mut me = Self {
unsafe_policy,
rust_name_tracker: RustNameTracker::new(),
extra_apis: Vec::new(),
type_converter,
bridge_name_tracker: BridgeNameTracker::new(),
type_config: type_database,
incomplete_types: Self::build_incomplete_type_set(&apis),
overload_trackers_by_mod: HashMap::new(),
pod_safe_types: Self::build_pod_safe_type_set(&apis),
generate_utilities: Self::should_generate_utilities(&apis),
};
let mut results = Vec::new();
for api in apis {
add_api_or_report_error(api.typename(), &mut results, || me.analyze_fn_api(api));
}
results.extend(me.extra_apis.into_iter().map(Self::make_extra_api_nonpod));
results
}
fn should_generate_utilities(apis: &[Api<PodAnalysis>]) -> bool {
apis.iter()
.any(|api| matches!(api.detail, ApiDetail::StringConstructor))
}
fn build_incomplete_type_set(apis: &[Api<PodAnalysis>]) -> HashSet<QualifiedName> {
apis.iter()
.filter_map(|api| match api.detail {
ApiDetail::ForwardDeclaration => Some(api.typename()),
_ => None,
})
.collect()
}
fn build_pod_safe_type_set(apis: &[Api<PodAnalysis>]) -> HashSet<QualifiedName> {
apis.iter()
.filter_map(|api| match api.detail {
ApiDetail::Type {
bindgen_mod_item: _,
analysis: TypeKind::Pod,
} => Some(api.typename()),
_ => None,
})
.chain(
known_types()
.get_pod_safe_types()
.filter_map(
|(tn, is_pod_safe)| {
if is_pod_safe {
Some(tn.clone())
} else {
None
}
},
),
)
.collect()
}
fn make_extra_api_nonpod(api: UnanalyzedApi) -> Api<FnAnalysis> {
let new_detail = match api.detail {
ApiDetail::ConcreteType { rs_definition } => ApiDetail::ConcreteType { rs_definition },
_ => panic!("Function analysis created an extra API which wasn't a concrete type"),
};
Api {
name: api.name,
deps: api.deps,
detail: new_detail,
}
}
fn analyze_fn_api(
&mut self,
api: Api<PodAnalysis>,
) -> Result<Option<Api<FnAnalysis>>, ConvertErrorWithContext> {
let mut new_deps = api.deps.clone();
let mut new_id = api.name.get_final_ident();
let api_detail = match api.detail {
ApiDetail::ConcreteType { rs_definition } => ApiDetail::ConcreteType { rs_definition },
ApiDetail::StringConstructor => ApiDetail::StringConstructor,
ApiDetail::Function { fun, analysis: _ } => {
let analysis = self.analyze_foreign_fn(&api.name.get_namespace(), &fun)?;
match analysis {
None => return Ok(None),
Some(FnAnalysisResult(analysis, id, fn_deps)) => {
new_deps = fn_deps;
new_id = id;
ApiDetail::Function { fun, analysis }
}
}
}
ApiDetail::Const { const_item } => ApiDetail::Const { const_item },
ApiDetail::Typedef { payload } => ApiDetail::Typedef { payload },
ApiDetail::CType { typename } => ApiDetail::CType { typename },
ApiDetail::Type {
bindgen_mod_item,
analysis,
} => ApiDetail::Type {
bindgen_mod_item,
analysis,
},
ApiDetail::ForwardDeclaration => ApiDetail::ForwardDeclaration,
ApiDetail::OpaqueTypedef => ApiDetail::OpaqueTypedef,
ApiDetail::IgnoredItem { err, ctx } => ApiDetail::IgnoredItem { err, ctx },
};
Ok(Some(Api {
name: QualifiedName::new(api.name.get_namespace(), new_id),
deps: new_deps,
detail: api_detail,
}))
}
fn convert_boxed_type(
&mut self,
ty: Box<Type>,
ns: &Namespace,
convert_ptrs_to_reference: bool,
) -> Result<(Box<Type>, HashSet<QualifiedName>, bool), ConvertError> {
let annotated = self.type_converter.convert_boxed_type(
ty,
ns,
convert_ptrs_to_reference,
&self.incomplete_types,
)?;
self.extra_apis.extend(annotated.extra_apis);
Ok((
annotated.ty,
annotated.types_encountered,
annotated.requires_unsafe,
))
}
fn get_cxx_bridge_name(
&mut self,
type_name: Option<&str>,
found_name: &str,
ns: &Namespace,
) -> String {
self.bridge_name_tracker
.get_unique_cxx_bridge_name(type_name, found_name, ns)
}
fn ok_to_use_rust_name(&mut self, rust_name: &str) -> bool {
self.rust_name_tracker.ok_to_use_rust_name(rust_name)
}
fn is_on_allowlist(&self, type_name: &QualifiedName) -> bool {
self.type_config.is_on_allowlist(&type_name.to_cpp_name())
}
fn should_be_unsafe(&self) -> bool {
self.unsafe_policy == UnsafePolicy::AllFunctionsUnsafe
}
fn analyze_foreign_fn(
&mut self,
ns: &Namespace,
func_information: &FuncToConvert,
) -> Result<Option<FnAnalysisResult>, ConvertErrorWithContext> {
let fun = &func_information.item;
let virtual_this = &func_information.virtual_this_type;
let initial_rust_name = fun.sig.ident.to_string();
if initial_rust_name.ends_with("_destructor") {
return Ok(None);
}
let original_name = Self::get_bindgen_original_name_annotation(&fun);
let diagnostic_display_name = original_name.as_ref().unwrap_or(&initial_rust_name);
let (reference_params, reference_return) = Self::get_reference_parameters_and_return(&fun);
let (param_details, bads): (Vec<_>, Vec<_>) = fun
.sig
.inputs
.iter()
.map(|i| {
self.convert_fn_arg(
i,
&ns,
diagnostic_display_name,
virtual_this.clone(),
&reference_params,
)
})
.partition(Result::is_ok);
let (mut params, mut param_details): (Punctuated<_, syn::Token![,]>, Vec<_>) =
param_details.into_iter().map(Result::unwrap).unzip();
let params_deps: HashSet<_> = param_details
.iter()
.map(|p| p.deps.iter().cloned())
.flatten()
.collect();
let self_ty = param_details
.iter()
.filter_map(|pd| pd.self_type.as_ref())
.next()
.cloned();
let requires_unsafe =
self.should_be_unsafe() || param_details.iter().any(|pd| pd.requires_unsafe);
let mut rust_name;
let name_probably_invalid_in_rust =
original_name.is_some() && initial_rust_name.ends_with('_');
let cpp_call_name = original_name.unwrap_or_else(|| initial_rust_name.clone());
let ideal_rust_name = if name_probably_invalid_in_rust {
initial_rust_name
} else {
cpp_call_name.clone()
};
let (is_static_method, self_ty) = if self_ty.is_none() {
let self_ty = func_information.self_ty.clone();
(self_ty.is_some(), self_ty)
} else {
(false, self_ty)
};
let (kind, error_context) = if let Some(self_ty) = self_ty {
if !self.is_on_allowlist(&self_ty) {
return Ok(None);
}
let type_ident = self_ty.get_final_item();
let overload_tracker = self.overload_trackers_by_mod.entry(ns.clone()).or_default();
rust_name = overload_tracker.get_method_real_name(&type_ident, ideal_rust_name);
let method_kind = if rust_name.starts_with(&type_ident) {
let constructor_suffix = &rust_name[type_ident.len()..];
rust_name = format!("make_unique{}", constructor_suffix);
params = params.into_iter().skip(1).collect();
param_details.remove(0);
MethodKind::Constructor
} else if is_static_method {
MethodKind::Static
} else if param_details.iter().any(|pd| pd.is_virtual) {
if Self::has_attr(&fun, "bindgen_pure_virtual") {
MethodKind::PureVirtual
} else {
MethodKind::Virtual
}
} else {
MethodKind::Normal
};
let error_context = ErrorContext::Method {
self_ty: self_ty.get_final_ident(),
method: make_ident(&rust_name),
};
(FnKind::Method(self_ty, method_kind), error_context)
} else {
let overload_tracker = self.overload_trackers_by_mod.entry(ns.clone()).or_default();
rust_name = overload_tracker.get_function_real_name(ideal_rust_name);
(FnKind::Function, ErrorContext::Item(make_ident(&rust_name)))
};
let cxxbridge_name = self.get_cxx_bridge_name(
match kind {
FnKind::Method(ref self_ty, ..) => Some(self_ty.get_final_item()),
FnKind::Function => None,
},
&rust_name,
&ns,
);
let mut cxxbridge_name = make_ident(&cxxbridge_name);
let contextualize_error = |err| ConvertErrorWithContext(err, Some(error_context));
if let Some(problem) = bads.into_iter().next() {
match problem {
Ok(_) => panic!("No error in the error"),
Err(problem) => return Err(contextualize_error(problem)),
}
}
if Self::has_attr(&fun, "bindgen_unused_template_param_in_arg_or_return") {
return Err(contextualize_error(ConvertError::UnusedTemplateParam));
}
let mut return_analysis = if let FnKind::Method(ref self_ty, MethodKind::Constructor) = kind
{
let constructed_type = self_ty.to_type_path();
let mut these_deps = HashSet::new();
these_deps.insert(self_ty.clone());
ReturnTypeAnalysis {
rt: parse_quote! {
-> #constructed_type
},
conversion: Some(TypeConversionPolicy::new_to_unique_ptr(parse_quote! {
#constructed_type
})),
was_reference: false,
deps: these_deps,
}
} else {
let r = self.convert_return_type(&fun.sig.output, &ns, reference_return);
match r {
Err(err) => return Err(contextualize_error(err)),
Ok(r) => r,
}
};
let mut deps = params_deps;
deps.extend(return_analysis.deps.drain());
if return_analysis.was_reference {
let num_input_references = param_details.iter().filter(|pd| pd.was_reference).count();
if num_input_references != 1 {
return Err(contextualize_error(ConvertError::NotOneInputReference(
rust_name,
)));
}
}
let mut ret_type = return_analysis.rt;
let ret_type_conversion = return_analysis.conversion;
let param_conversion_needed = param_details.iter().any(|b| b.conversion.cpp_work_needed());
let ret_type_conversion_needed = ret_type_conversion
.as_ref()
.map_or(false, |x| x.cpp_work_needed());
let wrapper_function_needed = match kind {
FnKind::Method(_, MethodKind::Static)
| FnKind::Method(_, MethodKind::Virtual)
| FnKind::Method(_, MethodKind::PureVirtual) => true,
FnKind::Method(..) if cxxbridge_name != rust_name => true,
_ if param_conversion_needed => true,
_ if ret_type_conversion_needed => true,
_ => false,
};
let cpp_wrapper = if wrapper_function_needed {
let cpp_construction_ident = make_ident(&cpp_call_name);
let joiner = if cxxbridge_name.to_string().ends_with('_') {
""
} else {
"_"
};
cxxbridge_name = make_ident(&format!("{}{}autocxx_wrapper", cxxbridge_name, joiner));
let (payload, has_receiver) = match kind {
FnKind::Method(_, MethodKind::Constructor) => {
(FunctionWrapperPayload::Constructor, false)
}
FnKind::Method(ref self_ty, MethodKind::Static) => (
FunctionWrapperPayload::StaticMethodCall(
ns.clone(),
self_ty.get_final_ident(),
cpp_construction_ident,
),
false,
),
FnKind::Method(..) => (
FunctionWrapperPayload::FunctionCall(ns.clone(), cpp_construction_ident),
true,
),
_ => (
FunctionWrapperPayload::FunctionCall(ns.clone(), cpp_construction_ident),
false,
),
};
if let Some(ref conversion) = ret_type_conversion {
let new_ret_type = conversion.unconverted_rust_type();
ret_type = parse_quote!(
-> #new_ret_type
);
}
params.clear();
for pd in ¶m_details {
let type_name = pd.conversion.converted_rust_type();
let arg_name = if pd.self_type.is_some()
&& !matches!(kind, FnKind::Method(_, MethodKind::Constructor))
{
parse_quote!(autocxx_gen_this)
} else {
pd.name.clone()
};
params.push(parse_quote!(
#arg_name: #type_name
));
}
Some(AdditionalNeed::FunctionWrapper(Box::new(FunctionWrapper {
payload,
wrapper_function_name: cxxbridge_name.clone(),
return_conversion: ret_type_conversion,
argument_conversion: param_details.iter().map(|d| d.conversion.clone()).collect(),
is_a_method: has_receiver,
})))
} else {
None
};
let vis = func_information.item.vis.clone();
validate_ident_ok_for_cxx(&cxxbridge_name.to_string()).map_err(contextualize_error)?;
let rust_name_ident = make_ident(&rust_name);
let (id, rust_rename_strategy) = match kind {
FnKind::Method(..) => (rust_name_ident, RustRenameStrategy::None),
FnKind::Function => {
let rust_name_ok = self.ok_to_use_rust_name(&rust_name);
if cxxbridge_name == rust_name {
(rust_name_ident, RustRenameStrategy::None)
} else if rust_name_ok {
(rust_name_ident, RustRenameStrategy::RenameUsingRustAttr)
} else {
(
cxxbridge_name.clone(),
RustRenameStrategy::RenameInOutputMod(rust_name_ident),
)
}
}
};
Ok(Some(FnAnalysisResult(
FnAnalysisBody {
cxxbridge_name,
rust_name,
rust_rename_strategy,
params,
kind,
ret_type,
param_details,
cpp_call_name,
requires_unsafe,
vis,
cpp_wrapper,
},
id,
deps,
)))
}
fn convert_fn_arg(
&mut self,
arg: &FnArg,
ns: &Namespace,
fn_name: &str,
virtual_this: Option<QualifiedName>,
reference_args: &HashSet<Ident>,
) -> Result<(FnArg, ArgumentAnalysis), ConvertError> {
Ok(match arg {
FnArg::Typed(pt) => {
let mut pt = pt.clone();
let mut self_type = None;
let old_pat = *pt.pat;
let mut is_virtual = false;
let mut treat_as_reference = false;
let new_pat = match old_pat {
syn::Pat::Ident(mut pp) if pp.ident == "this" => {
let this_type = match pt.ty.as_ref() {
Type::Ptr(TypePtr {
elem, mutability, ..
}) => match elem.as_ref() {
Type::Path(typ) => {
let mut this_type = QualifiedName::from_type_path(typ);
if this_type.is_cvoid() && pp.ident == "this" {
is_virtual = true;
this_type = virtual_this.ok_or_else(|| {
ConvertError::VirtualThisType(
ns.clone(),
fn_name.into(),
)
})?;
let this_type_path = this_type.to_type_path();
let const_token = if mutability.is_some() {
None
} else {
Some(syn::Token))
};
pt.ty = Box::new(parse_quote! {
* #mutability #const_token #this_type_path
});
}
Ok(this_type)
}
_ => Err(ConvertError::UnexpectedThisType(
ns.clone(),
fn_name.into(),
)),
},
_ => Err(ConvertError::UnexpectedThisType(ns.clone(), fn_name.into())),
}?;
self_type = Some(this_type);
pp.ident = Ident::new("self", pp.ident.span());
treat_as_reference = true;
syn::Pat::Ident(pp)
}
syn::Pat::Ident(pp) => {
validate_ident_ok_for_cxx(&pp.ident.to_string())?;
treat_as_reference = reference_args.contains(&pp.ident);
syn::Pat::Ident(pp)
}
_ => old_pat,
};
let (new_ty, deps, requires_unsafe) =
self.convert_boxed_type(pt.ty, ns, treat_as_reference)?;
let was_reference = matches!(new_ty.as_ref(), Type::Reference(_));
let conversion = self.argument_conversion_details(&new_ty);
pt.pat = Box::new(new_pat.clone());
pt.ty = new_ty;
(
FnArg::Typed(pt),
ArgumentAnalysis {
self_type,
name: new_pat,
conversion,
was_reference,
deps,
is_virtual,
requires_unsafe,
},
)
}
_ => panic!("Did not expect FnArg::Receiver to be generated by bindgen"),
})
}
fn argument_conversion_details(&self, ty: &Type) -> TypeConversionPolicy {
match ty {
Type::Path(p) => {
let tn = QualifiedName::from_type_path(p);
if self.pod_safe_types.contains(&tn) {
TypeConversionPolicy::new_unconverted(ty.clone())
} else if known_types().convertible_from_strs(&tn) && self.generate_utilities {
TypeConversionPolicy::new_from_str(ty.clone())
} else {
TypeConversionPolicy::new_from_unique_ptr(ty.clone())
}
}
_ => TypeConversionPolicy::new_unconverted(ty.clone()),
}
}
fn return_type_conversion_details(&self, ty: &Type) -> TypeConversionPolicy {
match ty {
Type::Path(p) => {
let tn = QualifiedName::from_type_path(p);
if self.pod_safe_types.contains(&tn) {
TypeConversionPolicy::new_unconverted(ty.clone())
} else {
TypeConversionPolicy::new_to_unique_ptr(ty.clone())
}
}
_ => TypeConversionPolicy::new_unconverted(ty.clone()),
}
}
fn convert_return_type(
&mut self,
rt: &ReturnType,
ns: &Namespace,
convert_ptr_to_reference: bool,
) -> Result<ReturnTypeAnalysis, ConvertError> {
let result = match rt {
ReturnType::Default => ReturnTypeAnalysis {
rt: ReturnType::Default,
was_reference: false,
conversion: None,
deps: HashSet::new(),
},
ReturnType::Type(rarrow, boxed_type) => {
let (boxed_type, deps, _) =
self.convert_boxed_type(boxed_type.clone(), ns, convert_ptr_to_reference)?;
let was_reference = matches!(boxed_type.as_ref(), Type::Reference(_));
let conversion = self.return_type_conversion_details(boxed_type.as_ref());
ReturnTypeAnalysis {
rt: ReturnType::Type(*rarrow, boxed_type),
conversion: Some(conversion),
was_reference,
deps,
}
}
};
Ok(result)
}
fn get_bindgen_original_name_annotation(fun: &ForeignItemFn) -> Option<String> {
fun.attrs
.iter()
.filter_map(|a| {
if a.path.is_ident("bindgen_original_name") {
let r: Result<LitStr, syn::Error> = a.parse_args();
match r {
Ok(ls) => Some(ls.value()),
Err(_) => None,
}
} else {
None
}
})
.next()
}
fn get_reference_parameters_and_return(fun: &ForeignItemFn) -> (HashSet<Ident>, bool) {
let mut ref_params = HashSet::new();
let mut ref_return = false;
for a in &fun.attrs {
if a.path.is_ident("bindgen_ret_type_reference") {
ref_return = true;
} else if a.path.is_ident("bindgen_arg_type_reference") {
let r: Result<Ident, syn::Error> = a.parse_args();
if let Ok(ls) = r {
ref_params.insert(ls);
}
}
}
(ref_params, ref_return)
}
fn has_attr(fun: &ForeignItemFn, attr_name: &str) -> bool {
fun.attrs.iter().any(|at| at.path.is_ident(attr_name))
}
}
impl Api<FnAnalysis> {
pub(crate) fn typename_for_allowlist(&self) -> QualifiedName {
match &self.detail {
ApiDetail::Function { fun: _, analysis } => match analysis.kind {
FnKind::Method(ref self_ty, _) => self_ty.clone(),
FnKind::Function => {
QualifiedName::new(&self.name.get_namespace(), make_ident(&analysis.rust_name))
}
},
_ => self.typename(),
}
}
pub(crate) fn additional_cpp(&self) -> Option<AdditionalNeed> {
match &self.detail {
ApiDetail::Function { fun: _, analysis } => analysis.cpp_wrapper.clone(),
ApiDetail::StringConstructor => Some(AdditionalNeed::MakeStringConstructor),
ApiDetail::ConcreteType { rs_definition } => {
Some(AdditionalNeed::ConcreteTemplatedTypeTypedef(
self.name.clone(),
rs_definition.clone(),
))
}
ApiDetail::CType { typename } => Some(AdditionalNeed::CTypeTypedef(typename.clone())),
_ => None,
}
}
}