Squashed 'third_party/autocxx/' content from commit 629e8fa53
git-subtree-dir: third_party/autocxx
git-subtree-split: 629e8fa531a633164c0b52e2a3cab536d4cd0849
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I62a03b0049f49adf029e0204639cdb5468dde1a1
diff --git a/engine/src/conversion/analysis/abstract_types.rs b/engine/src/conversion/analysis/abstract_types.rs
new file mode 100644
index 0000000..dac9684
--- /dev/null
+++ b/engine/src/conversion/analysis/abstract_types.rs
@@ -0,0 +1,174 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use super::{
+ fun::{
+ FnAnalysis, FnKind, FnPhase, FnPrePhase2, MethodKind, PodAndConstructorAnalysis,
+ TraitMethodKind,
+ },
+ pod::PodAnalysis,
+};
+use crate::conversion::{api::Api, apivec::ApiVec};
+use crate::conversion::{
+ api::TypeKind,
+ error_reporter::{convert_apis, convert_item_apis},
+ ConvertError,
+};
+use indexmap::set::IndexSet as HashSet;
+
+/// Spot types with pure virtual functions and mark them abstract.
+pub(crate) fn mark_types_abstract(mut apis: ApiVec<FnPrePhase2>) -> ApiVec<FnPrePhase2> {
+ let mut abstract_types: HashSet<_> = apis
+ .iter()
+ .filter_map(|api| match &api {
+ Api::Function {
+ analysis:
+ FnAnalysis {
+ kind:
+ FnKind::Method {
+ impl_for: self_ty_name,
+ method_kind: MethodKind::PureVirtual(_),
+ ..
+ },
+ ..
+ },
+ ..
+ } => Some(self_ty_name.clone()),
+ _ => None,
+ })
+ .collect();
+
+ // Spot any derived classes (recursively). Also, any types which have a base
+ // class that's not on the allowlist are presumed to be abstract, because we
+ // have no way of knowing (as they're not on the allowlist, there will be
+ // no methods associated so we won't be able to spot pure virtual methods).
+ let mut iterate = true;
+ while iterate {
+ iterate = false;
+ apis = apis
+ .into_iter()
+ .map(|api| {
+ match api {
+ Api::Struct {
+ analysis:
+ PodAndConstructorAnalysis {
+ pod:
+ PodAnalysis {
+ bases,
+ kind: TypeKind::Pod | TypeKind::NonPod,
+ castable_bases,
+ field_deps,
+ field_info,
+ is_generic,
+ in_anonymous_namespace,
+ },
+ constructors,
+ },
+ name,
+ details,
+ } if abstract_types.contains(&name.name)
+ || !abstract_types.is_disjoint(&bases) =>
+ {
+ abstract_types.insert(name.name.clone());
+ // Recurse in case there are further dependent types
+ iterate = true;
+ Api::Struct {
+ analysis: PodAndConstructorAnalysis {
+ pod: PodAnalysis {
+ bases,
+ kind: TypeKind::Abstract,
+ castable_bases,
+ field_deps,
+ field_info,
+ is_generic,
+ in_anonymous_namespace,
+ },
+ constructors,
+ },
+ name,
+ details,
+ }
+ }
+ _ => api,
+ }
+ })
+ .collect()
+ }
+
+ // We also need to remove any constructors belonging to these
+ // abstract types.
+ apis.retain(|api| {
+ !matches!(&api,
+ Api::Function {
+ analysis:
+ FnAnalysis {
+ kind: FnKind::Method{impl_for: self_ty, method_kind: MethodKind::Constructor{..}, ..}
+ | FnKind::TraitMethod{ kind: TraitMethodKind::CopyConstructor | TraitMethodKind::MoveConstructor, impl_for: self_ty, ..},
+ ..
+ },
+ ..
+ } if abstract_types.contains(self_ty))
+ });
+
+ // Finally, if there are any types which are nested inside other types,
+ // they can't be abstract. This is due to two small limitations in cxx.
+ // Imagine we have class Foo { class Bar }
+ // 1) using "type Foo = super::bindgen::root::Foo_Bar" results
+ // in the creation of std::unique_ptr code which isn't acceptable
+ // for an abtract class
+ // 2) using "type Foo;" isn't possible unless Foo is a top-level item
+ // within its namespace. Any outer names will be interpreted as namespace
+ // names and result in cxx generating "namespace Foo { class Bar }"".
+ let mut results = ApiVec::new();
+ convert_item_apis(apis, &mut results, |api| match api {
+ Api::Struct {
+ analysis:
+ PodAndConstructorAnalysis {
+ pod:
+ PodAnalysis {
+ kind: TypeKind::Abstract,
+ ..
+ },
+ ..
+ },
+ ..
+ } if api
+ .cpp_name()
+ .as_ref()
+ .map(|n| n.contains("::"))
+ .unwrap_or_default() =>
+ {
+ Err(ConvertError::AbstractNestedType)
+ }
+ _ => Ok(Box::new(std::iter::once(api))),
+ });
+ results
+}
+
+pub(crate) fn discard_ignored_functions(apis: ApiVec<FnPhase>) -> ApiVec<FnPhase> {
+ // Some APIs can't be generated, e.g. because they're protected.
+ // Now we've finished analyzing abstract types and constructors, we'll
+ // convert them to IgnoredItems.
+ let mut apis_new = ApiVec::new();
+ convert_apis(
+ apis,
+ &mut apis_new,
+ |name, fun, analysis| {
+ analysis.ignore_reason.clone()?;
+ Ok(Box::new(std::iter::once(Api::Function {
+ name,
+ fun,
+ analysis,
+ })))
+ },
+ Api::struct_unchanged,
+ Api::enum_unchanged,
+ Api::typedef_unchanged,
+ );
+ apis_new
+}
diff --git a/engine/src/conversion/analysis/allocators.rs b/engine/src/conversion/analysis/allocators.rs
new file mode 100644
index 0000000..3695de0
--- /dev/null
+++ b/engine/src/conversion/analysis/allocators.rs
@@ -0,0 +1,111 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Code to create functions to alloc and free while unitialized.
+
+use syn::{parse_quote, punctuated::Punctuated, token::Comma, FnArg, ReturnType};
+
+use crate::{
+ conversion::{
+ api::{Api, ApiName, CppVisibility, FuncToConvert, Provenance, References, TraitSynthesis},
+ apivec::ApiVec,
+ },
+ types::{make_ident, QualifiedName},
+};
+
+use super::{
+ fun::function_wrapper::{CppFunctionBody, CppFunctionKind},
+ pod::PodPhase,
+};
+
+pub(crate) fn create_alloc_and_frees(apis: ApiVec<PodPhase>) -> ApiVec<PodPhase> {
+ apis.into_iter()
+ .flat_map(|api| -> Box<dyn Iterator<Item = Api<PodPhase>>> {
+ match &api {
+ Api::Struct { name, .. } => {
+ Box::new(create_alloc_and_free(name.name.clone()).chain(std::iter::once(api)))
+ }
+ Api::Subclass { name, .. } => {
+ Box::new(create_alloc_and_free(name.cpp()).chain(std::iter::once(api)))
+ }
+ _ => Box::new(std::iter::once(api)),
+ }
+ })
+ .collect()
+}
+
+fn create_alloc_and_free(ty_name: QualifiedName) -> impl Iterator<Item = Api<PodPhase>> {
+ let typ = ty_name.to_type_path();
+ let free_inputs: Punctuated<FnArg, Comma> = parse_quote! {
+ arg0: *mut #typ
+ };
+ let alloc_return: ReturnType = parse_quote! {
+ -> *mut #typ
+ };
+ [
+ (
+ TraitSynthesis::AllocUninitialized(ty_name.clone()),
+ get_alloc_name(&ty_name),
+ Punctuated::new(),
+ alloc_return,
+ CppFunctionBody::AllocUninitialized(ty_name.clone()),
+ ),
+ (
+ TraitSynthesis::FreeUninitialized(ty_name.clone()),
+ get_free_name(&ty_name),
+ free_inputs,
+ ReturnType::Default,
+ CppFunctionBody::FreeUninitialized(ty_name.clone()),
+ ),
+ ]
+ .into_iter()
+ .map(
+ move |(synthesis, name, inputs, output, cpp_function_body)| {
+ let ident = name.get_final_ident();
+ let api_name = ApiName::new_from_qualified_name(name);
+ Api::Function {
+ name: api_name,
+ fun: Box::new(FuncToConvert {
+ ident,
+ doc_attrs: Vec::new(),
+ inputs,
+ output,
+ vis: parse_quote! { pub },
+ virtualness: crate::conversion::api::Virtualness::None,
+ cpp_vis: CppVisibility::Public,
+ special_member: None,
+ unused_template_param: false,
+ references: References::default(),
+ original_name: None,
+ self_ty: None,
+ synthesized_this_type: None,
+ synthetic_cpp: Some((cpp_function_body, CppFunctionKind::Function)),
+ add_to_trait: Some(synthesis),
+ is_deleted: false,
+ provenance: Provenance::SynthesizedOther,
+ variadic: false,
+ }),
+ analysis: (),
+ }
+ },
+ )
+}
+
+pub(crate) fn get_alloc_name(ty_name: &QualifiedName) -> QualifiedName {
+ get_name(ty_name, "alloc")
+}
+
+pub(crate) fn get_free_name(ty_name: &QualifiedName) -> QualifiedName {
+ get_name(ty_name, "free")
+}
+
+fn get_name(ty_name: &QualifiedName, label: &str) -> QualifiedName {
+ let name = format!("{}_{}", ty_name.get_final_item(), label);
+ let name_id = make_ident(name);
+ QualifiedName::new(ty_name.get_namespace(), name_id)
+}
diff --git a/engine/src/conversion/analysis/casts.rs b/engine/src/conversion/analysis/casts.rs
new file mode 100644
index 0000000..5f493f9
--- /dev/null
+++ b/engine/src/conversion/analysis/casts.rs
@@ -0,0 +1,141 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use itertools::Itertools;
+use quote::quote;
+use syn::{parse_quote, FnArg};
+
+use crate::{
+ conversion::{
+ api::{Api, ApiName, CastMutability, Provenance, References, TraitSynthesis},
+ apivec::ApiVec,
+ },
+ types::{make_ident, QualifiedName},
+};
+
+/// If A is a base of B, we might want to be able to cast from
+/// &B to &A, or from Pin<&mut A> to &B, or from Pin<&mut A> to &B.
+/// The first is OK; the others turn out to be hard due to all
+/// the Pin stuff. For now therefore, we simply don't allow them.
+/// But the related code may be useful in future so I'm keeping it around.
+const SUPPORT_MUTABLE_CASTS: bool = false;
+
+use super::{
+ fun::function_wrapper::{CppFunctionBody, CppFunctionKind},
+ pod::{PodAnalysis, PodPhase},
+};
+
+pub(crate) fn add_casts(apis: ApiVec<PodPhase>) -> ApiVec<PodPhase> {
+ apis.into_iter()
+ .flat_map(|api| {
+ let mut resultant_apis = match api {
+ Api::Struct {
+ ref name,
+ details: _,
+ ref analysis,
+ } => create_casts(&name.name, analysis).collect_vec(),
+ _ => Vec::new(),
+ };
+ resultant_apis.push(api);
+ resultant_apis.into_iter()
+ })
+ .collect()
+}
+
+fn create_casts<'a>(
+ name: &'a QualifiedName,
+ analysis: &'a PodAnalysis,
+) -> impl Iterator<Item = Api<PodPhase>> + 'a {
+ // Create casts only to base classes which are on the allowlist
+ // because otherwise we won't know for sure whether they're abstract or not.
+ analysis
+ .castable_bases
+ .iter()
+ .flat_map(move |base| cast_types().map(|mutable| create_cast(name, base, mutable)))
+}
+
+/// Iterate through the types of cast we should make.
+fn cast_types() -> impl Iterator<Item = CastMutability> {
+ if SUPPORT_MUTABLE_CASTS {
+ vec![
+ CastMutability::ConstToConst,
+ CastMutability::MutToConst,
+ CastMutability::MutToMut,
+ ]
+ .into_iter()
+ } else {
+ vec![CastMutability::ConstToConst].into_iter()
+ }
+}
+
+fn create_cast(from: &QualifiedName, to: &QualifiedName, mutable: CastMutability) -> Api<PodPhase> {
+ let name = name_for_cast(from, to, mutable);
+ let ident = name.get_final_ident();
+ let from_typ = from.to_type_path();
+ let to_typ = to.to_type_path();
+ let return_mutability = match mutable {
+ CastMutability::ConstToConst | CastMutability::MutToConst => quote! { const },
+ CastMutability::MutToMut => quote! { mut },
+ };
+ let param_mutability = match mutable {
+ CastMutability::ConstToConst => quote! { const },
+ CastMutability::MutToConst | CastMutability::MutToMut => quote! { mut },
+ };
+ let fnarg: FnArg = parse_quote! {
+ this: * #param_mutability #from_typ
+ };
+ Api::Function {
+ name: ApiName::new_from_qualified_name(name),
+ fun: Box::new(crate::conversion::api::FuncToConvert {
+ ident,
+ doc_attrs: Vec::new(),
+ inputs: [fnarg].into_iter().collect(),
+ output: parse_quote! {
+ -> * #return_mutability #to_typ
+ },
+ vis: parse_quote! { pub },
+ virtualness: crate::conversion::api::Virtualness::None,
+ cpp_vis: crate::conversion::api::CppVisibility::Public,
+ special_member: None,
+ unused_template_param: false,
+ references: References::new_with_this_and_return_as_reference(),
+ original_name: None,
+ self_ty: Some(from.clone()),
+ synthesized_this_type: None,
+ add_to_trait: Some(TraitSynthesis::Cast {
+ to_type: to.clone(),
+ mutable,
+ }),
+ synthetic_cpp: Some((CppFunctionBody::Cast, CppFunctionKind::Function)),
+ is_deleted: false,
+ provenance: Provenance::SynthesizedOther,
+ variadic: false,
+ }),
+ analysis: (),
+ }
+}
+
+fn name_for_cast(
+ from: &QualifiedName,
+ to: &QualifiedName,
+ mutable: CastMutability,
+) -> QualifiedName {
+ let suffix = match mutable {
+ CastMutability::ConstToConst => "",
+ CastMutability::MutToConst => "_to_const",
+ CastMutability::MutToMut => "_mut",
+ };
+ let name = format!(
+ "cast_{}_to_{}{}",
+ from.get_final_item(),
+ to.get_final_item(),
+ suffix
+ );
+ let name = make_ident(name);
+ QualifiedName::new(from.get_namespace(), name)
+}
diff --git a/engine/src/conversion/analysis/constructor_deps.rs b/engine/src/conversion/analysis/constructor_deps.rs
new file mode 100644
index 0000000..63b6dbe
--- /dev/null
+++ b/engine/src/conversion/analysis/constructor_deps.rs
@@ -0,0 +1,105 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::map::IndexMap as HashMap;
+
+use crate::{
+ conversion::{
+ api::{Api, ApiName, StructDetails, TypeKind},
+ apivec::ApiVec,
+ convert_error::ConvertErrorWithContext,
+ error_reporter::convert_apis,
+ },
+ types::QualifiedName,
+};
+
+use super::fun::{
+ FnAnalysis, FnKind, FnPhase, FnPrePhase2, PodAndConstructorAnalysis, PodAndDepAnalysis,
+ TraitMethodKind,
+};
+
+/// We've now analyzed all functions (including both implicit and explicit
+/// constructors). Decorate each struct with a note of its constructors,
+/// which will later be used as edges in the garbage collection, because
+/// typically any use of a type will require us to call its copy or move
+/// constructor. The same applies to its alloc/free functions.
+pub(crate) fn decorate_types_with_constructor_deps(apis: ApiVec<FnPrePhase2>) -> ApiVec<FnPhase> {
+ let mut constructors_and_allocators_by_type = find_important_constructors(&apis);
+ let mut results = ApiVec::new();
+ convert_apis(
+ apis,
+ &mut results,
+ Api::fun_unchanged,
+ |name, details, pod| {
+ decorate_struct(name, details, pod, &mut constructors_and_allocators_by_type)
+ },
+ Api::enum_unchanged,
+ Api::typedef_unchanged,
+ );
+ results
+}
+
+fn decorate_struct(
+ name: ApiName,
+ details: Box<StructDetails>,
+ fn_struct: PodAndConstructorAnalysis,
+ constructors_and_allocators_by_type: &mut HashMap<QualifiedName, Vec<QualifiedName>>,
+) -> Result<Box<dyn Iterator<Item = Api<FnPhase>>>, ConvertErrorWithContext> {
+ let pod = fn_struct.pod;
+ let is_abstract = matches!(pod.kind, TypeKind::Abstract);
+ let constructor_and_allocator_deps = if is_abstract || pod.is_generic {
+ Vec::new()
+ } else {
+ constructors_and_allocators_by_type
+ .remove(&name.name)
+ .unwrap_or_default()
+ };
+ Ok(Box::new(std::iter::once(Api::Struct {
+ name,
+ details,
+ analysis: PodAndDepAnalysis {
+ pod,
+ constructor_and_allocator_deps,
+ constructors: fn_struct.constructors,
+ },
+ })))
+}
+
+fn find_important_constructors(
+ apis: &ApiVec<FnPrePhase2>,
+) -> HashMap<QualifiedName, Vec<QualifiedName>> {
+ let mut results: HashMap<QualifiedName, Vec<QualifiedName>> = HashMap::new();
+ for api in apis.iter() {
+ if let Api::Function {
+ name,
+ analysis:
+ FnAnalysis {
+ kind:
+ FnKind::TraitMethod {
+ kind:
+ TraitMethodKind::Alloc
+ | TraitMethodKind::Dealloc
+ | TraitMethodKind::CopyConstructor
+ | TraitMethodKind::MoveConstructor,
+ impl_for,
+ ..
+ },
+ ignore_reason: Ok(_),
+ ..
+ },
+ ..
+ } = api
+ {
+ results
+ .entry(impl_for.clone())
+ .or_default()
+ .push(name.name.clone())
+ }
+ }
+ results
+}
diff --git a/engine/src/conversion/analysis/ctypes.rs b/engine/src/conversion/analysis/ctypes.rs
new file mode 100644
index 0000000..b2ce840
--- /dev/null
+++ b/engine/src/conversion/analysis/ctypes.rs
@@ -0,0 +1,36 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::map::IndexMap as HashMap;
+
+use syn::Ident;
+
+use crate::conversion::api::ApiName;
+use crate::conversion::apivec::ApiVec;
+use crate::types::Namespace;
+use crate::{conversion::api::Api, known_types::known_types, types::QualifiedName};
+
+use super::deps::HasDependencies;
+use super::fun::FnPhase;
+
+/// Spot any variable-length C types (e.g. unsigned long)
+/// used in the [Api]s and append those as extra APIs.
+pub(crate) fn append_ctype_information(apis: &mut ApiVec<FnPhase>) {
+ let ctypes: HashMap<Ident, QualifiedName> = apis
+ .iter()
+ .flat_map(|api| api.deps())
+ .filter(|ty| known_types().is_ctype(ty))
+ .map(|ty| (ty.get_final_ident(), ty.clone()))
+ .collect();
+ for (id, typename) in ctypes {
+ apis.push(Api::CType {
+ name: ApiName::new(&Namespace::new(), id),
+ typename,
+ });
+ }
+}
diff --git a/engine/src/conversion/analysis/deps.rs b/engine/src/conversion/analysis/deps.rs
new file mode 100644
index 0000000..7aca96c
--- /dev/null
+++ b/engine/src/conversion/analysis/deps.rs
@@ -0,0 +1,116 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use itertools::Itertools;
+
+use crate::{
+ conversion::api::{Api, TypeKind},
+ types::QualifiedName,
+};
+
+use super::{
+ fun::{FnPhase, FnPrePhase1, PodAndDepAnalysis},
+ pod::PodAnalysis,
+ tdef::TypedefAnalysis,
+};
+
+pub(crate) trait HasDependencies {
+ fn name(&self) -> &QualifiedName;
+ fn deps(&self) -> Box<dyn Iterator<Item = &QualifiedName> + '_>;
+
+ fn format_deps(&self) -> String {
+ self.deps().join(",")
+ }
+}
+
+impl HasDependencies for Api<FnPrePhase1> {
+ fn deps(&self) -> Box<dyn Iterator<Item = &QualifiedName> + '_> {
+ match self {
+ Api::Typedef {
+ old_tyname,
+ analysis: TypedefAnalysis { deps, .. },
+ ..
+ } => Box::new(old_tyname.iter().chain(deps.iter())),
+ Api::Struct {
+ analysis:
+ PodAnalysis {
+ kind: TypeKind::Pod,
+ bases,
+ field_deps,
+ ..
+ },
+ ..
+ } => Box::new(field_deps.iter().chain(bases.iter())),
+ Api::Function { analysis, .. } => Box::new(analysis.deps.iter()),
+ Api::Subclass {
+ name: _,
+ superclass,
+ } => Box::new(std::iter::once(superclass)),
+ Api::RustSubclassFn { details, .. } => Box::new(details.dependencies.iter()),
+ Api::RustFn { receiver, .. } => Box::new(receiver.iter()),
+ _ => Box::new(std::iter::empty()),
+ }
+ }
+
+ fn name(&self) -> &QualifiedName {
+ self.name()
+ }
+}
+
+impl HasDependencies for Api<FnPhase> {
+ /// Any dependencies on other APIs which this API has.
+ fn deps(&self) -> Box<dyn Iterator<Item = &QualifiedName> + '_> {
+ match self {
+ Api::Typedef {
+ old_tyname,
+ analysis: TypedefAnalysis { deps, .. },
+ ..
+ } => Box::new(old_tyname.iter().chain(deps.iter())),
+ Api::Struct {
+ analysis:
+ PodAndDepAnalysis {
+ pod:
+ PodAnalysis {
+ kind: TypeKind::Pod,
+ bases,
+ field_deps,
+ ..
+ },
+ constructor_and_allocator_deps,
+ ..
+ },
+ ..
+ } => Box::new(
+ field_deps
+ .iter()
+ .chain(bases.iter())
+ .chain(constructor_and_allocator_deps.iter()),
+ ),
+ Api::Struct {
+ analysis:
+ PodAndDepAnalysis {
+ constructor_and_allocator_deps,
+ ..
+ },
+ ..
+ } => Box::new(constructor_and_allocator_deps.iter()),
+ Api::Function { analysis, .. } => Box::new(analysis.deps.iter()),
+ Api::Subclass {
+ name: _,
+ superclass,
+ } => Box::new(std::iter::once(superclass)),
+ Api::RustSubclassFn { details, .. } => Box::new(details.dependencies.iter()),
+ Api::RustFn { receiver, .. } => Box::new(receiver.iter()),
+ _ => Box::new(std::iter::empty()),
+ }
+ }
+
+ fn name(&self) -> &QualifiedName {
+ self.name()
+ }
+}
diff --git a/engine/src/conversion/analysis/depth_first.rs b/engine/src/conversion/analysis/depth_first.rs
new file mode 100644
index 0000000..02459e1
--- /dev/null
+++ b/engine/src/conversion/analysis/depth_first.rs
@@ -0,0 +1,98 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::set::IndexSet as HashSet;
+use std::collections::VecDeque;
+use std::fmt::Debug;
+
+use itertools::Itertools;
+
+use crate::types::QualifiedName;
+
+use super::deps::HasDependencies;
+
+/// Return APIs in a depth-first order, i.e. those with no dependencies first.
+pub(super) fn depth_first<'a, T: HasDependencies + Debug + 'a>(
+ inputs: impl Iterator<Item = &'a T> + 'a,
+) -> impl Iterator<Item = &'a T> {
+ let queue: VecDeque<_> = inputs.collect();
+ let yet_to_do = queue.iter().map(|api| api.name()).collect();
+ DepthFirstIter { queue, yet_to_do }
+}
+
+struct DepthFirstIter<'a, T: HasDependencies + Debug> {
+ queue: VecDeque<&'a T>,
+ yet_to_do: HashSet<&'a QualifiedName>,
+}
+
+impl<'a, T: HasDependencies + Debug> Iterator for DepthFirstIter<'a, T> {
+ type Item = &'a T;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let first_candidate = self.queue.get(0).map(|api| api.name());
+ while let Some(candidate) = self.queue.pop_front() {
+ if !candidate.deps().any(|d| self.yet_to_do.contains(&d)) {
+ self.yet_to_do.remove(candidate.name());
+ return Some(candidate);
+ }
+ self.queue.push_back(candidate);
+ if self.queue.get(0).map(|api| api.name()) == first_candidate {
+ panic!(
+ "Failed to find a candidate; there must be a circular dependency. Queue is {}",
+ self.queue
+ .iter()
+ .map(|item| format!("{}: {}", item.name(), item.deps().join(",")))
+ .join("\n")
+ );
+ }
+ }
+ None
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::types::QualifiedName;
+
+ use super::{depth_first, HasDependencies};
+
+ #[derive(Debug)]
+ struct Thing(QualifiedName, Vec<QualifiedName>);
+
+ impl HasDependencies for Thing {
+ fn name(&self) -> &QualifiedName {
+ &self.0
+ }
+
+ fn deps(&self) -> Box<dyn Iterator<Item = &QualifiedName> + '_> {
+ Box::new(self.1.iter())
+ }
+ }
+
+ #[test]
+ fn test() {
+ let a = Thing(QualifiedName::new_from_cpp_name("a"), vec![]);
+ let b = Thing(
+ QualifiedName::new_from_cpp_name("b"),
+ vec![
+ QualifiedName::new_from_cpp_name("a"),
+ QualifiedName::new_from_cpp_name("c"),
+ ],
+ );
+ let c = Thing(
+ QualifiedName::new_from_cpp_name("c"),
+ vec![QualifiedName::new_from_cpp_name("a")],
+ );
+ let api_list = vec![a, b, c];
+ let mut it = depth_first(api_list.iter());
+ assert_eq!(it.next().unwrap().0, QualifiedName::new_from_cpp_name("a"));
+ assert_eq!(it.next().unwrap().0, QualifiedName::new_from_cpp_name("c"));
+ assert_eq!(it.next().unwrap().0, QualifiedName::new_from_cpp_name("b"));
+ assert!(it.next().is_none());
+ }
+}
diff --git a/engine/src/conversion/analysis/doc_label.rs b/engine/src/conversion/analysis/doc_label.rs
new file mode 100644
index 0000000..767da87
--- /dev/null
+++ b/engine/src/conversion/analysis/doc_label.rs
@@ -0,0 +1,17 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use proc_macro2::Span;
+use syn::{parse_quote, Attribute};
+
+pub(crate) fn make_doc_attrs(label: String) -> Vec<Attribute> {
+ let hexathorpe = syn::token::Pound(Span::call_site());
+ vec![parse_quote! {
+ #hexathorpe [doc = #label]
+ }]
+}
diff --git a/engine/src/conversion/analysis/fun/bridge_name_tracker.rs b/engine/src/conversion/analysis/fun/bridge_name_tracker.rs
new file mode 100644
index 0000000..7e81c59
--- /dev/null
+++ b/engine/src/conversion/analysis/fun/bridge_name_tracker.rs
@@ -0,0 +1,158 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::types::Namespace;
+use itertools::Itertools;
+use std::collections::HashMap;
+
+/// Type to generate unique names for entries in the [cxx::bridge]
+/// mod which is flat.
+///
+/// # All about the names involved in autocxx
+///
+/// A given function may have many, many names. From C++ to Rust...
+///
+/// 1. The actual C++ name. Fixed, obviously.
+/// 2. The name reported by bindgen. Not always the same, as
+/// bindgen may generate a different name for different overloads.
+/// See `overload_tracker` for the conversion here.
+/// 3. The name in the cxx::bridge mod. This is a flat namespace,
+/// and it's the responsibility of this type to generate a
+/// suitable name here.
+/// If this is different from the C++ name in (1), we'll
+/// add a #[cxx_name] attribute to the cxx::bridge declaration.
+/// 4. The name we wish to present to Rust users. Again, we have
+/// to take stock of the fact Rust doesn't support overloading
+/// so two different functions called 'get' may end up being
+/// 'get' and 'get1'. Yet, this may not be the same as the
+/// bindgen name in (2) because we wish to generate such number
+/// sequences on a per-type basis, whilst bindgen does it globally.
+///
+/// This fourth name, the final Rust user name, may be finagled
+/// into place in three different ways:
+/// 1. For methods, we generate an 'impl' block where the
+/// method name is the intended name, but it calls the
+/// cxxbridge name.
+/// 2. For simple functions, we use the #[rust_name] attribute.
+/// 3. Occasionally, there can be conflicts in the rust_name
+/// namespace (if there are two identically named functions
+/// in different C++ namespaces). That's detected by
+/// rust_name_tracker.rs. In such a case, we'll omit the
+/// #[rust_name] attribute and instead generate a 'use A = B;'
+/// declaration in the mod which we generate for the output
+/// namespace.
+#[derive(Default)]
+pub(crate) struct BridgeNameTracker {
+ next_cxx_bridge_name_for_prefix: HashMap<String, usize>,
+}
+
+impl BridgeNameTracker {
+ pub(crate) fn new() -> Self {
+ Self::default()
+ }
+
+ /// Figure out the least confusing unique name for this function in the
+ /// cxx::bridge section, which has a flat namespace.
+ /// We mostly just qualify the name with the namespace_with_underscores.
+ /// It doesn't really matter; we'll rebind these things to
+ /// better Rust-side names so it's really just a matter of how it shows up
+ /// in stack traces and for our own sanity as maintainers of autocxx.
+ /// This may become unnecessary if and when cxx supports hierarchic
+ /// namespace mods.
+ /// There is a slight advantage in using the same name as either the
+ /// Rust or C++ symbols as it reduces the amount of rebinding required
+ /// by means of cxx_name or rust_name attributes. In extreme cases it
+ /// may even allow us to remove whole impl blocks. So we may wish to try
+ /// harder to find better names in future instead of always prepending
+ /// the namespace.
+ pub(crate) fn get_unique_cxx_bridge_name(
+ &mut self,
+ type_name: Option<&str>,
+ found_name: &str,
+ ns: &Namespace,
+ ) -> String {
+ let found_name = if found_name == "new" {
+ "new_autocxx"
+ } else {
+ found_name
+ };
+ let count = self
+ .next_cxx_bridge_name_for_prefix
+ .entry(found_name.to_string())
+ .or_default();
+ if *count == 0 {
+ // Oh, good, we can use this function name as-is.
+ *count += 1;
+ return found_name.to_string();
+ }
+ let prefix = ns
+ .iter()
+ .cloned()
+ .chain(type_name.iter().map(|x| x.to_string()))
+ .chain(std::iter::once(found_name.to_string()))
+ .join("_");
+ let count = self
+ .next_cxx_bridge_name_for_prefix
+ .entry(prefix.clone())
+ .or_default();
+ if *count == 0 {
+ *count += 1;
+ prefix
+ } else {
+ let r = format!("{}_autocxx{}", prefix, count);
+ *count += 1;
+ r
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::types::Namespace;
+
+ use super::BridgeNameTracker;
+
+ #[test]
+ fn test() {
+ let mut bnt = BridgeNameTracker::new();
+ let ns_root = Namespace::new();
+ let ns_a = Namespace::from_user_input("A");
+ let ns_b = Namespace::from_user_input("B");
+ let ns_ab = Namespace::from_user_input("A::B");
+ assert_eq!(bnt.get_unique_cxx_bridge_name(None, "do", &ns_root), "do");
+ assert_eq!(
+ bnt.get_unique_cxx_bridge_name(None, "do", &ns_root),
+ "do_autocxx1"
+ );
+ assert_eq!(bnt.get_unique_cxx_bridge_name(None, "did", &ns_root), "did");
+ assert_eq!(
+ bnt.get_unique_cxx_bridge_name(Some("ty1"), "do", &ns_root),
+ "ty1_do"
+ );
+ assert_eq!(
+ bnt.get_unique_cxx_bridge_name(Some("ty1"), "do", &ns_root),
+ "ty1_do_autocxx1"
+ );
+ assert_eq!(
+ bnt.get_unique_cxx_bridge_name(Some("ty2"), "do", &ns_root),
+ "ty2_do"
+ );
+ assert_eq!(
+ bnt.get_unique_cxx_bridge_name(Some("ty"), "do", &ns_a),
+ "A_ty_do"
+ );
+ assert_eq!(
+ bnt.get_unique_cxx_bridge_name(Some("ty"), "do", &ns_b),
+ "B_ty_do"
+ );
+ assert_eq!(
+ bnt.get_unique_cxx_bridge_name(Some("ty"), "do", &ns_ab),
+ "A_B_ty_do"
+ );
+ }
+}
diff --git a/engine/src/conversion/analysis/fun/function_wrapper.rs b/engine/src/conversion/analysis/fun/function_wrapper.rs
new file mode 100644
index 0000000..f2f6aaa
--- /dev/null
+++ b/engine/src/conversion/analysis/fun/function_wrapper.rs
@@ -0,0 +1,213 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::{
+ conversion::api::SubclassName,
+ types::{Namespace, QualifiedName},
+};
+use syn::{parse_quote, Ident, Type};
+
+#[derive(Clone, Debug)]
+pub(crate) enum CppConversionType {
+ None,
+ Move,
+ FromUniquePtrToValue,
+ FromPtrToValue,
+ FromValueToUniquePtr,
+ FromPtrToMove,
+ /// Ignored in the sense that it isn't passed into the C++ function.
+ IgnoredPlacementPtrParameter,
+ FromReturnValueToPlacementPtr,
+}
+
+impl CppConversionType {
+ /// If we've found a function which does X to its parameter, what
+ /// is the opposite of X? This is used for subclasses where calls
+ /// from Rust to C++ might also involve calls from C++ to Rust.
+ fn inverse(&self) -> Self {
+ match self {
+ CppConversionType::None => CppConversionType::None,
+ CppConversionType::FromUniquePtrToValue | CppConversionType::FromPtrToValue => {
+ CppConversionType::FromValueToUniquePtr
+ }
+ CppConversionType::FromValueToUniquePtr => CppConversionType::FromUniquePtrToValue,
+ _ => panic!("Did not expect to have to invert this conversion"),
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum RustConversionType {
+ None,
+ FromStr,
+ ToBoxedUpHolder(SubclassName),
+ FromPinMaybeUninitToPtr,
+ FromPinMoveRefToPtr,
+ FromTypeToPtr,
+ FromValueParamToPtr,
+ FromPlacementParamToNewReturn,
+ FromRValueParamToPtr,
+}
+
+impl RustConversionType {
+ pub(crate) fn requires_mutability(&self) -> Option<syn::token::Mut> {
+ match self {
+ Self::FromPinMoveRefToPtr => Some(parse_quote! { mut }),
+ _ => None,
+ }
+ }
+}
+
+/// A policy for converting types. Conversion may occur on both the Rust and
+/// C++ side. The most complex example is a C++ function which takes
+/// std::string by value, which might do this:
+/// * Client Rust code: `&str`
+/// * Rust wrapper function: converts `&str` to `UniquePtr<CxxString>`
+/// * cxx::bridge mod: refers to `UniquePtr<CxxString>`
+/// * C++ wrapper function converts `std::unique_ptr<std::string>` to just
+/// `std::string`
+/// * Finally, the actual C++ API receives a `std::string` by value.
+/// The implementation here is distributed across this file, and
+/// `function_wrapper_rs` and `function_wrapper_cpp`.
+#[derive(Clone)]
+pub(crate) struct TypeConversionPolicy {
+ pub(crate) unwrapped_type: Type,
+ pub(crate) cpp_conversion: CppConversionType,
+ pub(crate) rust_conversion: RustConversionType,
+}
+
+impl TypeConversionPolicy {
+ pub(crate) fn new_unconverted(ty: Type) -> Self {
+ TypeConversionPolicy {
+ unwrapped_type: ty,
+ cpp_conversion: CppConversionType::None,
+ rust_conversion: RustConversionType::None,
+ }
+ }
+
+ pub(crate) fn new_to_unique_ptr(ty: Type) -> Self {
+ TypeConversionPolicy {
+ unwrapped_type: ty,
+ cpp_conversion: CppConversionType::FromValueToUniquePtr,
+ rust_conversion: RustConversionType::None,
+ }
+ }
+
+ pub(crate) fn new_for_placement_return(ty: Type) -> Self {
+ TypeConversionPolicy {
+ unwrapped_type: ty,
+ cpp_conversion: CppConversionType::FromReturnValueToPlacementPtr,
+ // Rust conversion is marked as none here, since this policy
+ // will be applied to the return value, and the Rust-side
+ // shenanigans applies to the placement new *parameter*
+ rust_conversion: RustConversionType::None,
+ }
+ }
+
+ pub(crate) fn cpp_work_needed(&self) -> bool {
+ !matches!(self.cpp_conversion, CppConversionType::None)
+ }
+
+ pub(crate) fn unconverted_rust_type(&self) -> Type {
+ match self.cpp_conversion {
+ CppConversionType::FromValueToUniquePtr => self.make_unique_ptr_type(),
+ _ => self.unwrapped_type.clone(),
+ }
+ }
+
+ pub(crate) fn converted_rust_type(&self) -> Type {
+ match self.cpp_conversion {
+ CppConversionType::FromUniquePtrToValue => self.make_unique_ptr_type(),
+ CppConversionType::FromPtrToValue => {
+ let innerty = &self.unwrapped_type;
+ parse_quote! {
+ *mut #innerty
+ }
+ }
+ _ => self.unwrapped_type.clone(),
+ }
+ }
+
+ fn make_unique_ptr_type(&self) -> Type {
+ let innerty = &self.unwrapped_type;
+ parse_quote! {
+ cxx::UniquePtr < #innerty >
+ }
+ }
+
+ pub(crate) fn rust_work_needed(&self) -> bool {
+ !matches!(self.rust_conversion, RustConversionType::None)
+ }
+
+ /// Subclass support involves calls from Rust -> C++, but
+ /// also from C++ -> Rust. Work out the correct argument conversion
+ /// type for the latter call, when given the former.
+ pub(crate) fn inverse(&self) -> Self {
+ Self {
+ unwrapped_type: self.unwrapped_type.clone(),
+ cpp_conversion: self.cpp_conversion.inverse(),
+ rust_conversion: self.rust_conversion.clone(),
+ }
+ }
+
+ pub(crate) fn bridge_unsafe_needed(&self) -> bool {
+ matches!(
+ self.rust_conversion,
+ RustConversionType::FromValueParamToPtr
+ | RustConversionType::FromRValueParamToPtr
+ | RustConversionType::FromPlacementParamToNewReturn
+ )
+ }
+
+ pub(crate) fn is_placement_parameter(&self) -> bool {
+ matches!(
+ self.cpp_conversion,
+ CppConversionType::IgnoredPlacementPtrParameter
+ )
+ }
+
+ pub(crate) fn populate_return_value(&self) -> bool {
+ !matches!(
+ self.cpp_conversion,
+ CppConversionType::FromReturnValueToPlacementPtr
+ )
+ }
+}
+
+#[derive(Clone)]
+pub(crate) enum CppFunctionBody {
+ FunctionCall(Namespace, Ident),
+ StaticMethodCall(Namespace, Ident, Ident),
+ PlacementNew(Namespace, Ident),
+ ConstructSuperclass(String),
+ Cast,
+ Destructor(Namespace, Ident),
+ AllocUninitialized(QualifiedName),
+ FreeUninitialized(QualifiedName),
+}
+
+#[derive(Clone)]
+pub(crate) enum CppFunctionKind {
+ Function,
+ Method,
+ Constructor,
+ ConstMethod,
+ SynthesizedConstructor,
+}
+
+#[derive(Clone)]
+pub(crate) struct CppFunction {
+ pub(crate) payload: CppFunctionBody,
+ pub(crate) wrapper_function_name: Ident,
+ pub(crate) original_cpp_name: String,
+ pub(crate) return_conversion: Option<TypeConversionPolicy>,
+ pub(crate) argument_conversion: Vec<TypeConversionPolicy>,
+ pub(crate) kind: CppFunctionKind,
+ pub(crate) pass_obs_field: bool,
+ pub(crate) qualification: Option<QualifiedName>,
+}
diff --git a/engine/src/conversion/analysis/fun/implicit_constructors.rs b/engine/src/conversion/analysis/fun/implicit_constructors.rs
new file mode 100644
index 0000000..469ed89
--- /dev/null
+++ b/engine/src/conversion/analysis/fun/implicit_constructors.rs
@@ -0,0 +1,695 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::map::IndexMap as HashMap;
+use indexmap::{map::Entry, set::IndexSet as HashSet};
+
+use syn::{Type, TypeArray};
+
+use crate::{
+ conversion::{
+ analysis::{depth_first::depth_first, pod::PodAnalysis, type_converter::TypeKind},
+ api::{Api, ApiName, CppVisibility, FuncToConvert, SpecialMemberKind},
+ apivec::ApiVec,
+ convert_error::ConvertErrorWithContext,
+ ConvertError,
+ },
+ known_types::{known_types, KnownTypeConstructorDetails},
+ types::QualifiedName,
+};
+
+use super::{FnAnalysis, FnKind, FnPrePhase1, MethodKind, ReceiverMutability, TraitMethodKind};
+
+/// Indicates what we found out about a category of special member function.
+///
+/// In the end, we only care whether it's public and exists, but we track a bit more information to
+/// support determining the information for dependent classes.
+#[derive(Debug, Copy, Clone)]
+pub(super) enum SpecialMemberFound {
+ /// This covers being deleted in any way:
+ /// * Explicitly deleted
+ /// * Implicitly defaulted when that means being deleted
+ /// * Explicitly defaulted when that means being deleted
+ ///
+ /// It also covers not being either user declared or implicitly defaulted.
+ NotPresent,
+ /// Implicit special member functions, indicated by this, are always public.
+ Implicit,
+ /// This covers being explicitly defaulted (when that is not deleted) or being user-defined.
+ Explicit(CppVisibility),
+}
+
+impl SpecialMemberFound {
+ /// Returns whether code outside of subclasses can call this special member function.
+ pub fn callable_any(&self) -> bool {
+ matches!(self, Self::Explicit(CppVisibility::Public) | Self::Implicit)
+ }
+
+ /// Returns whether code in a subclass can call this special member function.
+ pub fn callable_subclass(&self) -> bool {
+ matches!(
+ self,
+ Self::Explicit(CppVisibility::Public)
+ | Self::Explicit(CppVisibility::Protected)
+ | Self::Implicit
+ )
+ }
+
+ /// Returns whether this exists at all. Note that this will return true even if it's private,
+ /// which is generally not very useful, but does come into play for some rules around which
+ /// default special member functions are deleted vs don't exist.
+ pub fn exists(&self) -> bool {
+ matches!(self, Self::Explicit(_) | Self::Implicit)
+ }
+
+ pub fn exists_implicit(&self) -> bool {
+ matches!(self, Self::Implicit)
+ }
+
+ pub fn exists_explicit(&self) -> bool {
+ matches!(self, Self::Explicit(_))
+ }
+}
+
+/// Information about which special member functions exist based on the C++ rules.
+///
+/// Not all of this information is used directly, but we need to track it to determine the
+/// information we do need for classes which are used as members or base classes.
+#[derive(Debug, Clone)]
+pub(super) struct ItemsFound {
+ pub(super) default_constructor: SpecialMemberFound,
+ pub(super) destructor: SpecialMemberFound,
+ pub(super) const_copy_constructor: SpecialMemberFound,
+ /// Remember that [`const_copy_constructor`] may be used in place of this if it exists.
+ pub(super) non_const_copy_constructor: SpecialMemberFound,
+ pub(super) move_constructor: SpecialMemberFound,
+
+ /// The full name of the type. We identify instances by [`QualifiedName`], because that's
+ /// the only thing which [`FnKind::Method`] has to tie it to, and that's unique enough for
+ /// identification. However, when generating functions for implicit special members, we need
+ /// the extra information here.
+ ///
+ /// Will always be `Some` if any of the other fields are [`SpecialMemberFound::Implict`],
+ /// otherwise optional.
+ pub(super) name: Option<ApiName>,
+}
+
+impl ItemsFound {
+ /// Returns whether we should generate a default constructor wrapper, because bindgen won't do
+ /// one for the implicit default constructor which exists.
+ pub(super) fn implicit_default_constructor_needed(&self) -> bool {
+ self.default_constructor.exists_implicit()
+ }
+
+ /// Returns whether we should generate a copy constructor wrapper, because bindgen won't do one
+ /// for the implicit copy constructor which exists.
+ pub(super) fn implicit_copy_constructor_needed(&self) -> bool {
+ let any_implicit_copy = self.const_copy_constructor.exists_implicit()
+ || self.non_const_copy_constructor.exists_implicit();
+ let no_explicit_copy = !(self.const_copy_constructor.exists_explicit()
+ || self.non_const_copy_constructor.exists_explicit());
+ any_implicit_copy && no_explicit_copy
+ }
+
+ /// Returns whether we should generate a move constructor wrapper, because bindgen won't do one
+ /// for the implicit move constructor which exists.
+ pub(super) fn implicit_move_constructor_needed(&self) -> bool {
+ self.move_constructor.exists_implicit()
+ }
+
+ /// Returns whether we should generate a destructor wrapper, because bindgen won't do one for
+ /// the implicit destructor which exists.
+ pub(super) fn implicit_destructor_needed(&self) -> bool {
+ self.destructor.exists_implicit()
+ }
+}
+#[derive(Hash, Eq, PartialEq)]
+enum ExplicitKind {
+ DefaultConstructor,
+ ConstCopyConstructor,
+ NonConstCopyConstructor,
+ MoveConstructor,
+ OtherConstructor,
+ Destructor,
+ ConstCopyAssignmentOperator,
+ NonConstCopyAssignmentOperator,
+ MoveAssignmentOperator,
+}
+
+/// Denotes a specific kind of explicit member function that we found.
+#[derive(Hash, Eq, PartialEq)]
+struct ExplicitType {
+ ty: QualifiedName,
+ kind: ExplicitKind,
+}
+
+/// Includes information about an explicit special member function which was found.
+// TODO: Add Defaulted(CppVisibility) for https://github.com/google/autocxx/issues/815.
+#[derive(Copy, Clone, Debug)]
+enum ExplicitFound {
+ UserDefined(CppVisibility),
+ /// Note that this always means explicitly deleted, because this enum only represents
+ /// explicit declarations.
+ Deleted,
+ /// Indicates that we found more than one explicit of this kind. This is possible with most of
+ /// them, and we just bail and mostly act as if they're deleted. We'd have to decide whether
+ /// they're ambiguous to use them, which is really complicated.
+ Multiple,
+}
+
+/// Analyzes which constructors are present for each type.
+///
+/// If a type has explicit constructors, bindgen will generate corresponding
+/// constructor functions, which we'll have already converted to make_unique methods.
+/// For types with implicit constructors, we enumerate them here.
+///
+/// It is tempting to make this a separate analysis phase, to be run later than
+/// the function analysis; but that would make the code much more complex as it
+/// would need to output a `FnAnalysisBody`. By running it as part of this phase
+/// we can simply generate the sort of thing bindgen generates, then ask
+/// the existing code in this phase to figure out what to do with it.
+pub(super) fn find_constructors_present(
+ apis: &ApiVec<FnPrePhase1>,
+) -> HashMap<QualifiedName, ItemsFound> {
+ let (explicits, unknown_types) = find_explicit_items(apis);
+
+ // These contain all the classes we've seen so far with the relevant properties on their
+ // constructors of each kind. We iterate via [`depth_first`], so analyzing later classes
+ // just needs to check these.
+ //
+ // Important only to ask for a depth-first analysis of structs, because
+ // when all APIs are considered there may be reference loops and that would
+ // panic.
+ //
+ // These analyses include all bases and members of each class.
+ let mut all_items_found: HashMap<QualifiedName, ItemsFound> = HashMap::new();
+
+ for api in depth_first(apis.iter()) {
+ if let Api::Struct {
+ name,
+ analysis:
+ PodAnalysis {
+ bases,
+ field_info,
+ is_generic: false,
+ in_anonymous_namespace: false,
+ ..
+ },
+ details,
+ ..
+ } = api
+ {
+ let find_explicit = |kind: ExplicitKind| -> Option<&ExplicitFound> {
+ explicits.get(&ExplicitType {
+ ty: name.name.clone(),
+ kind,
+ })
+ };
+ let get_items_found = |qn: &QualifiedName| -> Option<ItemsFound> {
+ if let Some(constructor_details) = known_types().get_constructor_details(qn) {
+ Some(known_type_items_found(constructor_details))
+ } else {
+ all_items_found.get(qn).cloned()
+ }
+ };
+ let bases_items_found: Vec<_> = bases.iter().map_while(get_items_found).collect();
+ let fields_items_found: Vec<_> = field_info
+ .iter()
+ .filter_map(|field_info| match field_info.type_kind {
+ TypeKind::Regular | TypeKind::SubclassHolder(_) => match field_info.ty {
+ Type::Path(ref qn) => get_items_found(&QualifiedName::from_type_path(qn)),
+ Type::Array(TypeArray { ref elem, .. }) => match elem.as_ref() {
+ Type::Path(ref qn) => {
+ get_items_found(&QualifiedName::from_type_path(qn))
+ }
+ _ => None,
+ },
+ _ => None,
+ },
+ // TODO: https://github.com/google/autocxx/issues/865 Figure out how to
+ // differentiate between pointers and references coming from C++. Pointers
+ // have a default constructor.
+ TypeKind::Pointer
+ | TypeKind::Reference
+ | TypeKind::MutableReference
+ | TypeKind::RValueReference => Some(ItemsFound {
+ default_constructor: SpecialMemberFound::NotPresent,
+ destructor: SpecialMemberFound::Implicit,
+ const_copy_constructor: SpecialMemberFound::Implicit,
+ non_const_copy_constructor: SpecialMemberFound::NotPresent,
+ move_constructor: SpecialMemberFound::Implicit,
+ name: Some(name.clone()),
+ }),
+ })
+ .collect();
+ let has_rvalue_reference_fields = details.has_rvalue_reference_fields;
+
+ // Check that all the bases and field types are known first. This combined with
+ // iterating via [`depth_first`] means we can safely search in `items_found` for all of
+ // them.
+ //
+ // Conservatively, we will not acknowledge the existence of most defaulted or implicit
+ // special member functions for any struct/class where we don't fully understand all
+ // field types. However, we can still look for explictly declared versions and use
+ // those. See below for destructors.
+ //
+ // We need to extend our knowledge to understand the constructor behavior of things in
+ // known_types.rs, then we'll be able to cope with types which contain strings,
+ // unique_ptrs etc.
+ let items_found = if bases_items_found.len() != bases.len()
+ || fields_items_found.len() != field_info.len()
+ || unknown_types.contains(&name.name)
+ {
+ let is_explicit = |kind: ExplicitKind| -> SpecialMemberFound {
+ // TODO: For https://github.com/google/autocxx/issues/815, map
+ // ExplicitFound::Defaulted(_) to NotPresent.
+ match find_explicit(kind) {
+ None => SpecialMemberFound::NotPresent,
+ Some(ExplicitFound::Deleted | ExplicitFound::Multiple) => {
+ SpecialMemberFound::NotPresent
+ }
+ Some(ExplicitFound::UserDefined(visibility)) => {
+ SpecialMemberFound::Explicit(*visibility)
+ }
+ }
+ };
+ let items_found = ItemsFound {
+ default_constructor: is_explicit(ExplicitKind::DefaultConstructor),
+ destructor: match find_explicit(ExplicitKind::Destructor) {
+ // Assume that unknown types have destructors. This is common, and allows
+ // use to generate UniquePtr wrappers with them.
+ //
+ // However, this will generate C++ code that doesn't compile if the unknown
+ // type does not have an accessible destructor. Maybe we should have a way
+ // to disable that?
+ //
+ // TODO: For https://github.com/google/autocxx/issues/815, map
+ // ExplicitFound::Defaulted(_) to Explicit.
+ None => SpecialMemberFound::Implicit,
+ // If there are multiple destructors, assume that one of them will be
+ // selected by overload resolution.
+ Some(ExplicitFound::Multiple) => {
+ SpecialMemberFound::Explicit(CppVisibility::Public)
+ }
+ Some(ExplicitFound::Deleted) => SpecialMemberFound::NotPresent,
+ Some(ExplicitFound::UserDefined(visibility)) => {
+ SpecialMemberFound::Explicit(*visibility)
+ }
+ },
+ const_copy_constructor: is_explicit(ExplicitKind::ConstCopyConstructor),
+ non_const_copy_constructor: is_explicit(ExplicitKind::NonConstCopyConstructor),
+ move_constructor: is_explicit(ExplicitKind::MoveConstructor),
+ name: Some(name.clone()),
+ };
+ log::info!(
+ "Special member functions (explicits only) found for {:?}: {:?}",
+ name,
+ items_found
+ );
+ items_found
+ } else {
+ // If no user-declared constructors of any kind are provided for a class type (struct, class, or union),
+ // the compiler will always declare a default constructor as an inline public member of its class.
+ //
+ // The implicitly-declared or defaulted default constructor for class T is defined as deleted if any of the following is true:
+ // T has a member of reference type without a default initializer.
+ // T has a non-const-default-constructible const member without a default member initializer.
+ // T has a member (without a default member initializer) which has a deleted default constructor, or its default constructor is ambiguous or inaccessible from this constructor.
+ // T has a direct or virtual base which has a deleted default constructor, or it is ambiguous or inaccessible from this constructor.
+ // T has a direct or virtual base or a non-static data member which has a deleted destructor, or a destructor that is inaccessible from this constructor.
+ // T is a union with at least one variant member with non-trivial default constructor, and no variant member of T has a default member initializer. // we don't support unions anyway
+ // T is a non-union class with a variant member M with a non-trivial default constructor, and no variant member of the anonymous union containing M has a default member initializer.
+ // T is a union and all of its variant members are const. // we don't support unions anyway
+ //
+ // Variant members are the members of anonymous unions.
+ let default_constructor = {
+ let explicit = find_explicit(ExplicitKind::DefaultConstructor);
+ // TODO: For https://github.com/google/autocxx/issues/815, replace the first term with:
+ // explicit.map_or(true, |explicit_found| matches!(explicit_found, ExplicitFound::Defaulted(_)))
+ let have_defaulted = explicit.is_none()
+ && !explicits.iter().any(|(ExplicitType { ty, kind }, _)| {
+ ty == &name.name
+ && match *kind {
+ ExplicitKind::DefaultConstructor => false,
+ ExplicitKind::ConstCopyConstructor => true,
+ ExplicitKind::NonConstCopyConstructor => true,
+ ExplicitKind::MoveConstructor => true,
+ ExplicitKind::OtherConstructor => true,
+ ExplicitKind::Destructor => false,
+ ExplicitKind::ConstCopyAssignmentOperator => false,
+ ExplicitKind::NonConstCopyAssignmentOperator => false,
+ ExplicitKind::MoveAssignmentOperator => false,
+ }
+ });
+ if have_defaulted {
+ let bases_allow = bases_items_found.iter().all(|items_found| {
+ items_found.destructor.callable_subclass()
+ && items_found.default_constructor.callable_subclass()
+ });
+ // TODO: Allow member initializers for
+ // https://github.com/google/autocxx/issues/816.
+ let members_allow = fields_items_found.iter().all(|items_found| {
+ items_found.destructor.callable_any()
+ && items_found.default_constructor.callable_any()
+ });
+ if !has_rvalue_reference_fields && bases_allow && members_allow {
+ // TODO: For https://github.com/google/autocxx/issues/815, grab the
+ // visibility from an explicit default if present.
+ SpecialMemberFound::Implicit
+ } else {
+ SpecialMemberFound::NotPresent
+ }
+ } else if let Some(ExplicitFound::UserDefined(visibility)) = explicit {
+ SpecialMemberFound::Explicit(*visibility)
+ } else {
+ SpecialMemberFound::NotPresent
+ }
+ };
+
+ // If no user-declared prospective destructor is provided for a class type (struct, class, or union), the compiler will always declare a destructor as an inline public member of its class.
+ //
+ // The implicitly-declared or explicitly defaulted destructor for class T is defined as deleted if any of the following is true:
+ // T has a non-static data member that cannot be destructed (has deleted or inaccessible destructor)
+ // T has direct or virtual base class that cannot be destructed (has deleted or inaccessible destructors)
+ // T is a union and has a variant member with non-trivial destructor. // we don't support unions anyway
+ // The implicitly-declared destructor is virtual (because the base class has a virtual destructor) and the lookup for the deallocation function (operator delete()) results in a call to ambiguous, deleted, or inaccessible function.
+ let destructor = {
+ let explicit = find_explicit(ExplicitKind::Destructor);
+ // TODO: For https://github.com/google/autocxx/issues/815, replace the condition with:
+ // explicit.map_or(true, |explicit_found| matches!(explicit_found, ExplicitFound::Defaulted(_)))
+ if explicit.is_none() {
+ let bases_allow = bases_items_found
+ .iter()
+ .all(|items_found| items_found.destructor.callable_subclass());
+ let members_allow = fields_items_found
+ .iter()
+ .all(|items_found| items_found.destructor.callable_any());
+ if bases_allow && members_allow {
+ // TODO: For https://github.com/google/autocxx/issues/815, grab the
+ // visibility from an explicit default if present.
+ SpecialMemberFound::Implicit
+ } else {
+ SpecialMemberFound::NotPresent
+ }
+ } else if let Some(ExplicitFound::UserDefined(visibility)) = explicit {
+ SpecialMemberFound::Explicit(*visibility)
+ } else {
+ SpecialMemberFound::NotPresent
+ }
+ };
+
+ // If no user-defined copy constructors are provided for a class type (struct, class, or union),
+ // the compiler will always declare a copy constructor as a non-explicit inline public member of its class.
+ // This implicitly-declared copy constructor has the form T::T(const T&) if all of the following are true:
+ // each direct and virtual base B of T has a copy constructor whose parameters are const B& or const volatile B&;
+ // each non-static data member M of T of class type or array of class type has a copy constructor whose parameters are const M& or const volatile M&.
+ //
+ // The implicitly-declared or defaulted copy constructor for class T is defined as deleted if any of the following conditions are true:
+ // T is a union-like class and has a variant member with non-trivial copy constructor; // we don't support unions anyway
+ // T has a user-defined move constructor or move assignment operator (this condition only causes the implicitly-declared, not the defaulted, copy constructor to be deleted).
+ // T has non-static data members that cannot be copied (have deleted, inaccessible, or ambiguous copy constructors);
+ // T has direct or virtual base class that cannot be copied (has deleted, inaccessible, or ambiguous copy constructors);
+ // T has direct or virtual base class or a non-static data member with a deleted or inaccessible destructor;
+ // T has a data member of rvalue reference type;
+ let (const_copy_constructor, non_const_copy_constructor) = {
+ let explicit_const = find_explicit(ExplicitKind::ConstCopyConstructor);
+ let explicit_non_const = find_explicit(ExplicitKind::NonConstCopyConstructor);
+ let explicit_move = find_explicit(ExplicitKind::MoveConstructor);
+
+ // TODO: For https://github.com/google/autocxx/issues/815, replace both terms with something like:
+ // explicit.map_or(true, |explicit_found| matches!(explicit_found, ExplicitFound::Defaulted(_)))
+ let have_defaulted = explicit_const.is_none() && explicit_non_const.is_none();
+ if have_defaulted {
+ // TODO: For https://github.com/google/autocxx/issues/815, ignore this if
+ // the relevant (based on bases_are_const) copy constructor is explicitly defaulted.
+ let class_allows = explicit_move.is_none() && !has_rvalue_reference_fields;
+ let bases_allow = bases_items_found.iter().all(|items_found| {
+ items_found.destructor.callable_subclass()
+ && (items_found.const_copy_constructor.callable_subclass()
+ || items_found.non_const_copy_constructor.callable_subclass())
+ });
+ let members_allow = fields_items_found.iter().all(|items_found| {
+ items_found.destructor.callable_any()
+ && (items_found.const_copy_constructor.callable_any()
+ || items_found.non_const_copy_constructor.callable_any())
+ });
+ if class_allows && bases_allow && members_allow {
+ // TODO: For https://github.com/google/autocxx/issues/815, grab the
+ // visibility and existence of const and non-const from an explicit default if present.
+ let dependencies_are_const = bases_items_found
+ .iter()
+ .chain(fields_items_found.iter())
+ .all(|items_found| items_found.const_copy_constructor.exists());
+ if dependencies_are_const {
+ (SpecialMemberFound::Implicit, SpecialMemberFound::NotPresent)
+ } else {
+ (SpecialMemberFound::NotPresent, SpecialMemberFound::Implicit)
+ }
+ } else {
+ (
+ SpecialMemberFound::NotPresent,
+ SpecialMemberFound::NotPresent,
+ )
+ }
+ } else {
+ (
+ if let Some(ExplicitFound::UserDefined(visibility)) = explicit_const {
+ SpecialMemberFound::Explicit(*visibility)
+ } else {
+ SpecialMemberFound::NotPresent
+ },
+ if let Some(ExplicitFound::UserDefined(visibility)) = explicit_non_const
+ {
+ SpecialMemberFound::Explicit(*visibility)
+ } else {
+ SpecialMemberFound::NotPresent
+ },
+ )
+ }
+ };
+
+ // If no user-defined move constructors are provided for a class type (struct, class, or union), and all of the following is true:
+ // there are no user-declared copy constructors;
+ // there are no user-declared copy assignment operators;
+ // there are no user-declared move assignment operators;
+ // there is no user-declared destructor.
+ // then the compiler will declare a move constructor as a non-explicit inline public member of its class with the signature T::T(T&&).
+ //
+ // A class can have multiple move constructors, e.g. both T::T(const T&&) and T::T(T&&). If some user-defined move constructors are present, the user may still force the generation of the implicitly declared move constructor with the keyword default.
+ //
+ // The implicitly-declared or defaulted move constructor for class T is defined as deleted if any of the following is true:
+ // T has non-static data members that cannot be moved (have deleted, inaccessible, or ambiguous move constructors);
+ // T has direct or virtual base class that cannot be moved (has deleted, inaccessible, or ambiguous move constructors);
+ // T has direct or virtual base class with a deleted or inaccessible destructor;
+ // T is a union-like class and has a variant member with non-trivial move constructor. // we don't support unions anyway
+ let move_constructor = {
+ let explicit = find_explicit(ExplicitKind::MoveConstructor);
+ // TODO: For https://github.com/google/autocxx/issues/815, replace relevant terms with something like:
+ // explicit.map_or(true, |explicit_found| matches!(explicit_found, ExplicitFound::Defaulted(_)))
+ let have_defaulted = !(explicit.is_some()
+ || find_explicit(ExplicitKind::ConstCopyConstructor).is_some()
+ || find_explicit(ExplicitKind::NonConstCopyConstructor).is_some()
+ || find_explicit(ExplicitKind::ConstCopyAssignmentOperator).is_some()
+ || find_explicit(ExplicitKind::NonConstCopyAssignmentOperator).is_some()
+ || find_explicit(ExplicitKind::MoveAssignmentOperator).is_some()
+ || find_explicit(ExplicitKind::Destructor).is_some());
+ if have_defaulted {
+ let bases_allow = bases_items_found.iter().all(|items_found| {
+ items_found.destructor.callable_subclass()
+ && items_found.move_constructor.callable_subclass()
+ });
+ let members_allow = fields_items_found
+ .iter()
+ .all(|items_found| items_found.move_constructor.callable_any());
+ if bases_allow && members_allow {
+ // TODO: For https://github.com/google/autocxx/issues/815, grab the
+ // visibility from an explicit default if present.
+ SpecialMemberFound::Implicit
+ } else {
+ SpecialMemberFound::NotPresent
+ }
+ } else if let Some(ExplicitFound::UserDefined(visibility)) = explicit {
+ SpecialMemberFound::Explicit(*visibility)
+ } else {
+ SpecialMemberFound::NotPresent
+ }
+ };
+
+ let items_found = ItemsFound {
+ default_constructor,
+ destructor,
+ const_copy_constructor,
+ non_const_copy_constructor,
+ move_constructor,
+ name: Some(name.clone()),
+ };
+ log::info!(
+ "Special member items found for {:?}: {:?}",
+ name,
+ items_found
+ );
+ items_found
+ };
+ assert!(
+ all_items_found
+ .insert(name.name.clone(), items_found)
+ .is_none(),
+ "Duplicate struct: {:?}",
+ name
+ );
+ }
+ }
+
+ all_items_found
+}
+
+fn find_explicit_items(
+ apis: &ApiVec<FnPrePhase1>,
+) -> (HashMap<ExplicitType, ExplicitFound>, HashSet<QualifiedName>) {
+ let mut result = HashMap::new();
+ let mut merge_fun = |ty: QualifiedName, kind: ExplicitKind, fun: &FuncToConvert| match result
+ .entry(ExplicitType { ty, kind })
+ {
+ Entry::Vacant(entry) => {
+ entry.insert(if fun.is_deleted {
+ ExplicitFound::Deleted
+ } else {
+ ExplicitFound::UserDefined(fun.cpp_vis)
+ });
+ }
+ Entry::Occupied(mut entry) => {
+ entry.insert(ExplicitFound::Multiple);
+ }
+ };
+ let mut unknown_types = HashSet::new();
+ for api in apis.iter() {
+ match api {
+ Api::Function {
+ analysis:
+ FnAnalysis {
+ kind: FnKind::Method { impl_for, .. },
+ param_details,
+ ignore_reason:
+ Ok(()) | Err(ConvertErrorWithContext(ConvertError::AssignmentOperator, _)),
+ ..
+ },
+ fun,
+ ..
+ } if matches!(
+ fun.special_member,
+ Some(SpecialMemberKind::AssignmentOperator)
+ ) =>
+ {
+ let is_move_assignment_operator = !fun.references.rvalue_ref_params.is_empty();
+ merge_fun(
+ impl_for.clone(),
+ if is_move_assignment_operator {
+ ExplicitKind::MoveAssignmentOperator
+ } else {
+ let receiver_mutability = ¶m_details
+ .iter()
+ .next()
+ .unwrap()
+ .self_type
+ .as_ref()
+ .unwrap()
+ .1;
+ match receiver_mutability {
+ ReceiverMutability::Const => ExplicitKind::ConstCopyAssignmentOperator,
+ ReceiverMutability::Mutable => {
+ ExplicitKind::NonConstCopyAssignmentOperator
+ }
+ }
+ },
+ fun,
+ )
+ }
+ Api::Function {
+ analysis:
+ FnAnalysis {
+ kind: FnKind::Method { impl_for, .. },
+ ..
+ },
+ fun,
+ ..
+ } if matches!(
+ fun.special_member,
+ Some(SpecialMemberKind::AssignmentOperator)
+ ) =>
+ {
+ unknown_types.insert(impl_for.clone());
+ }
+ Api::Function {
+ analysis:
+ FnAnalysis {
+ kind:
+ FnKind::Method {
+ impl_for,
+ method_kind,
+ ..
+ },
+ ..
+ },
+ fun,
+ ..
+ } => match method_kind {
+ MethodKind::Constructor { is_default: true } => {
+ Some(ExplicitKind::DefaultConstructor)
+ }
+ MethodKind::Constructor { is_default: false } => {
+ Some(ExplicitKind::OtherConstructor)
+ }
+ _ => None,
+ }
+ .map_or((), |explicit_kind| {
+ merge_fun(impl_for.clone(), explicit_kind, fun)
+ }),
+ Api::Function {
+ analysis:
+ FnAnalysis {
+ kind: FnKind::TraitMethod { impl_for, kind, .. },
+ ..
+ },
+ fun,
+ ..
+ } => match kind {
+ TraitMethodKind::Destructor => Some(ExplicitKind::Destructor),
+ // In `analyze_foreign_fn` we mark non-const copy constructors as not being copy
+ // constructors for now, so we don't have to worry about them.
+ TraitMethodKind::CopyConstructor => Some(ExplicitKind::ConstCopyConstructor),
+ TraitMethodKind::MoveConstructor => Some(ExplicitKind::MoveConstructor),
+ _ => None,
+ }
+ .map_or((), |explicit_kind| {
+ merge_fun(impl_for.clone(), explicit_kind, fun)
+ }),
+ _ => (),
+ }
+ }
+ (result, unknown_types)
+}
+
+/// Returns the information for a given known type.
+fn known_type_items_found(constructor_details: KnownTypeConstructorDetails) -> ItemsFound {
+ let exists_public = SpecialMemberFound::Explicit(CppVisibility::Public);
+ let exists_public_if = |exists| {
+ if exists {
+ exists_public
+ } else {
+ SpecialMemberFound::NotPresent
+ }
+ };
+ ItemsFound {
+ default_constructor: exists_public,
+ destructor: exists_public,
+ const_copy_constructor: exists_public_if(constructor_details.has_const_copy_constructor),
+ non_const_copy_constructor: SpecialMemberFound::NotPresent,
+ move_constructor: exists_public_if(constructor_details.has_move_constructor),
+ name: None,
+ }
+}
diff --git a/engine/src/conversion/analysis/fun/mod.rs b/engine/src/conversion/analysis/fun/mod.rs
new file mode 100644
index 0000000..19340f9
--- /dev/null
+++ b/engine/src/conversion/analysis/fun/mod.rs
@@ -0,0 +1,2140 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+mod bridge_name_tracker;
+pub(crate) mod function_wrapper;
+mod implicit_constructors;
+mod overload_tracker;
+mod subclass;
+
+use crate::{
+ conversion::{
+ analysis::{
+ fun::function_wrapper::{CppConversionType, CppFunctionKind},
+ type_converter::{self, add_analysis, TypeConversionContext, TypeConverter},
+ },
+ api::{
+ ApiName, CastMutability, CppVisibility, FuncToConvert, NullPhase, Provenance,
+ References, SpecialMemberKind, SubclassName, TraitImplSignature, TraitSynthesis,
+ UnsafetyNeeded, Virtualness,
+ },
+ apivec::ApiVec,
+ convert_error::ErrorContext,
+ convert_error::{ConvertErrorWithContext, ErrorContextType},
+ error_reporter::{convert_apis, report_any_error},
+ },
+ known_types::known_types,
+ types::validate_ident_ok_for_rust,
+};
+use indexmap::map::IndexMap as HashMap;
+use indexmap::set::IndexSet as HashSet;
+
+use autocxx_parser::{ExternCppType, IncludeCppConfig, UnsafePolicy};
+use function_wrapper::{CppFunction, CppFunctionBody, TypeConversionPolicy};
+use itertools::Itertools;
+use proc_macro2::Span;
+use quote::quote;
+use syn::{
+ parse_quote, punctuated::Punctuated, token::Comma, FnArg, Ident, Pat, ReturnType, Type,
+ TypePtr, Visibility,
+};
+
+use crate::{
+ conversion::{
+ api::{AnalysisPhase, Api, TypeKind},
+ ConvertError,
+ },
+ types::{make_ident, validate_ident_ok_for_cxx, Namespace, QualifiedName},
+};
+
+use self::{
+ bridge_name_tracker::BridgeNameTracker,
+ function_wrapper::RustConversionType,
+ implicit_constructors::{find_constructors_present, ItemsFound},
+ overload_tracker::OverloadTracker,
+ subclass::{
+ create_subclass_constructor, create_subclass_fn_wrapper, create_subclass_function,
+ create_subclass_trait_item,
+ },
+};
+
+use super::{
+ doc_label::make_doc_attrs,
+ pod::{PodAnalysis, PodPhase},
+ tdef::TypedefAnalysis,
+ type_converter::{Annotated, PointerTreatment},
+};
+
+#[derive(Clone, Debug)]
+pub(crate) enum ReceiverMutability {
+ Const,
+ Mutable,
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum MethodKind {
+ Normal(ReceiverMutability),
+ Constructor { is_default: bool },
+ Static,
+ Virtual(ReceiverMutability),
+ PureVirtual(ReceiverMutability),
+}
+
+#[derive(Clone)]
+pub(crate) enum TraitMethodKind {
+ CopyConstructor,
+ MoveConstructor,
+ Cast,
+ Destructor,
+ Alloc,
+ Dealloc,
+}
+
+#[derive(Clone)]
+pub(crate) struct TraitMethodDetails {
+ pub(crate) trt: TraitImplSignature,
+ pub(crate) avoid_self: bool,
+ pub(crate) method_name: Ident,
+ /// For traits, where we're trying to implement a specific existing
+ /// interface, we may need to reorder the parameters to fit that
+ /// interface.
+ pub(crate) parameter_reordering: Option<Vec<usize>>,
+ /// The function we're calling from the trait requires unsafe even
+ /// though the trait and its function aren't.
+ pub(crate) trait_call_is_unsafe: bool,
+}
+
+#[derive(Clone)]
+pub(crate) enum FnKind {
+ Function,
+ Method {
+ method_kind: MethodKind,
+ impl_for: QualifiedName,
+ },
+ TraitMethod {
+ kind: TraitMethodKind,
+ /// The name of the type T for which we're implementing a trait,
+ /// though we may be actually implementing the trait for &mut T or
+ /// similar, so we store more details of both the type and the
+ /// method in `details`
+ impl_for: QualifiedName,
+ details: Box<TraitMethodDetails>,
+ },
+}
+
+/// Strategy for ensuring that the final, callable, Rust name
+/// is what the user originally expected.
+#[derive(Clone)]
+
+pub(crate) enum RustRenameStrategy {
+ /// cxx::bridge name matches user expectations
+ None,
+ /// Even the #[rust_name] attribute would cause conflicts, and we need
+ /// to use a 'use XYZ as ABC'
+ RenameInOutputMod(Ident),
+ /// This function requires us to generate a Rust function to do
+ /// parameter conversion.
+ RenameUsingWrapperFunction,
+}
+
+#[derive(Clone)]
+pub(crate) struct FnAnalysis {
+ /// Each entry in the cxx::bridge needs to have a unique name, even if
+ /// (from the perspective of Rust and C++) things are in different
+ /// namespaces/mods.
+ pub(crate) cxxbridge_name: Ident,
+ /// ... so record also the name under which we wish to expose it in Rust.
+ pub(crate) rust_name: String,
+ pub(crate) rust_rename_strategy: RustRenameStrategy,
+ pub(crate) params: Punctuated<FnArg, Comma>,
+ pub(crate) kind: FnKind,
+ pub(crate) ret_type: ReturnType,
+ pub(crate) param_details: Vec<ArgumentAnalysis>,
+ pub(crate) ret_conversion: Option<TypeConversionPolicy>,
+ pub(crate) requires_unsafe: UnsafetyNeeded,
+ pub(crate) vis: Visibility,
+ pub(crate) cpp_wrapper: Option<CppFunction>,
+ pub(crate) deps: HashSet<QualifiedName>,
+ /// Some methods still need to be recorded because we want
+ /// to (a) generate the ability to call superclasses, (b) create
+ /// subclass entries for them. But we do not want to have them
+ /// be externally callable.
+ pub(crate) ignore_reason: Result<(), ConvertErrorWithContext>,
+ /// Whether this can be called by external code. Not so for
+ /// protected methods.
+ pub(crate) externally_callable: bool,
+ /// Whether we need to generate a Rust-side calling function
+ pub(crate) rust_wrapper_needed: bool,
+}
+
+#[derive(Clone)]
+pub(crate) struct ArgumentAnalysis {
+ pub(crate) conversion: TypeConversionPolicy,
+ pub(crate) name: Pat,
+ pub(crate) self_type: Option<(QualifiedName, ReceiverMutability)>,
+ pub(crate) has_lifetime: bool,
+ pub(crate) deps: HashSet<QualifiedName>,
+ pub(crate) requires_unsafe: UnsafetyNeeded,
+ pub(crate) is_placement_return_destination: bool,
+}
+
+struct ReturnTypeAnalysis {
+ rt: ReturnType,
+ conversion: Option<TypeConversionPolicy>,
+ was_reference: bool,
+ deps: HashSet<QualifiedName>,
+ placement_param_needed: Option<(FnArg, ArgumentAnalysis)>,
+}
+
+impl Default for ReturnTypeAnalysis {
+ fn default() -> Self {
+ Self {
+ rt: parse_quote! {},
+ conversion: None,
+ was_reference: false,
+ deps: Default::default(),
+ placement_param_needed: None,
+ }
+ }
+}
+
+pub(crate) struct PodAndConstructorAnalysis {
+ pub(crate) pod: PodAnalysis,
+ pub(crate) constructors: PublicConstructors,
+}
+
+/// An analysis phase where we've analyzed each function, but
+/// haven't yet determined which constructors/etc. belong to each type.
+pub(crate) struct FnPrePhase1;
+
+impl AnalysisPhase for FnPrePhase1 {
+ type TypedefAnalysis = TypedefAnalysis;
+ type StructAnalysis = PodAnalysis;
+ type FunAnalysis = FnAnalysis;
+}
+
+/// An analysis phase where we've analyzed each function, and identified
+/// what implicit constructors/destructors are present in each type.
+pub(crate) struct FnPrePhase2;
+
+impl AnalysisPhase for FnPrePhase2 {
+ type TypedefAnalysis = TypedefAnalysis;
+ type StructAnalysis = PodAndConstructorAnalysis;
+ type FunAnalysis = FnAnalysis;
+}
+
+pub(crate) struct PodAndDepAnalysis {
+ pub(crate) pod: PodAnalysis,
+ pub(crate) constructor_and_allocator_deps: Vec<QualifiedName>,
+ pub(crate) constructors: PublicConstructors,
+}
+
+/// Analysis phase after we've finished analyzing functions and determined
+/// which constructors etc. belong to them.
+pub(crate) struct FnPhase;
+
+/// Indicates which kinds of public constructors are known to exist for a type.
+#[derive(Debug, Default, Copy, Clone)]
+pub(crate) struct PublicConstructors {
+ pub(crate) move_constructor: bool,
+ pub(crate) destructor: bool,
+}
+
+impl PublicConstructors {
+ fn from_items_found(items_found: &ItemsFound) -> Self {
+ Self {
+ move_constructor: items_found.move_constructor.callable_any(),
+ destructor: items_found.destructor.callable_any(),
+ }
+ }
+}
+
+impl AnalysisPhase for FnPhase {
+ type TypedefAnalysis = TypedefAnalysis;
+ type StructAnalysis = PodAndDepAnalysis;
+ type FunAnalysis = FnAnalysis;
+}
+
+/// Whether to allow highly optimized calls because this is a simple Rust->C++ call,
+/// or to use a simpler set of policies because this is a subclass call where
+/// we may have C++->Rust->C++ etc.
+#[derive(Copy, Clone)]
+enum TypeConversionSophistication {
+ Regular,
+ SimpleForSubclasses,
+}
+
+pub(crate) struct FnAnalyzer<'a> {
+ unsafe_policy: UnsafePolicy,
+ extra_apis: ApiVec<NullPhase>,
+ type_converter: TypeConverter<'a>,
+ bridge_name_tracker: BridgeNameTracker,
+ pod_safe_types: HashSet<QualifiedName>,
+ moveit_safe_types: HashSet<QualifiedName>,
+ config: &'a IncludeCppConfig,
+ overload_trackers_by_mod: HashMap<Namespace, OverloadTracker>,
+ subclasses_by_superclass: HashMap<QualifiedName, Vec<SubclassName>>,
+ nested_type_name_map: HashMap<QualifiedName, String>,
+ generic_types: HashSet<QualifiedName>,
+ types_in_anonymous_namespace: HashSet<QualifiedName>,
+ existing_superclass_trait_api_names: HashSet<QualifiedName>,
+}
+
+impl<'a> FnAnalyzer<'a> {
+ pub(crate) fn analyze_functions(
+ apis: ApiVec<PodPhase>,
+ unsafe_policy: UnsafePolicy,
+ config: &'a IncludeCppConfig,
+ ) -> ApiVec<FnPrePhase2> {
+ let mut me = Self {
+ unsafe_policy,
+ extra_apis: ApiVec::new(),
+ type_converter: TypeConverter::new(config, &apis),
+ bridge_name_tracker: BridgeNameTracker::new(),
+ config,
+ overload_trackers_by_mod: HashMap::new(),
+ pod_safe_types: Self::build_pod_safe_type_set(&apis),
+ moveit_safe_types: Self::build_correctly_sized_type_set(&apis),
+ subclasses_by_superclass: subclass::subclasses_by_superclass(&apis),
+ nested_type_name_map: Self::build_nested_type_map(&apis),
+ generic_types: Self::build_generic_type_set(&apis),
+ existing_superclass_trait_api_names: HashSet::new(),
+ types_in_anonymous_namespace: Self::build_types_in_anonymous_namespace(&apis),
+ };
+ let mut results = ApiVec::new();
+ convert_apis(
+ apis,
+ &mut results,
+ |name, fun, _| me.analyze_foreign_fn_and_subclasses(name, fun),
+ Api::struct_unchanged,
+ Api::enum_unchanged,
+ Api::typedef_unchanged,
+ );
+ let mut results = me.add_constructors_present(results);
+ me.add_subclass_constructors(&mut results);
+ results.extend(me.extra_apis.into_iter().map(add_analysis));
+ results
+ }
+
+ fn build_pod_safe_type_set(apis: &ApiVec<PodPhase>) -> HashSet<QualifiedName> {
+ apis.iter()
+ .filter_map(|api| match api {
+ Api::Struct {
+ analysis:
+ PodAnalysis {
+ kind: TypeKind::Pod,
+ ..
+ },
+ ..
+ } => Some(api.name().clone()),
+ Api::Enum { .. } => Some(api.name().clone()),
+ Api::ExternCppType { pod: true, .. } => Some(api.name().clone()),
+ _ => None,
+ })
+ .chain(
+ known_types()
+ .get_pod_safe_types()
+ .filter_map(
+ |(tn, is_pod_safe)| {
+ if is_pod_safe {
+ Some(tn)
+ } else {
+ None
+ }
+ },
+ ),
+ )
+ .collect()
+ }
+
+ /// Return the set of 'moveit safe' types. That must include only types where
+ /// the size is known to be correct.
+ fn build_correctly_sized_type_set(apis: &ApiVec<PodPhase>) -> HashSet<QualifiedName> {
+ apis.iter()
+ .filter(|api| {
+ matches!(
+ api,
+ Api::Struct { .. }
+ | Api::Enum { .. }
+ | Api::ExternCppType {
+ details: ExternCppType { opaque: false, .. },
+ ..
+ }
+ )
+ })
+ .map(|api| api.name().clone())
+ .chain(known_types().get_moveit_safe_types())
+ .collect()
+ }
+
+ fn build_generic_type_set(apis: &ApiVec<PodPhase>) -> HashSet<QualifiedName> {
+ apis.iter()
+ .filter_map(|api| match api {
+ Api::Struct {
+ analysis:
+ PodAnalysis {
+ is_generic: true, ..
+ },
+ ..
+ } => Some(api.name().clone()),
+ _ => None,
+ })
+ .collect()
+ }
+
+ fn build_types_in_anonymous_namespace(apis: &ApiVec<PodPhase>) -> HashSet<QualifiedName> {
+ apis.iter()
+ .filter_map(|api| match api {
+ Api::Struct {
+ analysis:
+ PodAnalysis {
+ in_anonymous_namespace: true,
+ ..
+ },
+ ..
+ } => Some(api.name().clone()),
+ _ => None,
+ })
+ .collect()
+ }
+
+ /// Builds a mapping from a qualified type name to the last 'nest'
+ /// of its name, if it has multiple elements.
+ fn build_nested_type_map(apis: &ApiVec<PodPhase>) -> HashMap<QualifiedName, String> {
+ apis.iter()
+ .filter_map(|api| match api {
+ Api::Struct { name, .. } | Api::Enum { name, .. } => {
+ let cpp_name = name
+ .cpp_name_if_present()
+ .cloned()
+ .unwrap_or_else(|| name.name.get_final_item().to_string());
+ cpp_name
+ .rsplit_once("::")
+ .map(|(_, suffix)| (name.name.clone(), suffix.to_string()))
+ }
+ _ => None,
+ })
+ .collect()
+ }
+
+ fn convert_boxed_type(
+ &mut self,
+ ty: Box<Type>,
+ ns: &Namespace,
+ pointer_treatment: PointerTreatment,
+ ) -> Result<Annotated<Box<Type>>, ConvertError> {
+ let ctx = TypeConversionContext::OuterType { pointer_treatment };
+ let mut annotated = self.type_converter.convert_boxed_type(ty, ns, &ctx)?;
+ self.extra_apis.append(&mut annotated.extra_apis);
+ Ok(annotated)
+ }
+
+ 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 is_on_allowlist(&self, type_name: &QualifiedName) -> bool {
+ self.config.is_on_allowlist(&type_name.to_cpp_name())
+ }
+
+ fn is_generic_type(&self, type_name: &QualifiedName) -> bool {
+ self.generic_types.contains(type_name)
+ }
+
+ #[allow(clippy::if_same_then_else)] // clippy bug doesn't notice the two
+ // closures below are different.
+ fn should_be_unsafe(
+ &self,
+ param_details: &[ArgumentAnalysis],
+ kind: &FnKind,
+ ) -> UnsafetyNeeded {
+ let unsafest_non_placement_param = UnsafetyNeeded::from_param_details(param_details, true);
+ let unsafest_param = UnsafetyNeeded::from_param_details(param_details, false);
+ match kind {
+ // Trait unsafety must always correspond to the norms for the
+ // trait we're implementing.
+ FnKind::TraitMethod {
+ kind:
+ TraitMethodKind::CopyConstructor
+ | TraitMethodKind::MoveConstructor
+ | TraitMethodKind::Alloc
+ | TraitMethodKind::Dealloc,
+ ..
+ } => UnsafetyNeeded::Always,
+ FnKind::TraitMethod { .. } => match unsafest_param {
+ UnsafetyNeeded::Always => UnsafetyNeeded::JustBridge,
+ _ => unsafest_param,
+ },
+ _ if self.unsafe_policy == UnsafePolicy::AllFunctionsUnsafe => UnsafetyNeeded::Always,
+ _ => match unsafest_non_placement_param {
+ UnsafetyNeeded::Always => UnsafetyNeeded::Always,
+ UnsafetyNeeded::JustBridge => match unsafest_param {
+ UnsafetyNeeded::Always => UnsafetyNeeded::JustBridge,
+ _ => unsafest_non_placement_param,
+ },
+ UnsafetyNeeded::None => match unsafest_param {
+ UnsafetyNeeded::Always => UnsafetyNeeded::JustBridge,
+ _ => unsafest_param,
+ },
+ },
+ }
+ }
+
+ fn add_subclass_constructors(&mut self, apis: &mut ApiVec<FnPrePhase2>) {
+ let mut results = ApiVec::new();
+
+ // Pre-assemble a list of types with known destructors, to avoid having to
+ // do a O(n^2) nested loop.
+ let types_with_destructors: HashSet<_> = apis
+ .iter()
+ .filter_map(|api| match api {
+ Api::Function {
+ fun,
+ analysis:
+ FnAnalysis {
+ kind: FnKind::TraitMethod { impl_for, .. },
+ ..
+ },
+ ..
+ } if matches!(
+ **fun,
+ FuncToConvert {
+ special_member: Some(SpecialMemberKind::Destructor),
+ is_deleted: false,
+ cpp_vis: CppVisibility::Public,
+ ..
+ }
+ ) =>
+ {
+ Some(impl_for)
+ }
+ _ => None,
+ })
+ .cloned()
+ .collect();
+
+ for api in apis.iter() {
+ if let Api::Function {
+ fun,
+ analysis:
+ analysis @ FnAnalysis {
+ kind:
+ FnKind::Method {
+ impl_for: sup,
+ method_kind: MethodKind::Constructor { .. },
+ ..
+ },
+ ..
+ },
+ ..
+ } = api
+ {
+ // If we don't have an accessible destructor, then std::unique_ptr cannot be
+ // instantiated for this C++ type.
+ if !types_with_destructors.contains(sup) {
+ continue;
+ }
+
+ for sub in self.subclasses_by_superclass(sup) {
+ // Create a subclass constructor. This is a synthesized function
+ // which didn't exist in the original C++.
+ let (subclass_constructor_func, subclass_constructor_name) =
+ create_subclass_constructor(sub, analysis, sup, fun);
+ self.analyze_and_add(
+ subclass_constructor_name.clone(),
+ subclass_constructor_func.clone(),
+ &mut results,
+ TypeConversionSophistication::Regular,
+ );
+ }
+ }
+ }
+ apis.extend(results.into_iter());
+ }
+
+ /// Analyze a given function, and any permutations of that function which
+ /// we might additionally generate (e.g. for subclasses.)
+ ///
+ /// Leaves the [`FnKind::Method::type_constructors`] at its default for [`add_constructors_present`]
+ /// to fill out.
+ fn analyze_foreign_fn_and_subclasses(
+ &mut self,
+ name: ApiName,
+ fun: Box<FuncToConvert>,
+ ) -> Result<Box<dyn Iterator<Item = Api<FnPrePhase1>>>, ConvertErrorWithContext> {
+ let (analysis, name) =
+ self.analyze_foreign_fn(name, &fun, TypeConversionSophistication::Regular, None);
+ let mut results = ApiVec::new();
+
+ // Consider whether we need to synthesize subclass items.
+ if let FnKind::Method {
+ impl_for: sup,
+ method_kind:
+ MethodKind::Virtual(receiver_mutability) | MethodKind::PureVirtual(receiver_mutability),
+ ..
+ } = &analysis.kind
+ {
+ let (simpler_analysis, _) = self.analyze_foreign_fn(
+ name.clone(),
+ &fun,
+ TypeConversionSophistication::SimpleForSubclasses,
+ Some(analysis.rust_name.clone()),
+ );
+ for sub in self.subclasses_by_superclass(sup) {
+ // For each subclass, we need to create a plain-C++ method to call its superclass
+ // and a Rust/C++ bridge API to call _that_.
+ // What we're generating here is entirely about the subclass, so the
+ // superclass's namespace is irrelevant. We generate
+ // all subclasses in the root namespace.
+ let is_pure_virtual = matches!(
+ &simpler_analysis.kind,
+ FnKind::Method {
+ method_kind: MethodKind::PureVirtual(..),
+ ..
+ }
+ );
+
+ let super_fn_call_name =
+ SubclassName::get_super_fn_name(&Namespace::new(), &analysis.rust_name);
+ let super_fn_api_name = SubclassName::get_super_fn_name(
+ &Namespace::new(),
+ &analysis.cxxbridge_name.to_string(),
+ );
+ let trait_api_name = SubclassName::get_trait_api_name(sup, &analysis.rust_name);
+
+ let mut subclass_fn_deps = vec![trait_api_name.clone()];
+ if !is_pure_virtual {
+ // Create a C++ API representing the superclass implementation (allowing
+ // calls from Rust->C++)
+ let maybe_wrap = create_subclass_fn_wrapper(&sub, &super_fn_call_name, &fun);
+ let super_fn_name = ApiName::new_from_qualified_name(super_fn_api_name);
+ let super_fn_call_api_name = self.analyze_and_add(
+ super_fn_name,
+ maybe_wrap,
+ &mut results,
+ TypeConversionSophistication::SimpleForSubclasses,
+ );
+ subclass_fn_deps.push(super_fn_call_api_name);
+ }
+
+ // Create the Rust API representing the subclass implementation (allowing calls
+ // from C++ -> Rust)
+ results.push(create_subclass_function(
+ // RustSubclassFn
+ &sub,
+ &simpler_analysis,
+ &name,
+ receiver_mutability,
+ sup,
+ subclass_fn_deps,
+ ));
+
+ // Create the trait item for the <superclass>_methods and <superclass>_supers
+ // traits. This is required per-superclass, not per-subclass, so don't
+ // create it if it already exists.
+ if !self
+ .existing_superclass_trait_api_names
+ .contains(&trait_api_name)
+ {
+ self.existing_superclass_trait_api_names
+ .insert(trait_api_name.clone());
+ results.push(create_subclass_trait_item(
+ ApiName::new_from_qualified_name(trait_api_name),
+ &simpler_analysis,
+ receiver_mutability,
+ sup.clone(),
+ is_pure_virtual,
+ ));
+ }
+ }
+ }
+
+ results.push(Api::Function {
+ fun,
+ analysis,
+ name,
+ });
+
+ Ok(Box::new(results.into_iter()))
+ }
+
+ /// Adds an API, usually a synthesized API. Returns the final calculated API name, which can be used
+ /// for others to depend on this.
+ fn analyze_and_add<P: AnalysisPhase<FunAnalysis = FnAnalysis>>(
+ &mut self,
+ name: ApiName,
+ new_func: Box<FuncToConvert>,
+ results: &mut ApiVec<P>,
+ sophistication: TypeConversionSophistication,
+ ) -> QualifiedName {
+ let (analysis, name) = self.analyze_foreign_fn(name, &new_func, sophistication, None);
+ results.push(Api::Function {
+ fun: new_func,
+ analysis,
+ name: name.clone(),
+ });
+ name.name
+ }
+
+ /// Determine how to materialize a function.
+ ///
+ /// The main job here is to determine whether a function can simply be noted
+ /// in the [cxx::bridge] mod and passed directly to cxx, or if it needs a Rust-side
+ /// wrapper function, or if it needs a C++-side wrapper function, or both.
+ /// We aim for the simplest case but, for example:
+ /// * We'll need a C++ wrapper for static methods
+ /// * We'll need a C++ wrapper for parameters which need to be wrapped and unwrapped
+ /// to [cxx::UniquePtr]
+ /// * We'll need a Rust wrapper if we've got a C++ wrapper and it's a method.
+ /// * We may need wrappers if names conflict.
+ /// etc.
+ /// The other major thing we do here is figure out naming for the function.
+ /// This depends on overloads, and what other functions are floating around.
+ /// The output of this analysis phase is used by both Rust and C++ codegen.
+ fn analyze_foreign_fn(
+ &mut self,
+ name: ApiName,
+ fun: &FuncToConvert,
+ sophistication: TypeConversionSophistication,
+ predetermined_rust_name: Option<String>,
+ ) -> (FnAnalysis, ApiName) {
+ let mut cpp_name = name.cpp_name_if_present().cloned();
+ let ns = name.name.get_namespace();
+
+ // Let's gather some pre-wisdom about the name of the function.
+ // We're shortly going to plunge into analyzing the parameters,
+ // and it would be nice to have some idea of the function name
+ // for diagnostics whilst we do that.
+ let initial_rust_name = fun.ident.to_string();
+ let diagnostic_display_name = cpp_name.as_ref().unwrap_or(&initial_rust_name);
+
+ // Now let's analyze all the parameters.
+ // See if any have annotations which our fork of bindgen has craftily inserted...
+ let (param_details, bads): (Vec<_>, Vec<_>) = fun
+ .inputs
+ .iter()
+ .map(|i| {
+ self.convert_fn_arg(
+ i,
+ ns,
+ diagnostic_display_name,
+ &fun.synthesized_this_type,
+ &fun.references,
+ true,
+ false,
+ None,
+ sophistication,
+ false,
+ )
+ })
+ .partition(Result::is_ok);
+ let (mut params, mut param_details): (Punctuated<_, Comma>, Vec<_>) =
+ param_details.into_iter().map(Result::unwrap).unzip();
+
+ let params_deps: HashSet<_> = param_details
+ .iter()
+ .flat_map(|p| p.deps.iter().cloned())
+ .collect();
+ let self_ty = param_details
+ .iter()
+ .filter_map(|pd| pd.self_type.as_ref())
+ .next()
+ .cloned();
+
+ // End of parameter processing.
+ // Work out naming, part one.
+ // bindgen may have mangled the name either because it's invalid Rust
+ // syntax (e.g. a keyword like 'async') or it's an overload.
+ // If the former, we respect that mangling. If the latter, we don't,
+ // because we'll add our own overload counting mangling later.
+ // Cases:
+ // function, IRN=foo, CN=<none> output: foo case 1
+ // function, IRN=move_, CN=move (keyword problem) output: move_ case 2
+ // function, IRN=foo1, CN=foo (overload) output: foo case 3
+ // method, IRN=A_foo, CN=foo output: foo case 4
+ // method, IRN=A_move, CN=move (keyword problem) output: move_ case 5
+ // method, IRN=A_foo1, CN=foo (overload) output: foo case 6
+ let ideal_rust_name = match &cpp_name {
+ None => initial_rust_name, // case 1
+ Some(cpp_name) => {
+ if initial_rust_name.ends_with('_') {
+ initial_rust_name // case 2
+ } else if validate_ident_ok_for_rust(cpp_name).is_err() {
+ format!("{}_", cpp_name) // case 5
+ } else {
+ cpp_name.to_string() // cases 3, 4, 6
+ }
+ }
+ };
+
+ // Let's spend some time figuring out the kind of this function (i.e. method,
+ // virtual function, etc.)
+ // Part one, work out if this is a static method.
+ let (is_static_method, self_ty, receiver_mutability) = match self_ty {
+ None => {
+ // Even if we can't find a 'self' parameter this could conceivably
+ // be a static method.
+ let self_ty = fun.self_ty.clone();
+ (self_ty.is_some(), self_ty, None)
+ }
+ Some((self_ty, receiver_mutability)) => {
+ (false, Some(self_ty), Some(receiver_mutability))
+ }
+ };
+
+ // Part two, work out if this is a function, or method, or whatever.
+ // First determine if this is actually a trait implementation.
+ let trait_details = self.trait_creation_details_for_synthetic_function(
+ &fun.add_to_trait,
+ ns,
+ &ideal_rust_name,
+ &self_ty,
+ );
+ let (kind, error_context, rust_name) = if let Some(trait_details) = trait_details {
+ trait_details
+ } else if let Some(self_ty) = self_ty {
+ // Some kind of method or static method.
+ let type_ident = self_ty.get_final_item();
+ // bindgen generates methods with the name:
+ // {class}_{method name}
+ // It then generates an impl section for the Rust type
+ // with the original name, but we currently discard that impl section.
+ // We want to feed cxx methods with just the method name, so let's
+ // strip off the class name.
+ let mut rust_name = ideal_rust_name;
+ let nested_type_ident = self
+ .nested_type_name_map
+ .get(&self_ty)
+ .map(|s| s.as_str())
+ .unwrap_or_else(|| self_ty.get_final_item());
+ if matches!(
+ fun.special_member,
+ Some(SpecialMemberKind::CopyConstructor | SpecialMemberKind::MoveConstructor)
+ ) {
+ let is_move =
+ matches!(fun.special_member, Some(SpecialMemberKind::MoveConstructor));
+ if let Some(constructor_suffix) = rust_name.strip_prefix(nested_type_ident) {
+ rust_name = format!("new{}", constructor_suffix);
+ }
+ rust_name = predetermined_rust_name
+ .unwrap_or_else(|| self.get_overload_name(ns, type_ident, rust_name));
+ let error_context = self.error_context_for_method(&self_ty, &rust_name);
+
+ // If this is 'None', then something weird is going on. We'll check for that
+ // later when we have enough context to generate useful errors.
+ let arg_is_reference = matches!(
+ param_details
+ .get(1)
+ .map(|param| ¶m.conversion.unwrapped_type),
+ Some(Type::Reference(_))
+ );
+ // Some exotic forms of copy constructor have const and/or volatile qualifiers.
+ // These are not sufficient to implement CopyNew, so we just treat them as regular
+ // constructors. We detect them by their argument being translated to Pin at this
+ // point.
+ if is_move || arg_is_reference {
+ let (kind, method_name, trait_id) = if is_move {
+ (
+ TraitMethodKind::MoveConstructor,
+ "move_new",
+ quote! { MoveNew },
+ )
+ } else {
+ (
+ TraitMethodKind::CopyConstructor,
+ "copy_new",
+ quote! { CopyNew },
+ )
+ };
+ let ty = Type::Path(self_ty.to_type_path());
+ (
+ FnKind::TraitMethod {
+ kind,
+ impl_for: self_ty,
+ details: Box::new(TraitMethodDetails {
+ trt: TraitImplSignature {
+ ty,
+ trait_signature: parse_quote! {
+ autocxx::moveit::new:: #trait_id
+ },
+ unsafety: Some(parse_quote! { unsafe }),
+ },
+ avoid_self: true,
+ method_name: make_ident(method_name),
+ parameter_reordering: Some(vec![1, 0]),
+ trait_call_is_unsafe: false,
+ }),
+ },
+ error_context,
+ rust_name,
+ )
+ } else {
+ (
+ FnKind::Method {
+ impl_for: self_ty,
+ method_kind: MethodKind::Constructor { is_default: false },
+ },
+ error_context,
+ rust_name,
+ )
+ }
+ } else if matches!(fun.special_member, Some(SpecialMemberKind::Destructor)) {
+ rust_name = predetermined_rust_name
+ .unwrap_or_else(|| self.get_overload_name(ns, type_ident, rust_name));
+ let error_context = self.error_context_for_method(&self_ty, &rust_name);
+ let ty = Type::Path(self_ty.to_type_path());
+ (
+ FnKind::TraitMethod {
+ kind: TraitMethodKind::Destructor,
+ impl_for: self_ty,
+ details: Box::new(TraitMethodDetails {
+ trt: TraitImplSignature {
+ ty,
+ trait_signature: parse_quote! {
+ Drop
+ },
+ unsafety: None,
+ },
+ avoid_self: false,
+ method_name: make_ident("drop"),
+ parameter_reordering: None,
+ trait_call_is_unsafe: false,
+ }),
+ },
+ error_context,
+ rust_name,
+ )
+ } else {
+ let method_kind = if let Some(constructor_suffix) =
+ constructor_with_suffix(&rust_name, nested_type_ident)
+ {
+ // It's a constructor. bindgen generates
+ // fn Type(this: *mut Type, ...args)
+ // We want
+ // fn new(this: *mut Type, ...args)
+ // Later code will spot this and re-enter us, and we'll make
+ // a duplicate function in the above 'if' clause like this:
+ // fn make_unique(...args) -> Type
+ // which later code will convert to
+ // fn make_unique(...args) -> UniquePtr<Type>
+ // If there are multiple constructors, bindgen generates
+ // new, new1, new2 etc. and we'll keep those suffixes.
+ rust_name = format!("new{}", constructor_suffix);
+ MethodKind::Constructor {
+ is_default: matches!(
+ fun.special_member,
+ Some(SpecialMemberKind::DefaultConstructor)
+ ),
+ }
+ } else if is_static_method {
+ MethodKind::Static
+ } else {
+ let receiver_mutability =
+ receiver_mutability.expect("Failed to find receiver details");
+ match fun.virtualness {
+ Virtualness::None => MethodKind::Normal(receiver_mutability),
+ Virtualness::Virtual => MethodKind::Virtual(receiver_mutability),
+ Virtualness::PureVirtual => MethodKind::PureVirtual(receiver_mutability),
+ }
+ };
+ // Disambiguate overloads.
+ let rust_name = predetermined_rust_name
+ .unwrap_or_else(|| self.get_overload_name(ns, type_ident, rust_name));
+ let error_context = self.error_context_for_method(&self_ty, &rust_name);
+ (
+ FnKind::Method {
+ impl_for: self_ty,
+ method_kind,
+ },
+ error_context,
+ rust_name,
+ )
+ }
+ } else {
+ // Not a method.
+ // What shall we call this function? It may be overloaded.
+ let rust_name = self.get_function_overload_name(ns, ideal_rust_name);
+ (
+ FnKind::Function,
+ ErrorContext::new_for_item(make_ident(&rust_name)),
+ rust_name,
+ )
+ };
+
+ // If we encounter errors from here on, we can give some context around
+ // where the error occurred such that we can put a marker in the output
+ // Rust code to indicate that a problem occurred (benefiting people using
+ // rust-analyzer or similar). Make a closure to make this easy.
+ let mut ignore_reason = Ok(());
+ let mut set_ignore_reason =
+ |err| ignore_reason = Err(ConvertErrorWithContext(err, Some(error_context.clone())));
+
+ // Now we have figured out the type of function (from its parameters)
+ // we might have determined that we have a constructor. If so,
+ // annoyingly, we need to go back and fiddle with the parameters in a
+ // different way. This is because we want the first parameter to be a
+ // pointer not a reference. For copy + move constructors, we also
+ // enforce Rust-side conversions to comply with moveit traits.
+ match kind {
+ FnKind::Method {
+ method_kind: MethodKind::Constructor { .. },
+ ..
+ } => {
+ self.reanalyze_parameter(
+ 0,
+ fun,
+ ns,
+ &rust_name,
+ &mut params,
+ &mut param_details,
+ None,
+ sophistication,
+ true,
+ false,
+ )
+ .unwrap_or_else(&mut set_ignore_reason);
+ }
+
+ FnKind::TraitMethod {
+ kind: TraitMethodKind::Destructor,
+ ..
+ } => {
+ self.reanalyze_parameter(
+ 0,
+ fun,
+ ns,
+ &rust_name,
+ &mut params,
+ &mut param_details,
+ Some(RustConversionType::FromTypeToPtr),
+ sophistication,
+ false,
+ false,
+ )
+ .unwrap_or_else(&mut set_ignore_reason);
+ }
+ FnKind::TraitMethod {
+ kind: TraitMethodKind::CopyConstructor,
+ ..
+ } => {
+ if param_details.len() < 2 {
+ set_ignore_reason(ConvertError::ConstructorWithOnlyOneParam);
+ }
+ if param_details.len() > 2 {
+ set_ignore_reason(ConvertError::ConstructorWithMultipleParams);
+ }
+ self.reanalyze_parameter(
+ 0,
+ fun,
+ ns,
+ &rust_name,
+ &mut params,
+ &mut param_details,
+ Some(RustConversionType::FromPinMaybeUninitToPtr),
+ sophistication,
+ false,
+ false,
+ )
+ .unwrap_or_else(&mut set_ignore_reason);
+ }
+
+ FnKind::TraitMethod {
+ kind: TraitMethodKind::MoveConstructor,
+ ..
+ } => {
+ if param_details.len() < 2 {
+ set_ignore_reason(ConvertError::ConstructorWithOnlyOneParam);
+ }
+ if param_details.len() > 2 {
+ set_ignore_reason(ConvertError::ConstructorWithMultipleParams);
+ }
+ self.reanalyze_parameter(
+ 0,
+ fun,
+ ns,
+ &rust_name,
+ &mut params,
+ &mut param_details,
+ Some(RustConversionType::FromPinMaybeUninitToPtr),
+ sophistication,
+ false,
+ false,
+ )
+ .unwrap_or_else(&mut set_ignore_reason);
+ self.reanalyze_parameter(
+ 1,
+ fun,
+ ns,
+ &rust_name,
+ &mut params,
+ &mut param_details,
+ Some(RustConversionType::FromPinMoveRefToPtr),
+ sophistication,
+ false,
+ true,
+ )
+ .unwrap_or_else(&mut set_ignore_reason);
+ }
+ _ => {}
+ }
+
+ // Now we can add context to the error, check for a variety of error
+ // cases. In each case, we continue to record the API, because it might
+ // influence our later decisions to generate synthetic constructors
+ // or note whether the type is abstract.
+ let externally_callable = match fun.cpp_vis {
+ CppVisibility::Private => {
+ set_ignore_reason(ConvertError::PrivateMethod);
+ false
+ }
+ CppVisibility::Protected => false,
+ CppVisibility::Public => true,
+ };
+ if fun.variadic {
+ set_ignore_reason(ConvertError::Variadic);
+ }
+ if let Some(problem) = bads.into_iter().next() {
+ match problem {
+ Ok(_) => panic!("No error in the error"),
+ Err(problem) => set_ignore_reason(problem),
+ }
+ } else if fun.unused_template_param {
+ // This indicates that bindgen essentially flaked out because templates
+ // were too complex.
+ set_ignore_reason(ConvertError::UnusedTemplateParam)
+ } else if matches!(
+ fun.special_member,
+ Some(SpecialMemberKind::AssignmentOperator)
+ ) {
+ // Be careful with the order of this if-else tree. Anything above here means we won't
+ // treat it as an assignment operator, but anything below we still consider when
+ // deciding which other C++ special member functions are implicitly defined.
+ set_ignore_reason(ConvertError::AssignmentOperator)
+ } else if fun.references.rvalue_ref_return {
+ set_ignore_reason(ConvertError::RValueReturn)
+ } else if fun.is_deleted {
+ set_ignore_reason(ConvertError::Deleted)
+ } else {
+ match kind {
+ FnKind::Method {
+ ref impl_for,
+ method_kind:
+ MethodKind::Constructor { .. }
+ | MethodKind::Normal(..)
+ | MethodKind::PureVirtual(..)
+ | MethodKind::Virtual(..),
+ ..
+ } if !known_types().is_cxx_acceptable_receiver(impl_for) => {
+ set_ignore_reason(ConvertError::UnsupportedReceiver);
+ }
+ FnKind::Method { ref impl_for, .. } if !self.is_on_allowlist(impl_for) => {
+ // Bindgen will output methods for types which have been encountered
+ // virally as arguments on other allowlisted types. But we don't want
+ // to generate methods unless the user has specifically asked us to.
+ // It may, for instance, be a private type.
+ set_ignore_reason(ConvertError::MethodOfNonAllowlistedType);
+ }
+ FnKind::Method { ref impl_for, .. } | FnKind::TraitMethod { ref impl_for, .. } => {
+ if self.is_generic_type(impl_for) {
+ set_ignore_reason(ConvertError::MethodOfGenericType);
+ }
+ if self.types_in_anonymous_namespace.contains(impl_for) {
+ set_ignore_reason(ConvertError::MethodInAnonymousNamespace);
+ }
+ }
+ _ => {}
+ }
+ };
+
+ // The name we use within the cxx::bridge mod may be different
+ // from both the C++ name and the Rust name, because it's a flat
+ // namespace so we might need to prepend some stuff to make it unique.
+ let cxxbridge_name = self.get_cxx_bridge_name(
+ match kind {
+ FnKind::Method { ref impl_for, .. } => Some(impl_for.get_final_item()),
+ FnKind::Function => None,
+ FnKind::TraitMethod { ref impl_for, .. } => Some(impl_for.get_final_item()),
+ },
+ &rust_name,
+ ns,
+ );
+ if cxxbridge_name != rust_name && cpp_name.is_none() {
+ cpp_name = Some(rust_name.clone());
+ }
+ let mut cxxbridge_name = make_ident(&cxxbridge_name);
+
+ // Analyze the return type, just as we previously did for the
+ // parameters.
+ let mut return_analysis = self
+ .convert_return_type(&fun.output, ns, &fun.references, sophistication)
+ .unwrap_or_else(|err| {
+ set_ignore_reason(err);
+ ReturnTypeAnalysis::default()
+ });
+ let mut deps = params_deps;
+ deps.extend(return_analysis.deps.drain(..));
+
+ // Sometimes, the return type will actually be a value type
+ // for which we instead want to _pass_ a pointer into which the value
+ // can be constructed. Handle that case here.
+ if let Some((extra_param, extra_param_details)) = return_analysis.placement_param_needed {
+ param_details.push(extra_param_details);
+ params.push(extra_param);
+ }
+
+ let requires_unsafe = self.should_be_unsafe(¶m_details, &kind);
+
+ let num_input_references = param_details.iter().filter(|pd| pd.has_lifetime).count();
+ if num_input_references != 1 && return_analysis.was_reference {
+ // cxx only allows functions to return a reference if they take exactly
+ // one reference as a parameter. Let's see...
+ set_ignore_reason(ConvertError::NotOneInputReference(rust_name.clone()));
+ }
+ let mut ret_type = return_analysis.rt;
+ let ret_type_conversion = return_analysis.conversion;
+
+ // Do we need to convert either parameters or return type?
+ 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());
+ // See https://github.com/dtolnay/cxx/issues/878 for the reason for this next line.
+ let effective_cpp_name = cpp_name.as_ref().unwrap_or(&rust_name);
+ let cpp_name_incompatible_with_cxx =
+ validate_ident_ok_for_rust(effective_cpp_name).is_err();
+ // If possible, we'll put knowledge of the C++ API directly into the cxx::bridge
+ // mod. However, there are various circumstances where cxx can't work with the existing
+ // C++ API and we need to create a C++ wrapper function which is more cxx-compliant.
+ // That wrapper function is included in the cxx::bridge, and calls through to the
+ // original function.
+ let wrapper_function_needed = match kind {
+ FnKind::Method {
+ method_kind:
+ MethodKind::Static
+ | MethodKind::Constructor { .. }
+ | MethodKind::Virtual(_)
+ | MethodKind::PureVirtual(_),
+ ..
+ }
+ | FnKind::TraitMethod {
+ kind:
+ TraitMethodKind::CopyConstructor
+ | TraitMethodKind::MoveConstructor
+ | TraitMethodKind::Destructor,
+ ..
+ } => true,
+ FnKind::Method { .. } if cxxbridge_name != rust_name => true,
+ _ if param_conversion_needed => true,
+ _ if ret_type_conversion_needed => true,
+ _ if cpp_name_incompatible_with_cxx => true,
+ _ if fun.synthetic_cpp.is_some() => true,
+ _ => false,
+ };
+
+ let cpp_wrapper = if wrapper_function_needed {
+ // Generate a new layer of C++ code to wrap/unwrap parameters
+ // and return values into/out of std::unique_ptrs.
+ let cpp_construction_ident = make_ident(&effective_cpp_name);
+ let joiner = if cxxbridge_name.to_string().ends_with('_') {
+ ""
+ } else {
+ "_"
+ };
+ cxxbridge_name = make_ident(&format!("{}{}autocxx_wrapper", cxxbridge_name, joiner));
+ let (payload, cpp_function_kind) = match fun.synthetic_cpp.as_ref().cloned() {
+ Some((payload, cpp_function_kind)) => (payload, cpp_function_kind),
+ None => match kind {
+ FnKind::Method {
+ ref impl_for,
+ method_kind: MethodKind::Constructor { .. },
+ ..
+ }
+ | FnKind::TraitMethod {
+ kind: TraitMethodKind::CopyConstructor | TraitMethodKind::MoveConstructor,
+ ref impl_for,
+ ..
+ } => (
+ CppFunctionBody::PlacementNew(ns.clone(), impl_for.get_final_ident()),
+ CppFunctionKind::Constructor,
+ ),
+ FnKind::TraitMethod {
+ kind: TraitMethodKind::Destructor,
+ ref impl_for,
+ ..
+ } => (
+ CppFunctionBody::Destructor(ns.clone(), impl_for.get_final_ident()),
+ CppFunctionKind::Function,
+ ),
+ FnKind::Method {
+ ref impl_for,
+ method_kind: MethodKind::Static,
+ ..
+ } => (
+ CppFunctionBody::StaticMethodCall(
+ ns.clone(),
+ impl_for.get_final_ident(),
+ cpp_construction_ident,
+ ),
+ CppFunctionKind::Function,
+ ),
+ FnKind::Method { .. } => (
+ CppFunctionBody::FunctionCall(ns.clone(), cpp_construction_ident),
+ CppFunctionKind::Method,
+ ),
+ _ => (
+ CppFunctionBody::FunctionCall(ns.clone(), cpp_construction_ident),
+ CppFunctionKind::Function,
+ ),
+ },
+ };
+ // Now modify the cxx::bridge entry we're going to make.
+ if let Some(ref conversion) = ret_type_conversion {
+ if conversion.populate_return_value() {
+ let new_ret_type = conversion.unconverted_rust_type();
+ ret_type = parse_quote!(
+ -> #new_ret_type
+ );
+ }
+ }
+
+ // Amend parameters for the function which we're asking cxx to generate.
+ params.clear();
+ for pd in ¶m_details {
+ let type_name = pd.conversion.converted_rust_type();
+ let arg_name = if pd.self_type.is_some() {
+ parse_quote!(autocxx_gen_this)
+ } else {
+ pd.name.clone()
+ };
+ params.push(parse_quote!(
+ #arg_name: #type_name
+ ));
+ }
+
+ Some(CppFunction {
+ payload,
+ wrapper_function_name: cxxbridge_name.clone(),
+ original_cpp_name: cpp_name
+ .as_ref()
+ .cloned()
+ .unwrap_or_else(|| cxxbridge_name.to_string()),
+ return_conversion: ret_type_conversion.clone(),
+ argument_conversion: param_details.iter().map(|d| d.conversion.clone()).collect(),
+ kind: cpp_function_kind,
+ pass_obs_field: false,
+ qualification: None,
+ })
+ } else {
+ None
+ };
+
+ let vis = fun.vis.clone();
+
+ let any_param_needs_rust_conversion = param_details
+ .iter()
+ .any(|pd| pd.conversion.rust_work_needed());
+
+ let rust_wrapper_needed = match kind {
+ FnKind::TraitMethod { .. } => true,
+ FnKind::Method { .. } => any_param_needs_rust_conversion || cxxbridge_name != rust_name,
+ _ => any_param_needs_rust_conversion,
+ };
+
+ // Naming, part two.
+ // Work out our final naming strategy.
+ validate_ident_ok_for_cxx(&cxxbridge_name.to_string()).unwrap_or_else(set_ignore_reason);
+ let rust_name_ident = make_ident(&rust_name);
+ let rust_rename_strategy = match kind {
+ _ if rust_wrapper_needed => RustRenameStrategy::RenameUsingWrapperFunction,
+ FnKind::Function if cxxbridge_name != rust_name => {
+ RustRenameStrategy::RenameInOutputMod(rust_name_ident)
+ }
+ _ => RustRenameStrategy::None,
+ };
+
+ let analysis = FnAnalysis {
+ cxxbridge_name: cxxbridge_name.clone(),
+ rust_name: rust_name.clone(),
+ rust_rename_strategy,
+ params,
+ ret_conversion: ret_type_conversion,
+ kind,
+ ret_type,
+ param_details,
+ requires_unsafe,
+ vis,
+ cpp_wrapper,
+ deps,
+ ignore_reason,
+ externally_callable,
+ rust_wrapper_needed,
+ };
+ let name = ApiName::new_with_cpp_name(ns, cxxbridge_name, cpp_name);
+ (analysis, name)
+ }
+
+ fn error_context_for_method(&self, self_ty: &QualifiedName, rust_name: &str) -> ErrorContext {
+ if self.is_generic_type(self_ty) {
+ // A 'method' error context would end up in an
+ // impl A {
+ // fn error_thingy
+ // }
+ // block. We can't impl A if it would need to be impl A<B>
+ ErrorContext::new_for_item(make_ident(rust_name))
+ } else {
+ ErrorContext::new_for_method(self_ty.get_final_ident(), make_ident(rust_name))
+ }
+ }
+
+ /// Applies a specific `force_rust_conversion` to the parameter at index
+ /// `param_idx`. Modifies `param_details` and `params` in place.
+ #[allow(clippy::too_many_arguments)] // it's true, but sticking with it for now
+ fn reanalyze_parameter(
+ &mut self,
+ param_idx: usize,
+ fun: &FuncToConvert,
+ ns: &Namespace,
+ rust_name: &str,
+ params: &mut Punctuated<FnArg, Comma>,
+ param_details: &mut [ArgumentAnalysis],
+ force_rust_conversion: Option<RustConversionType>,
+ sophistication: TypeConversionSophistication,
+ construct_into_self: bool,
+ is_move_constructor: bool,
+ ) -> Result<(), ConvertError> {
+ self.convert_fn_arg(
+ fun.inputs.iter().nth(param_idx).unwrap(),
+ ns,
+ rust_name,
+ &fun.synthesized_this_type,
+ &fun.references,
+ false,
+ is_move_constructor,
+ force_rust_conversion,
+ sophistication,
+ construct_into_self,
+ )
+ .map(|(new_arg, new_analysis)| {
+ param_details[param_idx] = new_analysis;
+ let mut params_before = params.clone().into_iter();
+ let prefix = params_before
+ .by_ref()
+ .take(param_idx)
+ .collect_vec()
+ .into_iter();
+ let suffix = params_before.skip(1);
+ *params = prefix
+ .chain(std::iter::once(new_arg))
+ .chain(suffix)
+ .collect()
+ })
+ }
+
+ fn get_overload_name(&mut self, ns: &Namespace, type_ident: &str, rust_name: String) -> String {
+ let overload_tracker = self.overload_trackers_by_mod.entry(ns.clone()).or_default();
+ overload_tracker.get_method_real_name(type_ident, rust_name)
+ }
+
+ /// Determine if this synthetic function should actually result in the implementation
+ /// of a trait, rather than a function/method.
+ fn trait_creation_details_for_synthetic_function(
+ &mut self,
+ synthesis: &Option<TraitSynthesis>,
+ ns: &Namespace,
+ ideal_rust_name: &str,
+ self_ty: &Option<QualifiedName>,
+ ) -> Option<(FnKind, ErrorContext, String)> {
+ synthesis.as_ref().and_then(|synthesis| match synthesis {
+ TraitSynthesis::Cast { to_type, mutable } => {
+ let rust_name = self.get_function_overload_name(ns, ideal_rust_name.to_string());
+ let from_type = self_ty.as_ref().unwrap();
+ let from_type_path = from_type.to_type_path();
+ let to_type = to_type.to_type_path();
+ let (trait_signature, ty, method_name) = match *mutable {
+ CastMutability::ConstToConst => (
+ parse_quote! {
+ AsRef < #to_type >
+ },
+ Type::Path(from_type_path),
+ "as_ref",
+ ),
+ CastMutability::MutToConst => (
+ parse_quote! {
+ AsRef < #to_type >
+ },
+ parse_quote! {
+ &'a mut ::std::pin::Pin < &'a mut #from_type_path >
+ },
+ "as_ref",
+ ),
+ CastMutability::MutToMut => (
+ parse_quote! {
+ autocxx::PinMut < #to_type >
+ },
+ parse_quote! {
+ ::std::pin::Pin < &'a mut #from_type_path >
+ },
+ "pin_mut",
+ ),
+ };
+ let method_name = make_ident(method_name);
+ Some((
+ FnKind::TraitMethod {
+ kind: TraitMethodKind::Cast,
+ impl_for: from_type.clone(),
+ details: Box::new(TraitMethodDetails {
+ trt: TraitImplSignature {
+ ty,
+ trait_signature,
+ unsafety: None,
+ },
+ avoid_self: false,
+ method_name,
+ parameter_reordering: None,
+ trait_call_is_unsafe: false,
+ }),
+ },
+ ErrorContext::new_for_item(make_ident(&rust_name)),
+ rust_name,
+ ))
+ }
+ TraitSynthesis::AllocUninitialized(ty) => self.generate_alloc_or_deallocate(
+ ideal_rust_name,
+ ty,
+ "allocate_uninitialized_cpp_storage",
+ TraitMethodKind::Alloc,
+ ),
+ TraitSynthesis::FreeUninitialized(ty) => self.generate_alloc_or_deallocate(
+ ideal_rust_name,
+ ty,
+ "free_uninitialized_cpp_storage",
+ TraitMethodKind::Dealloc,
+ ),
+ })
+ }
+
+ fn generate_alloc_or_deallocate(
+ &mut self,
+ ideal_rust_name: &str,
+ ty: &QualifiedName,
+ method_name: &str,
+ kind: TraitMethodKind,
+ ) -> Option<(FnKind, ErrorContext, String)> {
+ let rust_name =
+ self.get_function_overload_name(ty.get_namespace(), ideal_rust_name.to_string());
+ let typ = ty.to_type_path();
+ Some((
+ FnKind::TraitMethod {
+ impl_for: ty.clone(),
+ details: Box::new(TraitMethodDetails {
+ trt: TraitImplSignature {
+ ty: Type::Path(typ),
+ trait_signature: parse_quote! { autocxx::moveit::MakeCppStorage },
+ unsafety: Some(parse_quote! { unsafe }),
+ },
+ avoid_self: false,
+ method_name: make_ident(method_name),
+ parameter_reordering: None,
+ trait_call_is_unsafe: false,
+ }),
+ kind,
+ },
+ ErrorContext::new_for_item(make_ident(&rust_name)),
+ rust_name,
+ ))
+ }
+
+ fn get_function_overload_name(&mut self, ns: &Namespace, ideal_rust_name: String) -> String {
+ let overload_tracker = self.overload_trackers_by_mod.entry(ns.clone()).or_default();
+ overload_tracker.get_function_real_name(ideal_rust_name)
+ }
+
+ fn subclasses_by_superclass(&self, sup: &QualifiedName) -> impl Iterator<Item = SubclassName> {
+ match self.subclasses_by_superclass.get(sup) {
+ Some(subs) => subs.clone().into_iter(),
+ None => Vec::new().into_iter(),
+ }
+ }
+
+ #[allow(clippy::too_many_arguments)] // currently reasonably clear
+ fn convert_fn_arg(
+ &mut self,
+ arg: &FnArg,
+ ns: &Namespace,
+ fn_name: &str,
+ virtual_this: &Option<QualifiedName>,
+ references: &References,
+ treat_this_as_reference: bool,
+ is_move_constructor: bool,
+ force_rust_conversion: Option<RustConversionType>,
+ sophistication: TypeConversionSophistication,
+ construct_into_self: bool,
+ ) -> 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 pointer_treatment = PointerTreatment::Pointer;
+ let mut is_placement_return_destination = 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 receiver_mutability = if mutability.is_some() {
+ ReceiverMutability::Mutable
+ } else {
+ ReceiverMutability::Const
+ };
+
+ let this_type = if let Some(virtual_this) = virtual_this {
+ let this_type_path = virtual_this.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
+ });
+ virtual_this.clone()
+ } else {
+ QualifiedName::from_type_path(typ)
+ };
+ Ok((this_type, receiver_mutability))
+ }
+ _ => Err(ConvertError::UnexpectedThisType(QualifiedName::new(
+ ns,
+ make_ident(fn_name),
+ ))),
+ },
+ _ => Err(ConvertError::UnexpectedThisType(QualifiedName::new(
+ ns,
+ make_ident(fn_name),
+ ))),
+ }?;
+ self_type = Some(this_type);
+ is_placement_return_destination = construct_into_self;
+ if treat_this_as_reference {
+ pp.ident = Ident::new("self", pp.ident.span());
+ pointer_treatment = PointerTreatment::Reference;
+ }
+ syn::Pat::Ident(pp)
+ }
+ syn::Pat::Ident(pp) => {
+ validate_ident_ok_for_cxx(&pp.ident.to_string())?;
+ pointer_treatment = references.param_treatment(&pp.ident);
+ syn::Pat::Ident(pp)
+ }
+ _ => old_pat,
+ };
+ let is_placement_return_destination = is_placement_return_destination
+ || matches!(
+ force_rust_conversion,
+ Some(RustConversionType::FromPlacementParamToNewReturn)
+ );
+ let annotated_type = self.convert_boxed_type(pt.ty, ns, pointer_treatment)?;
+ let conversion = self.argument_conversion_details(
+ &annotated_type,
+ is_move_constructor,
+ force_rust_conversion,
+ sophistication,
+ );
+ let new_ty = annotated_type.ty;
+ pt.pat = Box::new(new_pat.clone());
+ pt.ty = new_ty;
+ let requires_unsafe =
+ if matches!(annotated_type.kind, type_converter::TypeKind::Pointer)
+ && !is_placement_return_destination
+ {
+ UnsafetyNeeded::Always
+ } else if conversion.bridge_unsafe_needed() || is_placement_return_destination {
+ UnsafetyNeeded::JustBridge
+ } else {
+ UnsafetyNeeded::None
+ };
+ (
+ FnArg::Typed(pt),
+ ArgumentAnalysis {
+ self_type,
+ name: new_pat,
+ conversion,
+ has_lifetime: matches!(
+ annotated_type.kind,
+ type_converter::TypeKind::Reference
+ | type_converter::TypeKind::MutableReference
+ ),
+ deps: annotated_type.types_encountered,
+ requires_unsafe,
+ is_placement_return_destination,
+ },
+ )
+ }
+ _ => panic!("Did not expect FnArg::Receiver to be generated by bindgen"),
+ })
+ }
+
+ fn argument_conversion_details(
+ &self,
+ annotated_type: &Annotated<Box<Type>>,
+ is_move_constructor: bool,
+ force_rust_conversion: Option<RustConversionType>,
+ sophistication: TypeConversionSophistication,
+ ) -> TypeConversionPolicy {
+ let is_subclass_holder = match &annotated_type.kind {
+ type_converter::TypeKind::SubclassHolder(holder) => Some(holder),
+ _ => None,
+ };
+ let is_rvalue_ref = matches!(
+ annotated_type.kind,
+ type_converter::TypeKind::RValueReference
+ );
+ let ty = &*annotated_type.ty;
+ if let Some(holder_id) = is_subclass_holder {
+ let subclass = SubclassName::from_holder_name(holder_id);
+ return {
+ let ty = parse_quote! {
+ rust::Box<#holder_id>
+ };
+ TypeConversionPolicy {
+ unwrapped_type: ty,
+ cpp_conversion: CppConversionType::Move,
+ rust_conversion: RustConversionType::ToBoxedUpHolder(subclass),
+ }
+ };
+ } else if matches!(
+ force_rust_conversion,
+ Some(RustConversionType::FromPlacementParamToNewReturn)
+ ) && matches!(sophistication, TypeConversionSophistication::Regular)
+ {
+ return TypeConversionPolicy {
+ unwrapped_type: ty.clone(),
+ cpp_conversion: CppConversionType::IgnoredPlacementPtrParameter,
+ rust_conversion: RustConversionType::FromPlacementParamToNewReturn,
+ };
+ }
+ match ty {
+ Type::Path(p) => {
+ let ty = ty.clone();
+ let tn = QualifiedName::from_type_path(p);
+ if self.pod_safe_types.contains(&tn) {
+ if known_types().lacks_copy_constructor(&tn) {
+ TypeConversionPolicy {
+ unwrapped_type: ty,
+ cpp_conversion: CppConversionType::Move,
+ rust_conversion: RustConversionType::None,
+ }
+ } else {
+ TypeConversionPolicy::new_unconverted(ty)
+ }
+ } else if known_types().convertible_from_strs(&tn)
+ && !self.config.exclude_utilities()
+ {
+ TypeConversionPolicy {
+ unwrapped_type: ty,
+ cpp_conversion: CppConversionType::FromUniquePtrToValue,
+ rust_conversion: RustConversionType::FromStr,
+ }
+ } else if matches!(
+ sophistication,
+ TypeConversionSophistication::SimpleForSubclasses
+ ) {
+ TypeConversionPolicy {
+ unwrapped_type: ty,
+ cpp_conversion: CppConversionType::FromUniquePtrToValue,
+ rust_conversion: RustConversionType::None,
+ }
+ } else {
+ TypeConversionPolicy {
+ unwrapped_type: ty,
+ cpp_conversion: CppConversionType::FromPtrToValue,
+ rust_conversion: RustConversionType::FromValueParamToPtr,
+ }
+ }
+ }
+ Type::Ptr(tp) => {
+ let rust_conversion = force_rust_conversion.unwrap_or(RustConversionType::None);
+ if is_move_constructor {
+ TypeConversionPolicy {
+ unwrapped_type: ty.clone(),
+ cpp_conversion: CppConversionType::FromPtrToMove,
+ rust_conversion,
+ }
+ } else if is_rvalue_ref {
+ TypeConversionPolicy {
+ unwrapped_type: *tp.elem.clone(),
+ cpp_conversion: CppConversionType::FromPtrToValue,
+ rust_conversion: RustConversionType::FromRValueParamToPtr,
+ }
+ } else {
+ TypeConversionPolicy {
+ unwrapped_type: ty.clone(),
+ cpp_conversion: CppConversionType::None,
+ rust_conversion,
+ }
+ }
+ }
+ _ => {
+ let rust_conversion = force_rust_conversion.unwrap_or(RustConversionType::None);
+ TypeConversionPolicy {
+ unwrapped_type: ty.clone(),
+ cpp_conversion: CppConversionType::None,
+ rust_conversion,
+ }
+ }
+ }
+ }
+
+ fn convert_return_type(
+ &mut self,
+ rt: &ReturnType,
+ ns: &Namespace,
+ references: &References,
+ sophistication: TypeConversionSophistication,
+ ) -> Result<ReturnTypeAnalysis, ConvertError> {
+ Ok(match rt {
+ ReturnType::Default => ReturnTypeAnalysis::default(),
+ ReturnType::Type(rarrow, boxed_type) => {
+ let annotated_type =
+ self.convert_boxed_type(boxed_type.clone(), ns, references.return_treatment())?;
+ let boxed_type = annotated_type.ty;
+ let ty: &Type = boxed_type.as_ref();
+ match ty {
+ Type::Path(p)
+ if !self
+ .pod_safe_types
+ .contains(&QualifiedName::from_type_path(p)) =>
+ {
+ let tn = QualifiedName::from_type_path(p);
+ if self.moveit_safe_types.contains(&tn)
+ && matches!(sophistication, TypeConversionSophistication::Regular)
+ {
+ // This is a non-POD type we want to return to Rust as an `impl New` so that callers
+ // can decide whether to store this on the stack or heap.
+ // That means, we do not literally _return_ it from C++ to Rust. Instead, our call
+ // from Rust to C++ will include an extra placement parameter into which the object
+ // is constructed.
+ let fnarg = parse_quote! {
+ placement_return_type: *mut #ty
+ };
+ let (fnarg, analysis) = self.convert_fn_arg(
+ &fnarg,
+ ns,
+ "",
+ &None,
+ &References::default(),
+ false,
+ false,
+ Some(RustConversionType::FromPlacementParamToNewReturn),
+ TypeConversionSophistication::Regular,
+ false,
+ )?;
+ ReturnTypeAnalysis {
+ rt: ReturnType::Default,
+ conversion: Some(TypeConversionPolicy::new_for_placement_return(
+ ty.clone(),
+ )),
+ was_reference: false,
+ deps: annotated_type.types_encountered,
+ placement_param_needed: Some((fnarg, analysis)),
+ }
+ } else {
+ // There are some types which we can't currently represent within a moveit::new::New.
+ // That's either because we are obliged to stick to existing protocols for compatibility
+ // (CxxString) or because they're a concrete type where we haven't attempted to do
+ // the analysis to work out the type's size. For these, we always return a plain old
+ // UniquePtr<T>. These restrictions may be fixed in future.
+ let conversion =
+ Some(TypeConversionPolicy::new_to_unique_ptr(ty.clone()));
+ ReturnTypeAnalysis {
+ rt: ReturnType::Type(*rarrow, boxed_type),
+ conversion,
+ was_reference: false,
+ deps: annotated_type.types_encountered,
+ placement_param_needed: None,
+ }
+ }
+ }
+ _ => {
+ let was_reference = matches!(boxed_type.as_ref(), Type::Reference(_));
+ let conversion = Some(TypeConversionPolicy::new_unconverted(ty.clone()));
+ ReturnTypeAnalysis {
+ rt: ReturnType::Type(*rarrow, boxed_type),
+ conversion,
+ was_reference,
+ deps: annotated_type.types_encountered,
+ placement_param_needed: None,
+ }
+ }
+ }
+ }
+ })
+ }
+
+ /// If a type has explicit constructors, bindgen will generate corresponding
+ /// constructor functions, which we'll have already converted to make_unique methods.
+ /// C++ mandates the synthesis of certain implicit constructors, to which we
+ /// need to create bindings too. We do that here.
+ /// It is tempting to make this a separate analysis phase, to be run later than
+ /// the function analysis; but that would make the code much more complex as it
+ /// would need to output a `FnAnalysisBody`. By running it as part of this phase
+ /// we can simply generate the sort of thing bindgen generates, then ask
+ /// the existing code in this phase to figure out what to do with it.
+ ///
+ /// Also fills out the [`PodAndConstructorAnalysis::constructors`] fields with information useful
+ /// for further analysis phases.
+ fn add_constructors_present(&mut self, mut apis: ApiVec<FnPrePhase1>) -> ApiVec<FnPrePhase2> {
+ let all_items_found = find_constructors_present(&apis);
+ for (self_ty, items_found) in all_items_found.iter() {
+ if self.config.exclude_impls {
+ // Remember that `find_constructors_present` mutates `apis`, so we always have to
+ // call that, even if we don't do anything with the return value. This is kind of
+ // messy, see the comment on this function for why.
+ continue;
+ }
+ if self
+ .config
+ .is_on_constructor_blocklist(&self_ty.to_cpp_name())
+ {
+ continue;
+ }
+ let path = self_ty.to_type_path();
+ if items_found.implicit_default_constructor_needed() {
+ self.synthesize_special_member(
+ items_found,
+ "default_ctor",
+ &mut apis,
+ SpecialMemberKind::DefaultConstructor,
+ parse_quote! { this: *mut #path },
+ References::default(),
+ );
+ }
+ if items_found.implicit_move_constructor_needed() {
+ self.synthesize_special_member(
+ items_found,
+ "move_ctor",
+ &mut apis,
+ SpecialMemberKind::MoveConstructor,
+ parse_quote! { this: *mut #path, other: *mut #path },
+ References {
+ rvalue_ref_params: [make_ident("other")].into_iter().collect(),
+ ..Default::default()
+ },
+ )
+ }
+ if items_found.implicit_copy_constructor_needed() {
+ self.synthesize_special_member(
+ items_found,
+ "const_copy_ctor",
+ &mut apis,
+ SpecialMemberKind::CopyConstructor,
+ parse_quote! { this: *mut #path, other: *const #path },
+ References {
+ ref_params: [make_ident("other")].into_iter().collect(),
+ ..Default::default()
+ },
+ )
+ }
+ if items_found.implicit_destructor_needed() {
+ self.synthesize_special_member(
+ items_found,
+ "destructor",
+ &mut apis,
+ SpecialMemberKind::Destructor,
+ parse_quote! { this: *mut #path },
+ References::default(),
+ );
+ }
+ }
+
+ // Also, annotate each type with the constructors we found.
+ let mut results = ApiVec::new();
+ convert_apis(
+ apis,
+ &mut results,
+ Api::fun_unchanged,
+ |name, details, analysis| {
+ let items_found = all_items_found.get(&name.name);
+ Ok(Box::new(std::iter::once(Api::Struct {
+ name,
+ details,
+ analysis: PodAndConstructorAnalysis {
+ pod: analysis,
+ constructors: if let Some(items_found) = items_found {
+ PublicConstructors::from_items_found(items_found)
+ } else {
+ PublicConstructors::default()
+ },
+ },
+ })))
+ },
+ Api::enum_unchanged,
+ Api::typedef_unchanged,
+ );
+ results
+ }
+
+ #[allow(clippy::too_many_arguments)] // it's true, but sticking with it for now
+ fn synthesize_special_member(
+ &mut self,
+ items_found: &ItemsFound,
+ label: &str,
+ apis: &mut ApiVec<FnPrePhase1>,
+ special_member: SpecialMemberKind,
+ inputs: Punctuated<FnArg, Comma>,
+ references: References,
+ ) {
+ let self_ty = items_found.name.as_ref().unwrap();
+ let ident = make_ident(self.config.uniquify_name_per_mod(&format!(
+ "{}_synthetic_{}",
+ self_ty.name.get_final_item(),
+ label
+ )));
+ let cpp_name = if matches!(special_member, SpecialMemberKind::DefaultConstructor) {
+ // Constructors (other than move or copy) are identified in `analyze_foreign_fn` by
+ // being suffixed with the cpp_name, so we have to produce that.
+ self.nested_type_name_map
+ .get(&self_ty.name)
+ .cloned()
+ .or_else(|| Some(self_ty.name.get_final_item().to_string()))
+ } else {
+ None
+ };
+ let fake_api_name =
+ ApiName::new_with_cpp_name(self_ty.name.get_namespace(), ident.clone(), cpp_name);
+ let self_ty = &self_ty.name;
+ let ns = self_ty.get_namespace().clone();
+ let mut any_errors = ApiVec::new();
+ apis.extend(
+ report_any_error(&ns, &mut any_errors, || {
+ self.analyze_foreign_fn_and_subclasses(
+ fake_api_name,
+ Box::new(FuncToConvert {
+ self_ty: Some(self_ty.clone()),
+ ident,
+ doc_attrs: make_doc_attrs(format!("Synthesized {}.", special_member)),
+ inputs,
+ output: ReturnType::Default,
+ vis: parse_quote! { pub },
+ virtualness: Virtualness::None,
+ cpp_vis: CppVisibility::Public,
+ special_member: Some(special_member),
+ unused_template_param: false,
+ references,
+ original_name: None,
+ synthesized_this_type: None,
+ is_deleted: false,
+ add_to_trait: None,
+ synthetic_cpp: None,
+ provenance: Provenance::SynthesizedOther,
+ variadic: false,
+ }),
+ )
+ })
+ .into_iter()
+ .flatten(),
+ );
+ apis.append(&mut any_errors);
+ }
+}
+
+/// Attempts to determine whether this function name is a constructor, and if so,
+/// returns the suffix.
+fn constructor_with_suffix<'a>(rust_name: &'a str, nested_type_ident: &str) -> Option<&'a str> {
+ let suffix = rust_name.strip_prefix(nested_type_ident);
+ suffix.and_then(|suffix| {
+ if suffix.is_empty() || suffix.parse::<u32>().is_ok() {
+ Some(suffix)
+ } else {
+ None
+ }
+ })
+}
+
+impl Api<FnPhase> {
+ pub(crate) fn name_for_allowlist(&self) -> QualifiedName {
+ match &self {
+ Api::Function { analysis, .. } => match analysis.kind {
+ FnKind::Method { ref impl_for, .. } => impl_for.clone(),
+ FnKind::TraitMethod { ref impl_for, .. } => impl_for.clone(),
+ FnKind::Function => {
+ QualifiedName::new(self.name().get_namespace(), make_ident(&analysis.rust_name))
+ }
+ },
+ Api::RustSubclassFn { subclass, .. } => subclass.0.name.clone(),
+ Api::IgnoredItem {
+ name,
+ ctx: Some(ctx),
+ ..
+ } => match ctx.get_type() {
+ ErrorContextType::Method { self_ty, .. } => {
+ QualifiedName::new(name.name.get_namespace(), self_ty.clone())
+ }
+ ErrorContextType::Item(id) => {
+ QualifiedName::new(name.name.get_namespace(), id.clone())
+ }
+ _ => name.name.clone(),
+ },
+ _ => self.name().clone(),
+ }
+ }
+
+ /// Whether this API requires generation of additional C++.
+ /// This seems an odd place for this function (as opposed to in the [codegen_cpp]
+ /// module) but, as it happens, even our Rust codegen phase needs to know if
+ /// more C++ is needed (so it can add #includes in the cxx mod).
+ /// And we can't answer the question _prior_ to this function analysis phase.
+ pub(crate) fn needs_cpp_codegen(&self) -> bool {
+ matches!(
+ &self,
+ Api::Function {
+ analysis: FnAnalysis {
+ cpp_wrapper: Some(..),
+ ignore_reason: Ok(_),
+ externally_callable: true,
+ ..
+ },
+ ..
+ } | Api::StringConstructor { .. }
+ | Api::ConcreteType { .. }
+ | Api::CType { .. }
+ | Api::RustSubclassFn { .. }
+ | Api::Subclass { .. }
+ | Api::Struct {
+ analysis: PodAndDepAnalysis {
+ pod: PodAnalysis {
+ kind: TypeKind::Pod,
+ ..
+ },
+ ..
+ },
+ ..
+ }
+ )
+ }
+
+ pub(crate) fn cxxbridge_name(&self) -> Option<Ident> {
+ match self {
+ Api::Function { ref analysis, .. } => Some(analysis.cxxbridge_name.clone()),
+ Api::StringConstructor { .. }
+ | Api::Const { .. }
+ | Api::IgnoredItem { .. }
+ | Api::RustSubclassFn { .. } => None,
+ _ => Some(self.name().get_final_ident()),
+ }
+ }
+}
diff --git a/engine/src/conversion/analysis/fun/overload_tracker.rs b/engine/src/conversion/analysis/fun/overload_tracker.rs
new file mode 100644
index 0000000..6fc532c
--- /dev/null
+++ b/engine/src/conversion/analysis/fun/overload_tracker.rs
@@ -0,0 +1,77 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use std::collections::HashMap;
+
+type Offsets = HashMap<String, usize>;
+
+/// Registry of all the overloads of a function found within a given
+/// namespace (i.e. mod in bindgen's output). If necessary we'll append
+/// a _nnn suffix to a function's Rust name to disambiguate overloads.
+/// Note that this is NOT necessarily the same as the suffix added by
+/// bindgen to disambiguate overloads it discovers. Its suffix is
+/// global across all functions, whereas ours is local within a given
+/// type.
+/// If bindgen adds a suffix it will be included in 'found_name'
+/// but not 'original_name' which is an annotation added by our autocxx-bindgen
+/// fork.
+#[derive(Default)]
+pub(crate) struct OverloadTracker {
+ offset_by_name: Offsets,
+ offset_by_type_and_name: HashMap<String, Offsets>,
+}
+
+impl OverloadTracker {
+ pub(crate) fn get_function_real_name(&mut self, found_name: String) -> String {
+ self.get_name(None, found_name)
+ }
+
+ pub(crate) fn get_method_real_name(&mut self, type_name: &str, found_name: String) -> String {
+ self.get_name(Some(type_name), found_name)
+ }
+
+ fn get_name(&mut self, type_name: Option<&str>, cpp_method_name: String) -> String {
+ let registry = match type_name {
+ Some(type_name) => self
+ .offset_by_type_and_name
+ .entry(type_name.to_string())
+ .or_default(),
+ None => &mut self.offset_by_name,
+ };
+ let offset = registry.entry(cpp_method_name.clone()).or_default();
+ let this_offset = *offset;
+ *offset += 1;
+ if this_offset == 0 {
+ cpp_method_name
+ } else {
+ format!("{}{}", cpp_method_name, this_offset)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::OverloadTracker;
+
+ #[test]
+ fn test_by_function() {
+ let mut ot = OverloadTracker::default();
+ assert_eq!(ot.get_function_real_name("bob".into()), "bob");
+ assert_eq!(ot.get_function_real_name("bob".into()), "bob1");
+ assert_eq!(ot.get_function_real_name("bob".into()), "bob2");
+ }
+
+ #[test]
+ fn test_by_method() {
+ let mut ot = OverloadTracker::default();
+ assert_eq!(ot.get_method_real_name("Ty1", "bob".into()), "bob");
+ assert_eq!(ot.get_method_real_name("Ty1", "bob".into()), "bob1");
+ assert_eq!(ot.get_method_real_name("Ty2", "bob".into()), "bob");
+ assert_eq!(ot.get_method_real_name("Ty2", "bob".into()), "bob1");
+ }
+}
diff --git a/engine/src/conversion/analysis/fun/subclass.rs b/engine/src/conversion/analysis/fun/subclass.rs
new file mode 100644
index 0000000..c017249
--- /dev/null
+++ b/engine/src/conversion/analysis/fun/subclass.rs
@@ -0,0 +1,249 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::map::IndexMap as HashMap;
+
+use syn::{parse_quote, FnArg, PatType, Type, TypePtr};
+
+use crate::conversion::analysis::fun::{FnKind, MethodKind, ReceiverMutability};
+use crate::conversion::analysis::pod::PodPhase;
+use crate::conversion::api::{
+ CppVisibility, FuncToConvert, Provenance, RustSubclassFnDetails, SubclassConstructorDetails,
+ SubclassName, SuperclassMethod, UnsafetyNeeded, Virtualness,
+};
+use crate::conversion::apivec::ApiVec;
+use crate::{
+ conversion::{
+ analysis::fun::function_wrapper::{
+ CppFunction, CppFunctionBody, CppFunctionKind, TypeConversionPolicy,
+ },
+ api::{Api, ApiName},
+ },
+ types::{make_ident, Namespace, QualifiedName},
+};
+
+use super::{FnAnalysis, FnPrePhase1};
+
+pub(super) fn subclasses_by_superclass(
+ apis: &ApiVec<PodPhase>,
+) -> HashMap<QualifiedName, Vec<SubclassName>> {
+ let mut subclasses_per_superclass: HashMap<QualifiedName, Vec<SubclassName>> = HashMap::new();
+
+ for api in apis.iter() {
+ if let Api::Subclass { name, superclass } = api {
+ subclasses_per_superclass
+ .entry(superclass.clone())
+ .or_default()
+ .push(name.clone());
+ }
+ }
+ subclasses_per_superclass
+}
+
+pub(super) fn create_subclass_fn_wrapper(
+ sub: &SubclassName,
+ super_fn_name: &QualifiedName,
+ fun: &FuncToConvert,
+) -> Box<FuncToConvert> {
+ let self_ty = Some(sub.cpp());
+ Box::new(FuncToConvert {
+ synthesized_this_type: self_ty.clone(),
+ self_ty,
+ ident: super_fn_name.get_final_ident(),
+ doc_attrs: fun.doc_attrs.clone(),
+ inputs: fun.inputs.clone(),
+ output: fun.output.clone(),
+ vis: fun.vis.clone(),
+ virtualness: Virtualness::None,
+ cpp_vis: CppVisibility::Public,
+ special_member: None,
+ unused_template_param: fun.unused_template_param,
+ original_name: None,
+ references: fun.references.clone(),
+ add_to_trait: fun.add_to_trait.clone(),
+ is_deleted: fun.is_deleted,
+ synthetic_cpp: None,
+ provenance: Provenance::SynthesizedOther,
+ variadic: fun.variadic,
+ })
+}
+
+pub(super) fn create_subclass_trait_item(
+ name: ApiName,
+ analysis: &FnAnalysis,
+ receiver_mutability: &ReceiverMutability,
+ receiver: QualifiedName,
+ is_pure_virtual: bool,
+) -> Api<FnPrePhase1> {
+ let param_names = analysis
+ .param_details
+ .iter()
+ .map(|pd| pd.name.clone())
+ .collect();
+ Api::SubclassTraitItem {
+ name,
+ details: SuperclassMethod {
+ name: make_ident(&analysis.rust_name),
+ params: analysis.params.clone(),
+ ret_type: analysis.ret_type.clone(),
+ param_names,
+ receiver_mutability: receiver_mutability.clone(),
+ requires_unsafe: UnsafetyNeeded::from_param_details(&analysis.param_details, false),
+ is_pure_virtual,
+ receiver,
+ },
+ }
+}
+
+pub(super) fn create_subclass_function(
+ sub: &SubclassName,
+ analysis: &super::FnAnalysis,
+ name: &ApiName,
+ receiver_mutability: &ReceiverMutability,
+ superclass: &QualifiedName,
+ dependencies: Vec<QualifiedName>,
+) -> Api<FnPrePhase1> {
+ let cpp = sub.cpp();
+ let holder_name = sub.holder();
+ let rust_call_name = make_ident(format!(
+ "{}_{}",
+ sub.0.name.get_final_item(),
+ name.name.get_final_item()
+ ));
+ let params = std::iter::once(parse_quote! {
+ me: & #holder_name
+ })
+ .chain(analysis.params.iter().skip(1).cloned())
+ .collect();
+ let kind = if matches!(receiver_mutability, ReceiverMutability::Mutable) {
+ CppFunctionKind::Method
+ } else {
+ CppFunctionKind::ConstMethod
+ };
+ let argument_conversion = analysis
+ .param_details
+ .iter()
+ .skip(1)
+ .map(|p| p.conversion.clone())
+ .collect();
+ Api::RustSubclassFn {
+ name: ApiName::new_in_root_namespace(rust_call_name.clone()),
+ subclass: sub.clone(),
+ details: Box::new(RustSubclassFnDetails {
+ params,
+ ret: analysis.ret_type.clone(),
+ method_name: make_ident(&analysis.rust_name),
+ cpp_impl: CppFunction {
+ payload: CppFunctionBody::FunctionCall(Namespace::new(), rust_call_name),
+ wrapper_function_name: make_ident(&analysis.rust_name),
+ original_cpp_name: name.cpp_name(),
+ return_conversion: analysis.ret_conversion.clone(),
+ argument_conversion,
+ kind,
+ pass_obs_field: true,
+ qualification: Some(cpp),
+ },
+ superclass: superclass.clone(),
+ receiver_mutability: receiver_mutability.clone(),
+ dependencies,
+ requires_unsafe: UnsafetyNeeded::from_param_details(&analysis.param_details, false),
+ is_pure_virtual: matches!(
+ analysis.kind,
+ FnKind::Method {
+ method_kind: MethodKind::PureVirtual(..),
+ ..
+ }
+ ),
+ }),
+ }
+}
+
+pub(super) fn create_subclass_constructor(
+ sub: SubclassName,
+ analysis: &FnAnalysis,
+ sup: &QualifiedName,
+ fun: &FuncToConvert,
+) -> (Box<FuncToConvert>, ApiName) {
+ let holder = sub.holder();
+ let cpp = sub.cpp();
+ let wrapper_function_name = cpp.get_final_ident();
+ let initial_arg = TypeConversionPolicy::new_unconverted(parse_quote! {
+ rust::Box< #holder >
+ });
+ let args = std::iter::once(initial_arg).chain(
+ analysis
+ .param_details
+ .iter()
+ .skip(1) // skip placement new destination
+ .map(|aa| aa.conversion.clone()),
+ );
+ let cpp_impl = CppFunction {
+ payload: CppFunctionBody::ConstructSuperclass(sup.to_cpp_name()),
+ wrapper_function_name,
+ return_conversion: None,
+ argument_conversion: args.collect(),
+ kind: CppFunctionKind::SynthesizedConstructor,
+ pass_obs_field: false,
+ qualification: Some(cpp.clone()),
+ original_cpp_name: cpp.to_cpp_name(),
+ };
+ let subclass_constructor_details = Box::new(SubclassConstructorDetails {
+ subclass: sub.clone(),
+ is_trivial: analysis.param_details.len() == 1, // just placement new
+ // destination, no other parameters
+ cpp_impl,
+ });
+ let subclass_constructor_name =
+ make_ident(format!("{}_{}", cpp.get_final_item(), cpp.get_final_item()));
+ let mut existing_params = fun.inputs.clone();
+ if let Some(FnArg::Typed(PatType { ty, .. })) = existing_params.first_mut() {
+ if let Type::Ptr(TypePtr { elem, .. }) = &mut **ty {
+ *elem = Box::new(Type::Path(sub.cpp().to_type_path()));
+ } else {
+ panic!("Unexpected self type parameter when creating subclass constructor");
+ }
+ } else {
+ panic!("Unexpected self type parameter when creating subclass constructor");
+ }
+ let mut existing_params = existing_params.into_iter();
+ let self_param = existing_params.next();
+ let boxed_holder_param: FnArg = parse_quote! {
+ peer: rust::Box<#holder>
+ };
+ let inputs = self_param
+ .into_iter()
+ .chain(std::iter::once(boxed_holder_param))
+ .chain(existing_params)
+ .collect();
+ let maybe_wrap = Box::new(FuncToConvert {
+ ident: subclass_constructor_name.clone(),
+ doc_attrs: fun.doc_attrs.clone(),
+ inputs,
+ output: fun.output.clone(),
+ vis: fun.vis.clone(),
+ virtualness: Virtualness::None,
+ cpp_vis: CppVisibility::Public,
+ special_member: fun.special_member.clone(),
+ original_name: None,
+ unused_template_param: fun.unused_template_param,
+ references: fun.references.clone(),
+ synthesized_this_type: Some(cpp.clone()),
+ self_ty: Some(cpp),
+ add_to_trait: None,
+ is_deleted: fun.is_deleted,
+ synthetic_cpp: None,
+ provenance: Provenance::SynthesizedSubclassConstructor(subclass_constructor_details),
+ variadic: fun.variadic,
+ });
+ let subclass_constructor_name = ApiName::new_with_cpp_name(
+ &Namespace::new(),
+ subclass_constructor_name,
+ Some(sub.cpp().get_final_item().to_string()),
+ );
+ (maybe_wrap, subclass_constructor_name)
+}
diff --git a/engine/src/conversion/analysis/gc.rs b/engine/src/conversion/analysis/gc.rs
new file mode 100644
index 0000000..0734f15
--- /dev/null
+++ b/engine/src/conversion/analysis/gc.rs
@@ -0,0 +1,70 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::map::IndexMap as HashMap;
+use indexmap::set::IndexSet as HashSet;
+
+use autocxx_parser::IncludeCppConfig;
+
+use crate::{
+ conversion::{api::Api, apivec::ApiVec},
+ types::QualifiedName,
+};
+
+use super::{deps::HasDependencies, fun::FnPhase};
+
+/// This is essentially mark-and-sweep garbage collection of the
+/// [Api]s that we've discovered. Why do we do this, you might wonder?
+/// It seems a bit strange given that we pass an explicit allowlist
+/// to bindgen.
+/// There are two circumstances under which we want to discard
+/// some of the APIs we encounter parsing the bindgen.
+/// 1) We simplify some struct to be non-POD. In this case, we'll
+/// discard all the fields within it. Those fields can be, and
+/// in fact often _are_, stuff which we have trouble converting
+/// e.g. std::string or std::string::value_type or
+/// my_derived_thing<std::basic_string::value_type> or some
+/// other permutation. In such cases, we want to discard those
+/// field types with prejudice.
+/// 2) block! may be used to ban certain APIs. This often eliminates
+/// some methods from a given struct/class. In which case, we
+/// don't care about the other parameter types passed into those
+/// APIs either.
+pub(crate) fn filter_apis_by_following_edges_from_allowlist(
+ apis: ApiVec<FnPhase>,
+ config: &IncludeCppConfig,
+) -> ApiVec<FnPhase> {
+ let mut todos: Vec<QualifiedName> = apis
+ .iter()
+ .filter(|api| {
+ let tnforal = api.name_for_allowlist();
+ config.is_on_allowlist(&tnforal.to_cpp_name())
+ })
+ .map(Api::name)
+ .cloned()
+ .collect();
+ let mut by_typename: HashMap<QualifiedName, ApiVec<FnPhase>> = HashMap::new();
+ for api in apis.into_iter() {
+ let tn = api.name().clone();
+ by_typename.entry(tn).or_default().push(api);
+ }
+ let mut done = HashSet::new();
+ let mut output = ApiVec::new();
+ while !todos.is_empty() {
+ let todo = todos.remove(0);
+ if done.contains(&todo) {
+ continue;
+ }
+ if let Some(mut these_apis) = by_typename.remove(&todo) {
+ todos.extend(these_apis.iter().flat_map(|api| api.deps().cloned()));
+ output.append(&mut these_apis);
+ } // otherwise, probably an intrinsic e.g. uint32_t.
+ done.insert(todo);
+ }
+ output
+}
diff --git a/engine/src/conversion/analysis/mod.rs b/engine/src/conversion/analysis/mod.rs
new file mode 100644
index 0000000..d733011
--- /dev/null
+++ b/engine/src/conversion/analysis/mod.rs
@@ -0,0 +1,28 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+pub(crate) mod abstract_types;
+pub(crate) mod allocators;
+pub(crate) mod casts;
+pub(crate) mod constructor_deps;
+pub(crate) mod ctypes;
+pub(crate) mod deps;
+mod depth_first;
+mod doc_label;
+pub(crate) mod fun;
+pub(crate) mod gc;
+mod name_check;
+pub(crate) mod pod; // hey, that rhymes
+pub(crate) mod remove_ignored;
+mod replace_hopeless_typedef_targets;
+pub(crate) mod tdef;
+mod type_converter;
+
+pub(crate) use name_check::check_names;
+pub(crate) use replace_hopeless_typedef_targets::replace_hopeless_typedef_targets;
+pub(crate) use type_converter::PointerTreatment;
diff --git a/engine/src/conversion/analysis/name_check.rs b/engine/src/conversion/analysis/name_check.rs
new file mode 100644
index 0000000..7547c7c
--- /dev/null
+++ b/engine/src/conversion/analysis/name_check.rs
@@ -0,0 +1,115 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::map::IndexMap as HashMap;
+
+use syn::Ident;
+
+use crate::{
+ conversion::{
+ api::{Api, SubclassName},
+ apivec::ApiVec,
+ error_reporter::convert_item_apis,
+ ConvertError,
+ },
+ types::{validate_ident_ok_for_cxx, QualifiedName},
+};
+
+use super::fun::FnPhase;
+
+/// Do some final checks that the names we've come up with can be represented
+/// within cxx.
+pub(crate) fn check_names(apis: ApiVec<FnPhase>) -> ApiVec<FnPhase> {
+ // If any items have names which can't be represented by cxx,
+ // abort. This check should ideally be done at the times we fill in the
+ // `name` field of each `api` in the first place, at parse time, though
+ // as the `name` field of each API may change during various analysis phases,
+ // currently it seems better to do it here to ensure we respect
+ // the output of any such changes.
+ let mut intermediate = ApiVec::new();
+ convert_item_apis(apis, &mut intermediate, |api| match api {
+ Api::Typedef { ref name, .. }
+ | Api::ForwardDeclaration { ref name, .. }
+ | Api::OpaqueTypedef { ref name, .. }
+ | Api::Const { ref name, .. }
+ | Api::Enum { ref name, .. }
+ | Api::Struct { ref name, .. } => {
+ validate_all_segments_ok_for_cxx(name.name.segment_iter())?;
+ if let Some(cpp_name) = name.cpp_name_if_present() {
+ // The C++ name might itself be outer_type::inner_type and thus may
+ // have multiple segments.
+ validate_all_segments_ok_for_cxx(
+ QualifiedName::new_from_cpp_name(cpp_name).segment_iter(),
+ )?;
+ }
+ Ok(Box::new(std::iter::once(api)))
+ }
+ Api::Subclass {
+ name: SubclassName(ref name),
+ ref superclass,
+ } => {
+ validate_all_segments_ok_for_cxx(name.name.segment_iter())?;
+ validate_all_segments_ok_for_cxx(superclass.segment_iter())?;
+ Ok(Box::new(std::iter::once(api)))
+ }
+ Api::Function { ref name, .. } => {
+ // we don't handle function names here because
+ // the function analysis does an equivalent check. Instead of just rejecting
+ // the function, it creates a wrapper function instead with a more
+ // palatable name. That's preferable to rejecting the API entirely.
+ validate_all_segments_ok_for_cxx(name.name.segment_iter())?;
+ Ok(Box::new(std::iter::once(api)))
+ }
+ Api::ConcreteType { .. }
+ | Api::CType { .. }
+ | Api::StringConstructor { .. }
+ | Api::RustType { .. }
+ | Api::RustSubclassFn { .. }
+ | Api::RustFn { .. }
+ | Api::SubclassTraitItem { .. }
+ | Api::ExternCppType { .. }
+ | Api::IgnoredItem { .. } => Ok(Box::new(std::iter::once(api))),
+ });
+
+ // Reject any names which are duplicates within the cxx bridge mod,
+ // that has a flat namespace.
+ let mut names_found: HashMap<Ident, Vec<String>> = HashMap::new();
+ for api in intermediate.iter() {
+ let my_name = api.cxxbridge_name();
+ if let Some(name) = my_name {
+ let e = names_found.entry(name).or_default();
+ e.push(api.name_info().name.to_string());
+ }
+ }
+ let mut results = ApiVec::new();
+ convert_item_apis(intermediate, &mut results, |api| {
+ let my_name = api.cxxbridge_name();
+ if let Some(name) = my_name {
+ let symbols_for_this_name = names_found.entry(name).or_default();
+ if symbols_for_this_name.len() > 1usize {
+ Err(ConvertError::DuplicateCxxBridgeName(
+ symbols_for_this_name.clone(),
+ ))
+ } else {
+ Ok(Box::new(std::iter::once(api)))
+ }
+ } else {
+ Ok(Box::new(std::iter::once(api)))
+ }
+ });
+ results
+}
+
+fn validate_all_segments_ok_for_cxx(
+ items: impl Iterator<Item = String>,
+) -> Result<(), ConvertError> {
+ for seg in items {
+ validate_ident_ok_for_cxx(&seg)?;
+ }
+ Ok(())
+}
diff --git a/engine/src/conversion/analysis/pod/byvalue_checker.rs b/engine/src/conversion/analysis/pod/byvalue_checker.rs
new file mode 100644
index 0000000..de72eec
--- /dev/null
+++ b/engine/src/conversion/analysis/pod/byvalue_checker.rs
@@ -0,0 +1,343 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::conversion::apivec::ApiVec;
+use crate::{conversion::ConvertError, known_types::known_types};
+use crate::{
+ conversion::{
+ analysis::tdef::TypedefPhase,
+ api::{Api, TypedefKind},
+ },
+ types::{Namespace, QualifiedName},
+};
+use autocxx_parser::IncludeCppConfig;
+use std::collections::HashMap;
+use syn::{ItemStruct, Type};
+
+#[derive(Clone)]
+enum PodState {
+ UnsafeToBePod(String),
+ SafeToBePod,
+ IsPod,
+ IsAlias(QualifiedName),
+}
+
+#[derive(Clone)]
+struct StructDetails {
+ state: PodState,
+ dependent_structs: Vec<QualifiedName>,
+}
+
+impl StructDetails {
+ fn new(state: PodState) -> Self {
+ StructDetails {
+ state,
+ dependent_structs: Vec::new(),
+ }
+ }
+}
+
+/// Type which is able to check whether it's safe to make a type
+/// fully representable by cxx. For instance if it is a struct containing
+/// a struct containing a std::string, the answer is no, because that
+/// std::string contains a self-referential pointer.
+/// It is possible that this is duplicative of the information stored
+/// elsewhere in the `Api` list and could possibly be removed or simplified.
+pub struct ByValueChecker {
+ // Mapping from type name to whether it is safe to be POD
+ results: HashMap<QualifiedName, StructDetails>,
+}
+
+impl ByValueChecker {
+ pub fn new() -> Self {
+ let mut results = HashMap::new();
+ for (tn, by_value_safe) in known_types().get_pod_safe_types() {
+ let safety = if by_value_safe {
+ PodState::IsPod
+ } else {
+ PodState::UnsafeToBePod(format!("type {} is not safe for POD", tn))
+ };
+ results.insert(tn.clone(), StructDetails::new(safety));
+ }
+ ByValueChecker { results }
+ }
+
+ /// Scan APIs to work out which are by-value safe. Constructs a [ByValueChecker]
+ /// that others can use to query the results.
+ pub(crate) fn new_from_apis(
+ apis: &ApiVec<TypedefPhase>,
+ config: &IncludeCppConfig,
+ ) -> Result<ByValueChecker, ConvertError> {
+ let mut byvalue_checker = ByValueChecker::new();
+ for blocklisted in config.get_blocklist() {
+ let tn = QualifiedName::new_from_cpp_name(blocklisted);
+ let safety = PodState::UnsafeToBePod(format!("type {} is on the blocklist", &tn));
+ byvalue_checker
+ .results
+ .insert(tn, StructDetails::new(safety));
+ }
+ for api in apis.iter() {
+ match api {
+ Api::Typedef { analysis, .. } => {
+ let name = api.name();
+ let typedef_type = match analysis.kind {
+ TypedefKind::Type(ref type_item) => match type_item.ty.as_ref() {
+ Type::Path(typ) => {
+ let target_tn = QualifiedName::from_type_path(typ);
+ known_types().consider_substitution(&target_tn)
+ }
+ _ => None,
+ },
+ TypedefKind::Use(_, ref ty) => match **ty {
+ Type::Path(ref typ) => {
+ let target_tn = QualifiedName::from_type_path(typ);
+ known_types().consider_substitution(&target_tn)
+ }
+ _ => None,
+ },
+ };
+ match &typedef_type {
+ Some(typ) => {
+ byvalue_checker.results.insert(
+ name.clone(),
+ StructDetails::new(PodState::IsAlias(
+ QualifiedName::from_type_path(typ),
+ )),
+ );
+ }
+ None => byvalue_checker.ingest_nonpod_type(name.clone()),
+ }
+ }
+ Api::Struct { details, .. } => {
+ byvalue_checker.ingest_struct(&details.item, api.name().get_namespace())
+ }
+ Api::Enum { .. } => {
+ byvalue_checker
+ .results
+ .insert(api.name().clone(), StructDetails::new(PodState::IsPod));
+ }
+ Api::ExternCppType { pod: true, .. } => {
+ byvalue_checker
+ .results
+ .insert(api.name().clone(), StructDetails::new(PodState::IsPod));
+ }
+ _ => {}
+ }
+ }
+ let pod_requests = config
+ .get_pod_requests()
+ .iter()
+ .map(|ty| QualifiedName::new_from_cpp_name(ty))
+ .collect();
+ byvalue_checker
+ .satisfy_requests(pod_requests)
+ .map_err(ConvertError::UnsafePodType)?;
+ Ok(byvalue_checker)
+ }
+
+ fn ingest_struct(&mut self, def: &ItemStruct, ns: &Namespace) {
+ // For this struct, work out whether it _could_ be safe as a POD.
+ let tyname = QualifiedName::new(ns, def.ident.clone());
+ let mut field_safety_problem = PodState::SafeToBePod;
+ let fieldlist = Self::get_field_types(def);
+ for ty_id in &fieldlist {
+ match self.results.get(ty_id) {
+ None => {
+ field_safety_problem = PodState::UnsafeToBePod(format!(
+ "Type {} could not be POD because its dependent type {} isn't known",
+ tyname, ty_id
+ ));
+ break;
+ }
+ Some(deets) => {
+ if let PodState::UnsafeToBePod(reason) = &deets.state {
+ let new_reason = format!("Type {} could not be POD because its dependent type {} isn't safe to be POD. Because: {}", tyname, ty_id, reason);
+ field_safety_problem = PodState::UnsafeToBePod(new_reason);
+ break;
+ }
+ }
+ }
+ }
+ if Self::has_vtable(def) {
+ let reason = format!(
+ "Type {} could not be POD because it has virtual functions.",
+ tyname
+ );
+ field_safety_problem = PodState::UnsafeToBePod(reason);
+ }
+ let mut my_details = StructDetails::new(field_safety_problem);
+ my_details.dependent_structs = fieldlist;
+ self.results.insert(tyname, my_details);
+ }
+
+ fn ingest_nonpod_type(&mut self, tyname: QualifiedName) {
+ let new_reason = format!("Type {} is a typedef to a complex type", tyname);
+ self.results.insert(
+ tyname,
+ StructDetails::new(PodState::UnsafeToBePod(new_reason)),
+ );
+ }
+
+ fn satisfy_requests(&mut self, mut requests: Vec<QualifiedName>) -> Result<(), String> {
+ while !requests.is_empty() {
+ let ty_id = requests.remove(requests.len() - 1);
+ let deets = self.results.get_mut(&ty_id);
+ let mut alias_to_consider = None;
+ match deets {
+ None => {
+ return Err(format!(
+ "Unable to make {} POD because we never saw a struct definition",
+ ty_id
+ ))
+ }
+ Some(deets) => match &deets.state {
+ PodState::UnsafeToBePod(error_msg) => return Err(error_msg.clone()),
+ PodState::IsPod => {}
+ PodState::SafeToBePod => {
+ deets.state = PodState::IsPod;
+ requests.extend_from_slice(&deets.dependent_structs);
+ }
+ PodState::IsAlias(target_type) => {
+ alias_to_consider = Some(target_type.clone());
+ }
+ },
+ }
+ // Do the following outside the match to avoid borrow checker violation.
+ if let Some(alias) = alias_to_consider {
+ match self.results.get(&alias) {
+ None => requests.extend_from_slice(&[alias, ty_id]), // try again after resolving alias target
+ Some(alias_target_deets) => {
+ self.results.get_mut(&ty_id).unwrap().state =
+ alias_target_deets.state.clone();
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Return whether a given type is POD (i.e. can be represented by value in Rust) or not.
+ /// Unless we've got a definite record that it _is_, we return false.
+ /// Some types won't be in our `results` map. For example: (a) AutocxxConcrete types
+ /// which we've synthesized; (b) types we couldn't parse but returned ignorable
+ /// errors so that we could continue. Assume non-POD for all such cases.
+ pub fn is_pod(&self, ty_id: &QualifiedName) -> bool {
+ matches!(
+ self.results.get(ty_id),
+ Some(StructDetails {
+ state: PodState::IsPod,
+ dependent_structs: _,
+ })
+ )
+ }
+
+ fn get_field_types(def: &ItemStruct) -> Vec<QualifiedName> {
+ let mut results = Vec::new();
+ for f in &def.fields {
+ let fty = &f.ty;
+ if let Type::Path(p) = fty {
+ results.push(QualifiedName::from_type_path(p));
+ }
+ // TODO handle anything else which bindgen might spit out, e.g. arrays?
+ }
+ results
+ }
+
+ fn has_vtable(def: &ItemStruct) -> bool {
+ for f in &def.fields {
+ if f.ident.as_ref().map(|id| id == "vtable_").unwrap_or(false) {
+ return true;
+ }
+ }
+ false
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::ByValueChecker;
+ use crate::types::{Namespace, QualifiedName};
+ use syn::{parse_quote, Ident, ItemStruct};
+
+ fn ty_from_ident(id: &Ident) -> QualifiedName {
+ QualifiedName::new_from_cpp_name(&id.to_string())
+ }
+
+ #[test]
+ fn test_primitive_by_itself() {
+ let bvc = ByValueChecker::new();
+ let t_id = QualifiedName::new_from_cpp_name("u32");
+ assert!(bvc.is_pod(&t_id));
+ }
+
+ #[test]
+ fn test_primitives() {
+ let mut bvc = ByValueChecker::new();
+ let t: ItemStruct = parse_quote! {
+ struct Foo {
+ a: i32,
+ b: i64,
+ }
+ };
+ let t_id = ty_from_ident(&t.ident);
+ bvc.ingest_struct(&t, &Namespace::new());
+ bvc.satisfy_requests(vec![t_id.clone()]).unwrap();
+ assert!(bvc.is_pod(&t_id));
+ }
+
+ #[test]
+ fn test_nested_primitives() {
+ let mut bvc = ByValueChecker::new();
+ let t: ItemStruct = parse_quote! {
+ struct Foo {
+ a: i32,
+ b: i64,
+ }
+ };
+ bvc.ingest_struct(&t, &Namespace::new());
+ let t: ItemStruct = parse_quote! {
+ struct Bar {
+ a: Foo,
+ b: i64,
+ }
+ };
+ let t_id = ty_from_ident(&t.ident);
+ bvc.ingest_struct(&t, &Namespace::new());
+ bvc.satisfy_requests(vec![t_id.clone()]).unwrap();
+ assert!(bvc.is_pod(&t_id));
+ }
+
+ #[test]
+ fn test_with_up() {
+ let mut bvc = ByValueChecker::new();
+ let t: ItemStruct = parse_quote! {
+ struct Bar {
+ a: cxx::UniquePtr<CxxString>,
+ b: i64,
+ }
+ };
+ let t_id = ty_from_ident(&t.ident);
+ bvc.ingest_struct(&t, &Namespace::new());
+ bvc.satisfy_requests(vec![t_id.clone()]).unwrap();
+ assert!(bvc.is_pod(&t_id));
+ }
+
+ #[test]
+ fn test_with_cxxstring() {
+ let mut bvc = ByValueChecker::new();
+ let t: ItemStruct = parse_quote! {
+ struct Bar {
+ a: CxxString,
+ b: i64,
+ }
+ };
+ let t_id = ty_from_ident(&t.ident);
+ bvc.ingest_struct(&t, &Namespace::new());
+ assert!(bvc.satisfy_requests(vec![t_id]).is_err());
+ }
+}
diff --git a/engine/src/conversion/analysis/pod/mod.rs b/engine/src/conversion/analysis/pod/mod.rs
new file mode 100644
index 0000000..6722c23
--- /dev/null
+++ b/engine/src/conversion/analysis/pod/mod.rs
@@ -0,0 +1,257 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+mod byvalue_checker;
+
+use indexmap::map::IndexMap as HashMap;
+use indexmap::set::IndexSet as HashSet;
+
+use autocxx_parser::IncludeCppConfig;
+use byvalue_checker::ByValueChecker;
+use syn::{ItemEnum, ItemStruct, Type, Visibility};
+
+use crate::{
+ conversion::{
+ analysis::type_converter::{self, add_analysis, TypeConversionContext, TypeConverter},
+ api::{AnalysisPhase, Api, ApiName, CppVisibility, NullPhase, StructDetails, TypeKind},
+ apivec::ApiVec,
+ convert_error::{ConvertErrorWithContext, ErrorContext},
+ error_reporter::convert_apis,
+ parse::BindgenSemanticAttributes,
+ ConvertError,
+ },
+ types::{Namespace, QualifiedName},
+};
+
+use super::tdef::{TypedefAnalysis, TypedefPhase};
+
+pub(crate) struct FieldInfo {
+ pub(crate) ty: Type,
+ pub(crate) type_kind: type_converter::TypeKind,
+}
+
+pub(crate) struct PodAnalysis {
+ pub(crate) kind: TypeKind,
+ pub(crate) bases: HashSet<QualifiedName>,
+ /// Base classes for which we should create casts.
+ /// That's just those which are on the allowlist,
+ /// because otherwise we don't know whether they're
+ /// abstract or not.
+ pub(crate) castable_bases: HashSet<QualifiedName>,
+ pub(crate) field_deps: HashSet<QualifiedName>,
+ pub(crate) field_info: Vec<FieldInfo>,
+ pub(crate) is_generic: bool,
+ pub(crate) in_anonymous_namespace: bool,
+}
+
+pub(crate) struct PodPhase;
+
+impl AnalysisPhase for PodPhase {
+ type TypedefAnalysis = TypedefAnalysis;
+ type StructAnalysis = PodAnalysis;
+ type FunAnalysis = ();
+}
+
+/// In our set of APIs, work out which ones are safe to represent
+/// by value in Rust (e.g. they don't have a destructor) and record
+/// as such. Return a set of APIs annotated with extra metadata,
+/// and an object which can be used to query the POD status of any
+/// type whether or not it's one of the [Api]s.
+pub(crate) fn analyze_pod_apis(
+ apis: ApiVec<TypedefPhase>,
+ config: &IncludeCppConfig,
+) -> Result<ApiVec<PodPhase>, ConvertError> {
+ // This next line will return an error if any of the 'generate_pod'
+ // directives from the user can't be met because, for instance,
+ // a type contains a std::string or some other type which can't be
+ // held safely by value in Rust.
+ let byvalue_checker = ByValueChecker::new_from_apis(&apis, config)?;
+ let mut extra_apis = ApiVec::new();
+ let mut type_converter = TypeConverter::new(config, &apis);
+ let mut results = ApiVec::new();
+ convert_apis(
+ apis,
+ &mut results,
+ Api::fun_unchanged,
+ |name, details, _| {
+ analyze_struct(
+ &byvalue_checker,
+ &mut type_converter,
+ &mut extra_apis,
+ name,
+ details,
+ config,
+ )
+ },
+ analyze_enum,
+ Api::typedef_unchanged,
+ );
+ // Conceivably, the process of POD-analysing the first set of APIs could result
+ // in us creating new APIs to concretize generic types.
+ let extra_apis: ApiVec<PodPhase> = extra_apis.into_iter().map(add_analysis).collect();
+ let mut more_extra_apis = ApiVec::new();
+ convert_apis(
+ extra_apis,
+ &mut results,
+ Api::fun_unchanged,
+ |name, details, _| {
+ analyze_struct(
+ &byvalue_checker,
+ &mut type_converter,
+ &mut more_extra_apis,
+ name,
+ details,
+ config,
+ )
+ },
+ analyze_enum,
+ Api::typedef_unchanged,
+ );
+ assert!(more_extra_apis.is_empty());
+ Ok(results)
+}
+
+fn analyze_enum(
+ name: ApiName,
+ mut item: ItemEnum,
+) -> Result<Box<dyn Iterator<Item = Api<PodPhase>>>, ConvertErrorWithContext> {
+ let metadata = BindgenSemanticAttributes::new_retaining_others(&mut item.attrs);
+ metadata.check_for_fatal_attrs(&name.name.get_final_ident())?;
+ Ok(Box::new(std::iter::once(Api::Enum { name, item })))
+}
+
+fn analyze_struct(
+ byvalue_checker: &ByValueChecker,
+ type_converter: &mut TypeConverter,
+ extra_apis: &mut ApiVec<NullPhase>,
+ name: ApiName,
+ mut details: Box<StructDetails>,
+ config: &IncludeCppConfig,
+) -> Result<Box<dyn Iterator<Item = Api<PodPhase>>>, ConvertErrorWithContext> {
+ let id = name.name.get_final_ident();
+ if details.vis != CppVisibility::Public {
+ return Err(ConvertErrorWithContext(
+ ConvertError::NonPublicNestedType,
+ Some(ErrorContext::new_for_item(id)),
+ ));
+ }
+ let metadata = BindgenSemanticAttributes::new_retaining_others(&mut details.item.attrs);
+ metadata.check_for_fatal_attrs(&id)?;
+ let bases = get_bases(&details.item);
+ let mut field_deps = HashSet::new();
+ let mut field_info = Vec::new();
+ let field_conversion_errors = get_struct_field_types(
+ type_converter,
+ name.name.get_namespace(),
+ &details.item,
+ &mut field_deps,
+ &mut field_info,
+ extra_apis,
+ );
+ let type_kind = if byvalue_checker.is_pod(&name.name) {
+ // It's POD so any errors encountered parsing its fields are important.
+ // Let's not allow anything to be POD if it's got rvalue reference fields.
+ if details.has_rvalue_reference_fields {
+ return Err(ConvertErrorWithContext(
+ ConvertError::RValueReferenceField,
+ Some(ErrorContext::new_for_item(id)),
+ ));
+ }
+ if let Some(err) = field_conversion_errors.into_iter().next() {
+ return Err(ConvertErrorWithContext(
+ err,
+ Some(ErrorContext::new_for_item(id)),
+ ));
+ }
+ TypeKind::Pod
+ } else {
+ TypeKind::NonPod
+ };
+ let castable_bases = bases
+ .iter()
+ .filter(|(_, is_public)| **is_public)
+ .map(|(base, _)| base)
+ .filter(|base| config.is_on_allowlist(&base.to_cpp_name()))
+ .cloned()
+ .collect();
+ let is_generic = !details.item.generics.params.is_empty();
+ let in_anonymous_namespace = name
+ .name
+ .ns_segment_iter()
+ .any(|ns| ns.starts_with("_bindgen_mod"));
+ Ok(Box::new(std::iter::once(Api::Struct {
+ name,
+ details,
+ analysis: PodAnalysis {
+ kind: type_kind,
+ bases: bases.into_keys().collect(),
+ castable_bases,
+ field_deps,
+ field_info,
+ is_generic,
+ in_anonymous_namespace,
+ },
+ })))
+}
+
+fn get_struct_field_types(
+ type_converter: &mut TypeConverter,
+ ns: &Namespace,
+ s: &ItemStruct,
+ field_deps: &mut HashSet<QualifiedName>,
+ field_info: &mut Vec<FieldInfo>,
+ extra_apis: &mut ApiVec<NullPhase>,
+) -> Vec<ConvertError> {
+ let mut convert_errors = Vec::new();
+ for f in &s.fields {
+ let annotated =
+ type_converter.convert_type(f.ty.clone(), ns, &TypeConversionContext::WithinReference);
+ match annotated {
+ Ok(mut r) => {
+ extra_apis.append(&mut r.extra_apis);
+ // Skip base classes represented as fields. Anything which wants to include bases can chain
+ // those to the list we're building.
+ if !f
+ .ident
+ .as_ref()
+ .map(|id| {
+ id.to_string().starts_with("_base")
+ || id.to_string().starts_with("__bindgen_padding")
+ })
+ .unwrap_or(false)
+ {
+ field_deps.extend(r.types_encountered);
+ field_info.push(FieldInfo {
+ ty: r.ty,
+ type_kind: r.kind,
+ });
+ }
+ }
+ Err(e) => convert_errors.push(e),
+ };
+ }
+ convert_errors
+}
+
+/// Map to whether the bases are public.
+fn get_bases(item: &ItemStruct) -> HashMap<QualifiedName, bool> {
+ item.fields
+ .iter()
+ .filter_map(|f| {
+ let is_public = matches!(f.vis, Visibility::Public(_));
+ match &f.ty {
+ Type::Path(typ) => f
+ .ident
+ .as_ref()
+ .filter(|id| id.to_string().starts_with("_base"))
+ .map(|_| (QualifiedName::from_type_path(typ), is_public)),
+ _ => None,
+ }
+ })
+ .collect()
+}
diff --git a/engine/src/conversion/analysis/remove_ignored.rs b/engine/src/conversion/analysis/remove_ignored.rs
new file mode 100644
index 0000000..bd11b13
--- /dev/null
+++ b/engine/src/conversion/analysis/remove_ignored.rs
@@ -0,0 +1,95 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::set::IndexSet as HashSet;
+
+use super::deps::HasDependencies;
+use super::fun::{FnAnalysis, FnKind, FnPhase};
+use crate::conversion::apivec::ApiVec;
+use crate::conversion::{convert_error::ErrorContext, ConvertError};
+use crate::{conversion::api::Api, known_types};
+
+/// Remove any APIs which depend on other items which have been ignored.
+/// We also eliminate any APIs that depend on some type that we just don't
+/// know about at all. In either case, we don't simply remove the type, but instead
+/// replace it with an error marker.
+pub(crate) fn filter_apis_by_ignored_dependents(mut apis: ApiVec<FnPhase>) -> ApiVec<FnPhase> {
+ let (ignored_items, valid_items): (Vec<&Api<_>>, Vec<&Api<_>>) = apis
+ .iter()
+ .partition(|api| matches!(api, Api::IgnoredItem { .. }));
+ let mut ignored_items: HashSet<_> = ignored_items
+ .into_iter()
+ .map(|api| api.name().clone())
+ .collect();
+ let valid_types: HashSet<_> = valid_items
+ .into_iter()
+ .flat_map(|api| api.valid_types())
+ .collect();
+ let mut iterate_again = true;
+ while iterate_again {
+ iterate_again = false;
+ apis = apis
+ .into_iter()
+ .map(|api| {
+ let ignored_dependents: HashSet<_> = api
+ .deps()
+ .filter(|dep| ignored_items.contains(*dep))
+ .cloned()
+ .collect();
+ if !ignored_dependents.is_empty() {
+ iterate_again = true;
+ ignored_items.insert(api.name().clone());
+ create_ignore_item(api, ConvertError::IgnoredDependent(ignored_dependents))
+ } else {
+ let mut missing_deps = api.deps().filter(|dep| {
+ !valid_types.contains(*dep) && !known_types().is_known_type(dep)
+ });
+ let first = missing_deps.next();
+ std::mem::drop(missing_deps);
+ if let Some(missing_dep) = first.cloned() {
+ create_ignore_item(api, ConvertError::UnknownDependentType(missing_dep))
+ } else {
+ api
+ }
+ }
+ })
+ .collect();
+ }
+ apis
+}
+
+fn create_ignore_item(api: Api<FnPhase>, err: ConvertError) -> Api<FnPhase> {
+ let id = api.name().get_final_ident();
+ log::info!("Marking as ignored: {} because {}", id.to_string(), err);
+ Api::IgnoredItem {
+ name: api.name_info().clone(),
+ err,
+ ctx: match api {
+ Api::Function {
+ analysis:
+ FnAnalysis {
+ kind: FnKind::TraitMethod { .. },
+ ..
+ },
+ ..
+ } => None,
+ Api::Function {
+ analysis:
+ FnAnalysis {
+ kind:
+ FnKind::Method {
+ impl_for: self_ty, ..
+ },
+ ..
+ },
+ ..
+ } => Some(ErrorContext::new_for_method(self_ty.get_final_ident(), id)),
+ _ => Some(ErrorContext::new_for_item(id)),
+ },
+ }
+}
diff --git a/engine/src/conversion/analysis/replace_hopeless_typedef_targets.rs b/engine/src/conversion/analysis/replace_hopeless_typedef_targets.rs
new file mode 100644
index 0000000..8d5d033
--- /dev/null
+++ b/engine/src/conversion/analysis/replace_hopeless_typedef_targets.rs
@@ -0,0 +1,100 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use autocxx_parser::IncludeCppConfig;
+use indexmap::set::IndexSet as HashSet;
+
+use crate::{
+ conversion::{
+ analysis::tdef::TypedefAnalysis,
+ api::Api,
+ apivec::ApiVec,
+ convert_error::{ConvertErrorWithContext, ErrorContext},
+ ConvertError,
+ },
+ types::QualifiedName,
+};
+
+use super::pod::PodPhase;
+/// Where we find a typedef pointing at something we can't represent,
+/// e.g. because it uses too many template parameters, break the link.
+/// Use the typedef as a first-class type.
+pub(crate) fn replace_hopeless_typedef_targets(
+ config: &IncludeCppConfig,
+ apis: ApiVec<PodPhase>,
+) -> ApiVec<PodPhase> {
+ let ignored_types: HashSet<QualifiedName> = apis
+ .iter()
+ .filter_map(|api| match api {
+ Api::IgnoredItem { .. } => Some(api.name()),
+ _ => None,
+ })
+ .cloned()
+ .collect();
+ let ignored_forward_declarations: HashSet<QualifiedName> = apis
+ .iter()
+ .filter_map(|api| match api {
+ Api::ForwardDeclaration { err: Some(_), .. } => Some(api.name()),
+ _ => None,
+ })
+ .cloned()
+ .collect();
+ // Convert any Typedefs which depend on these things into OpaqueTypedefs
+ // instead.
+ // And, after this point we no longer need special knowledge of forward
+ // declarations with errors, so just convert them into regular IgnoredItems too.
+ apis.into_iter()
+ .map(|api| match api {
+ Api::Typedef {
+ ref name,
+ analysis: TypedefAnalysis { ref deps, .. },
+ ..
+ } if !ignored_types.is_disjoint(deps) =>
+ // This typedef depended on something we ignored.
+ // Ideally, we'd turn it into an opaque item.
+ // We can't do that if this is an inner type,
+ // because we have no way to know if it's abstract or not,
+ // and we can't represent inner types in cxx without knowing
+ // that.
+ {
+ let name_id = name.name.get_final_ident();
+ if api
+ .cpp_name()
+ .as_ref()
+ .map(|n| n.contains("::"))
+ .unwrap_or_default()
+ {
+ Api::IgnoredItem {
+ name: api.name_info().clone(),
+ err: ConvertError::NestedOpaqueTypedef,
+ ctx: Some(ErrorContext::new_for_item(name_id)),
+ }
+ } else {
+ Api::OpaqueTypedef {
+ name: api.name_info().clone(),
+ forward_declaration: !config
+ .instantiable
+ .contains(&name.name.to_cpp_name()),
+ }
+ }
+ }
+ Api::Typedef {
+ analysis: TypedefAnalysis { ref deps, .. },
+ ..
+ } if !ignored_forward_declarations.is_disjoint(deps) => Api::OpaqueTypedef {
+ name: api.name_info().clone(),
+ forward_declaration: true,
+ },
+ Api::ForwardDeclaration {
+ name,
+ err: Some(ConvertErrorWithContext(err, ctx)),
+ } => Api::IgnoredItem { name, err, ctx },
+ _ => api,
+ })
+ .collect()
+}
diff --git a/engine/src/conversion/analysis/tdef.rs b/engine/src/conversion/analysis/tdef.rs
new file mode 100644
index 0000000..5a635f8
--- /dev/null
+++ b/engine/src/conversion/analysis/tdef.rs
@@ -0,0 +1,128 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use indexmap::set::IndexSet as HashSet;
+
+use autocxx_parser::IncludeCppConfig;
+use syn::ItemType;
+
+use crate::{
+ conversion::{
+ analysis::type_converter::{add_analysis, Annotated, TypeConversionContext, TypeConverter},
+ api::{AnalysisPhase, Api, ApiName, NullPhase, TypedefKind},
+ apivec::ApiVec,
+ convert_error::{ConvertErrorWithContext, ErrorContext},
+ error_reporter::convert_apis,
+ parse::BindgenSemanticAttributes,
+ ConvertError,
+ },
+ types::QualifiedName,
+};
+
+pub(crate) struct TypedefAnalysis {
+ pub(crate) kind: TypedefKind,
+ pub(crate) deps: HashSet<QualifiedName>,
+}
+
+/// Analysis phase where typedef analysis has been performed but no other
+/// analyses just yet.
+pub(crate) struct TypedefPhase;
+
+impl AnalysisPhase for TypedefPhase {
+ type TypedefAnalysis = TypedefAnalysis;
+ type StructAnalysis = ();
+ type FunAnalysis = ();
+}
+
+#[allow(clippy::needless_collect)] // we need the extra collect because the closure borrows extra_apis
+pub(crate) fn convert_typedef_targets(
+ config: &IncludeCppConfig,
+ apis: ApiVec<NullPhase>,
+) -> ApiVec<TypedefPhase> {
+ let mut type_converter = TypeConverter::new(config, &apis);
+ let mut extra_apis = ApiVec::new();
+ let mut results = ApiVec::new();
+ convert_apis(
+ apis,
+ &mut results,
+ Api::fun_unchanged,
+ Api::struct_unchanged,
+ Api::enum_unchanged,
+ |name, item, old_tyname, _| {
+ Ok(Box::new(std::iter::once(match item {
+ TypedefKind::Type(ity) => get_replacement_typedef(
+ name,
+ ity,
+ old_tyname,
+ &mut type_converter,
+ &mut extra_apis,
+ )?,
+ TypedefKind::Use { .. } => Api::Typedef {
+ name,
+ item: item.clone(),
+ old_tyname,
+ analysis: TypedefAnalysis {
+ kind: item,
+ deps: HashSet::new(),
+ },
+ },
+ })))
+ },
+ );
+ results.extend(extra_apis.into_iter().map(add_analysis));
+ results
+}
+
+fn get_replacement_typedef(
+ name: ApiName,
+ ity: ItemType,
+ old_tyname: Option<QualifiedName>,
+ type_converter: &mut TypeConverter,
+ extra_apis: &mut ApiVec<NullPhase>,
+) -> Result<Api<TypedefPhase>, ConvertErrorWithContext> {
+ if !ity.generics.params.is_empty() {
+ return Err(ConvertErrorWithContext(
+ ConvertError::TypedefTakesGenericParameters,
+ Some(ErrorContext::new_for_item(name.name.get_final_ident())),
+ ));
+ }
+ let mut converted_type = ity.clone();
+ let metadata = BindgenSemanticAttributes::new_retaining_others(&mut converted_type.attrs);
+ metadata.check_for_fatal_attrs(&ity.ident)?;
+ let type_conversion_results = type_converter.convert_type(
+ (*ity.ty).clone(),
+ name.name.get_namespace(),
+ &TypeConversionContext::WithinReference,
+ );
+ match type_conversion_results {
+ Err(err) => Err(ConvertErrorWithContext(
+ err,
+ Some(ErrorContext::new_for_item(name.name.get_final_ident())),
+ )),
+ Ok(Annotated {
+ ty: syn::Type::Path(ref typ),
+ ..
+ }) if QualifiedName::from_type_path(typ) == name.name => Err(ConvertErrorWithContext(
+ ConvertError::InfinitelyRecursiveTypedef(name.name.clone()),
+ Some(ErrorContext::new_for_item(name.name.get_final_ident())),
+ )),
+ Ok(mut final_type) => {
+ converted_type.ty = Box::new(final_type.ty.clone());
+ extra_apis.append(&mut final_type.extra_apis);
+ Ok(Api::Typedef {
+ name,
+ item: TypedefKind::Type(ity),
+ old_tyname,
+ analysis: TypedefAnalysis {
+ kind: TypedefKind::Type(converted_type),
+ deps: final_type.types_encountered,
+ },
+ })
+ }
+ }
+}
diff --git a/engine/src/conversion/analysis/type_converter.rs b/engine/src/conversion/analysis/type_converter.rs
new file mode 100644
index 0000000..afdda8a
--- /dev/null
+++ b/engine/src/conversion/analysis/type_converter.rs
@@ -0,0 +1,686 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use crate::{
+ conversion::{
+ api::{AnalysisPhase, Api, ApiName, NullPhase, TypedefKind, UnanalyzedApi},
+ apivec::ApiVec,
+ codegen_cpp::type_to_cpp::type_to_cpp,
+ ConvertError,
+ },
+ known_types::{known_types, CxxGenericType},
+ types::{make_ident, Namespace, QualifiedName},
+};
+use autocxx_parser::IncludeCppConfig;
+use indexmap::map::IndexMap as HashMap;
+use indexmap::set::IndexSet as HashSet;
+use itertools::Itertools;
+use proc_macro2::Ident;
+use quote::ToTokens;
+use syn::{
+ parse_quote, punctuated::Punctuated, token::Comma, GenericArgument, PathArguments, PathSegment,
+ Type, TypePath, TypePtr,
+};
+
+use super::tdef::TypedefAnalysis;
+
+/// Certain kinds of type may require special handling by callers.
+#[derive(Debug)]
+pub(crate) enum TypeKind {
+ Regular,
+ Pointer,
+ SubclassHolder(Ident),
+ Reference,
+ RValueReference,
+ MutableReference,
+}
+
+/// Results of some type conversion, annotated with a list of every type encountered,
+/// and optionally any extra APIs we need in order to use this type.
+pub(crate) struct Annotated<T> {
+ pub(crate) ty: T,
+ pub(crate) types_encountered: HashSet<QualifiedName>,
+ pub(crate) extra_apis: ApiVec<NullPhase>,
+ pub(crate) kind: TypeKind,
+}
+
+impl<T> Annotated<T> {
+ fn new(
+ ty: T,
+ types_encountered: HashSet<QualifiedName>,
+ extra_apis: ApiVec<NullPhase>,
+ kind: TypeKind,
+ ) -> Self {
+ Self {
+ ty,
+ types_encountered,
+ extra_apis,
+ kind,
+ }
+ }
+
+ fn map<T2, F: FnOnce(T) -> T2>(self, fun: F) -> Annotated<T2> {
+ Annotated {
+ ty: fun(self.ty),
+ types_encountered: self.types_encountered,
+ extra_apis: self.extra_apis,
+ kind: self.kind,
+ }
+ }
+}
+
+/// How to interpret a pointer which we encounter during type conversion.
+#[derive(Clone, Copy)]
+pub(crate) enum PointerTreatment {
+ Pointer,
+ Reference,
+ RValueReference,
+}
+
+/// Options when converting a type.
+/// It's possible we could add more policies here in future.
+/// For example, Rust in general allows type names containing
+/// __, whereas cxx doesn't. If we could identify cases where
+/// a type will only ever be used in a bindgen context,
+/// we could be more liberal. At the moment though, all outputs
+/// from [TypeConverter] _might_ be used in the [cxx::bridge].
+pub(crate) enum TypeConversionContext {
+ WithinReference,
+ WithinContainer,
+ OuterType { pointer_treatment: PointerTreatment },
+}
+
+impl TypeConversionContext {
+ fn pointer_treatment(&self) -> PointerTreatment {
+ match self {
+ Self::WithinReference | Self::WithinContainer => PointerTreatment::Pointer,
+ Self::OuterType { pointer_treatment } => *pointer_treatment,
+ }
+ }
+ fn allow_instantiation_of_forward_declaration(&self) -> bool {
+ matches!(self, Self::WithinReference)
+ }
+}
+
+/// A type which can convert from a type encountered in `bindgen`
+/// output to the sort of type we should represeent to `cxx`.
+/// As a simple example, `std::string` should be replaced
+/// with [CxxString]. This also involves keeping track
+/// of typedefs, and any instantiated concrete types.
+///
+/// To do this conversion correctly, this type relies on
+/// inspecting the pre-existing list of APIs.
+pub(crate) struct TypeConverter<'a> {
+ types_found: HashSet<QualifiedName>,
+ typedefs: HashMap<QualifiedName, Type>,
+ concrete_templates: HashMap<String, QualifiedName>,
+ forward_declarations: HashSet<QualifiedName>,
+ ignored_types: HashSet<QualifiedName>,
+ config: &'a IncludeCppConfig,
+}
+
+impl<'a> TypeConverter<'a> {
+ pub(crate) fn new<A: AnalysisPhase>(config: &'a IncludeCppConfig, apis: &ApiVec<A>) -> Self
+ where
+ A::TypedefAnalysis: TypedefTarget,
+ {
+ Self {
+ types_found: find_types(apis),
+ typedefs: Self::find_typedefs(apis),
+ concrete_templates: Self::find_concrete_templates(apis),
+ forward_declarations: Self::find_incomplete_types(apis),
+ ignored_types: Self::find_ignored_types(apis),
+ config,
+ }
+ }
+
+ pub(crate) fn convert_boxed_type(
+ &mut self,
+ ty: Box<Type>,
+ ns: &Namespace,
+ ctx: &TypeConversionContext,
+ ) -> Result<Annotated<Box<Type>>, ConvertError> {
+ Ok(self.convert_type(*ty, ns, ctx)?.map(Box::new))
+ }
+
+ pub(crate) fn convert_type(
+ &mut self,
+ ty: Type,
+ ns: &Namespace,
+ ctx: &TypeConversionContext,
+ ) -> Result<Annotated<Type>, ConvertError> {
+ let result = match ty {
+ Type::Path(p) => {
+ let newp = self.convert_type_path(p, ns)?;
+ if let Type::Path(newpp) = &newp.ty {
+ let qn = QualifiedName::from_type_path(newpp);
+ if !ctx.allow_instantiation_of_forward_declaration()
+ && self.forward_declarations.contains(&qn)
+ {
+ return Err(ConvertError::TypeContainingForwardDeclaration(qn));
+ }
+ // Special handling because rust_Str (as emitted by bindgen)
+ // doesn't simply get renamed to a different type _identifier_.
+ // This plain type-by-value (as far as bindgen is concerned)
+ // is actually a &str.
+ if known_types().should_dereference_in_cpp(&qn) {
+ Annotated::new(
+ Type::Reference(parse_quote! {
+ &str
+ }),
+ newp.types_encountered,
+ newp.extra_apis,
+ TypeKind::Reference,
+ )
+ } else {
+ newp
+ }
+ } else {
+ newp
+ }
+ }
+ Type::Reference(mut r) => {
+ let innerty =
+ self.convert_boxed_type(r.elem, ns, &TypeConversionContext::WithinReference)?;
+ r.elem = innerty.ty;
+ Annotated::new(
+ Type::Reference(r),
+ innerty.types_encountered,
+ innerty.extra_apis,
+ TypeKind::Reference,
+ )
+ }
+ Type::Array(mut arr) => {
+ let innerty =
+ self.convert_type(*arr.elem, ns, &TypeConversionContext::WithinReference)?;
+ arr.elem = Box::new(innerty.ty);
+ Annotated::new(
+ Type::Array(arr),
+ innerty.types_encountered,
+ innerty.extra_apis,
+ TypeKind::Regular,
+ )
+ }
+ Type::Ptr(ptr) => self.convert_ptr(ptr, ns, ctx.pointer_treatment())?,
+ _ => return Err(ConvertError::UnknownType(ty.to_token_stream().to_string())),
+ };
+ Ok(result)
+ }
+
+ fn convert_type_path(
+ &mut self,
+ mut typ: TypePath,
+ ns: &Namespace,
+ ) -> Result<Annotated<Type>, ConvertError> {
+ // First, qualify any unqualified paths.
+ if typ.path.segments.iter().next().unwrap().ident != "root" {
+ let ty = QualifiedName::from_type_path(&typ);
+ // If the type looks like it is unqualified, check we know it
+ // already, and if not, qualify it according to the current
+ // namespace. This is a bit of a shortcut compared to having a full
+ // resolution pass which can search all known namespaces.
+ if !known_types().is_known_type(&ty) {
+ let num_segments = typ.path.segments.len();
+ if num_segments > 1 {
+ return Err(ConvertError::UnsupportedBuiltInType(ty));
+ }
+ if !self.types_found.contains(&ty) {
+ typ.path.segments = std::iter::once(&"root".to_string())
+ .chain(ns.iter())
+ .map(|s| {
+ let i = make_ident(s);
+ parse_quote! { #i }
+ })
+ .chain(typ.path.segments.into_iter())
+ .collect();
+ }
+ }
+ }
+
+ let original_tn = QualifiedName::from_type_path(&typ);
+ original_tn.validate_ok_for_cxx()?;
+ if self.config.is_on_blocklist(&original_tn.to_cpp_name()) {
+ return Err(ConvertError::Blocked(original_tn));
+ }
+ let mut deps = HashSet::new();
+
+ // Now convert this type itself.
+ deps.insert(original_tn.clone());
+ // First let's see if this is a typedef.
+ let (typ, tn) = match self.resolve_typedef(&original_tn)? {
+ None => (typ, original_tn),
+ Some(Type::Path(resolved_tp)) => {
+ let resolved_tn = QualifiedName::from_type_path(resolved_tp);
+ deps.insert(resolved_tn.clone());
+ (resolved_tp.clone(), resolved_tn)
+ }
+ Some(Type::Ptr(resolved_tp)) => {
+ return Ok(Annotated::new(
+ Type::Ptr(resolved_tp.clone()),
+ deps,
+ ApiVec::new(),
+ TypeKind::Pointer,
+ ))
+ }
+ Some(other) => {
+ return Ok(Annotated::new(
+ other.clone(),
+ deps,
+ ApiVec::new(),
+ TypeKind::Regular,
+ ))
+ }
+ };
+
+ // Now let's see if it's a known type.
+ // (We may entirely reject some types at this point too.)
+ let mut typ = match known_types().consider_substitution(&tn) {
+ Some(mut substitute_type) => {
+ if let Some(last_seg_args) =
+ typ.path.segments.into_iter().last().map(|ps| ps.arguments)
+ {
+ let last_seg = substitute_type.path.segments.last_mut().unwrap();
+ last_seg.arguments = last_seg_args;
+ }
+ substitute_type
+ }
+ None => typ,
+ };
+
+ let mut extra_apis = ApiVec::new();
+ let mut kind = TypeKind::Regular;
+
+ // Finally let's see if it's generic.
+ if let Some(last_seg) = Self::get_generic_args(&mut typ) {
+ let generic_behavior = known_types().cxx_generic_behavior(&tn);
+ let forward_declarations_ok = generic_behavior == CxxGenericType::Rust;
+ if generic_behavior != CxxGenericType::Not {
+ // this is a type of generic understood by cxx (e.g. CxxVector)
+ // so let's convert any generic type arguments. This recurses.
+ if let PathArguments::AngleBracketed(ref mut ab) = last_seg.arguments {
+ let mut innerty = self.convert_punctuated(
+ ab.args.clone(),
+ ns,
+ &TypeConversionContext::WithinContainer,
+ )?;
+ ab.args = innerty.ty;
+ kind = self.confirm_inner_type_is_acceptable_generic_payload(
+ &ab.args,
+ &tn,
+ generic_behavior,
+ forward_declarations_ok,
+ )?;
+ deps.extend(innerty.types_encountered.drain(..));
+ } else {
+ return Err(ConvertError::TemplatedTypeContainingNonPathArg(tn.clone()));
+ }
+ } else {
+ // Oh poop. It's a generic type which cxx won't be able to handle.
+ // We'll have to come up with a concrete type in both the cxx::bridge (in Rust)
+ // and a corresponding typedef in C++.
+ // Let's first see if this is a concrete version of a templated type
+ // which we already rejected. Some, but possibly not all, of the reasons
+ // for its rejection would also apply to any concrete types we
+ // make. Err on the side of caution. In future we may be able to relax
+ // this a bit.
+ let qn = QualifiedName::from_type_path(&typ); // ignores generic params
+ if self.ignored_types.contains(&qn) {
+ return Err(ConvertError::ConcreteVersionOfIgnoredTemplate);
+ }
+ let (new_tn, api) = self.get_templated_typename(&Type::Path(typ))?;
+ extra_apis.extend(api.into_iter());
+ deps.remove(&tn);
+ typ = new_tn.to_type_path();
+ deps.insert(new_tn);
+ }
+ }
+ Ok(Annotated::new(Type::Path(typ), deps, extra_apis, kind))
+ }
+
+ fn get_generic_args(typ: &mut TypePath) -> Option<&mut PathSegment> {
+ match typ.path.segments.last_mut() {
+ Some(s) if !s.arguments.is_empty() => Some(s),
+ _ => None,
+ }
+ }
+
+ fn convert_punctuated<P>(
+ &mut self,
+ pun: Punctuated<GenericArgument, P>,
+ ns: &Namespace,
+ ctx: &TypeConversionContext,
+ ) -> Result<Annotated<Punctuated<GenericArgument, P>>, ConvertError>
+ where
+ P: Default,
+ {
+ let mut new_pun = Punctuated::new();
+ let mut types_encountered = HashSet::new();
+ let mut extra_apis = ApiVec::new();
+ for arg in pun.into_iter() {
+ new_pun.push(match arg {
+ GenericArgument::Type(t) => {
+ let mut innerty = self.convert_type(t, ns, ctx)?;
+ types_encountered.extend(innerty.types_encountered.drain(..));
+ extra_apis.append(&mut innerty.extra_apis);
+ GenericArgument::Type(innerty.ty)
+ }
+ _ => arg,
+ });
+ }
+ Ok(Annotated::new(
+ new_pun,
+ types_encountered,
+ extra_apis,
+ TypeKind::Regular,
+ ))
+ }
+
+ fn resolve_typedef<'b>(&'b self, tn: &QualifiedName) -> Result<Option<&'b Type>, ConvertError> {
+ let mut encountered = HashSet::new();
+ let mut tn = tn.clone();
+ let mut previous_typ = None;
+ loop {
+ let r = self.typedefs.get(&tn);
+ match r {
+ Some(Type::Path(typ)) => {
+ previous_typ = r;
+ let new_tn = QualifiedName::from_type_path(typ);
+ if encountered.contains(&new_tn) {
+ return Err(ConvertError::InfinitelyRecursiveTypedef(tn.clone()));
+ }
+ encountered.insert(new_tn.clone());
+ tn = new_tn;
+ }
+ None => return Ok(previous_typ),
+ _ => return Ok(r),
+ }
+ }
+ }
+
+ fn convert_ptr(
+ &mut self,
+ mut ptr: TypePtr,
+ ns: &Namespace,
+ pointer_treatment: PointerTreatment,
+ ) -> Result<Annotated<Type>, ConvertError> {
+ match pointer_treatment {
+ PointerTreatment::Pointer => {
+ crate::known_types::ensure_pointee_is_valid(&ptr)?;
+ let innerty =
+ self.convert_boxed_type(ptr.elem, ns, &TypeConversionContext::WithinReference)?;
+ ptr.elem = innerty.ty;
+ Ok(Annotated::new(
+ Type::Ptr(ptr),
+ innerty.types_encountered,
+ innerty.extra_apis,
+ TypeKind::Pointer,
+ ))
+ }
+ PointerTreatment::Reference => {
+ let mutability = ptr.mutability;
+ let elem =
+ self.convert_boxed_type(ptr.elem, ns, &TypeConversionContext::WithinReference)?;
+ // TODO - in the future, we should check if this is a rust::Str and throw
+ // a wobbler if not. rust::Str should only be seen _by value_ in C++
+ // headers; it manifests as &str in Rust but on the C++ side it must
+ // be a plain value. We should detect and abort.
+ let mut outer = elem.map(|elem| match mutability {
+ Some(_) => Type::Path(parse_quote! {
+ ::std::pin::Pin < & #mutability #elem >
+ }),
+ None => Type::Reference(parse_quote! {
+ & #elem
+ }),
+ });
+ outer.kind = if mutability.is_some() {
+ TypeKind::MutableReference
+ } else {
+ TypeKind::Reference
+ };
+ Ok(outer)
+ }
+ PointerTreatment::RValueReference => {
+ crate::known_types::ensure_pointee_is_valid(&ptr)?;
+ let innerty =
+ self.convert_boxed_type(ptr.elem, ns, &TypeConversionContext::WithinReference)?;
+ ptr.elem = innerty.ty;
+ Ok(Annotated::new(
+ Type::Ptr(ptr),
+ innerty.types_encountered,
+ innerty.extra_apis,
+ TypeKind::RValueReference,
+ ))
+ }
+ }
+ }
+
+ fn get_templated_typename(
+ &mut self,
+ rs_definition: &Type,
+ ) -> Result<(QualifiedName, Option<UnanalyzedApi>), ConvertError> {
+ let count = self.concrete_templates.len();
+ // We just use this as a hash key, essentially.
+ // TODO: Once we've completed the TypeConverter refactoring (see #220),
+ // pass in an actual original_name_map here.
+ let cpp_definition = type_to_cpp(rs_definition, &HashMap::new())?;
+ let e = self.concrete_templates.get(&cpp_definition);
+ match e {
+ Some(tn) => Ok((tn.clone(), None)),
+ None => {
+ let synthetic_ident = format!(
+ "{}_AutocxxConcrete",
+ cpp_definition.replace(|c: char| !(c.is_ascii_alphanumeric() || c == '_'), "_")
+ );
+ // Remove runs of multiple _s. Trying to avoid a dependency on
+ // regex.
+ let synthetic_ident = synthetic_ident
+ .split('_')
+ .filter(|s| !s.is_empty())
+ .join("_");
+ // Ensure we're not duplicating some existing concrete template name.
+ // If so, we'll invent a name which is guaranteed to be unique.
+ let synthetic_ident = match self
+ .concrete_templates
+ .values()
+ .map(|n| n.get_final_item())
+ .find(|s| s == &synthetic_ident)
+ {
+ None => synthetic_ident,
+ Some(_) => format!("AutocxxConcrete{}", count),
+ };
+ let api = UnanalyzedApi::ConcreteType {
+ name: ApiName::new_in_root_namespace(make_ident(&synthetic_ident)),
+ cpp_definition: cpp_definition.clone(),
+ rs_definition: Some(Box::new(rs_definition.clone())),
+ };
+ self.concrete_templates
+ .insert(cpp_definition, api.name().clone());
+ Ok((api.name().clone(), Some(api)))
+ }
+ }
+ }
+
+ fn confirm_inner_type_is_acceptable_generic_payload(
+ &self,
+ path_args: &Punctuated<GenericArgument, Comma>,
+ desc: &QualifiedName,
+ generic_behavior: CxxGenericType,
+ forward_declarations_ok: bool,
+ ) -> Result<TypeKind, ConvertError> {
+ for inner in path_args {
+ match inner {
+ GenericArgument::Type(Type::Path(typ)) => {
+ let inner_qn = QualifiedName::from_type_path(typ);
+ if !forward_declarations_ok && self.forward_declarations.contains(&inner_qn) {
+ return Err(ConvertError::TypeContainingForwardDeclaration(inner_qn));
+ }
+ match generic_behavior {
+ CxxGenericType::Rust => {
+ if !inner_qn.get_namespace().is_empty() {
+ return Err(ConvertError::RustTypeWithAPath(inner_qn));
+ }
+ if !self.config.is_rust_type(&inner_qn.get_final_ident()) {
+ return Err(ConvertError::BoxContainingNonRustType(inner_qn));
+ }
+ if self
+ .config
+ .is_subclass_holder(&inner_qn.get_final_ident().to_string())
+ {
+ return Ok(TypeKind::SubclassHolder(inner_qn.get_final_ident()));
+ } else {
+ return Ok(TypeKind::Regular);
+ }
+ }
+ CxxGenericType::CppPtr => {
+ if !known_types().permissible_within_unique_ptr(&inner_qn) {
+ return Err(ConvertError::InvalidTypeForCppPtr(inner_qn));
+ }
+ }
+ CxxGenericType::CppVector => {
+ if !known_types().permissible_within_vector(&inner_qn) {
+ return Err(ConvertError::InvalidTypeForCppVector(inner_qn));
+ }
+ if matches!(
+ typ.path.segments.last().map(|ps| &ps.arguments),
+ Some(
+ PathArguments::Parenthesized(_)
+ | PathArguments::AngleBracketed(_)
+ )
+ ) {
+ return Err(ConvertError::GenericsWithinVector);
+ }
+ }
+ _ => {}
+ }
+ }
+ _ => {
+ return Err(ConvertError::TemplatedTypeContainingNonPathArg(
+ desc.clone(),
+ ))
+ }
+ }
+ }
+ Ok(TypeKind::Regular)
+ }
+
+ fn find_typedefs<A: AnalysisPhase>(apis: &ApiVec<A>) -> HashMap<QualifiedName, Type>
+ where
+ A::TypedefAnalysis: TypedefTarget,
+ {
+ apis.iter()
+ .filter_map(|api| match &api {
+ Api::Typedef { analysis, .. } => analysis
+ .get_target()
+ .cloned()
+ .map(|ty| (api.name().clone(), ty)),
+ _ => None,
+ })
+ .collect()
+ }
+
+ fn find_concrete_templates<A: AnalysisPhase>(
+ apis: &ApiVec<A>,
+ ) -> HashMap<String, QualifiedName> {
+ apis.iter()
+ .filter_map(|api| match &api {
+ Api::ConcreteType { cpp_definition, .. } => {
+ Some((cpp_definition.clone(), api.name().clone()))
+ }
+ _ => None,
+ })
+ .collect()
+ }
+
+ fn find_incomplete_types<A: AnalysisPhase>(apis: &ApiVec<A>) -> HashSet<QualifiedName> {
+ apis.iter()
+ .filter_map(|api| match api {
+ Api::ForwardDeclaration { .. }
+ | Api::OpaqueTypedef {
+ forward_declaration: true,
+ ..
+ } => Some(api.name()),
+ _ => None,
+ })
+ .cloned()
+ .collect()
+ }
+
+ fn find_ignored_types<A: AnalysisPhase>(apis: &ApiVec<A>) -> HashSet<QualifiedName> {
+ apis.iter()
+ .filter_map(|api| match api {
+ Api::IgnoredItem { .. } => Some(api.name()),
+ _ => None,
+ })
+ .cloned()
+ .collect()
+ }
+}
+
+/// Processing functions sometimes results in new types being materialized.
+/// These types haven't been through the analysis phases (chicken and egg
+/// problem) but fortunately, don't need to. We need to keep the type
+/// system happy by adding an [ApiAnalysis] but in practice, for the sorts
+/// of things that get created, it's always blank.
+pub(crate) fn add_analysis<A: AnalysisPhase>(api: UnanalyzedApi) -> Api<A> {
+ match api {
+ Api::ConcreteType {
+ name,
+ rs_definition,
+ cpp_definition,
+ } => Api::ConcreteType {
+ name,
+ rs_definition,
+ cpp_definition,
+ },
+ Api::IgnoredItem { name, err, ctx } => Api::IgnoredItem { name, err, ctx },
+ _ => panic!("Function analysis created an unexpected type of extra API"),
+ }
+}
+pub(crate) trait TypedefTarget {
+ fn get_target(&self) -> Option<&Type>;
+}
+
+impl TypedefTarget for () {
+ fn get_target(&self) -> Option<&Type> {
+ None
+ }
+}
+
+impl TypedefTarget for TypedefAnalysis {
+ fn get_target(&self) -> Option<&Type> {
+ Some(match self.kind {
+ TypedefKind::Type(ref ty) => &ty.ty,
+ TypedefKind::Use(_, ref ty) => ty,
+ })
+ }
+}
+
+pub(crate) fn find_types<A: AnalysisPhase>(apis: &ApiVec<A>) -> HashSet<QualifiedName> {
+ apis.iter()
+ .filter_map(|api| match api {
+ Api::ForwardDeclaration { .. }
+ | Api::OpaqueTypedef { .. }
+ | Api::ConcreteType { .. }
+ | Api::Typedef { .. }
+ | Api::Enum { .. }
+ | Api::Struct { .. }
+ | Api::Subclass { .. }
+ | Api::ExternCppType { .. }
+ | Api::RustType { .. } => Some(api.name()),
+ Api::StringConstructor { .. }
+ | Api::Function { .. }
+ | Api::Const { .. }
+ | Api::CType { .. }
+ | Api::RustSubclassFn { .. }
+ | Api::IgnoredItem { .. }
+ | Api::SubclassTraitItem { .. }
+ | Api::RustFn { .. } => None,
+ })
+ .cloned()
+ .collect()
+}