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/ast_discoverer.rs b/engine/src/ast_discoverer.rs
new file mode 100644
index 0000000..8840899
--- /dev/null
+++ b/engine/src/ast_discoverer.rs
@@ -0,0 +1,726 @@
+// 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::{
+ directive_names::{EXTERN_RUST_FUN, EXTERN_RUST_TYPE},
+ RustFun, RustPath,
+};
+use itertools::Itertools;
+use proc_macro2::Ident;
+use syn::{
+ parse_quote, punctuated::Punctuated, Attribute, Binding, Expr, ExprAssign, ExprAssignOp,
+ ExprAwait, ExprBinary, ExprBox, ExprBreak, ExprCast, ExprField, ExprGroup, ExprLet, ExprParen,
+ ExprReference, ExprTry, ExprType, ExprUnary, ImplItem, Item, ItemEnum, ItemStruct, Pat, PatBox,
+ PatReference, PatSlice, PatTuple, Path, Receiver, ReturnType, Signature, Stmt, TraitItem, Type,
+ TypeArray, TypeGroup, TypeParamBound, TypeParen, TypePtr, TypeReference, TypeSlice,
+};
+use thiserror::Error;
+
+#[derive(Default)]
+pub(super) struct Discoveries {
+ pub(super) cpp_list: HashSet<String>,
+ pub(super) extern_rust_funs: Vec<RustFun>,
+ pub(super) extern_rust_types: Vec<RustPath>,
+}
+
+#[derive(Error, Debug)]
+pub enum DiscoveryErr {
+ #[error("#[extern_rust_function] was attached to a method in an impl block that was too complex for autocxx. autocxx supports only \"impl X {{...}}\" where X is a single identifier, not a path or more complex type.")]
+ FoundExternRustFunOnTypeWithoutClearReceiver,
+ #[error("#[extern_rust_function] was attached to a method taking no parameters.")]
+ NonReferenceReceiver,
+ #[error("#[extern_rust_function] was attached to a method taking a receiver by value.")]
+ NoParameterOnMethod,
+ #[error("#[extern_rust_function] was in an impl block nested wihtin another block. This is only supported in the outermost mod of a file, alongside the include_cpp!.")]
+ FoundExternRustFunWithinMod,
+}
+
+impl Discoveries {
+ pub(super) fn search_item(
+ &mut self,
+ item: &Item,
+ mod_path: Option<RustPath>,
+ ) -> Result<(), DiscoveryErr> {
+ let mut this_mod = PerModDiscoveries {
+ discoveries: self,
+ mod_path,
+ };
+ this_mod.search_item(item)
+ }
+
+ pub(crate) fn found_allowlist(&self) -> bool {
+ !self.cpp_list.is_empty()
+ }
+
+ pub(crate) fn found_rust(&self) -> bool {
+ !self.extern_rust_funs.is_empty() || !self.extern_rust_types.is_empty()
+ }
+
+ pub(crate) fn extend(&mut self, other: Self) {
+ self.cpp_list.extend(other.cpp_list);
+ self.extern_rust_funs.extend(other.extern_rust_funs);
+ self.extern_rust_types.extend(other.extern_rust_types);
+ }
+}
+
+struct PerModDiscoveries<'a> {
+ discoveries: &'a mut Discoveries,
+ mod_path: Option<RustPath>,
+}
+
+impl<'b> PerModDiscoveries<'b> {
+ fn deeper_path(&self, id: &Ident) -> RustPath {
+ match &self.mod_path {
+ None => RustPath::new_from_ident(id.clone()),
+ Some(mod_path) => mod_path.append(id.clone()),
+ }
+ }
+
+ fn search_item(&mut self, item: &Item) -> Result<(), DiscoveryErr> {
+ match item {
+ Item::Fn(fun) => {
+ for stmt in &fun.block.stmts {
+ self.search_stmt(stmt)?
+ }
+ self.search_return_type(&fun.sig.output)?;
+ for i in &fun.sig.inputs {
+ match i {
+ syn::FnArg::Receiver(_) => {}
+ syn::FnArg::Typed(pt) => {
+ self.search_pat(&pt.pat)?;
+ self.search_type(&pt.ty)?;
+ }
+ }
+ }
+ if Self::has_attr(&fun.attrs, EXTERN_RUST_FUN) {
+ self.discoveries.extern_rust_funs.push(RustFun {
+ path: self.deeper_path(&fun.sig.ident),
+ sig: fun.sig.clone(),
+ receiver: None,
+ });
+ }
+ }
+ Item::Impl(imp) => {
+ let receiver = match imp.trait_ {
+ // We do not allow 'extern_rust_fun' on trait impls
+ Some(_) => None,
+ None => match &*imp.self_ty {
+ Type::Path(typ) => {
+ let mut segs = typ.path.segments.iter();
+ let id = segs.next();
+ if let Some(seg) = id {
+ if segs.next().is_some() {
+ None
+ } else {
+ Some(self.deeper_path(&seg.ident))
+ }
+ } else {
+ None
+ }
+ }
+ _ => None,
+ },
+ };
+ for item in &imp.items {
+ self.search_impl_item(item, receiver.as_ref())?
+ }
+ }
+ Item::Mod(md) => {
+ if let Some((_, items)) = &md.content {
+ let mod_path = Some(self.deeper_path(&md.ident));
+ let mut new_mod = PerModDiscoveries {
+ discoveries: self.discoveries,
+ mod_path,
+ };
+ for item in items {
+ new_mod.search_item(item)?
+ }
+ }
+ }
+ Item::Trait(tr) => {
+ for item in &tr.items {
+ self.search_trait_item(item)?
+ }
+ }
+ Item::Struct(ItemStruct { ident, attrs, .. })
+ | Item::Enum(ItemEnum { ident, attrs, .. })
+ if Self::has_attr(attrs, EXTERN_RUST_TYPE) =>
+ {
+ self.discoveries
+ .extern_rust_types
+ .push(self.deeper_path(ident));
+ }
+ _ => {}
+ }
+ Ok(())
+ }
+
+ fn search_path(&mut self, path: &Path) -> Result<(), DiscoveryErr> {
+ let mut seg_iter = path.segments.iter();
+ if let Some(first_seg) = seg_iter.next() {
+ if first_seg.ident == "ffi" {
+ self.discoveries
+ .cpp_list
+ .insert(seg_iter.map(|seg| seg.ident.to_string()).join("::"));
+ }
+ }
+ for seg in path.segments.iter() {
+ self.search_path_arguments(&seg.arguments)?;
+ }
+ Ok(())
+ }
+
+ fn search_trait_item(&mut self, itm: &TraitItem) -> Result<(), DiscoveryErr> {
+ if let TraitItem::Method(itm) = itm {
+ if let Some(block) = &itm.default {
+ self.search_stmts(block.stmts.iter())?
+ }
+ }
+ Ok(())
+ }
+
+ fn search_stmts<'a>(
+ &mut self,
+ stmts: impl Iterator<Item = &'a Stmt>,
+ ) -> Result<(), DiscoveryErr> {
+ for stmt in stmts {
+ self.search_stmt(stmt)?
+ }
+ Ok(())
+ }
+
+ fn search_stmt(&mut self, stmt: &Stmt) -> Result<(), DiscoveryErr> {
+ match stmt {
+ Stmt::Local(lcl) => {
+ if let Some((_, expr)) = &lcl.init {
+ self.search_expr(expr)?
+ }
+ self.search_pat(&lcl.pat)
+ }
+ Stmt::Item(itm) => self.search_item(itm),
+ Stmt::Expr(exp) | Stmt::Semi(exp, _) => self.search_expr(exp),
+ }
+ }
+
+ fn search_expr(&mut self, expr: &Expr) -> Result<(), DiscoveryErr> {
+ match expr {
+ Expr::Path(exp) => {
+ self.search_path(&exp.path)?;
+ }
+ Expr::Macro(_) => {}
+ Expr::Array(array) => self.search_exprs(array.elems.iter())?,
+ Expr::Assign(ExprAssign { left, right, .. })
+ | Expr::AssignOp(ExprAssignOp { left, right, .. })
+ | Expr::Binary(ExprBinary { left, right, .. }) => {
+ self.search_expr(left)?;
+ self.search_expr(right)?;
+ }
+ Expr::Async(ass) => self.search_stmts(ass.block.stmts.iter())?,
+ Expr::Await(ExprAwait { base, .. }) | Expr::Field(ExprField { base, .. }) => {
+ self.search_expr(base)?
+ }
+ Expr::Block(blck) => self.search_stmts(blck.block.stmts.iter())?,
+ Expr::Box(ExprBox { expr, .. })
+ | Expr::Break(ExprBreak {
+ expr: Some(expr), ..
+ })
+ | Expr::Cast(ExprCast { expr, .. })
+ | Expr::Group(ExprGroup { expr, .. })
+ | Expr::Paren(ExprParen { expr, .. })
+ | Expr::Reference(ExprReference { expr, .. })
+ | Expr::Try(ExprTry { expr, .. })
+ | Expr::Type(ExprType { expr, .. })
+ | Expr::Unary(ExprUnary { expr, .. }) => self.search_expr(expr)?,
+ Expr::Call(exc) => {
+ self.search_expr(&exc.func)?;
+ self.search_exprs(exc.args.iter())?;
+ }
+ Expr::Closure(cls) => self.search_expr(&cls.body)?,
+ Expr::Continue(_)
+ | Expr::Lit(_)
+ | Expr::Break(ExprBreak { expr: None, .. })
+ | Expr::Verbatim(_) => {}
+ Expr::ForLoop(fl) => {
+ self.search_expr(&fl.expr)?;
+ self.search_stmts(fl.body.stmts.iter())?;
+ }
+ Expr::If(exif) => {
+ self.search_expr(&exif.cond)?;
+ self.search_stmts(exif.then_branch.stmts.iter())?;
+ if let Some((_, else_branch)) = &exif.else_branch {
+ self.search_expr(else_branch)?;
+ }
+ }
+ Expr::Index(exidx) => {
+ self.search_expr(&exidx.expr)?;
+ self.search_expr(&exidx.index)?;
+ }
+ Expr::Let(ExprLet { expr, pat, .. }) => {
+ self.search_expr(expr)?;
+ self.search_pat(pat)?;
+ }
+ Expr::Loop(exloo) => self.search_stmts(exloo.body.stmts.iter())?,
+ Expr::Match(exm) => {
+ self.search_expr(&exm.expr)?;
+ for a in &exm.arms {
+ self.search_expr(&a.body)?;
+ if let Some((_, guard)) = &a.guard {
+ self.search_expr(guard)?;
+ }
+ }
+ }
+ Expr::MethodCall(mtc) => {
+ self.search_expr(&mtc.receiver)?;
+ self.search_exprs(mtc.args.iter())?;
+ }
+ Expr::Range(exr) => {
+ self.search_option_expr(&exr.from)?;
+ self.search_option_expr(&exr.to)?;
+ }
+ Expr::Repeat(exr) => {
+ self.search_expr(&exr.expr)?;
+ self.search_expr(&exr.len)?;
+ }
+ Expr::Return(exret) => {
+ if let Some(expr) = &exret.expr {
+ self.search_expr(expr)?;
+ }
+ }
+ Expr::Struct(exst) => {
+ for f in &exst.fields {
+ self.search_expr(&f.expr)?;
+ }
+ self.search_option_expr(&exst.rest)?;
+ }
+ Expr::TryBlock(extb) => self.search_stmts(extb.block.stmts.iter())?,
+ Expr::Tuple(ext) => self.search_exprs(ext.elems.iter())?,
+ Expr::Unsafe(exs) => self.search_stmts(exs.block.stmts.iter())?,
+ Expr::While(exw) => {
+ self.search_expr(&exw.cond)?;
+ self.search_stmts(exw.body.stmts.iter())?;
+ }
+ Expr::Yield(exy) => self.search_option_expr(&exy.expr)?,
+ _ => {}
+ }
+ Ok(())
+ }
+
+ fn search_option_expr(&mut self, expr: &Option<Box<Expr>>) -> Result<(), DiscoveryErr> {
+ if let Some(expr) = &expr {
+ self.search_expr(expr)?;
+ }
+ Ok(())
+ }
+
+ fn search_exprs<'a>(
+ &mut self,
+ exprs: impl Iterator<Item = &'a Expr>,
+ ) -> Result<(), DiscoveryErr> {
+ for e in exprs {
+ self.search_expr(e)?;
+ }
+ Ok(())
+ }
+
+ fn search_impl_item(
+ &mut self,
+ impl_item: &ImplItem,
+ receiver: Option<&RustPath>,
+ ) -> Result<(), DiscoveryErr> {
+ if let ImplItem::Method(itm) = impl_item {
+ if Self::has_attr(&itm.attrs, EXTERN_RUST_FUN) {
+ if self.mod_path.is_some() {
+ return Err(DiscoveryErr::FoundExternRustFunWithinMod);
+ }
+ if let Some(receiver) = receiver {
+ // We have a method which we want to put into the cxx::bridge's
+ // "extern Rust" block.
+ let sig = add_receiver(&itm.sig, receiver.get_final_ident())?;
+ assert!(receiver.len() == 1);
+ self.discoveries.extern_rust_funs.push(RustFun {
+ path: self.deeper_path(&itm.sig.ident),
+ sig,
+ receiver: Some(receiver.get_final_ident().clone()),
+ });
+ self.discoveries.extern_rust_types.push(receiver.clone())
+ } else {
+ return Err(DiscoveryErr::FoundExternRustFunOnTypeWithoutClearReceiver);
+ }
+ }
+ for stmt in &itm.block.stmts {
+ self.search_stmt(stmt)?
+ }
+ }
+ Ok(())
+ }
+
+ fn search_pat(&mut self, pat: &Pat) -> Result<(), DiscoveryErr> {
+ match pat {
+ Pat::Box(PatBox { pat, .. }) | Pat::Reference(PatReference { pat, .. }) => {
+ self.search_pat(pat)
+ }
+ Pat::Ident(_) | Pat::Lit(_) | Pat::Macro(_) | Pat::Range(_) | Pat::Rest(_) => Ok(()),
+ Pat::Or(pator) => {
+ for case in &pator.cases {
+ self.search_pat(case)?;
+ }
+ Ok(())
+ }
+ Pat::Path(pp) => self.search_path(&pp.path),
+ Pat::Slice(PatSlice { elems, .. }) | Pat::Tuple(PatTuple { elems, .. }) => {
+ for case in elems {
+ self.search_pat(case)?;
+ }
+ Ok(())
+ }
+ Pat::Struct(ps) => {
+ self.search_path(&ps.path)?;
+ for f in &ps.fields {
+ self.search_pat(&f.pat)?;
+ }
+ Ok(())
+ }
+ Pat::TupleStruct(tps) => {
+ self.search_path(&tps.path)?;
+ for f in &tps.pat.elems {
+ self.search_pat(f)?;
+ }
+ Ok(())
+ }
+ Pat::Type(pt) => {
+ self.search_pat(&pt.pat)?;
+ self.search_type(&pt.ty)
+ }
+ _ => Ok(()),
+ }
+ }
+
+ fn search_type(&mut self, ty: &Type) -> Result<(), DiscoveryErr> {
+ match ty {
+ Type::Array(TypeArray { elem, .. })
+ | Type::Group(TypeGroup { elem, .. })
+ | Type::Paren(TypeParen { elem, .. })
+ | Type::Ptr(TypePtr { elem, .. })
+ | Type::Reference(TypeReference { elem, .. })
+ | Type::Slice(TypeSlice { elem, .. }) => self.search_type(elem)?,
+ Type::BareFn(tf) => {
+ for input in &tf.inputs {
+ self.search_type(&input.ty)?;
+ }
+ self.search_return_type(&tf.output)?;
+ }
+ Type::ImplTrait(tyit) => {
+ for b in &tyit.bounds {
+ if let syn::TypeParamBound::Trait(tyt) = b {
+ self.search_path(&tyt.path)?
+ }
+ }
+ }
+ Type::Infer(_) | Type::Macro(_) | Type::Never(_) => {}
+ Type::Path(typ) => self.search_path(&typ.path)?,
+ Type::TraitObject(tto) => self.search_type_param_bounds(&tto.bounds)?,
+ Type::Tuple(tt) => {
+ for e in &tt.elems {
+ self.search_type(e)?
+ }
+ }
+ _ => {}
+ }
+ Ok(())
+ }
+
+ fn search_type_param_bounds(
+ &mut self,
+ bounds: &Punctuated<TypeParamBound, syn::token::Add>,
+ ) -> Result<(), DiscoveryErr> {
+ for b in bounds {
+ if let syn::TypeParamBound::Trait(tpbt) = b {
+ self.search_path(&tpbt.path)?
+ }
+ }
+ Ok(())
+ }
+
+ fn search_return_type(&mut self, output: &ReturnType) -> Result<(), DiscoveryErr> {
+ if let ReturnType::Type(_, ty) = &output {
+ self.search_type(ty)
+ } else {
+ Ok(())
+ }
+ }
+
+ fn search_path_arguments(
+ &mut self,
+ arguments: &syn::PathArguments,
+ ) -> Result<(), DiscoveryErr> {
+ match arguments {
+ syn::PathArguments::None => {}
+ syn::PathArguments::AngleBracketed(paab) => {
+ for arg in &paab.args {
+ match arg {
+ syn::GenericArgument::Lifetime(_) => {}
+ syn::GenericArgument::Type(ty)
+ | syn::GenericArgument::Binding(Binding { ty, .. }) => {
+ self.search_type(ty)?
+ }
+ syn::GenericArgument::Constraint(c) => {
+ self.search_type_param_bounds(&c.bounds)?
+ }
+ syn::GenericArgument::Const(c) => self.search_expr(c)?,
+ }
+ }
+ }
+ syn::PathArguments::Parenthesized(pas) => {
+ self.search_return_type(&pas.output)?;
+ for t in &pas.inputs {
+ self.search_type(t)?;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn has_attr(attrs: &[Attribute], attr_name: &str) -> bool {
+ attrs.iter().any(|attr| {
+ attr.path
+ .segments
+ .last()
+ .map(|seg| seg.ident == attr_name)
+ .unwrap_or_default()
+ })
+ }
+}
+
+/// Take a method signature that may be `fn a(&self)`
+/// and turn it into `fn a(self: &A)` which is what we will
+/// need to specify to cxx.
+fn add_receiver(sig: &Signature, receiver: &Ident) -> Result<Signature, DiscoveryErr> {
+ let mut sig = sig.clone();
+ match sig.inputs.iter_mut().next() {
+ Some(first_arg) => match first_arg {
+ syn::FnArg::Receiver(Receiver {
+ reference: Some(_),
+ mutability: Some(_),
+ ..
+ }) => {
+ *first_arg = parse_quote! {
+ self: &mut #receiver
+ }
+ }
+ syn::FnArg::Receiver(Receiver {
+ reference: Some(_),
+ mutability: None,
+ ..
+ }) => {
+ *first_arg = parse_quote! {
+ self: &#receiver
+ }
+ }
+ syn::FnArg::Receiver(..) => return Err(DiscoveryErr::NonReferenceReceiver),
+ syn::FnArg::Typed(_) => {}
+ },
+ None => return Err(DiscoveryErr::NoParameterOnMethod),
+ }
+ Ok(sig)
+}
+
+#[cfg(test)]
+mod tests {
+ use quote::{quote, ToTokens};
+ use syn::{parse_quote, ImplItemMethod};
+
+ use crate::{ast_discoverer::add_receiver, types::make_ident};
+
+ use super::Discoveries;
+
+ fn assert_cpp_found(discoveries: &Discoveries) {
+ assert!(!discoveries.cpp_list.is_empty());
+ assert!(discoveries.cpp_list.iter().next().unwrap() == "xxx");
+ }
+
+ #[test]
+ fn test_mod_plain_call() {
+ let mut discoveries = Discoveries::default();
+ let itm = parse_quote! {
+ mod foo {
+ fn bar() {
+ ffi::xxx()
+ }
+ }
+ };
+ discoveries.search_item(&itm, None).unwrap();
+ assert_cpp_found(&discoveries);
+ }
+
+ #[test]
+ fn test_plain_call() {
+ let mut discoveries = Discoveries::default();
+ let itm = parse_quote! {
+ fn bar() {
+ ffi::xxx()
+ }
+ };
+ discoveries.search_item(&itm, None).unwrap();
+ assert_cpp_found(&discoveries);
+ }
+
+ #[test]
+ fn test_plain_call_with_semi() {
+ let mut discoveries = Discoveries::default();
+ let itm = parse_quote! {
+ fn bar() {
+ ffi::xxx();
+ }
+ };
+ discoveries.search_item(&itm, None).unwrap();
+ assert_cpp_found(&discoveries);
+ }
+
+ #[test]
+ fn test_in_ns() {
+ let mut discoveries = Discoveries::default();
+ let itm = parse_quote! {
+ fn bar() {
+ ffi::a::b::xxx();
+ }
+ };
+ discoveries.search_item(&itm, None).unwrap();
+ assert!(!discoveries.cpp_list.is_empty());
+ assert!(discoveries.cpp_list.iter().next().unwrap() == "a::b::xxx");
+ }
+
+ #[test]
+ fn test_deep_nested_thingy() {
+ let mut discoveries = Discoveries::default();
+ let itm = parse_quote! {
+ fn bar() {
+ a + 3 * foo(ffi::xxx());
+ }
+ };
+ discoveries.search_item(&itm, None).unwrap();
+ assert_cpp_found(&discoveries);
+ }
+
+ #[test]
+ fn test_ty_in_let() {
+ let mut discoveries = Discoveries::default();
+ let itm = parse_quote! {
+ fn bar() {
+ let foo: ffi::xxx = bar();
+ }
+ };
+ discoveries.search_item(&itm, None).unwrap();
+ assert_cpp_found(&discoveries);
+ }
+
+ #[test]
+ fn test_ty_in_fn() {
+ let mut discoveries = Discoveries::default();
+ let itm = parse_quote! {
+ fn bar(a: &mut ffi::xxx) {
+ }
+ };
+ discoveries.search_item(&itm, None).unwrap();
+ assert_cpp_found(&discoveries);
+ }
+
+ #[test]
+ fn test_ty_in_fn_up() {
+ let mut discoveries = Discoveries::default();
+ let itm = parse_quote! {
+ fn bar(a: cxx::UniquePtr<ffi::xxx>) {
+ }
+ };
+ discoveries.search_item(&itm, None).unwrap();
+ assert_cpp_found(&discoveries);
+ }
+
+ #[test]
+ fn test_extern_rust_fun() {
+ let mut discoveries = Discoveries::default();
+ let itm = parse_quote! {
+ #[autocxx::extern_rust::extern_rust_function]
+ fn bar(a: cxx::UniquePtr<ffi::xxx>) {
+ }
+ };
+ discoveries.search_item(&itm, None).unwrap();
+ assert!(discoveries.extern_rust_funs.get(0).unwrap().sig.ident == "bar");
+ }
+
+ #[test]
+ fn test_extern_rust_method() {
+ let mut discoveries = Discoveries::default();
+ let itm = parse_quote! {
+ impl A {
+ #[autocxx::extern_rust::extern_rust_function]
+ fn bar(&self) {
+ }
+ }
+ };
+ discoveries.search_item(&itm, None).unwrap();
+ assert!(discoveries.extern_rust_funs.get(0).unwrap().sig.ident == "bar");
+ }
+
+ #[test]
+ fn test_extern_rust_ty() {
+ let mut discoveries = Discoveries::default();
+ let itm = parse_quote! {
+ #[autocxx::extern_rust::extern_rust_type]
+ struct Bar {
+
+ }
+ };
+ discoveries.search_item(&itm, None).unwrap();
+ assert!(
+ discoveries
+ .extern_rust_types
+ .get(0)
+ .unwrap()
+ .get_final_ident()
+ == "Bar"
+ );
+ }
+
+ #[test]
+ fn test_add_receiver() {
+ let meth: ImplItemMethod = parse_quote! {
+ fn a(&self) {}
+ };
+ let a = make_ident("A");
+ assert_eq!(
+ add_receiver(&meth.sig, &a)
+ .unwrap()
+ .to_token_stream()
+ .to_string(),
+ quote! { fn a(self: &A) }.to_string()
+ );
+
+ let meth: ImplItemMethod = parse_quote! {
+ fn a(&mut self, b: u32) -> Foo {}
+ };
+ assert_eq!(
+ add_receiver(&meth.sig, &a)
+ .unwrap()
+ .to_token_stream()
+ .to_string(),
+ quote! { fn a(self: &mut A, b: u32) -> Foo }.to_string()
+ );
+
+ let meth: ImplItemMethod = parse_quote! {
+ fn a(self) {}
+ };
+ assert!(add_receiver(&meth.sig, &a).is_err());
+
+ let meth: ImplItemMethod = parse_quote! {
+ fn a() {}
+ };
+ assert!(add_receiver(&meth.sig, &a).is_err());
+ }
+}
diff --git a/engine/src/builder.rs b/engine/src/builder.rs
new file mode 100644
index 0000000..d0b5199
--- /dev/null
+++ b/engine/src/builder.rs
@@ -0,0 +1,301 @@
+// 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 autocxx_parser::file_locations::FileLocationStrategy;
+use miette::Diagnostic;
+use thiserror::Error;
+
+use crate::generate_rs_single;
+use crate::{strip_system_headers, CppCodegenOptions, ParseError, RebuildDependencyRecorder};
+use std::ffi::OsStr;
+use std::ffi::OsString;
+use std::fs::File;
+use std::io::Write;
+use std::marker::PhantomData;
+use std::path::{Path, PathBuf};
+
+/// Errors returned during creation of a [`cc::Build`] from an include_cxx
+/// macro.
+#[derive(Error, Diagnostic, Debug)]
+#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
+pub enum BuilderError {
+ #[error("cxx couldn't handle our generated bindings - could be a bug in autocxx: {0}")]
+ InvalidCxx(cxx_gen::Error),
+ #[error(transparent)]
+ #[diagnostic(transparent)]
+ ParseError(ParseError),
+ #[error("we couldn't write the generated code to disk at {1}: {0}")]
+ FileWriteFail(std::io::Error, PathBuf),
+ #[error("no include_cpp! macro was found")]
+ NoIncludeCxxMacrosFound,
+ #[error("could not create a directory {1}: {0}")]
+ UnableToCreateDirectory(std::io::Error, PathBuf),
+}
+
+#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
+pub type BuilderBuild = cc::Build;
+
+/// For test purposes only, a [`cc::Build`] and lists of Rust and C++
+/// files generated.
+#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
+pub struct BuilderSuccess(pub BuilderBuild, pub Vec<PathBuf>, pub Vec<PathBuf>);
+
+/// Results of a build.
+#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
+pub type BuilderResult = Result<BuilderSuccess, BuilderError>;
+
+/// The context in which a builder object lives. Callbacks for various
+/// purposes.
+#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
+pub trait BuilderContext {
+ /// Perform any initialization specific to the context in which this
+ /// builder lives.
+ fn setup() {}
+
+ /// Create a dependency recorder, if any.
+ fn get_dependency_recorder() -> Option<Box<dyn RebuildDependencyRecorder>>;
+}
+
+/// An object to allow building of bindings from a `build.rs` file.
+///
+/// It would be unusual to use this directly - see the `autocxx_build` or
+/// `autocxx_gen` crates.
+#[cfg_attr(feature = "nightly", doc(cfg(feature = "build")))]
+pub struct Builder<'a, BuilderContext> {
+ rs_file: PathBuf,
+ autocxx_incs: Vec<OsString>,
+ extra_clang_args: Vec<String>,
+ dependency_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
+ custom_gendir: Option<PathBuf>,
+ auto_allowlist: bool,
+ cpp_codegen_options: CppCodegenOptions<'a>,
+ // This member is to ensure that this type is parameterized
+ // by a BuilderContext. The goal is to balance three needs:
+ // (1) have most of the functionality over in autocxx_engine,
+ // (2) expose this type to users of autocxx_build and to
+ // make it easy for callers simply to call Builder::new,
+ // (3) ensure that such a Builder does a few tasks specific to its use
+ // in a cargo environment.
+ ctx: PhantomData<BuilderContext>,
+}
+
+impl<CTX: BuilderContext> Builder<'_, CTX> {
+ /// Create a new Builder object. You'll need to pass in the Rust file
+ /// which contains the bindings (typically an `include_cpp!` macro
+ /// though `autocxx` can also handle manually-crafted `cxx::bridge`
+ /// bindings), and a list of include directories which should be searched
+ /// by autocxx as it tries to hunt for the include files specified
+ /// within the `include_cpp!` macro.
+ ///
+ /// Usually after this you'd call [`build`].
+ pub fn new(
+ rs_file: impl AsRef<Path>,
+ autocxx_incs: impl IntoIterator<Item = impl AsRef<OsStr>>,
+ ) -> Self {
+ CTX::setup();
+ Self {
+ rs_file: rs_file.as_ref().to_path_buf(),
+ autocxx_incs: autocxx_incs
+ .into_iter()
+ .map(|s| s.as_ref().to_os_string())
+ .collect(),
+ extra_clang_args: Vec::new(),
+ dependency_recorder: CTX::get_dependency_recorder(),
+ custom_gendir: None,
+ auto_allowlist: false,
+ cpp_codegen_options: CppCodegenOptions::default(),
+ ctx: PhantomData,
+ }
+ }
+
+ /// Specify extra arguments for clang.
+ pub fn extra_clang_args(mut self, extra_clang_args: &[&str]) -> Self {
+ self.extra_clang_args = extra_clang_args.iter().map(|s| s.to_string()).collect();
+ self
+ }
+
+ /// Where to generate the code.
+ pub fn custom_gendir(mut self, custom_gendir: PathBuf) -> Self {
+ self.custom_gendir = Some(custom_gendir);
+ self
+ }
+
+ /// Update C++ code generation options. See [`CppCodegenOptions`] for details.
+ pub fn cpp_codegen_options<F>(mut self, modifier: F) -> Self
+ where
+ F: FnOnce(&mut CppCodegenOptions),
+ {
+ modifier(&mut self.cpp_codegen_options);
+ self
+ }
+
+ /// Automatically discover uses of the C++ `ffi` mod and generate the allowlist
+ /// from that.
+ /// This is a highly experimental option, not currently recommended.
+ /// It doesn't work in the following cases:
+ /// * Static function calls on types within the FFI mod.
+ /// * Anything inside a macro invocation.
+ /// * You're using a different name for your `ffi` mod
+ /// * You're using multiple FFI mods
+ /// * You've got usages scattered across files beyond that with the
+ /// `include_cpp` invocation
+ /// * You're using `use` statements to rename mods or items. If this
+ /// proves to be a promising or helpful direction, autocxx would be happy
+ /// to accept pull requests to remove some of these limitations.
+ pub fn auto_allowlist(mut self, do_it: bool) -> Self {
+ self.auto_allowlist = do_it;
+ self
+ }
+
+ /// Whether to suppress inclusion of system headers (`memory`, `string` etc.)
+ /// from generated C++ bindings code. This should not normally be used,
+ /// but can occasionally be useful if you're reducing a test case and you
+ /// have a preprocessed header file which already contains absolutely everything
+ /// that the bindings could ever need.
+ pub fn suppress_system_headers(mut self, do_it: bool) -> Self {
+ self.cpp_codegen_options.suppress_system_headers = do_it;
+ self
+ }
+
+ /// An annotation optionally to include on each C++ function.
+ /// For example to export the symbol from a library.
+ pub fn cxx_impl_annotations(mut self, cxx_impl_annotations: Option<String>) -> Self {
+ self.cpp_codegen_options.cxx_impl_annotations = cxx_impl_annotations;
+ self
+ }
+
+ /// Build autocxx C++ files and return a [`cc::Build`] you can use to build
+ /// more from a build.rs file.
+ ///
+ /// The error type returned by this function supports [`miette::Diagnostic`],
+ /// so if you use the `miette` crate and its `fancy` feature, then simply
+ /// return a `miette::Result` from your main function, you should get nicely
+ /// printed diagnostics.
+ pub fn build(self) -> Result<BuilderBuild, BuilderError> {
+ self.build_listing_files().map(|r| r.0)
+ }
+
+ /// For use in tests only, this does the build and returns additional information
+ /// about the files generated which can subsequently be examined for correctness.
+ /// In production, please use simply [`build`].
+ pub fn build_listing_files(self) -> Result<BuilderSuccess, BuilderError> {
+ let clang_args = &self
+ .extra_clang_args
+ .iter()
+ .map(|s| &s[..])
+ .collect::<Vec<_>>();
+ rust_version_check();
+ let gen_location_strategy = match self.custom_gendir {
+ None => FileLocationStrategy::new(),
+ Some(custom_dir) => FileLocationStrategy::Custom(custom_dir),
+ };
+ let incdir = gen_location_strategy.get_include_dir();
+ ensure_created(&incdir)?;
+ let cxxdir = gen_location_strategy.get_cxx_dir();
+ ensure_created(&cxxdir)?;
+ let rsdir = gen_location_strategy.get_rs_dir();
+ ensure_created(&rsdir)?;
+ // We are incredibly unsophisticated in our directory arrangement here
+ // compared to cxx. I have no doubt that we will need to replicate just
+ // about everything cxx does, in due course...
+ // Write cxx.h to that location, as it may be needed by
+ // some of our generated code.
+ write_to_file(
+ &incdir,
+ "cxx.h",
+ &Self::get_cxx_header_bytes(self.cpp_codegen_options.suppress_system_headers),
+ )?;
+
+ let autocxx_inc = build_autocxx_inc(self.autocxx_incs, &incdir);
+ gen_location_strategy.set_cargo_env_vars_for_build();
+
+ let mut parsed_file = crate::parse_file(self.rs_file, self.auto_allowlist)
+ .map_err(BuilderError::ParseError)?;
+ parsed_file
+ .resolve_all(
+ autocxx_inc,
+ clang_args,
+ self.dependency_recorder,
+ &self.cpp_codegen_options,
+ )
+ .map_err(BuilderError::ParseError)?;
+ let mut counter = 0;
+ let mut builder = cc::Build::new();
+ builder.cpp(true);
+ if std::env::var_os("AUTOCXX_ASAN").is_some() {
+ builder.flag_if_supported("-fsanitize=address");
+ }
+ let mut generated_rs = Vec::new();
+ let mut generated_cpp = Vec::new();
+ builder.includes(parsed_file.include_dirs());
+ for include_cpp in parsed_file.get_cpp_buildables() {
+ let generated_code = include_cpp
+ .generate_h_and_cxx(&self.cpp_codegen_options)
+ .map_err(BuilderError::InvalidCxx)?;
+ for filepair in generated_code.0 {
+ let fname = format!("gen{}.cxx", counter);
+ counter += 1;
+ if let Some(implementation) = &filepair.implementation {
+ let gen_cxx_path = write_to_file(&cxxdir, &fname, implementation)?;
+ builder.file(&gen_cxx_path);
+ generated_cpp.push(gen_cxx_path);
+ }
+ write_to_file(&incdir, &filepair.header_name, &filepair.header)?;
+ generated_cpp.push(incdir.join(filepair.header_name));
+ }
+ }
+
+ for rs_output in parsed_file.get_rs_outputs() {
+ let rs = generate_rs_single(rs_output);
+ generated_rs.push(write_to_file(&rsdir, &rs.filename, rs.code.as_bytes())?);
+ }
+ if counter == 0 {
+ Err(BuilderError::NoIncludeCxxMacrosFound)
+ } else {
+ Ok(BuilderSuccess(builder, generated_rs, generated_cpp))
+ }
+ }
+
+ fn get_cxx_header_bytes(suppress_system_headers: bool) -> Vec<u8> {
+ strip_system_headers(crate::HEADER.as_bytes().to_vec(), suppress_system_headers)
+ }
+}
+
+fn ensure_created(dir: &Path) -> Result<(), BuilderError> {
+ std::fs::create_dir_all(dir)
+ .map_err(|e| BuilderError::UnableToCreateDirectory(e, dir.to_path_buf()))
+}
+
+fn build_autocxx_inc<I, T>(paths: I, extra_path: &Path) -> Vec<PathBuf>
+where
+ I: IntoIterator<Item = T>,
+ T: AsRef<OsStr>,
+{
+ paths
+ .into_iter()
+ .map(|p| PathBuf::from(p.as_ref()))
+ .chain(std::iter::once(extra_path.to_path_buf()))
+ .collect()
+}
+
+fn write_to_file(dir: &Path, filename: &str, content: &[u8]) -> Result<PathBuf, BuilderError> {
+ let path = dir.join(filename);
+ try_write_to_file(&path, content).map_err(|e| BuilderError::FileWriteFail(e, path.clone()))?;
+ Ok(path)
+}
+
+fn try_write_to_file(path: &Path, content: &[u8]) -> std::io::Result<()> {
+ let mut f = File::create(path)?;
+ f.write_all(content)
+}
+
+fn rust_version_check() {
+ if !version_check::is_min_version("1.54.0").unwrap_or(false) {
+ panic!("Rust 1.54 or later is required.")
+ }
+}
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()
+}
diff --git a/engine/src/conversion/api.rs b/engine/src/conversion/api.rs
new file mode 100644
index 0000000..3d04674
--- /dev/null
+++ b/engine/src/conversion/api.rs
@@ -0,0 +1,715 @@
+// 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 std::fmt::Display;
+
+use crate::types::{make_ident, Namespace, QualifiedName};
+use autocxx_parser::{ExternCppType, RustFun, RustPath};
+use itertools::Itertools;
+use quote::ToTokens;
+use syn::{
+ parse::Parse,
+ punctuated::Punctuated,
+ token::{Comma, Unsafe},
+ Attribute, FnArg, Ident, ItemConst, ItemEnum, ItemStruct, ItemType, ItemUse, LitBool, LitInt,
+ Pat, ReturnType, Type, Visibility,
+};
+
+use super::{
+ analysis::{
+ fun::{
+ function_wrapper::{CppFunction, CppFunctionBody, CppFunctionKind},
+ ReceiverMutability,
+ },
+ PointerTreatment,
+ },
+ convert_error::{ConvertErrorWithContext, ErrorContext},
+ ConvertError,
+};
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub(crate) enum TypeKind {
+ Pod, // trivial. Can be moved and copied in Rust.
+ NonPod, // has destructor or non-trivial move constructors. Can only hold by UniquePtr
+ Abstract, // has pure virtual members - can't even generate UniquePtr.
+ // It's possible that the type itself isn't pure virtual, but it inherits from
+ // some other type which is pure virtual. Alternatively, maybe we just don't
+ // know if the base class is pure virtual because it wasn't on the allowlist,
+ // in which case we'll err on the side of caution.
+}
+
+/// C++ visibility.
+#[derive(Debug, Clone, PartialEq, Eq, Copy)]
+pub(crate) enum CppVisibility {
+ Public,
+ Protected,
+ Private,
+}
+
+/// Details about a C++ struct.
+pub(crate) struct StructDetails {
+ pub(crate) vis: CppVisibility,
+ pub(crate) item: ItemStruct,
+ pub(crate) layout: Option<Layout>,
+ pub(crate) has_rvalue_reference_fields: bool,
+}
+
+/// Layout of a type, equivalent to the same type in ir/layout.rs in bindgen
+#[derive(Clone)]
+pub(crate) struct Layout {
+ /// The size (in bytes) of this layout.
+ pub(crate) size: usize,
+ /// The alignment (in bytes) of this layout.
+ pub(crate) align: usize,
+ /// Whether this layout's members are packed or not.
+ pub(crate) packed: bool,
+}
+
+impl Parse for Layout {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+ let size: LitInt = input.parse()?;
+ input.parse::<syn::token::Comma>()?;
+ let align: LitInt = input.parse()?;
+ input.parse::<syn::token::Comma>()?;
+ let packed: LitBool = input.parse()?;
+ Ok(Layout {
+ size: size.base10_parse().unwrap(),
+ align: align.base10_parse().unwrap(),
+ packed: packed.value(),
+ })
+ }
+}
+
+#[derive(Clone)]
+pub(crate) enum Virtualness {
+ None,
+ Virtual,
+ PureVirtual,
+}
+
+#[derive(Clone, Copy)]
+pub(crate) enum CastMutability {
+ ConstToConst,
+ MutToConst,
+ MutToMut,
+}
+
+/// Indicates that this function (which is synthetic) should
+/// be a trait implementation rather than a method or free function.
+#[derive(Clone)]
+pub(crate) enum TraitSynthesis {
+ Cast {
+ to_type: QualifiedName,
+ mutable: CastMutability,
+ },
+ AllocUninitialized(QualifiedName),
+ FreeUninitialized(QualifiedName),
+}
+
+/// Details of a subclass constructor.
+/// TODO: zap this; replace with an extra API.
+#[derive(Clone)]
+pub(crate) struct SubclassConstructorDetails {
+ pub(crate) subclass: SubclassName,
+ pub(crate) is_trivial: bool,
+ /// Implementation of the constructor _itself_ as distinct
+ /// from any wrapper function we create to call it.
+ pub(crate) cpp_impl: CppFunction,
+}
+
+/// Contributions to traits representing C++ superclasses that
+/// we may implement as Rust subclasses.
+#[derive(Clone)]
+pub(crate) struct SuperclassMethod {
+ pub(crate) name: Ident,
+ pub(crate) receiver: QualifiedName,
+ pub(crate) params: Punctuated<FnArg, Comma>,
+ pub(crate) param_names: Vec<Pat>,
+ pub(crate) ret_type: ReturnType,
+ pub(crate) receiver_mutability: ReceiverMutability,
+ pub(crate) requires_unsafe: UnsafetyNeeded,
+ pub(crate) is_pure_virtual: bool,
+}
+
+/// Information about references (as opposed to pointers) to be found
+/// within the function signature. This is derived from bindgen annotations
+/// which is why it's not within `FuncToConvert::inputs`
+#[derive(Default, Clone)]
+pub(crate) struct References {
+ pub(crate) rvalue_ref_params: HashSet<Ident>,
+ pub(crate) ref_params: HashSet<Ident>,
+ pub(crate) ref_return: bool,
+ pub(crate) rvalue_ref_return: bool,
+}
+
+impl References {
+ pub(crate) fn new_with_this_and_return_as_reference() -> Self {
+ Self {
+ ref_return: true,
+ ref_params: [make_ident("this")].into_iter().collect(),
+ ..Default::default()
+ }
+ }
+ pub(crate) fn param_treatment(&self, param: &Ident) -> PointerTreatment {
+ if self.rvalue_ref_params.contains(param) {
+ PointerTreatment::RValueReference
+ } else if self.ref_params.contains(param) {
+ PointerTreatment::Reference
+ } else {
+ PointerTreatment::Pointer
+ }
+ }
+ pub(crate) fn return_treatment(&self) -> PointerTreatment {
+ if self.rvalue_ref_return {
+ PointerTreatment::RValueReference
+ } else if self.ref_return {
+ PointerTreatment::Reference
+ } else {
+ PointerTreatment::Pointer
+ }
+ }
+}
+
+#[derive(Clone)]
+pub(crate) struct TraitImplSignature {
+ pub(crate) ty: Type,
+ pub(crate) trait_signature: Type,
+ /// The trait is 'unsafe' itself
+ pub(crate) unsafety: Option<Unsafe>,
+}
+
+impl Eq for TraitImplSignature {}
+
+impl PartialEq for TraitImplSignature {
+ fn eq(&self, other: &Self) -> bool {
+ totokens_equal(&self.unsafety, &other.unsafety)
+ && totokens_equal(&self.ty, &other.ty)
+ && totokens_equal(&self.trait_signature, &other.trait_signature)
+ }
+}
+
+fn totokens_to_string<T: ToTokens>(a: &T) -> String {
+ a.to_token_stream().to_string()
+}
+
+fn totokens_equal<T: ToTokens>(a: &T, b: &T) -> bool {
+ totokens_to_string(a) == totokens_to_string(b)
+}
+
+fn hash_totokens<T: ToTokens, H: std::hash::Hasher>(a: &T, state: &mut H) {
+ use std::hash::Hash;
+ totokens_to_string(a).hash(state)
+}
+
+impl std::hash::Hash for TraitImplSignature {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ hash_totokens(&self.ty, state);
+ hash_totokens(&self.trait_signature, state);
+ hash_totokens(&self.unsafety, state);
+ }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum SpecialMemberKind {
+ DefaultConstructor,
+ CopyConstructor,
+ MoveConstructor,
+ Destructor,
+ AssignmentOperator,
+}
+
+impl Display for SpecialMemberKind {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ SpecialMemberKind::DefaultConstructor => "default constructor",
+ SpecialMemberKind::CopyConstructor => "copy constructor",
+ SpecialMemberKind::MoveConstructor => "move constructor",
+ SpecialMemberKind::Destructor => "destructor",
+ SpecialMemberKind::AssignmentOperator => "assignment operator",
+ }
+ )
+ }
+}
+
+#[derive(Clone)]
+pub(crate) enum Provenance {
+ Bindgen,
+ SynthesizedOther,
+ SynthesizedSubclassConstructor(Box<SubclassConstructorDetails>),
+}
+
+/// A C++ function for which we need to generate bindings, but haven't
+/// yet analyzed in depth. This is little more than a `ForeignItemFn`
+/// broken down into its constituent parts, plus some metadata from the
+/// surrounding bindgen parsing context.
+///
+/// Some parts of the code synthesize additional functions and then
+/// pass them through the same pipeline _as if_ they were discovered
+/// during normal bindgen parsing. If that happens, they'll create one
+/// of these structures, and typically fill in some of the
+/// `synthesized_*` members which are not filled in from bindgen.
+#[derive(Clone)]
+pub(crate) struct FuncToConvert {
+ pub(crate) provenance: Provenance,
+ pub(crate) ident: Ident,
+ pub(crate) doc_attrs: Vec<Attribute>,
+ pub(crate) inputs: Punctuated<FnArg, Comma>,
+ pub(crate) variadic: bool,
+ pub(crate) output: ReturnType,
+ pub(crate) vis: Visibility,
+ pub(crate) virtualness: Virtualness,
+ pub(crate) cpp_vis: CppVisibility,
+ pub(crate) special_member: Option<SpecialMemberKind>,
+ pub(crate) unused_template_param: bool,
+ pub(crate) references: References,
+ pub(crate) original_name: Option<String>,
+ /// Used for static functions only. For all other functons,
+ /// this is figured out from the receiver type in the inputs.
+ pub(crate) self_ty: Option<QualifiedName>,
+ /// If we wish to use a different 'this' type than the original
+ /// method receiver, e.g. because we're making a subclass
+ /// constructor, fill it in here.
+ pub(crate) synthesized_this_type: Option<QualifiedName>,
+ /// If this function should actually belong to a trait.
+ pub(crate) add_to_trait: Option<TraitSynthesis>,
+ /// If Some, this function didn't really exist in the original
+ /// C++ and instead we're synthesizing it.
+ pub(crate) synthetic_cpp: Option<(CppFunctionBody, CppFunctionKind)>,
+ pub(crate) is_deleted: bool,
+}
+
+/// Layers of analysis which may be applied to decorate each API.
+/// See description of the purpose of this trait within `Api`.
+pub(crate) trait AnalysisPhase {
+ type TypedefAnalysis;
+ type StructAnalysis;
+ type FunAnalysis;
+}
+
+/// No analysis has been applied to this API.
+pub(crate) struct NullPhase;
+
+impl AnalysisPhase for NullPhase {
+ type TypedefAnalysis = ();
+ type StructAnalysis = ();
+ type FunAnalysis = ();
+}
+
+#[derive(Clone)]
+pub(crate) enum TypedefKind {
+ Use(ItemUse, Box<Type>),
+ Type(ItemType),
+}
+
+/// Name information for an API. This includes the name by
+/// which we know it in Rust, and its C++ name, which may differ.
+#[derive(Clone, Hash, PartialEq, Eq)]
+pub(crate) struct ApiName {
+ pub(crate) name: QualifiedName,
+ cpp_name: Option<String>,
+}
+
+impl ApiName {
+ pub(crate) fn new(ns: &Namespace, id: Ident) -> Self {
+ Self::new_from_qualified_name(QualifiedName::new(ns, id))
+ }
+
+ pub(crate) fn new_with_cpp_name(ns: &Namespace, id: Ident, cpp_name: Option<String>) -> Self {
+ Self {
+ name: QualifiedName::new(ns, id),
+ cpp_name,
+ }
+ }
+
+ pub(crate) fn new_from_qualified_name(name: QualifiedName) -> Self {
+ Self {
+ name,
+ cpp_name: None,
+ }
+ }
+
+ pub(crate) fn new_in_root_namespace(id: Ident) -> Self {
+ Self::new(&Namespace::new(), id)
+ }
+
+ pub(crate) fn cpp_name(&self) -> String {
+ self.cpp_name
+ .as_ref()
+ .cloned()
+ .unwrap_or_else(|| self.name.get_final_item().to_string())
+ }
+
+ pub(crate) fn qualified_cpp_name(&self) -> String {
+ let cpp_name = self.cpp_name();
+ self.name
+ .ns_segment_iter()
+ .cloned()
+ .chain(std::iter::once(cpp_name))
+ .join("::")
+ }
+
+ pub(crate) fn cpp_name_if_present(&self) -> Option<&String> {
+ self.cpp_name.as_ref()
+ }
+}
+
+impl std::fmt::Debug for ApiName {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.name)?;
+ if let Some(cpp_name) = &self.cpp_name {
+ write!(f, " (cpp={})", cpp_name)?;
+ }
+ Ok(())
+ }
+}
+
+/// A name representing a subclass.
+/// This is a simple newtype wrapper which exists such that
+/// we can consistently generate the names of the various subsidiary
+/// types which are required both in C++ and Rust codegen.
+#[derive(Clone, Hash, PartialEq, Eq, Debug)]
+pub(crate) struct SubclassName(pub(crate) ApiName);
+
+impl SubclassName {
+ pub(crate) fn new(id: Ident) -> Self {
+ Self(ApiName::new_in_root_namespace(id))
+ }
+ pub(crate) fn from_holder_name(id: &Ident) -> Self {
+ Self::new(make_ident(id.to_string().strip_suffix("Holder").unwrap()))
+ }
+ pub(crate) fn id(&self) -> Ident {
+ self.0.name.get_final_ident()
+ }
+ /// Generate the name for the 'Holder' type
+ pub(crate) fn holder(&self) -> Ident {
+ self.with_suffix("Holder")
+ }
+ /// Generate the name for the 'Cpp' type
+ pub(crate) fn cpp(&self) -> QualifiedName {
+ let id = self.with_suffix("Cpp");
+ QualifiedName::new(self.0.name.get_namespace(), id)
+ }
+ pub(crate) fn cpp_remove_ownership(&self) -> Ident {
+ self.with_suffix("Cpp_remove_ownership")
+ }
+ pub(crate) fn remove_ownership(&self) -> Ident {
+ self.with_suffix("_remove_ownership")
+ }
+ fn with_suffix(&self, suffix: &str) -> Ident {
+ make_ident(format!("{}{}", self.0.name.get_final_item(), suffix))
+ }
+ pub(crate) fn get_trait_api_name(sup: &QualifiedName, method_name: &str) -> QualifiedName {
+ QualifiedName::new(
+ sup.get_namespace(),
+ make_ident(format!(
+ "{}_{}_trait_item",
+ sup.get_final_item(),
+ method_name
+ )),
+ )
+ }
+ // TODO this and the following should probably include both class name and method name
+ pub(crate) fn get_super_fn_name(superclass_namespace: &Namespace, id: &str) -> QualifiedName {
+ let id = make_ident(format!("{}_super", id));
+ QualifiedName::new(superclass_namespace, id)
+ }
+ pub(crate) fn get_methods_trait_name(superclass_name: &QualifiedName) -> QualifiedName {
+ Self::with_qualified_name_suffix(superclass_name, "methods")
+ }
+ pub(crate) fn get_supers_trait_name(superclass_name: &QualifiedName) -> QualifiedName {
+ Self::with_qualified_name_suffix(superclass_name, "supers")
+ }
+
+ fn with_qualified_name_suffix(name: &QualifiedName, suffix: &str) -> QualifiedName {
+ let id = make_ident(format!("{}_{}", name.get_final_item(), suffix));
+ QualifiedName::new(name.get_namespace(), id)
+ }
+}
+
+#[derive(strum_macros::Display)]
+/// Different types of API we might encounter.
+///
+/// This type is parameterized over an `ApiAnalysis`. This is any additional
+/// information which we wish to apply to our knowledge of our APIs later
+/// during analysis phases.
+///
+/// This is not as high-level as the equivalent types in `cxx` or `bindgen`,
+/// because sometimes we pass on the `bindgen` output directly in the
+/// Rust codegen output.
+///
+/// This derives from [strum_macros::Display] because we want to be
+/// able to debug-print the enum discriminant without worrying about
+/// the fact that their payloads may not be `Debug` or `Display`.
+/// (Specifically, allowing `syn` Types to be `Debug` requires
+/// enabling syn's `extra-traits` feature which increases compile time.)
+pub(crate) enum Api<T: AnalysisPhase> {
+ /// A forward declaration, which we mustn't store in a UniquePtr.
+ ForwardDeclaration {
+ name: ApiName,
+ /// If we found a problem parsing this forward declaration, we'll
+ /// ephemerally store the error here, as opposed to immediately
+ /// converting it to an `IgnoredItem`. That's because the
+ /// 'replace_hopeless_typedef_targets' analysis phase needs to spot
+ /// cases where there was an error which was _also_ a forward declaration.
+ /// That phase will then discard such Api::ForwardDeclarations
+ /// and replace them with normal Api::IgnoredItems.
+ err: Option<ConvertErrorWithContext>,
+ },
+ /// We found a typedef to something that we didn't fully understand.
+ /// We'll treat it as an opaque unsized type.
+ OpaqueTypedef {
+ name: ApiName,
+ /// Further store whether this was a typedef to a forward declaration.
+ /// If so we can't allow it to live in a UniquePtr, just like a regular
+ /// Api::ForwardDeclaration.
+ forward_declaration: bool,
+ },
+ /// A synthetic type we've manufactured in order to
+ /// concretize some templated C++ type.
+ ConcreteType {
+ name: ApiName,
+ rs_definition: Option<Box<Type>>,
+ cpp_definition: String,
+ },
+ /// A simple note that we want to make a constructor for
+ /// a `std::string` on the heap.
+ StringConstructor { name: ApiName },
+ /// A function. May include some analysis.
+ Function {
+ name: ApiName,
+ fun: Box<FuncToConvert>,
+ analysis: T::FunAnalysis,
+ },
+ /// A constant.
+ Const {
+ name: ApiName,
+ const_item: ItemConst,
+ },
+ /// A typedef found in the bindgen output which we wish
+ /// to pass on in our output
+ Typedef {
+ name: ApiName,
+ item: TypedefKind,
+ old_tyname: Option<QualifiedName>,
+ analysis: T::TypedefAnalysis,
+ },
+ /// An enum encountered in the
+ /// `bindgen` output.
+ Enum { name: ApiName, item: ItemEnum },
+ /// A struct encountered in the
+ /// `bindgen` output.
+ Struct {
+ name: ApiName,
+ details: Box<StructDetails>,
+ analysis: T::StructAnalysis,
+ },
+ /// A variable-length C integer type (e.g. int, unsigned long).
+ CType {
+ name: ApiName,
+ typename: QualifiedName,
+ },
+ /// Some item which couldn't be processed by autocxx for some reason.
+ /// We will have emitted a warning message about this, but we want
+ /// to mark that it's ignored so that we don't attempt to process
+ /// dependent items.
+ IgnoredItem {
+ name: ApiName,
+ err: ConvertError,
+ ctx: Option<ErrorContext>,
+ },
+ /// A Rust type which is not a C++ type.
+ RustType { name: ApiName, path: RustPath },
+ /// A function for the 'extern Rust' block which is not a C++ type.
+ RustFn {
+ name: ApiName,
+ details: RustFun,
+ receiver: Option<QualifiedName>,
+ },
+ /// Some function for the extern "Rust" block.
+ RustSubclassFn {
+ name: ApiName,
+ subclass: SubclassName,
+ details: Box<RustSubclassFnDetails>,
+ },
+ /// A Rust subclass of a C++ class.
+ Subclass {
+ name: SubclassName,
+ superclass: QualifiedName,
+ },
+ /// Contributions to the traits representing superclass methods that we might
+ /// subclass in Rust.
+ SubclassTraitItem {
+ name: ApiName,
+ details: SuperclassMethod,
+ },
+ /// A type which we shouldn't ourselves generate, but can use in functions
+ /// and so-forth by referring to some definition elsewhere.
+ ExternCppType {
+ name: ApiName,
+ details: ExternCppType,
+ pod: bool,
+ },
+}
+
+pub(crate) struct RustSubclassFnDetails {
+ pub(crate) params: Punctuated<FnArg, Comma>,
+ pub(crate) ret: ReturnType,
+ pub(crate) cpp_impl: CppFunction,
+ pub(crate) method_name: Ident,
+ pub(crate) superclass: QualifiedName,
+ pub(crate) receiver_mutability: ReceiverMutability,
+ pub(crate) dependencies: Vec<QualifiedName>,
+ pub(crate) requires_unsafe: UnsafetyNeeded,
+ pub(crate) is_pure_virtual: bool,
+}
+
+#[derive(Clone, Debug)]
+pub(crate) enum UnsafetyNeeded {
+ None,
+ JustBridge,
+ Always,
+}
+
+impl<T: AnalysisPhase> Api<T> {
+ pub(crate) fn name_info(&self) -> &ApiName {
+ match self {
+ Api::ForwardDeclaration { name, .. } => name,
+ Api::OpaqueTypedef { name, .. } => name,
+ Api::ConcreteType { name, .. } => name,
+ Api::StringConstructor { name } => name,
+ Api::Function { name, .. } => name,
+ Api::Const { name, .. } => name,
+ Api::Typedef { name, .. } => name,
+ Api::Enum { name, .. } => name,
+ Api::Struct { name, .. } => name,
+ Api::CType { name, .. } => name,
+ Api::IgnoredItem { name, .. } => name,
+ Api::RustType { name, .. } => name,
+ Api::RustFn { name, .. } => name,
+ Api::RustSubclassFn { name, .. } => name,
+ Api::Subclass { name, .. } => &name.0,
+ Api::SubclassTraitItem { name, .. } => name,
+ Api::ExternCppType { name, .. } => name,
+ }
+ }
+
+ /// The name of this API as used in Rust code.
+ /// For types, it's important that this never changes, since
+ /// functions or other types may refer to this.
+ /// Yet for functions, this may not actually be the name
+ /// used in the [cxx::bridge] mod - see
+ /// [Api<FnAnalysis>::cxxbridge_name]
+ pub(crate) fn name(&self) -> &QualifiedName {
+ &self.name_info().name
+ }
+
+ /// The name recorded for use in C++, if and only if
+ /// it differs from Rust.
+ pub(crate) fn cpp_name(&self) -> &Option<String> {
+ &self.name_info().cpp_name
+ }
+
+ /// The name for use in C++, whether or not it differs
+ /// from Rust.
+ pub(crate) fn effective_cpp_name(&self) -> &str {
+ self.cpp_name()
+ .as_deref()
+ .unwrap_or_else(|| self.name().get_final_item())
+ }
+
+ /// If this API turns out to have the same QualifiedName as another,
+ /// whether it's OK to just discard it?
+ pub(crate) fn discard_duplicates(&self) -> bool {
+ matches!(self, Api::IgnoredItem { .. })
+ }
+
+ pub(crate) fn valid_types(&self) -> Box<dyn Iterator<Item = QualifiedName>> {
+ match self {
+ Api::Subclass { name, .. } => Box::new(
+ vec![
+ self.name().clone(),
+ QualifiedName::new(&Namespace::new(), name.holder()),
+ name.cpp(),
+ ]
+ .into_iter(),
+ ),
+ _ => Box::new(std::iter::once(self.name().clone())),
+ }
+ }
+}
+
+impl<T: AnalysisPhase> std::fmt::Debug for Api<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{:?} (kind={})", self.name_info(), self)
+ }
+}
+
+pub(crate) type UnanalyzedApi = Api<NullPhase>;
+
+impl<T: AnalysisPhase> Api<T> {
+ pub(crate) fn typedef_unchanged(
+ name: ApiName,
+ item: TypedefKind,
+ old_tyname: Option<QualifiedName>,
+ analysis: T::TypedefAnalysis,
+ ) -> Result<Box<dyn Iterator<Item = Api<T>>>, ConvertErrorWithContext>
+ where
+ T: 'static,
+ {
+ Ok(Box::new(std::iter::once(Api::Typedef {
+ name,
+ item,
+ old_tyname,
+ analysis,
+ })))
+ }
+
+ pub(crate) fn struct_unchanged(
+ name: ApiName,
+ details: Box<StructDetails>,
+ analysis: T::StructAnalysis,
+ ) -> Result<Box<dyn Iterator<Item = Api<T>>>, ConvertErrorWithContext>
+ where
+ T: 'static,
+ {
+ Ok(Box::new(std::iter::once(Api::Struct {
+ name,
+ details,
+ analysis,
+ })))
+ }
+
+ pub(crate) fn fun_unchanged(
+ name: ApiName,
+ fun: Box<FuncToConvert>,
+ analysis: T::FunAnalysis,
+ ) -> Result<Box<dyn Iterator<Item = Api<T>>>, ConvertErrorWithContext>
+ where
+ T: 'static,
+ {
+ Ok(Box::new(std::iter::once(Api::Function {
+ name,
+ fun,
+ analysis,
+ })))
+ }
+
+ pub(crate) fn enum_unchanged(
+ name: ApiName,
+ item: ItemEnum,
+ ) -> Result<Box<dyn Iterator<Item = Api<T>>>, ConvertErrorWithContext>
+ where
+ T: 'static,
+ {
+ Ok(Box::new(std::iter::once(Api::Enum { name, item })))
+ }
+}
diff --git a/engine/src/conversion/apivec.rs b/engine/src/conversion/apivec.rs
new file mode 100644
index 0000000..1e440cc
--- /dev/null
+++ b/engine/src/conversion/apivec.rs
@@ -0,0 +1,137 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use indexmap::set::IndexSet as HashSet;
+
+use crate::{
+ conversion::{api::ApiName, convert_error::ErrorContext, ConvertError},
+ types::QualifiedName,
+};
+
+use super::api::{AnalysisPhase, Api};
+
+/// Newtype wrapper for a list of APIs, which enforces the invariant
+/// that each API has a unique name.
+///
+/// Specifically, each API should have a unique [`QualifiedName`] which is kept
+/// within an [`ApiName`]. The [`QualifiedName`] is used to refer to this API
+/// from others, e.g. to represent edges in the graph used for garbage collection,
+/// so that's why this uniqueness is so important.
+///
+/// At present, this type also refuses to allow mutation of an API once it
+/// has been added to a set. This is because the autocxx engine is
+/// fundamentally organized into lots of analysis phases, each one _adding_
+/// fields rather than mutating earlier fields. The idea here is that it's
+/// impossible for stupid future maintainers (i.e. me) to make errors by
+/// referring to fields before they're filled in. If a field exists, it's
+/// correct.
+///
+/// While this is currently the case, it's possible that in future we could
+/// see legitimate reasons to break this latter invariant and allow mutation
+/// of APIs within an existing `ApiVec`. But it's extremely important that
+/// the naming-uniqueness-invariant remains, so any such mutation should
+/// allow mutation only of other fields, not the name.
+pub(crate) struct ApiVec<P: AnalysisPhase> {
+ apis: Vec<Api<P>>,
+ names: HashSet<QualifiedName>,
+}
+
+impl<P: AnalysisPhase> ApiVec<P> {
+ pub(crate) fn push(&mut self, api: Api<P>) {
+ let name = api.name();
+ let already_contains = self.already_contains(name);
+ if already_contains {
+ if api.discard_duplicates() {
+ // This is already an IgnoredItem or something else where
+ // we can silently drop it.
+ log::info!("Discarding duplicate API for {}", name);
+ } else {
+ log::info!(
+ "Duplicate API for {} - removing all of them and replacing with an IgnoredItem.",
+ name
+ );
+ self.retain(|api| api.name() != name);
+ self.push(Api::IgnoredItem {
+ name: ApiName::new_from_qualified_name(name.clone()),
+ err: ConvertError::DuplicateItemsFoundInParsing,
+ ctx: Some(ErrorContext::new_for_item(name.get_final_ident())),
+ })
+ }
+ } else {
+ self.names.insert(name.clone());
+ self.apis.push(api)
+ }
+ }
+
+ fn already_contains(&self, name: &QualifiedName) -> bool {
+ self.names.contains(name)
+ }
+
+ pub(crate) fn new() -> Self {
+ Self::default()
+ }
+
+ pub(crate) fn append(&mut self, more: &mut ApiVec<P>) {
+ self.extend(more.apis.drain(..))
+ }
+
+ pub(crate) fn extend(&mut self, it: impl Iterator<Item = Api<P>>) {
+ // Could be optimized in future
+ for api in it {
+ self.push(api)
+ }
+ }
+
+ pub(crate) fn iter(&self) -> impl Iterator<Item = &Api<P>> {
+ self.apis.iter()
+ }
+
+ pub(crate) fn into_iter(self) -> impl Iterator<Item = Api<P>> {
+ self.apis.into_iter()
+ }
+
+ pub(crate) fn is_empty(&self) -> bool {
+ self.apis.is_empty()
+ }
+
+ pub fn retain<F>(&mut self, f: F)
+ where
+ F: FnMut(&Api<P>) -> bool,
+ {
+ self.apis.retain(f);
+ self.names.clear();
+ self.names
+ .extend(self.apis.iter().map(|api| api.name()).cloned());
+ }
+}
+
+impl<P: AnalysisPhase> Default for ApiVec<P> {
+ fn default() -> Self {
+ Self {
+ apis: Default::default(),
+ names: Default::default(),
+ }
+ }
+}
+
+impl<P: AnalysisPhase> FromIterator<Api<P>> for ApiVec<P> {
+ fn from_iter<I: IntoIterator<Item = Api<P>>>(iter: I) -> Self {
+ let mut this = ApiVec::new();
+ for i in iter {
+ // Could be optimized in future
+ this.push(i);
+ }
+ this
+ }
+}
diff --git a/engine/src/conversion/codegen_cpp/function_wrapper_cpp.rs b/engine/src/conversion/codegen_cpp/function_wrapper_cpp.rs
new file mode 100644
index 0000000..b2e1ea3
--- /dev/null
+++ b/engine/src/conversion/codegen_cpp/function_wrapper_cpp.rs
@@ -0,0 +1,83 @@
+// 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::{
+ analysis::fun::function_wrapper::{CppConversionType, TypeConversionPolicy},
+ ConvertError,
+};
+
+use super::type_to_cpp::{type_to_cpp, CppNameMap};
+
+impl TypeConversionPolicy {
+ pub(super) fn unconverted_type(
+ &self,
+ cpp_name_map: &CppNameMap,
+ ) -> Result<String, ConvertError> {
+ match self.cpp_conversion {
+ CppConversionType::FromUniquePtrToValue => self.unique_ptr_wrapped_type(cpp_name_map),
+ CppConversionType::FromPtrToValue => {
+ Ok(format!("{}*", self.unwrapped_type_as_string(cpp_name_map)?))
+ }
+ _ => self.unwrapped_type_as_string(cpp_name_map),
+ }
+ }
+
+ pub(super) fn converted_type(&self, cpp_name_map: &CppNameMap) -> Result<String, ConvertError> {
+ match self.cpp_conversion {
+ CppConversionType::FromValueToUniquePtr => self.unique_ptr_wrapped_type(cpp_name_map),
+ _ => self.unwrapped_type_as_string(cpp_name_map),
+ }
+ }
+
+ fn unwrapped_type_as_string(&self, cpp_name_map: &CppNameMap) -> Result<String, ConvertError> {
+ type_to_cpp(&self.unwrapped_type, cpp_name_map)
+ }
+
+ fn unique_ptr_wrapped_type(
+ &self,
+ original_name_map: &CppNameMap,
+ ) -> Result<String, ConvertError> {
+ Ok(format!(
+ "std::unique_ptr<{}>",
+ self.unwrapped_type_as_string(original_name_map)?
+ ))
+ }
+
+ pub(super) fn cpp_conversion(
+ &self,
+ var_name: &str,
+ cpp_name_map: &CppNameMap,
+ is_return: bool,
+ ) -> Result<Option<String>, ConvertError> {
+ // If is_return we want to avoid unnecessary std::moves because they
+ // make RVO less effective
+ Ok(match self.cpp_conversion {
+ CppConversionType::None | CppConversionType::FromReturnValueToPlacementPtr => {
+ Some(var_name.to_string())
+ }
+ CppConversionType::Move => Some(format!("std::move({})", var_name)),
+ CppConversionType::FromUniquePtrToValue | CppConversionType::FromPtrToMove => {
+ Some(format!("std::move(*{})", var_name))
+ }
+ CppConversionType::FromValueToUniquePtr => Some(format!(
+ "std::make_unique<{}>({})",
+ self.unconverted_type(cpp_name_map)?,
+ var_name
+ )),
+ CppConversionType::FromPtrToValue => {
+ let dereference = format!("*{}", var_name);
+ Some(if is_return {
+ dereference
+ } else {
+ format!("std::move({})", dereference)
+ })
+ }
+ CppConversionType::IgnoredPlacementPtrParameter => None,
+ })
+ }
+}
diff --git a/engine/src/conversion/codegen_cpp/mod.rs b/engine/src/conversion/codegen_cpp/mod.rs
new file mode 100644
index 0000000..02e92b2
--- /dev/null
+++ b/engine/src/conversion/codegen_cpp/mod.rs
@@ -0,0 +1,724 @@
+// 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 function_wrapper_cpp;
+mod new_and_delete_prelude;
+pub(crate) mod type_to_cpp;
+
+use crate::{
+ conversion::analysis::fun::{function_wrapper::CppFunctionKind, FnAnalysis},
+ types::{make_ident, QualifiedName},
+ CppCodegenOptions, CppFilePair,
+};
+use autocxx_parser::IncludeCppConfig;
+use indexmap::map::IndexMap as HashMap;
+use indexmap::set::IndexSet as HashSet;
+use itertools::Itertools;
+use std::borrow::Cow;
+use type_to_cpp::{original_name_map_from_apis, type_to_cpp, CppNameMap};
+
+use self::type_to_cpp::{
+ final_ident_using_original_name_map, namespaced_name_using_original_name_map,
+};
+
+use super::{
+ analysis::{
+ fun::{
+ function_wrapper::{CppFunction, CppFunctionBody},
+ FnPhase, PodAndDepAnalysis,
+ },
+ pod::PodAnalysis,
+ },
+ api::{Api, Provenance, SubclassName, TypeKind},
+ apivec::ApiVec,
+ ConvertError,
+};
+
+#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Hash)]
+enum Header {
+ System(&'static str),
+ CxxH,
+ CxxgenH,
+ NewDeletePrelude,
+}
+
+impl Header {
+ fn include_stmt(
+ &self,
+ cpp_codegen_options: &CppCodegenOptions,
+ cxxgen_header_name: &str,
+ ) -> String {
+ let blank = "".to_string();
+ match self {
+ Self::System(name) => format!("#include <{}>", name),
+ Self::CxxH => {
+ let prefix = cpp_codegen_options.path_to_cxx_h.as_ref().unwrap_or(&blank);
+ format!("#include \"{}cxx.h\"", prefix)
+ }
+ Self::CxxgenH => {
+ let prefix = cpp_codegen_options
+ .path_to_cxxgen_h
+ .as_ref()
+ .unwrap_or(&blank);
+ format!("#include \"{}{}\"", prefix, cxxgen_header_name)
+ }
+ Header::NewDeletePrelude => new_and_delete_prelude::NEW_AND_DELETE_PRELUDE.to_string(),
+ }
+ }
+
+ fn is_system(&self) -> bool {
+ matches!(self, Header::System(_) | Header::CxxH)
+ }
+}
+
+enum ConversionDirection {
+ RustCallsCpp,
+ CppCallsCpp,
+ CppCallsRust,
+}
+
+/// Some extra snippet of C++ which we (autocxx) need to generate, beyond
+/// that which cxx itself generates.
+#[derive(Default)]
+struct ExtraCpp {
+ type_definition: Option<String>, // are output before main declarations
+ declaration: Option<String>,
+ definition: Option<String>,
+ headers: Vec<Header>,
+ cpp_headers: Vec<Header>,
+}
+
+/// Generates additional C++ glue functions needed by autocxx.
+/// In some ways it would be preferable to be able to pass snippets
+/// of C++ through to `cxx` for inclusion in the C++ file which it
+/// generates, and perhaps we'll explore that in future. But for now,
+/// autocxx generates its own _additional_ C++ files which therefore
+/// need to be built and included in linking procedures.
+pub(crate) struct CppCodeGenerator<'a> {
+ additional_functions: Vec<ExtraCpp>,
+ inclusions: String,
+ original_name_map: CppNameMap,
+ config: &'a IncludeCppConfig,
+ cpp_codegen_options: &'a CppCodegenOptions<'a>,
+ cxxgen_header_name: &'a str,
+}
+
+struct SubclassFunction<'a> {
+ fun: &'a CppFunction,
+ is_pure_virtual: bool,
+}
+
+impl<'a> CppCodeGenerator<'a> {
+ pub(crate) fn generate_cpp_code(
+ inclusions: String,
+ apis: &ApiVec<FnPhase>,
+ config: &'a IncludeCppConfig,
+ cpp_codegen_options: &CppCodegenOptions,
+ cxxgen_header_name: &str,
+ ) -> Result<Option<CppFilePair>, ConvertError> {
+ let mut gen = CppCodeGenerator {
+ additional_functions: Vec::new(),
+ inclusions,
+ original_name_map: original_name_map_from_apis(apis),
+ config,
+ cpp_codegen_options,
+ cxxgen_header_name,
+ };
+ // The 'filter' on the following line is designed to ensure we don't accidentally
+ // end up out of sync with needs_cpp_codegen
+ gen.add_needs(apis.iter().filter(|api| api.needs_cpp_codegen()))?;
+ Ok(gen.generate())
+ }
+
+ // It's important to keep this in sync with Api::needs_cpp_codegen.
+ fn add_needs<'b>(
+ &mut self,
+ apis: impl Iterator<Item = &'a Api<FnPhase>>,
+ ) -> Result<(), ConvertError> {
+ let mut constructors_by_subclass: HashMap<SubclassName, Vec<&CppFunction>> = HashMap::new();
+ let mut methods_by_subclass: HashMap<SubclassName, Vec<SubclassFunction>> = HashMap::new();
+ let mut deferred_apis = Vec::new();
+ for api in apis {
+ match &api {
+ Api::StringConstructor { .. } => self.generate_string_constructor(),
+ Api::Function {
+ analysis:
+ FnAnalysis {
+ cpp_wrapper: Some(cpp_wrapper),
+ ignore_reason: Ok(_),
+ externally_callable: true,
+ ..
+ },
+ fun,
+ ..
+ } => {
+ if let Provenance::SynthesizedSubclassConstructor(details) = &fun.provenance {
+ constructors_by_subclass
+ .entry(details.subclass.clone())
+ .or_default()
+ .push(&details.cpp_impl);
+ }
+ self.generate_cpp_function(cpp_wrapper)?
+ }
+ Api::ConcreteType {
+ rs_definition,
+ cpp_definition,
+ ..
+ } => {
+ let effective_cpp_definition = match rs_definition {
+ Some(rs_definition) => {
+ Cow::Owned(type_to_cpp(rs_definition, &self.original_name_map)?)
+ }
+ None => Cow::Borrowed(cpp_definition),
+ };
+
+ self.generate_typedef(api.name(), &effective_cpp_definition)
+ }
+ Api::CType { typename, .. } => self.generate_ctype_typedef(typename),
+ Api::Subclass { .. } => deferred_apis.push(api),
+ Api::RustSubclassFn {
+ subclass, details, ..
+ } => {
+ methods_by_subclass
+ .entry(subclass.clone())
+ .or_default()
+ .push(SubclassFunction {
+ fun: &details.cpp_impl,
+ is_pure_virtual: details.is_pure_virtual,
+ });
+ }
+ Api::Struct {
+ name,
+ analysis:
+ PodAndDepAnalysis {
+ pod:
+ PodAnalysis {
+ kind: TypeKind::Pod,
+ ..
+ },
+ ..
+ },
+ ..
+ } => {
+ self.generate_pod_assertion(name.qualified_cpp_name());
+ }
+ _ => panic!("Should have filtered on needs_cpp_codegen"),
+ }
+ }
+
+ for api in deferred_apis.into_iter() {
+ match api {
+ Api::Subclass { name, superclass } => self.generate_subclass(
+ superclass,
+ name,
+ constructors_by_subclass.remove(name).unwrap_or_default(),
+ methods_by_subclass.remove(name).unwrap_or_default(),
+ )?,
+ _ => panic!("Unexpected deferred API"),
+ }
+ }
+ Ok(())
+ }
+
+ fn generate(&self) -> Option<CppFilePair> {
+ if self.additional_functions.is_empty() {
+ None
+ } else {
+ let headers = self.collect_headers(|additional_need| &additional_need.headers);
+ let cpp_headers = self.collect_headers(|additional_need| &additional_need.cpp_headers);
+ let type_definitions = self.concat_additional_items(|x| x.type_definition.as_ref());
+ let declarations = self.concat_additional_items(|x| x.declaration.as_ref());
+ let declarations = format!(
+ "#ifndef __AUTOCXXGEN_H__\n#define __AUTOCXXGEN_H__\n\n{}\n{}\n{}\n{}#endif // __AUTOCXXGEN_H__\n",
+ headers, self.inclusions, type_definitions, declarations
+ );
+ log::info!("Additional C++ decls:\n{}", declarations);
+ let header_name = self
+ .cpp_codegen_options
+ .autocxxgen_header_namer
+ .name_header(self.config.get_mod_name().to_string());
+ let implementation = if self
+ .additional_functions
+ .iter()
+ .any(|x| x.definition.is_some())
+ {
+ let definitions = self.concat_additional_items(|x| x.definition.as_ref());
+ let definitions = format!(
+ "#include \"{}\"\n{}\n{}",
+ header_name, cpp_headers, definitions
+ );
+ log::info!("Additional C++ defs:\n{}", definitions);
+ Some(definitions.into_bytes())
+ } else {
+ None
+ };
+ Some(CppFilePair {
+ header: declarations.into_bytes(),
+ implementation,
+ header_name,
+ })
+ }
+ }
+
+ fn collect_headers<F>(&self, filter: F) -> String
+ where
+ F: Fn(&ExtraCpp) -> &[Header],
+ {
+ let cpp_headers: HashSet<_> = self
+ .additional_functions
+ .iter()
+ .flat_map(|x| filter(x).iter())
+ .filter(|x| !self.cpp_codegen_options.suppress_system_headers || !x.is_system())
+ .collect(); // uniqify
+ cpp_headers
+ .iter()
+ .map(|x| x.include_stmt(self.cpp_codegen_options, self.cxxgen_header_name))
+ .join("\n")
+ }
+
+ fn concat_additional_items<F>(&self, field_access: F) -> String
+ where
+ F: FnMut(&ExtraCpp) -> Option<&String>,
+ {
+ let mut s = self
+ .additional_functions
+ .iter()
+ .flat_map(field_access)
+ .join("\n");
+ s.push('\n');
+ s
+ }
+
+ fn generate_pod_assertion(&mut self, name: String) {
+ // These assertions are generated by cxx for trivial ExternTypes but
+ // *only if* such types are used as trivial types in the cxx::bridge.
+ // It's possible for types which we generate to be used even without
+ // passing through the cxx::bridge, and as we generate Drop impls, that
+ // can result in destructors for nested types being called multiple times
+ // if we represent them as trivial types. So generate an extra
+ // assertion to make sure.
+ let declaration = Some(format!("static_assert(::rust::IsRelocatable<{}>::value, \"type {} should be trivially move constructible and trivially destructible to be used with generate_pod! in autocxx\");", name, name));
+ self.additional_functions.push(ExtraCpp {
+ declaration,
+ headers: vec![Header::CxxH],
+ ..Default::default()
+ })
+ }
+
+ fn generate_string_constructor(&mut self) {
+ let makestring_name = self.config.get_makestring_name();
+ let declaration = Some(format!("inline std::unique_ptr<std::string> {}(::rust::Str str) {{ return std::make_unique<std::string>(std::string(str)); }}", makestring_name));
+ self.additional_functions.push(ExtraCpp {
+ declaration,
+ headers: vec![
+ Header::System("memory"),
+ Header::System("string"),
+ Header::CxxH,
+ ],
+ ..Default::default()
+ })
+ }
+
+ fn generate_cpp_function(&mut self, details: &CppFunction) -> Result<(), ConvertError> {
+ self.additional_functions
+ .push(self.generate_cpp_function_inner(
+ details,
+ false,
+ ConversionDirection::RustCallsCpp,
+ false,
+ None,
+ )?);
+ Ok(())
+ }
+
+ fn generate_cpp_function_inner(
+ &self,
+ details: &CppFunction,
+ avoid_this: bool,
+ conversion_direction: ConversionDirection,
+ requires_rust_declarations: bool,
+ force_name: Option<&str>,
+ ) -> Result<ExtraCpp, ConvertError> {
+ // Even if the original function call is in a namespace,
+ // we generate this wrapper in the global namespace.
+ // We could easily do this the other way round, and when
+ // cxx::bridge comes to support nested namespace mods then
+ // we wil wish to do that to avoid name conflicts. However,
+ // at the moment this is simpler because it avoids us having
+ // to generate namespace blocks in the generated C++.
+ let is_a_method = !avoid_this
+ && matches!(
+ details.kind,
+ CppFunctionKind::Method
+ | CppFunctionKind::ConstMethod
+ | CppFunctionKind::Constructor
+ );
+ let name = match force_name {
+ Some(n) => n.to_string(),
+ None => details.wrapper_function_name.to_string(),
+ };
+ let get_arg_name = |counter: usize| -> String {
+ if is_a_method && counter == 0 {
+ // For method calls that we generate, the first
+ // argument name needs to be such that we recognize
+ // it as a method in the second invocation of
+ // bridge_converter after it's flowed again through
+ // bindgen.
+ // TODO this may not be the case any longer. We
+ // may be able to remove this.
+ "autocxx_gen_this".to_string()
+ } else {
+ format!("arg{}", counter)
+ }
+ };
+ // If this returns a non-POD value, we may instead wish to emplace
+ // it into a parameter, let's see.
+ let args: Result<Vec<_>, _> = details
+ .argument_conversion
+ .iter()
+ .enumerate()
+ .map(|(counter, ty)| {
+ Ok(format!(
+ "{} {}",
+ match conversion_direction {
+ ConversionDirection::RustCallsCpp =>
+ ty.unconverted_type(&self.original_name_map)?,
+ ConversionDirection::CppCallsCpp =>
+ ty.converted_type(&self.original_name_map)?,
+ ConversionDirection::CppCallsRust =>
+ ty.inverse().unconverted_type(&self.original_name_map)?,
+ },
+ get_arg_name(counter)
+ ))
+ })
+ .collect();
+ let args = args?.join(", ");
+ let default_return = match details.kind {
+ CppFunctionKind::SynthesizedConstructor => "",
+ _ => "void",
+ };
+ let ret_type = details
+ .return_conversion
+ .as_ref()
+ .and_then(|x| match conversion_direction {
+ ConversionDirection::RustCallsCpp => {
+ if x.populate_return_value() {
+ Some(x.converted_type(&self.original_name_map))
+ } else {
+ None
+ }
+ }
+ ConversionDirection::CppCallsCpp => {
+ Some(x.unconverted_type(&self.original_name_map))
+ }
+ ConversionDirection::CppCallsRust => {
+ Some(x.inverse().converted_type(&self.original_name_map))
+ }
+ })
+ .unwrap_or_else(|| Ok(default_return.to_string()))?;
+ let constness = match details.kind {
+ CppFunctionKind::ConstMethod => " const",
+ _ => "",
+ };
+ let declaration = format!("{} {}({}){}", ret_type, name, args, constness);
+ let qualification = if let Some(qualification) = &details.qualification {
+ format!("{}::", qualification.to_cpp_name())
+ } else {
+ "".to_string()
+ };
+ let qualified_declaration = format!(
+ "{} {}{}({}){}",
+ ret_type, qualification, name, args, constness
+ );
+ // Whether there's a placement param in which to put the return value
+ let placement_param = details
+ .argument_conversion
+ .iter()
+ .enumerate()
+ .filter_map(|(counter, conv)| {
+ if conv.is_placement_parameter() {
+ Some(get_arg_name(counter))
+ } else {
+ None
+ }
+ })
+ .next();
+ // Arguments to underlying function call
+ let arg_list: Result<Vec<_>, _> = details
+ .argument_conversion
+ .iter()
+ .enumerate()
+ .map(|(counter, conv)| match conversion_direction {
+ ConversionDirection::RustCallsCpp => {
+ conv.cpp_conversion(&get_arg_name(counter), &self.original_name_map, false)
+ }
+ ConversionDirection::CppCallsCpp => Ok(Some(get_arg_name(counter))),
+ ConversionDirection::CppCallsRust => conv.inverse().cpp_conversion(
+ &get_arg_name(counter),
+ &self.original_name_map,
+ false,
+ ),
+ })
+ .collect();
+ let mut arg_list = arg_list?.into_iter().flatten();
+ let receiver = if is_a_method { arg_list.next() } else { None };
+ if matches!(&details.payload, CppFunctionBody::ConstructSuperclass(_)) {
+ arg_list.next();
+ }
+ let arg_list = if details.pass_obs_field {
+ std::iter::once("*obs".to_string())
+ .chain(arg_list)
+ .join(",")
+ } else {
+ arg_list.join(", ")
+ };
+ let (mut underlying_function_call, field_assignments, need_allocators) = match &details
+ .payload
+ {
+ CppFunctionBody::Cast => (arg_list, "".to_string(), false),
+ CppFunctionBody::PlacementNew(ns, id) => {
+ let ty_id = QualifiedName::new(ns, id.clone());
+ let ty_id = self.namespaced_name(&ty_id);
+ (
+ format!("new ({}) {}({})", receiver.unwrap(), ty_id, arg_list),
+ "".to_string(),
+ false,
+ )
+ }
+ CppFunctionBody::Destructor(ns, id) => {
+ let ty_id = QualifiedName::new(ns, id.clone());
+ let ty_id = final_ident_using_original_name_map(&ty_id, &self.original_name_map);
+ (format!("{}->~{}()", arg_list, ty_id), "".to_string(), false)
+ }
+ CppFunctionBody::FunctionCall(ns, id) => match receiver {
+ Some(receiver) => (
+ format!("{}.{}({})", receiver, id, arg_list),
+ "".to_string(),
+ false,
+ ),
+ None => {
+ let underlying_function_call = ns
+ .into_iter()
+ .cloned()
+ .chain(std::iter::once(id.to_string()))
+ .join("::");
+ (
+ format!("{}({})", underlying_function_call, arg_list),
+ "".to_string(),
+ false,
+ )
+ }
+ },
+ CppFunctionBody::StaticMethodCall(ns, ty_id, fn_id) => {
+ let underlying_function_call = ns
+ .into_iter()
+ .cloned()
+ .chain([ty_id.to_string(), fn_id.to_string()].iter().cloned())
+ .join("::");
+ (
+ format!("{}({})", underlying_function_call, arg_list),
+ "".to_string(),
+ false,
+ )
+ }
+ CppFunctionBody::ConstructSuperclass(_) => ("".to_string(), arg_list, false),
+ CppFunctionBody::AllocUninitialized(ty) => {
+ let namespaced_ty = self.namespaced_name(ty);
+ (
+ format!("new_appropriately<{}>();", namespaced_ty,),
+ "".to_string(),
+ true,
+ )
+ }
+ CppFunctionBody::FreeUninitialized(ty) => (
+ format!("delete_appropriately<{}>(arg0);", self.namespaced_name(ty)),
+ "".to_string(),
+ true,
+ ),
+ };
+ if let Some(ret) = &details.return_conversion {
+ let call_itself = match conversion_direction {
+ ConversionDirection::RustCallsCpp => {
+ ret.cpp_conversion(&underlying_function_call, &self.original_name_map, true)?
+ }
+ ConversionDirection::CppCallsCpp => Some(underlying_function_call),
+ ConversionDirection::CppCallsRust => ret.inverse().cpp_conversion(
+ &underlying_function_call,
+ &self.original_name_map,
+ true,
+ )?,
+ }
+ .expect(
+ "Expected some conversion type for return value which resulted in a parameter name",
+ );
+
+ underlying_function_call = match placement_param {
+ Some(placement_param) => {
+ let tyname = type_to_cpp(&ret.unwrapped_type, &self.original_name_map)?;
+ format!("new({}) {}({})", placement_param, tyname, call_itself)
+ }
+ None => format!("return {}", call_itself),
+ };
+ };
+ if !underlying_function_call.is_empty() {
+ underlying_function_call = format!("{};", underlying_function_call);
+ }
+ let field_assignments =
+ if let CppFunctionBody::ConstructSuperclass(superclass_name) = &details.payload {
+ let superclass_assignments = if field_assignments.is_empty() {
+ "".to_string()
+ } else {
+ format!("{}({}), ", superclass_name, field_assignments)
+ };
+ format!(": {}obs(std::move(arg0))", superclass_assignments)
+ } else {
+ "".into()
+ };
+ let definition_after_sig =
+ format!("{} {{ {} }}", field_assignments, underlying_function_call,);
+ let (declaration, definition) = if requires_rust_declarations {
+ (
+ Some(format!("{};", declaration)),
+ Some(format!(
+ "{} {}",
+ qualified_declaration, definition_after_sig
+ )),
+ )
+ } else {
+ (
+ Some(format!("inline {} {}", declaration, definition_after_sig)),
+ None,
+ )
+ };
+ let mut headers = vec![Header::System("memory")];
+ if need_allocators {
+ headers.push(Header::System("stddef.h"));
+ headers.push(Header::NewDeletePrelude);
+ }
+ Ok(ExtraCpp {
+ declaration,
+ definition,
+ headers,
+ ..Default::default()
+ })
+ }
+
+ fn namespaced_name(&self, name: &QualifiedName) -> String {
+ namespaced_name_using_original_name_map(name, &self.original_name_map)
+ }
+
+ fn generate_ctype_typedef(&mut self, tn: &QualifiedName) {
+ let cpp_name = tn.to_cpp_name();
+ self.generate_typedef(tn, &cpp_name)
+ }
+
+ fn generate_typedef(&mut self, tn: &QualifiedName, definition: &str) {
+ let our_name = tn.get_final_item();
+ self.additional_functions.push(ExtraCpp {
+ type_definition: Some(format!("typedef {} {};", definition, our_name)),
+ ..Default::default()
+ })
+ }
+
+ fn generate_subclass(
+ &mut self,
+ superclass: &QualifiedName,
+ subclass: &SubclassName,
+ constructors: Vec<&CppFunction>,
+ methods: Vec<SubclassFunction>,
+ ) -> Result<(), ConvertError> {
+ let holder = subclass.holder();
+ self.additional_functions.push(ExtraCpp {
+ type_definition: Some(format!("struct {};", holder)),
+ ..Default::default()
+ });
+ let mut method_decls = Vec::new();
+ for method in methods {
+ // First the method which calls from C++ to Rust
+ let mut fn_impl = self.generate_cpp_function_inner(
+ method.fun,
+ true,
+ ConversionDirection::CppCallsRust,
+ true,
+ Some(&method.fun.original_cpp_name),
+ )?;
+ method_decls.push(fn_impl.declaration.take().unwrap());
+ self.additional_functions.push(fn_impl);
+ // And now the function to be called from Rust for default implementation (calls superclass in C++)
+ if !method.is_pure_virtual {
+ let mut super_method = method.fun.clone();
+ super_method.pass_obs_field = false;
+ super_method.wrapper_function_name = SubclassName::get_super_fn_name(
+ superclass.get_namespace(),
+ &method.fun.wrapper_function_name.to_string(),
+ )
+ .get_final_ident();
+ super_method.payload = CppFunctionBody::StaticMethodCall(
+ superclass.get_namespace().clone(),
+ superclass.get_final_ident(),
+ make_ident(&method.fun.original_cpp_name),
+ );
+ let mut super_fn_impl = self.generate_cpp_function_inner(
+ &super_method,
+ true,
+ ConversionDirection::CppCallsCpp,
+ false,
+ None,
+ )?;
+ method_decls.push(super_fn_impl.declaration.take().unwrap());
+ self.additional_functions.push(super_fn_impl);
+ }
+ }
+ // In future, for each superclass..
+ let super_name = superclass.get_final_item();
+ method_decls.push(format!(
+ "const {}& As_{}() const {{ return *this; }}",
+ super_name, super_name,
+ ));
+ method_decls.push(format!(
+ "{}& As_{}_mut() {{ return *this; }}",
+ super_name, super_name
+ ));
+ // And now constructors
+ let mut constructor_decls: Vec<String> = Vec::new();
+ for constructor in constructors {
+ let mut fn_impl = self.generate_cpp_function_inner(
+ constructor,
+ false,
+ ConversionDirection::CppCallsCpp,
+ false,
+ None,
+ )?;
+ let decl = fn_impl.declaration.take().unwrap();
+ constructor_decls.push(decl);
+ self.additional_functions.push(fn_impl);
+ }
+ self.additional_functions.push(ExtraCpp {
+ type_definition: Some(format!(
+ "class {} : {}\n{{\npublic:\n{}\n{}\nvoid {}() const;\nprivate:rust::Box<{}> obs;\nvoid really_remove_ownership();\n\n}};",
+ subclass.cpp(),
+ superclass.to_cpp_name(),
+ constructor_decls.join("\n"),
+ method_decls.join("\n"),
+ subclass.cpp_remove_ownership(),
+ holder
+ )),
+ definition: Some(format!(
+ "void {}::{}() const {{\nconst_cast<{}*>(this)->really_remove_ownership();\n}}\nvoid {}::really_remove_ownership() {{\nauto new_obs = {}(std::move(obs));\nobs = std::move(new_obs);\n}}\n",
+ subclass.cpp(),
+ subclass.cpp_remove_ownership(),
+ subclass.cpp(),
+ subclass.cpp(),
+ subclass.remove_ownership()
+ )),
+ cpp_headers: vec![Header::CxxgenH],
+ ..Default::default()
+ });
+ Ok(())
+ }
+}
diff --git a/engine/src/conversion/codegen_cpp/new_and_delete_prelude.rs b/engine/src/conversion/codegen_cpp/new_and_delete_prelude.rs
new file mode 100644
index 0000000..d860789
--- /dev/null
+++ b/engine/src/conversion/codegen_cpp/new_and_delete_prelude.rs
@@ -0,0 +1,43 @@
+// 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 indoc::indoc;
+
+/// This is logic to call either an overloaded operator new/delete
+/// or the standard one.
+/// The SFINAE magic here is: int is a better match than long,
+/// and so the versions which match class-specific operator new/delete
+/// will be used in preference to the general global ::operator new/delete.
+pub(super) static NEW_AND_DELETE_PRELUDE: &str = indoc! {"
+ #ifndef AUTOCXX_NEW_AND_DELETE_PRELUDE
+ #define AUTOCXX_NEW_AND_DELETE_PRELUDE
+ // Mechanics to call custom operator new and delete
+ template <typename T>
+ auto delete_imp(T *ptr, int) -> decltype((void)T::operator delete(ptr)) {
+ T::operator delete(ptr);
+ }
+ template <typename T> void delete_imp(T *ptr, long) { ::operator delete(ptr); }
+ template <typename T> void delete_appropriately(T *obj) {
+ // 0 is a better match for the first 'delete_imp' so will match
+ // preferentially.
+ delete_imp(obj, 0);
+ }
+ template <typename T>
+ auto new_imp(size_t count, int) -> decltype(T::operator new(count)) {
+ return T::operator new(count);
+ }
+ template <typename T> void *new_imp(size_t count, long) {
+ return ::operator new(count);
+ }
+ template <typename T> T *new_appropriately() {
+ // 0 is a better match for the first 'delete_imp' so will match
+ // preferentially.
+ return static_cast<T *>(new_imp<T>(sizeof(T), 0));
+ }
+ #endif // AUTOCXX_NEW_AND_DELETE_PRELUDE
+"};
diff --git a/engine/src/conversion/codegen_cpp/type_to_cpp.rs b/engine/src/conversion/codegen_cpp/type_to_cpp.rs
new file mode 100644
index 0000000..7febb7a
--- /dev/null
+++ b/engine/src/conversion/codegen_cpp/type_to_cpp.rs
@@ -0,0 +1,140 @@
+// 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, AnalysisPhase, ConvertError},
+ types::QualifiedName,
+};
+use indexmap::map::IndexMap as HashMap;
+use itertools::Itertools;
+use quote::ToTokens;
+use std::iter::once;
+use syn::{Token, Type};
+
+/// Map from QualifiedName to original C++ name. Original C++ name does not
+/// include the namespace; this can be assumed to be the same as the namespace
+/// in the QualifiedName.
+pub(crate) type CppNameMap = HashMap<QualifiedName, String>;
+
+pub(crate) fn original_name_map_from_apis<T: AnalysisPhase>(apis: &ApiVec<T>) -> CppNameMap {
+ apis.iter()
+ .filter_map(|api| {
+ api.cpp_name()
+ .as_ref()
+ .map(|cpp_name| (api.name().clone(), cpp_name.clone()))
+ })
+ .collect()
+}
+
+pub(crate) fn namespaced_name_using_original_name_map(
+ qual_name: &QualifiedName,
+ original_name_map: &CppNameMap,
+) -> String {
+ if let Some(cpp_name) = original_name_map.get(qual_name) {
+ qual_name
+ .get_namespace()
+ .iter()
+ .chain(once(cpp_name))
+ .join("::")
+ } else {
+ qual_name.to_cpp_name()
+ }
+}
+
+pub(crate) fn final_ident_using_original_name_map(
+ qual_name: &QualifiedName,
+ original_name_map: &CppNameMap,
+) -> String {
+ match original_name_map.get(qual_name) {
+ Some(original_name) => {
+ // If we have an original name, this may be a nested struct
+ // (e.g. A::B). The final ident here is just 'B' so...
+ original_name
+ .rsplit_once("::")
+ .map_or(original_name.clone(), |(_, original_name)| {
+ original_name.to_string()
+ })
+ }
+ None => qual_name.get_final_cpp_item(),
+ }
+}
+
+pub(crate) fn type_to_cpp(ty: &Type, cpp_name_map: &CppNameMap) -> Result<String, ConvertError> {
+ match ty {
+ Type::Path(typ) => {
+ // If this is a std::unique_ptr we do need to pass
+ // its argument through.
+ let qual_name = QualifiedName::from_type_path(typ);
+ let root = namespaced_name_using_original_name_map(&qual_name, cpp_name_map);
+ if root == "Pin" {
+ // Strip all Pins from type names when describing them in C++.
+ let inner_type = &typ.path.segments.last().unwrap().arguments;
+ if let syn::PathArguments::AngleBracketed(ab) = inner_type {
+ let inner_type = ab.args.iter().next().unwrap();
+ if let syn::GenericArgument::Type(gat) = inner_type {
+ return type_to_cpp(gat, cpp_name_map);
+ }
+ }
+ panic!("Pin<...> didn't contain the inner types we expected");
+ }
+ let suffix = match &typ.path.segments.last().unwrap().arguments {
+ syn::PathArguments::AngleBracketed(ab) => {
+ let results: Result<Vec<_>, _> = ab
+ .args
+ .iter()
+ .map(|x| match x {
+ syn::GenericArgument::Type(gat) => type_to_cpp(gat, cpp_name_map),
+ _ => Ok("".to_string()),
+ })
+ .collect();
+ Some(results?.join(", "))
+ }
+ syn::PathArguments::None | syn::PathArguments::Parenthesized(_) => None,
+ };
+ match suffix {
+ None => Ok(root),
+ Some(suffix) => Ok(format!("{}<{}>", root, suffix)),
+ }
+ }
+ Type::Reference(typr) => match &*typr.elem {
+ Type::Path(typ) if typ.path.is_ident("str") => Ok("rust::Str".into()),
+ _ => Ok(format!(
+ "{}{}&",
+ get_mut_string(&typr.mutability),
+ type_to_cpp(typr.elem.as_ref(), cpp_name_map)?
+ )),
+ },
+ Type::Ptr(typp) => Ok(format!(
+ "{}{}*",
+ get_mut_string(&typp.mutability),
+ type_to_cpp(typp.elem.as_ref(), cpp_name_map)?
+ )),
+ Type::Array(_)
+ | Type::BareFn(_)
+ | Type::Group(_)
+ | Type::ImplTrait(_)
+ | Type::Infer(_)
+ | Type::Macro(_)
+ | Type::Never(_)
+ | Type::Paren(_)
+ | Type::Slice(_)
+ | Type::TraitObject(_)
+ | Type::Tuple(_)
+ | Type::Verbatim(_) => Err(ConvertError::UnsupportedType(
+ ty.to_token_stream().to_string(),
+ )),
+ _ => Err(ConvertError::UnknownType(ty.to_token_stream().to_string())),
+ }
+}
+
+fn get_mut_string(mutability: &Option<Token![mut]>) -> &'static str {
+ match mutability {
+ None => "const ",
+ Some(_) => "",
+ }
+}
diff --git a/engine/src/conversion/codegen_rs/fun_codegen.rs b/engine/src/conversion/codegen_rs/fun_codegen.rs
new file mode 100644
index 0000000..7c32b1b
--- /dev/null
+++ b/engine/src/conversion/codegen_rs/fun_codegen.rs
@@ -0,0 +1,424 @@
+// 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 std::borrow::Cow;
+
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+use syn::{
+ parse::Parser,
+ parse_quote,
+ punctuated::Punctuated,
+ token::{Comma, Unsafe},
+ Attribute, FnArg, ForeignItem, Ident, ImplItem, Item, ReturnType,
+};
+
+use super::{
+ function_wrapper_rs::RustParamConversion,
+ maybe_unsafes_to_tokens,
+ unqualify::{unqualify_params, unqualify_ret_type},
+ ImplBlockDetails, MaybeUnsafeStmt, RsCodegenResult, TraitImplBlockDetails, Use,
+};
+use crate::{
+ conversion::{
+ analysis::fun::{
+ ArgumentAnalysis, FnAnalysis, FnKind, MethodKind, RustRenameStrategy,
+ TraitMethodDetails,
+ },
+ api::UnsafetyNeeded,
+ },
+ types::{Namespace, QualifiedName},
+};
+use crate::{
+ conversion::{api::FuncToConvert, codegen_rs::lifetime::add_explicit_lifetime_if_necessary},
+ types::make_ident,
+};
+
+impl UnsafetyNeeded {
+ pub(crate) fn bridge_token(&self) -> Option<Unsafe> {
+ match self {
+ UnsafetyNeeded::None => None,
+ _ => Some(parse_quote! { unsafe }),
+ }
+ }
+
+ pub(crate) fn wrapper_token(&self) -> Option<Unsafe> {
+ match self {
+ UnsafetyNeeded::Always => Some(parse_quote! { unsafe }),
+ _ => None,
+ }
+ }
+
+ pub(crate) fn from_param_details(params: &[ArgumentAnalysis], ignore_placements: bool) -> Self {
+ params.iter().fold(UnsafetyNeeded::None, |accumulator, pd| {
+ if matches!(accumulator, UnsafetyNeeded::Always) {
+ UnsafetyNeeded::Always
+ } else if (pd.self_type.is_some() || pd.is_placement_return_destination)
+ && ignore_placements
+ {
+ if matches!(
+ pd.requires_unsafe,
+ UnsafetyNeeded::Always | UnsafetyNeeded::JustBridge
+ ) {
+ UnsafetyNeeded::JustBridge
+ } else {
+ accumulator
+ }
+ } else if matches!(pd.requires_unsafe, UnsafetyNeeded::Always) {
+ UnsafetyNeeded::Always
+ } else if matches!(accumulator, UnsafetyNeeded::JustBridge)
+ || matches!(pd.requires_unsafe, UnsafetyNeeded::JustBridge)
+ {
+ UnsafetyNeeded::JustBridge
+ } else {
+ UnsafetyNeeded::None
+ }
+ })
+ }
+}
+
+pub(super) fn gen_function(
+ ns: &Namespace,
+ fun: FuncToConvert,
+ analysis: FnAnalysis,
+ cpp_call_name: String,
+ non_pod_types: &HashSet<QualifiedName>,
+) -> RsCodegenResult {
+ if analysis.ignore_reason.is_err() || !analysis.externally_callable {
+ return RsCodegenResult::default();
+ }
+ let cxxbridge_name = analysis.cxxbridge_name;
+ let rust_name = &analysis.rust_name;
+ let ret_type = analysis.ret_type;
+ let param_details = analysis.param_details;
+ let wrapper_function_needed = analysis.cpp_wrapper.is_some();
+ let params = analysis.params;
+ let vis = analysis.vis;
+ let kind = analysis.kind;
+ let doc_attrs = fun.doc_attrs;
+
+ let mut cpp_name_attr = Vec::new();
+ let mut impl_entry = None;
+ let mut trait_impl_entry = None;
+ let mut bindgen_mod_items = Vec::new();
+ let always_unsafe_due_to_trait_definition = match kind {
+ FnKind::TraitMethod { ref details, .. } => details.trait_call_is_unsafe,
+ _ => false,
+ };
+ let fn_generator = FnGenerator {
+ param_details: ¶m_details,
+ cxxbridge_name: &cxxbridge_name,
+ rust_name,
+ unsafety: &analysis.requires_unsafe,
+ always_unsafe_due_to_trait_definition,
+ doc_attrs: &doc_attrs,
+ non_pod_types,
+ };
+ // In rare occasions, we might need to give an explicit lifetime.
+ let (lifetime_tokens, params, ret_type) = add_explicit_lifetime_if_necessary(
+ ¶m_details,
+ params,
+ Cow::Borrowed(&ret_type),
+ non_pod_types,
+ true,
+ );
+
+ if analysis.rust_wrapper_needed {
+ match kind {
+ FnKind::Method {
+ ref impl_for,
+ method_kind: MethodKind::Constructor { .. },
+ ..
+ } => {
+ // Constructor.
+ impl_entry = Some(fn_generator.generate_constructor_impl(impl_for));
+ }
+ FnKind::Method {
+ ref impl_for,
+ ref method_kind,
+ ..
+ } => {
+ // Method, or static method.
+ impl_entry = Some(fn_generator.generate_method_impl(
+ matches!(method_kind, MethodKind::Constructor { .. }),
+ impl_for,
+ &ret_type,
+ ));
+ }
+ FnKind::TraitMethod { ref details, .. } => {
+ trait_impl_entry = Some(fn_generator.generate_trait_impl(details, &ret_type));
+ }
+ _ => {
+ // Generate plain old function
+ bindgen_mod_items.push(fn_generator.generate_function_impl(&ret_type));
+ }
+ }
+ }
+
+ let materialization = match kind {
+ FnKind::Method { .. } | FnKind::TraitMethod { .. } => None,
+ FnKind::Function => match analysis.rust_rename_strategy {
+ _ if analysis.rust_wrapper_needed => {
+ Some(Use::SpecificNameFromBindgen(make_ident(rust_name)))
+ }
+ RustRenameStrategy::RenameInOutputMod(ref alias) => {
+ Some(Use::UsedFromCxxBridgeWithAlias(alias.clone()))
+ }
+ _ => Some(Use::UsedFromCxxBridge),
+ },
+ };
+ if cxxbridge_name != cpp_call_name && !wrapper_function_needed {
+ cpp_name_attr = Attribute::parse_outer
+ .parse2(quote!(
+ #[cxx_name = #cpp_call_name]
+ ))
+ .unwrap();
+ }
+
+ // Finally - namespace support. All the Types in everything
+ // above this point are fully qualified. We need to unqualify them.
+ // We need to do that _after_ the above wrapper_function_needed
+ // work, because it relies upon spotting fully qualified names like
+ // std::unique_ptr. However, after it's done its job, all such
+ // well-known types should be unqualified already (e.g. just UniquePtr)
+ // and the following code will act to unqualify only those types
+ // which the user has declared.
+ let params = unqualify_params(params);
+ let ret_type = unqualify_ret_type(ret_type.into_owned());
+ // And we need to make an attribute for the namespace that the function
+ // itself is in.
+ let namespace_attr = if ns.is_empty() || wrapper_function_needed {
+ Vec::new()
+ } else {
+ let namespace_string = ns.to_string();
+ Attribute::parse_outer
+ .parse2(quote!(
+ #[namespace = #namespace_string]
+ ))
+ .unwrap()
+ };
+ // At last, actually generate the cxx::bridge entry.
+ let bridge_unsafety = analysis.requires_unsafe.bridge_token();
+ let extern_c_mod_item = ForeignItem::Fn(parse_quote!(
+ #(#namespace_attr)*
+ #(#cpp_name_attr)*
+ #(#doc_attrs)*
+ #vis #bridge_unsafety fn #cxxbridge_name #lifetime_tokens ( #params ) #ret_type;
+ ));
+ RsCodegenResult {
+ extern_c_mod_items: vec![extern_c_mod_item],
+ bindgen_mod_items,
+ impl_entry,
+ trait_impl_entry,
+ materializations: materialization.into_iter().collect(),
+ ..Default::default()
+ }
+}
+
+/// Knows how to generate a given function.
+#[derive(Clone)]
+struct FnGenerator<'a> {
+ param_details: &'a [ArgumentAnalysis],
+ cxxbridge_name: &'a Ident,
+ rust_name: &'a str,
+ unsafety: &'a UnsafetyNeeded,
+ always_unsafe_due_to_trait_definition: bool,
+ doc_attrs: &'a Vec<Attribute>,
+ non_pod_types: &'a HashSet<QualifiedName>,
+}
+
+impl<'a> FnGenerator<'a> {
+ fn common_parts<'b>(
+ &self,
+ avoid_self: bool,
+ parameter_reordering: &Option<Vec<usize>>,
+ ret_type: &'b ReturnType,
+ ) -> (
+ Option<TokenStream>,
+ Punctuated<FnArg, Comma>,
+ std::borrow::Cow<'b, ReturnType>,
+ TokenStream,
+ ) {
+ let mut wrapper_params: Punctuated<FnArg, Comma> = Punctuated::new();
+ let mut local_variables = Vec::new();
+ let mut arg_list = Vec::new();
+ let mut ptr_arg_name = None;
+ let mut ret_type = Cow::Borrowed(ret_type);
+ let mut any_conversion_requires_unsafe = false;
+ for pd in self.param_details {
+ let wrapper_arg_name = if pd.self_type.is_some() && !avoid_self {
+ parse_quote!(self)
+ } else {
+ pd.name.clone()
+ };
+ let rust_for_param = pd.conversion.rust_conversion(wrapper_arg_name.clone());
+ match rust_for_param {
+ RustParamConversion::Param {
+ ty,
+ conversion,
+ local_variables: mut these_local_variables,
+ conversion_requires_unsafe,
+ } => {
+ arg_list.push(conversion.clone());
+ local_variables.append(&mut these_local_variables);
+ if pd.is_placement_return_destination {
+ ptr_arg_name = Some(conversion);
+ } else {
+ let param_mutability = pd.conversion.rust_conversion.requires_mutability();
+ wrapper_params.push(parse_quote!(
+ #param_mutability #wrapper_arg_name: #ty
+ ));
+ }
+ any_conversion_requires_unsafe =
+ conversion_requires_unsafe || any_conversion_requires_unsafe;
+ }
+ RustParamConversion::ReturnValue { ty } => {
+ ptr_arg_name = Some(pd.name.to_token_stream());
+ ret_type = Cow::Owned(parse_quote! {
+ -> impl autocxx::moveit::new::New<Output = #ty>
+ });
+ arg_list.push(pd.name.to_token_stream());
+ }
+ }
+ }
+ if let Some(parameter_reordering) = ¶meter_reordering {
+ wrapper_params = Self::reorder_parameters(wrapper_params, parameter_reordering);
+ }
+ let (lifetime_tokens, wrapper_params, ret_type) = add_explicit_lifetime_if_necessary(
+ self.param_details,
+ wrapper_params,
+ ret_type,
+ self.non_pod_types,
+ false,
+ );
+
+ let cxxbridge_name = self.cxxbridge_name;
+ let call_body = MaybeUnsafeStmt::maybe_unsafe(
+ quote! {
+ cxxbridge::#cxxbridge_name ( #(#arg_list),* )
+ },
+ any_conversion_requires_unsafe || matches!(self.unsafety, UnsafetyNeeded::JustBridge),
+ );
+ let call_stmts = if let Some(ptr_arg_name) = ptr_arg_name {
+ let mut closure_stmts = local_variables;
+ closure_stmts.push(MaybeUnsafeStmt::binary(
+ quote! { let #ptr_arg_name = unsafe { #ptr_arg_name.get_unchecked_mut().as_mut_ptr() };},
+ quote! { let #ptr_arg_name = #ptr_arg_name.get_unchecked_mut().as_mut_ptr();},
+ ));
+ closure_stmts.push(call_body);
+ let closure_stmts = maybe_unsafes_to_tokens(closure_stmts, true);
+ vec![MaybeUnsafeStmt::needs_unsafe(parse_quote! {
+ autocxx::moveit::new::by_raw(move |#ptr_arg_name| {
+ #closure_stmts
+ })
+ })]
+ } else {
+ let mut call_stmts = local_variables;
+ call_stmts.push(call_body);
+ call_stmts
+ };
+ let context_is_unsafe = matches!(self.unsafety, UnsafetyNeeded::Always)
+ || self.always_unsafe_due_to_trait_definition;
+ let call_body = maybe_unsafes_to_tokens(call_stmts, context_is_unsafe);
+ (lifetime_tokens, wrapper_params, ret_type, call_body)
+ }
+
+ /// Generate an 'impl Type { methods-go-here }' item
+ fn generate_method_impl(
+ &self,
+ avoid_self: bool,
+ impl_block_type_name: &QualifiedName,
+ ret_type: &ReturnType,
+ ) -> Box<ImplBlockDetails> {
+ let (lifetime_tokens, wrapper_params, ret_type, call_body) =
+ self.common_parts(avoid_self, &None, ret_type);
+ let rust_name = make_ident(self.rust_name);
+ let unsafety = self.unsafety.wrapper_token();
+ let doc_attrs = self.doc_attrs;
+ Box::new(ImplBlockDetails {
+ item: ImplItem::Method(parse_quote! {
+ #(#doc_attrs)*
+ pub #unsafety fn #rust_name #lifetime_tokens ( #wrapper_params ) #ret_type {
+ #call_body
+ }
+ }),
+ ty: impl_block_type_name.get_final_ident(),
+ })
+ }
+
+ /// Generate an 'impl Trait for Type { methods-go-here }' in its entrety.
+ fn generate_trait_impl(
+ &self,
+ details: &TraitMethodDetails,
+ ret_type: &ReturnType,
+ ) -> Box<TraitImplBlockDetails> {
+ let (lifetime_tokens, wrapper_params, ret_type, call_body) =
+ self.common_parts(details.avoid_self, &details.parameter_reordering, ret_type);
+ let doc_attrs = self.doc_attrs;
+ let unsafety = self.unsafety.wrapper_token();
+ let key = details.trt.clone();
+ let method_name = &details.method_name;
+ let item = parse_quote! {
+ #(#doc_attrs)*
+ #unsafety fn #method_name #lifetime_tokens ( #wrapper_params ) #ret_type {
+ #call_body
+ }
+ };
+ Box::new(TraitImplBlockDetails { item, key })
+ }
+
+ /// Generate a 'impl Type { methods-go-here }' item which is a constructor
+ /// for use with moveit traits.
+ fn generate_constructor_impl(
+ &self,
+ impl_block_type_name: &QualifiedName,
+ ) -> Box<ImplBlockDetails> {
+ let ret_type: ReturnType = parse_quote! { -> impl autocxx::moveit::new::New<Output=Self> };
+ let (lifetime_tokens, wrapper_params, ret_type, call_body) =
+ self.common_parts(true, &None, &ret_type);
+ let rust_name = make_ident(&self.rust_name);
+ let doc_attrs = self.doc_attrs;
+ let unsafety = self.unsafety.wrapper_token();
+ Box::new(ImplBlockDetails {
+ item: ImplItem::Method(parse_quote! {
+ #(#doc_attrs)*
+ pub #unsafety fn #rust_name #lifetime_tokens ( #wrapper_params ) #ret_type {
+ #call_body
+ }
+ }),
+ ty: impl_block_type_name.get_final_ident(),
+ })
+ }
+
+ /// Generate a function call wrapper
+ fn generate_function_impl(&self, ret_type: &ReturnType) -> Item {
+ let (lifetime_tokens, wrapper_params, ret_type, call_body) =
+ self.common_parts(false, &None, ret_type);
+ let rust_name = make_ident(self.rust_name);
+ let doc_attrs = self.doc_attrs;
+ let unsafety = self.unsafety.wrapper_token();
+ Item::Fn(parse_quote! {
+ #(#doc_attrs)*
+ pub #unsafety fn #rust_name #lifetime_tokens ( #wrapper_params ) #ret_type {
+ #call_body
+ }
+ })
+ }
+
+ fn reorder_parameters(
+ params: Punctuated<FnArg, Comma>,
+ parameter_ordering: &[usize],
+ ) -> Punctuated<FnArg, Comma> {
+ let old_params = params.into_iter().collect::<Vec<_>>();
+ parameter_ordering
+ .iter()
+ .map(|n| old_params.get(*n).unwrap().clone())
+ .collect()
+ }
+}
diff --git a/engine/src/conversion/codegen_rs/function_wrapper_rs.rs b/engine/src/conversion/codegen_rs/function_wrapper_rs.rs
new file mode 100644
index 0000000..a3fc71f
--- /dev/null
+++ b/engine/src/conversion/codegen_rs/function_wrapper_rs.rs
@@ -0,0 +1,172 @@
+// 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 proc_macro2::TokenStream;
+use syn::{Pat, Type, TypePtr};
+
+use crate::{
+ conversion::analysis::fun::function_wrapper::{RustConversionType, TypeConversionPolicy},
+ types::make_ident,
+};
+use quote::quote;
+use syn::parse_quote;
+
+use super::MaybeUnsafeStmt;
+
+/// Output Rust snippets for how to deal with a given parameter.
+pub(super) enum RustParamConversion {
+ Param {
+ ty: Type,
+ local_variables: Vec<MaybeUnsafeStmt>,
+ conversion: TokenStream,
+ conversion_requires_unsafe: bool,
+ },
+ ReturnValue {
+ ty: Type,
+ },
+}
+
+impl TypeConversionPolicy {
+ /// If returns `None` then this parameter should be omitted entirely.
+ pub(super) fn rust_conversion(&self, var: Pat) -> RustParamConversion {
+ match self.rust_conversion {
+ RustConversionType::None => RustParamConversion::Param {
+ ty: self.converted_rust_type(),
+ local_variables: Vec::new(),
+ conversion: quote! { #var },
+ conversion_requires_unsafe: false,
+ },
+ RustConversionType::FromStr => RustParamConversion::Param {
+ ty: parse_quote! { impl ToCppString },
+ local_variables: Vec::new(),
+ conversion: quote! ( #var .into_cpp() ),
+ conversion_requires_unsafe: false,
+ },
+ RustConversionType::ToBoxedUpHolder(ref sub) => {
+ let holder_type = sub.holder();
+ let id = sub.id();
+ let ty = parse_quote! { autocxx::subclass::CppSubclassRustPeerHolder<
+ super::super::super:: #id>
+ };
+ RustParamConversion::Param {
+ ty,
+ local_variables: Vec::new(),
+ conversion: quote! {
+ Box::new(#holder_type(#var))
+ },
+ conversion_requires_unsafe: false,
+ }
+ }
+ RustConversionType::FromPinMaybeUninitToPtr => {
+ let ty = match &self.unwrapped_type {
+ Type::Ptr(TypePtr { elem, .. }) => &*elem,
+ _ => panic!("Not a ptr"),
+ };
+ let ty = parse_quote! {
+ ::std::pin::Pin<&mut ::std::mem::MaybeUninit< #ty >>
+ };
+ RustParamConversion::Param {
+ ty,
+ local_variables: Vec::new(),
+ conversion: quote! {
+ #var.get_unchecked_mut().as_mut_ptr()
+ },
+ conversion_requires_unsafe: true,
+ }
+ }
+ RustConversionType::FromPinMoveRefToPtr => {
+ let ty = match &self.unwrapped_type {
+ Type::Ptr(TypePtr { elem, .. }) => &*elem,
+ _ => panic!("Not a ptr"),
+ };
+ let ty = parse_quote! {
+ ::std::pin::Pin<autocxx::moveit::MoveRef< '_, #ty >>
+ };
+ RustParamConversion::Param {
+ ty,
+ local_variables: Vec::new(),
+ conversion: quote! {
+ { let r: &mut _ = ::std::pin::Pin::into_inner_unchecked(#var.as_mut());
+ r
+ }
+ },
+ conversion_requires_unsafe: true,
+ }
+ }
+ RustConversionType::FromTypeToPtr => {
+ let ty = match &self.unwrapped_type {
+ Type::Ptr(TypePtr { elem, .. }) => &*elem,
+ _ => panic!("Not a ptr"),
+ };
+ let ty = parse_quote! { &mut #ty };
+ RustParamConversion::Param {
+ ty,
+ local_variables: Vec::new(),
+ conversion: quote! {
+ #var
+ },
+ conversion_requires_unsafe: false,
+ }
+ }
+ RustConversionType::FromValueParamToPtr | RustConversionType::FromRValueParamToPtr => {
+ let (handler_type, param_trait) = match self.rust_conversion {
+ RustConversionType::FromValueParamToPtr => ("ValueParamHandler", "ValueParam"),
+ RustConversionType::FromRValueParamToPtr => {
+ ("RValueParamHandler", "RValueParam")
+ }
+ _ => unreachable!(),
+ };
+ let handler_type = make_ident(handler_type);
+ let param_trait = make_ident(param_trait);
+ let var_name = if let Pat::Ident(pti) = &var {
+ &pti.ident
+ } else {
+ panic!("Unexpected non-ident parameter name");
+ };
+ let space_var_name = make_ident(format!("{}_space", var_name));
+ let ty = &self.unwrapped_type;
+ let ty = parse_quote! { impl autocxx::#param_trait<#ty> };
+ // This is the usual trick to put something on the stack, then
+ // immediately shadow the variable name so it can't be accessed or moved.
+ RustParamConversion::Param {
+ ty,
+ local_variables: vec![
+ MaybeUnsafeStmt::new(
+ quote! { let mut #space_var_name = autocxx::#handler_type::default(); },
+ ),
+ MaybeUnsafeStmt::binary(
+ quote! { let mut #space_var_name =
+ unsafe { ::std::pin::Pin::new_unchecked(&mut #space_var_name) };
+ },
+ quote! { let mut #space_var_name =
+ ::std::pin::Pin::new_unchecked(&mut #space_var_name);
+ },
+ ),
+ MaybeUnsafeStmt::needs_unsafe(
+ quote! { #space_var_name.as_mut().populate(#var_name); },
+ ),
+ ],
+ conversion: quote! {
+ #space_var_name.get_ptr()
+ },
+ conversion_requires_unsafe: false,
+ }
+ }
+ // This type of conversion means that this function parameter appears in the cxx::bridge
+ // but not in the arguments for the wrapper function, because instead we return an
+ // impl New which uses the cxx::bridge function's pointer parameter.
+ RustConversionType::FromPlacementParamToNewReturn => {
+ let ty = match &self.unwrapped_type {
+ Type::Ptr(TypePtr { elem, .. }) => *(*elem).clone(),
+ _ => panic!("Not a ptr"),
+ };
+ RustParamConversion::ReturnValue { ty }
+ }
+ }
+ }
+}
diff --git a/engine/src/conversion/codegen_rs/impl_item_creator.rs b/engine/src/conversion/codegen_rs/impl_item_creator.rs
new file mode 100644
index 0000000..89ef896
--- /dev/null
+++ b/engine/src/conversion/codegen_rs/impl_item_creator.rs
@@ -0,0 +1,41 @@
+// 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 autocxx_parser::IncludeCppConfig;
+use syn::{parse_quote, Ident, Item};
+
+pub(crate) fn create_impl_items(
+ id: &Ident,
+ movable: bool,
+ destroyable: bool,
+ config: &IncludeCppConfig,
+) -> Vec<Item> {
+ if config.exclude_impls {
+ return vec![];
+ }
+ let mut results = Vec::new();
+ if destroyable {
+ results.extend([
+ Item::Impl(parse_quote! {
+ impl UniquePtr<#id> {}
+ }),
+ Item::Impl(parse_quote! {
+ impl SharedPtr<#id> {}
+ }),
+ Item::Impl(parse_quote! {
+ impl WeakPtr<#id> {}
+ }),
+ ]);
+ }
+ if movable {
+ results.push(Item::Impl(parse_quote! {
+ impl CxxVector<#id> {}
+ }))
+ }
+ results
+}
diff --git a/engine/src/conversion/codegen_rs/lifetime.rs b/engine/src/conversion/codegen_rs/lifetime.rs
new file mode 100644
index 0000000..ea2c782
--- /dev/null
+++ b/engine/src/conversion/codegen_rs/lifetime.rs
@@ -0,0 +1,205 @@
+// 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 crate::{
+ conversion::analysis::fun::{
+ function_wrapper::RustConversionType, ArgumentAnalysis, ReceiverMutability,
+ },
+ types::QualifiedName,
+};
+use indexmap::set::IndexSet as HashSet;
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+use std::borrow::Cow;
+use syn::{
+ parse_quote, punctuated::Punctuated, token::Comma, FnArg, GenericArgument, PatType, Path,
+ PathSegment, ReturnType, Type, TypePath, TypeReference,
+};
+
+/// Function which can add explicit lifetime parameters to function signatures
+/// where necessary, based on analysis of parameters and return types.
+/// This is necessary in three cases:
+/// 1) where the parameter is a Pin<&mut T>
+/// and the return type is some kind of reference - because lifetime elision
+/// is not smart enough to see inside a Pin.
+/// 2) as a workaround for https://github.com/dtolnay/cxx/issues/1024, where the
+/// input parameter is a non-POD type but the output reference is a POD or
+/// built-in type
+/// 3) Any parameter is any form of reference, and we're returning an `impl New`
+/// 3a) an 'impl ValueParam' counts as a reference.
+pub(crate) fn add_explicit_lifetime_if_necessary<'r>(
+ param_details: &[ArgumentAnalysis],
+ mut params: Punctuated<FnArg, Comma>,
+ ret_type: Cow<'r, ReturnType>,
+ non_pod_types: &HashSet<QualifiedName>,
+ assert_all_parameters_are_references: bool,
+) -> (
+ Option<TokenStream>,
+ Punctuated<FnArg, Comma>,
+ Cow<'r, ReturnType>,
+) {
+ let has_mutable_receiver = param_details.iter().any(|pd| {
+ matches!(pd.self_type, Some((_, ReceiverMutability::Mutable)))
+ && !pd.is_placement_return_destination
+ });
+
+ let any_param_is_reference = param_details.iter().any(|pd| {
+ pd.has_lifetime
+ || matches!(
+ pd.conversion.rust_conversion,
+ RustConversionType::FromValueParamToPtr
+ )
+ });
+ let return_type_is_impl = return_type_is_impl(&ret_type);
+ let non_pod_ref_param = reference_parameter_is_non_pod_reference(¶ms, non_pod_types);
+ let ret_type_pod = return_type_is_pod_or_known_type_reference(&ret_type, non_pod_types);
+ let returning_impl_with_a_reference_param = return_type_is_impl && any_param_is_reference;
+ let hits_1024_bug = non_pod_ref_param && ret_type_pod;
+ if !(has_mutable_receiver || hits_1024_bug || returning_impl_with_a_reference_param) {
+ return (None, params, ret_type);
+ }
+ let new_return_type = match ret_type.as_ref() {
+ ReturnType::Type(rarrow, boxed_type) => match boxed_type.as_ref() {
+ Type::Reference(rtr) => {
+ let mut new_rtr = rtr.clone();
+ new_rtr.lifetime = Some(parse_quote! { 'a });
+ Some(ReturnType::Type(
+ *rarrow,
+ Box::new(Type::Reference(new_rtr)),
+ ))
+ }
+ Type::Path(typ) => {
+ let mut new_path = typ.clone();
+ add_lifetime_to_pinned_reference(&mut new_path.path.segments)
+ .ok()
+ .map(|_| ReturnType::Type(*rarrow, Box::new(Type::Path(new_path))))
+ }
+ Type::ImplTrait(tyit) => {
+ let old_tyit = tyit.to_token_stream();
+ Some(parse_quote! {
+ #rarrow #old_tyit + 'a
+ })
+ }
+ _ => None,
+ },
+ _ => None,
+ };
+
+ match new_return_type {
+ None => (None, params, ret_type),
+ Some(new_return_type) => {
+ for mut param in params.iter_mut() {
+ match &mut param {
+ FnArg::Typed(PatType { ty, .. }) => match ty.as_mut() {
+ Type::Path(TypePath {
+ path: Path { segments, .. },
+ ..
+ }) => add_lifetime_to_pinned_reference(segments).unwrap_or_else(|e| {
+ if assert_all_parameters_are_references {
+ panic!("Expected a pinned reference: {:?}", e)
+ }
+ }),
+ Type::Reference(tyr) => add_lifetime_to_reference(tyr),
+ Type::ImplTrait(tyit) => add_lifetime_to_impl_trait(tyit),
+ _ if assert_all_parameters_are_references => {
+ panic!("Expected Pin<&mut T> or &T")
+ }
+ _ => {}
+ },
+ _ if assert_all_parameters_are_references => panic!("Unexpected fnarg"),
+ _ => {}
+ }
+ }
+
+ (Some(quote! { <'a> }), params, Cow::Owned(new_return_type))
+ }
+ }
+}
+
+fn reference_parameter_is_non_pod_reference(
+ params: &Punctuated<FnArg, Comma>,
+ non_pod_types: &HashSet<QualifiedName>,
+) -> bool {
+ params.iter().any(|param| match param {
+ FnArg::Typed(PatType { ty, .. }) => match ty.as_ref() {
+ Type::Reference(TypeReference { elem, .. }) => match elem.as_ref() {
+ Type::Path(typ) => {
+ let qn = QualifiedName::from_type_path(typ);
+ non_pod_types.contains(&qn)
+ }
+ _ => false,
+ },
+ _ => false,
+ },
+ _ => false,
+ })
+}
+
+fn return_type_is_pod_or_known_type_reference(
+ ret_type: &ReturnType,
+ non_pod_types: &HashSet<QualifiedName>,
+) -> bool {
+ match ret_type {
+ ReturnType::Type(_, boxed_type) => match boxed_type.as_ref() {
+ Type::Reference(rtr) => match rtr.elem.as_ref() {
+ Type::Path(typ) => {
+ let qn = QualifiedName::from_type_path(typ);
+ !non_pod_types.contains(&qn)
+ }
+ _ => false,
+ },
+ _ => false,
+ },
+ _ => false,
+ }
+}
+
+fn return_type_is_impl(ret_type: &ReturnType) -> bool {
+ matches!(ret_type, ReturnType::Type(_, boxed_type) if matches!(boxed_type.as_ref(), Type::ImplTrait(..)))
+}
+
+#[derive(Debug)]
+enum AddLifetimeError {
+ WasNotPin,
+}
+
+fn add_lifetime_to_pinned_reference(
+ segments: &mut Punctuated<PathSegment, syn::token::Colon2>,
+) -> Result<(), AddLifetimeError> {
+ static EXPECTED_SEGMENTS: &[(&str, bool)] = &[
+ ("std", false),
+ ("pin", false),
+ ("Pin", true), // true = act on the arguments of this segment
+ ];
+
+ for (seg, (expected_name, act)) in segments.iter_mut().zip(EXPECTED_SEGMENTS.iter()) {
+ if seg.ident != expected_name {
+ return Err(AddLifetimeError::WasNotPin);
+ }
+ if *act {
+ match &mut seg.arguments {
+ syn::PathArguments::AngleBracketed(aba) => match aba.args.iter_mut().next() {
+ Some(GenericArgument::Type(Type::Reference(tyr))) => {
+ add_lifetime_to_reference(tyr);
+ }
+ _ => panic!("Expected generic args with a reference"),
+ },
+ _ => panic!("Expected angle bracketed args"),
+ }
+ }
+ }
+ Ok(())
+}
+
+fn add_lifetime_to_reference(tyr: &mut syn::TypeReference) {
+ tyr.lifetime = Some(parse_quote! { 'a })
+}
+
+fn add_lifetime_to_impl_trait(tyit: &mut syn::TypeImplTrait) {
+ tyit.bounds
+ .push(syn::TypeParamBound::Lifetime(parse_quote! { 'a }))
+}
diff --git a/engine/src/conversion/codegen_rs/mod.rs b/engine/src/conversion/codegen_rs/mod.rs
new file mode 100644
index 0000000..d488d52
--- /dev/null
+++ b/engine/src/conversion/codegen_rs/mod.rs
@@ -0,0 +1,1379 @@
+// 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 fun_codegen;
+mod function_wrapper_rs;
+mod impl_item_creator;
+mod lifetime;
+mod namespace_organizer;
+mod non_pod_struct;
+pub(crate) mod unqualify;
+
+use indexmap::map::IndexMap as HashMap;
+use indexmap::set::IndexSet as HashSet;
+
+use autocxx_parser::{ExternCppType, IncludeCppConfig, RustFun};
+
+use itertools::Itertools;
+use proc_macro2::{Span, TokenStream};
+use syn::{
+ parse_quote, punctuated::Punctuated, token::Comma, Attribute, Expr, FnArg, ForeignItem,
+ ForeignItemFn, Ident, ImplItem, Item, ItemForeignMod, ItemMod, TraitItem, TypePath,
+};
+
+use crate::{
+ conversion::{
+ codegen_rs::{
+ non_pod_struct::{make_non_pod, new_non_pod_struct},
+ unqualify::{unqualify_params, unqualify_ret_type},
+ },
+ doc_attr::get_doc_attrs,
+ },
+ types::{make_ident, Namespace, QualifiedName},
+};
+use impl_item_creator::create_impl_items;
+
+use self::{
+ fun_codegen::gen_function,
+ namespace_organizer::{HasNs, NamespaceEntries},
+};
+
+use super::{
+ analysis::{
+ fun::{FnPhase, PodAndDepAnalysis, ReceiverMutability},
+ pod::PodAnalysis,
+ },
+ api::{AnalysisPhase, Api, SubclassName, TypeKind, TypedefKind},
+ convert_error::ErrorContextType,
+};
+use super::{
+ api::{Layout, Provenance, RustSubclassFnDetails, SuperclassMethod, TraitImplSignature},
+ apivec::ApiVec,
+ codegen_cpp::type_to_cpp::{
+ namespaced_name_using_original_name_map, original_name_map_from_apis, CppNameMap,
+ },
+};
+use super::{convert_error::ErrorContext, ConvertError};
+use quote::quote;
+
+/// An entry which needs to go into an `impl` block for a given type.
+struct ImplBlockDetails {
+ item: ImplItem,
+ ty: Ident,
+}
+
+struct TraitImplBlockDetails {
+ item: TraitItem,
+ key: TraitImplSignature,
+}
+
+/// Whether and how this item should be exposed in the mods constructed
+/// for actual end-user use.
+#[derive(Clone)]
+enum Use {
+ /// Uses from cxx::bridge
+ UsedFromCxxBridge,
+ /// 'use' points to cxx::bridge with a different name
+ UsedFromCxxBridgeWithAlias(Ident),
+ /// 'use' directive points to bindgen
+ UsedFromBindgen,
+ /// 'use' a specific name from bindgen.
+ SpecificNameFromBindgen(Ident),
+ /// Some kind of custom item
+ Custom(Box<Item>),
+}
+
+fn get_string_items() -> Vec<Item> {
+ [
+ Item::Trait(parse_quote! {
+ pub trait ToCppString {
+ fn into_cpp(self) -> cxx::UniquePtr<cxx::CxxString>;
+ }
+ }),
+ // We can't just impl<T: AsRef<str>> ToCppString for T
+ // because the compiler says that this trait could be implemented
+ // in future for cxx::UniquePtr<cxx::CxxString>. Fair enough.
+ Item::Impl(parse_quote! {
+ impl ToCppString for &str {
+ fn into_cpp(self) -> cxx::UniquePtr<cxx::CxxString> {
+ make_string(self)
+ }
+ }
+ }),
+ Item::Impl(parse_quote! {
+ impl ToCppString for String {
+ fn into_cpp(self) -> cxx::UniquePtr<cxx::CxxString> {
+ make_string(&self)
+ }
+ }
+ }),
+ Item::Impl(parse_quote! {
+ impl ToCppString for &String {
+ fn into_cpp(self) -> cxx::UniquePtr<cxx::CxxString> {
+ make_string(self)
+ }
+ }
+ }),
+ Item::Impl(parse_quote! {
+ impl ToCppString for cxx::UniquePtr<cxx::CxxString> {
+ fn into_cpp(self) -> cxx::UniquePtr<cxx::CxxString> {
+ self
+ }
+ }
+ }),
+ ]
+ .to_vec()
+}
+
+/// Type which handles generation of Rust code.
+/// In practice, much of the "generation" involves connecting together
+/// existing lumps of code within the Api structures.
+pub(crate) struct RsCodeGenerator<'a> {
+ include_list: &'a [String],
+ bindgen_mod: ItemMod,
+ original_name_map: CppNameMap,
+ config: &'a IncludeCppConfig,
+ header_name: Option<String>,
+}
+
+impl<'a> RsCodeGenerator<'a> {
+ /// Generate code for a set of APIs that was discovered during parsing.
+ pub(crate) fn generate_rs_code(
+ all_apis: ApiVec<FnPhase>,
+ include_list: &'a [String],
+ bindgen_mod: ItemMod,
+ config: &'a IncludeCppConfig,
+ header_name: Option<String>,
+ ) -> Vec<Item> {
+ let c = Self {
+ include_list,
+ bindgen_mod,
+ original_name_map: original_name_map_from_apis(&all_apis),
+ config,
+ header_name,
+ };
+ c.rs_codegen(all_apis)
+ }
+
+ fn rs_codegen(mut self, all_apis: ApiVec<FnPhase>) -> Vec<Item> {
+ // ... and now let's start to generate the output code.
+ // First off, when we generate structs we may need to add some methods
+ // if they're superclasses.
+ let methods_by_superclass = self.accumulate_superclass_methods(&all_apis);
+ let subclasses_with_a_single_trivial_constructor =
+ find_trivially_constructed_subclasses(&all_apis);
+ let non_pod_types = find_non_pod_types(&all_apis);
+ // Now let's generate the Rust code.
+ let (rs_codegen_results_and_namespaces, additional_cpp_needs): (Vec<_>, Vec<_>) = all_apis
+ .into_iter()
+ .map(|api| {
+ let more_cpp_needed = api.needs_cpp_codegen();
+ let name = api.name().clone();
+ let gen = self.generate_rs_for_api(
+ api,
+ &methods_by_superclass,
+ &subclasses_with_a_single_trivial_constructor,
+ &non_pod_types,
+ );
+ ((name, gen), more_cpp_needed)
+ })
+ .unzip();
+ // First, the hierarchy of mods containing lots of 'use' statements
+ // which is the final API exposed as 'ffi'.
+ let mut use_statements =
+ Self::generate_final_use_statements(&rs_codegen_results_and_namespaces);
+ // And work out what we need for the bindgen mod.
+ let bindgen_root_items =
+ self.generate_final_bindgen_mods(&rs_codegen_results_and_namespaces);
+ // Both of the above ('use' hierarchy and bindgen mod) are organized into
+ // sub-mods by namespace. From here on, things are flat.
+ let (_, rs_codegen_results): (Vec<_>, Vec<_>) =
+ rs_codegen_results_and_namespaces.into_iter().unzip();
+ let (extern_c_mod_items, extern_rust_mod_items, all_items, bridge_items): (
+ Vec<_>,
+ Vec<_>,
+ Vec<_>,
+ Vec<_>,
+ ) = rs_codegen_results
+ .into_iter()
+ .map(|api| {
+ (
+ api.extern_c_mod_items,
+ api.extern_rust_mod_items,
+ api.global_items,
+ api.bridge_items,
+ )
+ })
+ .multiunzip();
+ // Items for the [cxx::bridge] mod...
+ let mut bridge_items: Vec<Item> = bridge_items.into_iter().flatten().collect();
+ // Things to include in the "extern "C"" mod passed within the cxx::bridge
+ let mut extern_c_mod_items: Vec<ForeignItem> =
+ extern_c_mod_items.into_iter().flatten().collect();
+ // The same for extern "Rust"
+ let mut extern_rust_mod_items = extern_rust_mod_items.into_iter().flatten().collect();
+ // And a list of global items to include at the top level.
+ let mut all_items: Vec<Item> = all_items.into_iter().flatten().collect();
+ // And finally any C++ we need to generate. And by "we" I mean autocxx not cxx.
+ let has_additional_cpp_needs = additional_cpp_needs.into_iter().any(std::convert::identity);
+ extern_c_mod_items.extend(self.build_include_foreign_items(has_additional_cpp_needs));
+ // We will always create an extern "C" mod even if bindgen
+ // didn't generate one, e.g. because it only generated types.
+ // We still want cxx to know about those types.
+ let mut extern_c_mod: ItemForeignMod = parse_quote!(
+ extern "C++" {}
+ );
+ extern_c_mod.items.append(&mut extern_c_mod_items);
+ bridge_items.push(Self::make_foreign_mod_unsafe(extern_c_mod));
+ let mut extern_rust_mod: ItemForeignMod = parse_quote!(
+ extern "Rust" {}
+ );
+ extern_rust_mod.items.append(&mut extern_rust_mod_items);
+ bridge_items.push(Item::ForeignMod(extern_rust_mod));
+ // The extensive use of parse_quote here could end up
+ // being a performance bottleneck. If so, we might want
+ // to set the 'contents' field of the ItemMod
+ // structures directly.
+ if !bindgen_root_items.is_empty() {
+ self.bindgen_mod.vis = parse_quote! {};
+ self.bindgen_mod.content.as_mut().unwrap().1 = vec![Item::Mod(parse_quote! {
+ pub(super) mod root {
+ #(#bindgen_root_items)*
+ }
+ })];
+ all_items.push(Item::Mod(self.bindgen_mod));
+ }
+ all_items.push(Item::Mod(parse_quote! {
+ #[cxx::bridge]
+ mod cxxbridge {
+ #(#bridge_items)*
+ }
+ }));
+
+ all_items.push(Item::Use(parse_quote! {
+ #[allow(unused_imports)]
+ use bindgen::root;
+ }));
+ all_items.append(&mut use_statements);
+ all_items
+ }
+
+ fn accumulate_superclass_methods(
+ &self,
+ apis: &ApiVec<FnPhase>,
+ ) -> HashMap<QualifiedName, Vec<SuperclassMethod>> {
+ let mut results = HashMap::new();
+ results.extend(
+ self.config
+ .superclasses()
+ .map(|sc| (QualifiedName::new_from_cpp_name(sc), Vec::new())),
+ );
+ for api in apis.iter() {
+ if let Api::SubclassTraitItem { details, .. } = api {
+ let list = results.get_mut(&details.receiver);
+ if let Some(list) = list {
+ list.push(details.clone());
+ }
+ }
+ }
+ results
+ }
+
+ fn make_foreign_mod_unsafe(ifm: ItemForeignMod) -> Item {
+ // At the moment syn does not support outputting 'unsafe extern "C"' except in verbatim
+ // items. See https://github.com/dtolnay/syn/pull/938
+ Item::Verbatim(quote! {
+ unsafe #ifm
+ })
+ }
+
+ fn build_include_foreign_items(&self, has_additional_cpp_needs: bool) -> Vec<ForeignItem> {
+ let extra_inclusion = if has_additional_cpp_needs {
+ Some(self.header_name.clone().unwrap())
+ } else {
+ None
+ };
+ let chained = self.include_list.iter().chain(extra_inclusion.iter());
+ chained
+ .map(|inc| {
+ ForeignItem::Macro(parse_quote! {
+ include!(#inc);
+ })
+ })
+ .collect()
+ }
+
+ /// Generate lots of 'use' statements to pull cxxbridge items into the output
+ /// mod hierarchy according to C++ namespaces.
+ fn generate_final_use_statements(
+ input_items: &[(QualifiedName, RsCodegenResult)],
+ ) -> Vec<Item> {
+ let mut output_items = Vec::new();
+ let ns_entries = NamespaceEntries::new(input_items);
+ Self::append_child_use_namespace(&ns_entries, &mut output_items);
+ output_items
+ }
+
+ fn append_child_use_namespace(
+ ns_entries: &NamespaceEntries<(QualifiedName, RsCodegenResult)>,
+ output_items: &mut Vec<Item>,
+ ) {
+ for (name, codegen) in ns_entries.entries() {
+ output_items.extend(codegen.materializations.iter().map(|materialization| {
+ match materialization {
+ Use::UsedFromCxxBridgeWithAlias(ref alias) => {
+ Self::generate_cxx_use_stmt(name, Some(alias))
+ }
+ Use::UsedFromCxxBridge => Self::generate_cxx_use_stmt(name, None),
+ Use::UsedFromBindgen => Self::generate_bindgen_use_stmt(name),
+ Use::SpecificNameFromBindgen(id) => {
+ let name = QualifiedName::new(name.get_namespace(), id.clone());
+ Self::generate_bindgen_use_stmt(&name)
+ }
+ Use::Custom(item) => *item.clone(),
+ }
+ }));
+ }
+ for (child_name, child_ns_entries) in ns_entries.children() {
+ if child_ns_entries.is_empty() {
+ continue;
+ }
+ let child_id = make_ident(child_name);
+ let mut new_mod: ItemMod = parse_quote!(
+ pub mod #child_id {
+ }
+ );
+ Self::append_child_use_namespace(
+ child_ns_entries,
+ &mut new_mod.content.as_mut().unwrap().1,
+ );
+ output_items.push(Item::Mod(new_mod));
+ }
+ }
+
+ fn append_uses_for_ns(&mut self, items: &mut Vec<Item>, ns: &Namespace) {
+ let super_duper = std::iter::repeat(make_ident("super")); // I'll get my coat
+ let supers = super_duper.clone().take(ns.depth() + 2);
+ items.push(Item::Use(parse_quote! {
+ #[allow(unused_imports)]
+ use self::
+ #(#supers)::*
+ ::cxxbridge;
+ }));
+ if !self.config.exclude_utilities() {
+ let supers = super_duper.clone().take(ns.depth() + 2);
+ items.push(Item::Use(parse_quote! {
+ #[allow(unused_imports)]
+ use self::
+ #(#supers)::*
+ ::ToCppString;
+ }));
+ }
+ let supers = super_duper.take(ns.depth() + 1);
+ items.push(Item::Use(parse_quote! {
+ #[allow(unused_imports)]
+ use self::
+ #(#supers)::*
+ ::root;
+ }));
+ }
+
+ fn append_child_bindgen_namespace(
+ &mut self,
+ ns_entries: &NamespaceEntries<(QualifiedName, RsCodegenResult)>,
+ output_items: &mut Vec<Item>,
+ ns: &Namespace,
+ ) {
+ let mut impl_entries_by_type: HashMap<_, Vec<_>> = HashMap::new();
+ let mut trait_impl_entries_by_trait_and_ty: HashMap<_, Vec<_>> = HashMap::new();
+ for item in ns_entries.entries() {
+ output_items.extend(item.1.bindgen_mod_items.iter().cloned());
+ if let Some(impl_entry) = &item.1.impl_entry {
+ impl_entries_by_type
+ .entry(impl_entry.ty.clone())
+ .or_default()
+ .push(&impl_entry.item);
+ }
+ if let Some(trait_impl_entry) = &item.1.trait_impl_entry {
+ trait_impl_entries_by_trait_and_ty
+ .entry(trait_impl_entry.key.clone())
+ .or_default()
+ .push(&trait_impl_entry.item);
+ }
+ }
+ for (ty, entries) in impl_entries_by_type.into_iter() {
+ output_items.push(Item::Impl(parse_quote! {
+ impl #ty {
+ #(#entries)*
+ }
+ }))
+ }
+ for (key, entries) in trait_impl_entries_by_trait_and_ty.into_iter() {
+ let unsafety = key.unsafety;
+ let ty = key.ty;
+ let trt = key.trait_signature;
+ output_items.push(Item::Impl(parse_quote! {
+ #unsafety impl #trt for #ty {
+ #(#entries)*
+ }
+ }))
+ }
+ for (child_name, child_ns_entries) in ns_entries.children() {
+ let new_ns = ns.push((*child_name).clone());
+ let child_id = make_ident(child_name);
+
+ let mut inner_output_items = Vec::new();
+ self.append_child_bindgen_namespace(child_ns_entries, &mut inner_output_items, &new_ns);
+ if !inner_output_items.is_empty() {
+ let mut new_mod: ItemMod = parse_quote!(
+ pub mod #child_id {
+ }
+ );
+ self.append_uses_for_ns(&mut inner_output_items, &new_ns);
+ new_mod.content.as_mut().unwrap().1 = inner_output_items;
+ output_items.push(Item::Mod(new_mod));
+ }
+ }
+ }
+
+ fn id_to_expr(id: &Ident) -> Expr {
+ parse_quote! { #id }
+ }
+
+ fn generate_final_bindgen_mods(
+ &mut self,
+ input_items: &[(QualifiedName, RsCodegenResult)],
+ ) -> Vec<Item> {
+ let mut output_items = Vec::new();
+ let ns = Namespace::new();
+ let ns_entries = NamespaceEntries::new(input_items);
+ self.append_child_bindgen_namespace(&ns_entries, &mut output_items, &ns);
+ self.append_uses_for_ns(&mut output_items, &ns);
+ output_items
+ }
+
+ fn generate_rs_for_api(
+ &self,
+ api: Api<FnPhase>,
+ associated_methods: &HashMap<QualifiedName, Vec<SuperclassMethod>>,
+ subclasses_with_a_single_trivial_constructor: &HashSet<QualifiedName>,
+ non_pod_types: &HashSet<QualifiedName>,
+ ) -> RsCodegenResult {
+ let name = api.name().clone();
+ let id = name.get_final_ident();
+ let cpp_call_name = api.effective_cpp_name().to_string();
+ match api {
+ Api::StringConstructor { .. } => {
+ let make_string_name = make_ident(self.config.get_makestring_name());
+ RsCodegenResult {
+ extern_c_mod_items: vec![ForeignItem::Fn(parse_quote!(
+ fn #make_string_name(str_: &str) -> UniquePtr<CxxString>;
+ ))],
+ global_items: get_string_items(),
+ materializations: vec![Use::UsedFromCxxBridgeWithAlias(make_ident(
+ "make_string",
+ ))],
+ ..Default::default()
+ }
+ }
+ Api::Function { fun, analysis, .. } => gen_function(
+ name.get_namespace(),
+ *fun,
+ analysis,
+ cpp_call_name,
+ non_pod_types,
+ ),
+ Api::Const { const_item, .. } => RsCodegenResult {
+ bindgen_mod_items: vec![Item::Const(const_item)],
+ materializations: vec![Use::UsedFromBindgen],
+ ..Default::default()
+ },
+ Api::Typedef { analysis, .. } => RsCodegenResult {
+ bindgen_mod_items: vec![match analysis.kind {
+ TypedefKind::Type(type_item) => Item::Type(type_item),
+ TypedefKind::Use(use_item, _) => Item::Use(use_item),
+ }],
+ materializations: vec![Use::UsedFromBindgen],
+ ..Default::default()
+ },
+ Api::Struct {
+ details,
+ analysis:
+ PodAndDepAnalysis {
+ pod:
+ PodAnalysis {
+ is_generic, kind, ..
+ },
+ constructors,
+ ..
+ },
+ ..
+ } => {
+ let doc_attrs = get_doc_attrs(&details.item.attrs);
+ let layout = details.layout.clone();
+ self.generate_type(
+ &name,
+ id,
+ kind,
+ constructors.move_constructor,
+ constructors.destructor,
+ || Some((Item::Struct(details.item), doc_attrs)),
+ associated_methods,
+ layout,
+ is_generic,
+ )
+ }
+ Api::Enum { item, .. } => {
+ let doc_attrs = get_doc_attrs(&item.attrs);
+ self.generate_type(
+ &name,
+ id,
+ TypeKind::Pod,
+ true,
+ true,
+ || Some((Item::Enum(item), doc_attrs)),
+ associated_methods,
+ None,
+ false,
+ )
+ }
+ Api::ForwardDeclaration { .. }
+ | Api::ConcreteType { .. }
+ | Api::OpaqueTypedef { .. } => self.generate_type(
+ &name,
+ id,
+ TypeKind::Abstract,
+ false, // assume for now that these types can't be kept in a Vector
+ true, // assume for now that these types can be put in a smart pointer
+ || None,
+ associated_methods,
+ None,
+ false,
+ ),
+ Api::CType { .. } => RsCodegenResult {
+ extern_c_mod_items: vec![ForeignItem::Verbatim(quote! {
+ type #id = autocxx::#id;
+ })],
+ ..Default::default()
+ },
+ Api::RustType { path, .. } => RsCodegenResult {
+ global_items: vec![parse_quote! {
+ use super::#path;
+ }],
+ extern_rust_mod_items: vec![parse_quote! {
+ type #id;
+ }],
+ ..Default::default()
+ },
+ Api::RustFn {
+ details:
+ RustFun {
+ path,
+ sig,
+ receiver: None,
+ ..
+ },
+ ..
+ } => RsCodegenResult {
+ global_items: vec![parse_quote! {
+ use super::#path;
+ }],
+ extern_rust_mod_items: vec![parse_quote! {
+ #sig;
+ }],
+ ..Default::default()
+ },
+ Api::RustFn {
+ details:
+ RustFun {
+ sig,
+ receiver: Some(_),
+ ..
+ },
+ ..
+ } => RsCodegenResult {
+ extern_rust_mod_items: vec![parse_quote! {
+ #sig;
+ }],
+ ..Default::default()
+ },
+ Api::RustSubclassFn {
+ details, subclass, ..
+ } => Self::generate_subclass_fn(id, *details, subclass),
+ Api::Subclass {
+ name, superclass, ..
+ } => {
+ let methods = associated_methods.get(&superclass);
+ let generate_peer_constructor =
+ subclasses_with_a_single_trivial_constructor.contains(&name.0.name);
+ self.generate_subclass(name, &superclass, methods, generate_peer_constructor)
+ }
+ Api::ExternCppType {
+ details: ExternCppType { rust_path, .. },
+ ..
+ } => self.generate_extern_cpp_type(&name, rust_path, name.ns_segment_iter().count()),
+ Api::IgnoredItem {
+ err,
+ ctx: Some(ctx),
+ ..
+ } => Self::generate_error_entry(err, ctx),
+ Api::IgnoredItem { .. } | Api::SubclassTraitItem { .. } => RsCodegenResult::default(),
+ }
+ }
+
+ fn generate_subclass(
+ &self,
+ sub: SubclassName,
+ superclass: &QualifiedName,
+ methods: Option<&Vec<SuperclassMethod>>,
+ generate_peer_constructor: bool,
+ ) -> RsCodegenResult {
+ let super_name = superclass.get_final_item();
+ let super_path = superclass.to_type_path();
+ let super_cxxxbridge_id = superclass.get_final_ident();
+ let id = sub.id();
+ let holder = sub.holder();
+ let full_cpp = sub.cpp();
+ let cpp_path = full_cpp.to_type_path();
+ let cpp_id = full_cpp.get_final_ident();
+ let mut global_items = Vec::new();
+ global_items.push(parse_quote! {
+ pub use bindgen::root::#holder;
+ });
+ let relinquish_ownership_call = sub.cpp_remove_ownership();
+ let mut bindgen_mod_items = vec![
+ parse_quote! {
+ pub use cxxbridge::#cpp_id;
+ },
+ parse_quote! {
+ pub struct #holder(pub autocxx::subclass::CppSubclassRustPeerHolder<super::super::super::#id>);
+ },
+ parse_quote! {
+ impl autocxx::subclass::CppSubclassCppPeer for #cpp_id {
+ fn relinquish_ownership(&self) {
+ self.#relinquish_ownership_call();
+ }
+ }
+ },
+ ];
+ let mut extern_c_mod_items = vec![
+ self.generate_cxxbridge_type(&full_cpp, false, Vec::new()),
+ parse_quote! {
+ fn #relinquish_ownership_call(self: &#cpp_id);
+ },
+ ];
+ if let Some(methods) = methods {
+ let supers = SubclassName::get_supers_trait_name(superclass).to_type_path();
+ let methods_impls: Vec<ImplItem> = methods
+ .iter()
+ .filter(|m| !m.is_pure_virtual)
+ .map(|m| {
+ let cpp_super_method_name =
+ SubclassName::get_super_fn_name(&Namespace::new(), &m.name.to_string())
+ .get_final_ident();
+ let mut params = m.params.clone();
+ let ret = &m.ret_type.clone();
+ let (peer_fn, first_param) = match m.receiver_mutability {
+ ReceiverMutability::Const => ("peer", parse_quote!(&self)),
+ ReceiverMutability::Mutable => ("peer_mut", parse_quote!(&mut self)),
+ };
+ let peer_fn = make_ident(peer_fn);
+ *(params.iter_mut().next().unwrap()) = first_param;
+ let param_names = m.param_names.iter().skip(1);
+ let unsafe_token = m.requires_unsafe.wrapper_token();
+ parse_quote! {
+ #unsafe_token fn #cpp_super_method_name(#params) #ret {
+ use autocxx::subclass::CppSubclass;
+ self.#peer_fn().#cpp_super_method_name(#(#param_names),*)
+ }
+ }
+ })
+ .collect();
+ if !methods_impls.is_empty() {
+ bindgen_mod_items.push(parse_quote! {
+ #[allow(non_snake_case)]
+ impl #supers for super::super::super::#id {
+ #(#methods_impls)*
+ }
+ });
+ }
+ }
+ if generate_peer_constructor {
+ bindgen_mod_items.push(parse_quote! {
+ impl autocxx::subclass::CppPeerConstructor<#cpp_id> for super::super::super::#id {
+ fn make_peer(&mut self, peer_holder: autocxx::subclass::CppSubclassRustPeerHolder<Self>) -> cxx::UniquePtr<#cpp_path> {
+ use autocxx::moveit::EmplaceUnpinned;
+ cxx::UniquePtr::emplace(#cpp_id :: new(peer_holder))
+ }
+ }
+ })
+ };
+
+ // Once for each superclass, in future...
+ let as_id = make_ident(format!("As_{}", super_name));
+ extern_c_mod_items.push(parse_quote! {
+ fn #as_id(self: &#cpp_id) -> &#super_cxxxbridge_id;
+ });
+ let as_mut_id = make_ident(format!("As_{}_mut", super_name));
+ extern_c_mod_items.push(parse_quote! {
+ fn #as_mut_id(self: Pin<&mut #cpp_id>) -> Pin<&mut #super_cxxxbridge_id>;
+ });
+ bindgen_mod_items.push(parse_quote! {
+ impl AsRef<#super_path> for super::super::super::#id {
+ fn as_ref(&self) -> &cxxbridge::#super_cxxxbridge_id {
+ use autocxx::subclass::CppSubclass;
+ self.peer().#as_id()
+ }
+ }
+ });
+ // TODO it would be nice to impl AsMut here but pin prevents us
+ bindgen_mod_items.push(parse_quote! {
+ impl super::super::super::#id {
+ pub fn pin_mut(&mut self) -> ::std::pin::Pin<&mut cxxbridge::#super_cxxxbridge_id> {
+ use autocxx::subclass::CppSubclass;
+ self.peer_mut().#as_mut_id()
+ }
+ }
+ });
+ let remove_ownership = sub.remove_ownership();
+ global_items.push(parse_quote! {
+ #[allow(non_snake_case)]
+ pub fn #remove_ownership(me: Box<#holder>) -> Box<#holder> {
+ Box::new(#holder(me.0.relinquish_ownership()))
+ }
+ });
+ RsCodegenResult {
+ extern_c_mod_items,
+ // For now we just assume we can't keep subclasses in vectors, but we can put them in
+ // smart pointers.
+ // That's the reason for the 'false' and 'true'
+ bridge_items: create_impl_items(&cpp_id, false, true, self.config),
+ bindgen_mod_items,
+ materializations: vec![Use::Custom(Box::new(parse_quote! {
+ pub use cxxbridge::#cpp_id;
+ }))],
+ global_items,
+ extern_rust_mod_items: vec![
+ parse_quote! {
+ pub type #holder;
+ },
+ parse_quote! {
+ fn #remove_ownership(me: Box<#holder>) -> Box<#holder>;
+ },
+ ],
+ ..Default::default()
+ }
+ }
+
+ fn generate_subclass_fn(
+ api_name: Ident,
+ details: RustSubclassFnDetails,
+ subclass: SubclassName,
+ ) -> RsCodegenResult {
+ let params = details.params;
+ let ret = details.ret;
+ let unsafe_token = details.requires_unsafe.wrapper_token();
+ let global_def = quote! { #unsafe_token fn #api_name(#params) #ret };
+ let params = unqualify_params(params);
+ let ret = unqualify_ret_type(ret);
+ let method_name = details.method_name;
+ let cxxbridge_decl: ForeignItemFn =
+ parse_quote! { #unsafe_token fn #api_name(#params) #ret; };
+ let args: Punctuated<Expr, Comma> =
+ Self::args_from_sig(&cxxbridge_decl.sig.inputs).collect();
+ let superclass_id = details.superclass.get_final_ident();
+ let methods_trait = SubclassName::get_methods_trait_name(&details.superclass);
+ let methods_trait = methods_trait.to_type_path();
+ let (deref_ty, deref_call, borrow, mut_token) = match details.receiver_mutability {
+ ReceiverMutability::Const => ("Deref", "deref", "try_borrow", None),
+ ReceiverMutability::Mutable => (
+ "DerefMut",
+ "deref_mut",
+ "try_borrow_mut",
+ Some(syn::token::Mut(Span::call_site())),
+ ),
+ };
+ let deref_ty = make_ident(deref_ty);
+ let deref_call = make_ident(deref_call);
+ let borrow = make_ident(borrow);
+ let destroy_panic_msg = format!("Rust subclass API (method {} of subclass {} of superclass {}) called after subclass destroyed", method_name, subclass.0.name, superclass_id);
+ let reentrancy_panic_msg = format!("Rust subclass API (method {} of subclass {} of superclass {}) called whilst subclass already borrowed - likely a re-entrant call", method_name, subclass.0.name, superclass_id);
+ RsCodegenResult {
+ global_items: vec![parse_quote! {
+ #global_def {
+ let rc = me.0
+ .get()
+ .expect(#destroy_panic_msg);
+ let #mut_token b = rc
+ .as_ref()
+ .#borrow()
+ .expect(#reentrancy_panic_msg);
+ let r = std::ops::#deref_ty::#deref_call(& #mut_token b);
+ #methods_trait :: #method_name
+ (r,
+ #args)
+ }
+ }],
+ extern_rust_mod_items: vec![ForeignItem::Fn(cxxbridge_decl)],
+ ..Default::default()
+ }
+ }
+
+ fn args_from_sig(params: &Punctuated<FnArg, Comma>) -> impl Iterator<Item = Expr> + '_ {
+ params.iter().skip(1).filter_map(|fnarg| match fnarg {
+ syn::FnArg::Receiver(_) => None,
+ syn::FnArg::Typed(fnarg) => match &*fnarg.pat {
+ syn::Pat::Ident(id) => Some(Self::id_to_expr(&id.ident)),
+ _ => None,
+ },
+ })
+ }
+
+ #[allow(clippy::too_many_arguments)] // currently the least unclear way
+ fn generate_type<F>(
+ &self,
+ name: &QualifiedName,
+ id: Ident,
+ type_kind: TypeKind,
+ movable: bool,
+ destroyable: bool,
+ item_creator: F,
+ associated_methods: &HashMap<QualifiedName, Vec<SuperclassMethod>>,
+ layout: Option<Layout>,
+ is_generic: bool,
+ ) -> RsCodegenResult
+ where
+ F: FnOnce() -> Option<(Item, Vec<Attribute>)>,
+ {
+ let mut bindgen_mod_items = Vec::new();
+ let mut materializations = vec![Use::UsedFromBindgen];
+ Self::add_superclass_stuff_to_type(
+ name,
+ &mut bindgen_mod_items,
+ &mut materializations,
+ associated_methods.get(name),
+ );
+ let orig_item = item_creator();
+ let doc_attrs = orig_item
+ .as_ref()
+ .map(|maybe_item| maybe_item.1.clone())
+ .unwrap_or_default();
+ // We have a choice here to either:
+ // a) tell cxx to generate an opaque type using 'type A;'
+ // b) generate a concrete type definition, e.g. by using bindgen's
+ // or doing our own, and then telling cxx 'type A = bindgen::A';'
+ match type_kind {
+ TypeKind::Pod | TypeKind::NonPod => {
+ // Feed cxx "type T = root::bindgen::T"
+ // For non-POD types, there might be the option of simply giving
+ // cxx a "type T;" as we do for abstract types below. There's
+ // two reasons we don't:
+ // a) we want to specify size and alignment for the sake of
+ // moveit;
+ // b) for nested types such as 'A::B', there is no combination
+ // of cxx-acceptable attributes which will inform cxx that
+ // A is a class rather than a namespace.
+ let mut item = orig_item
+ .expect("Instantiable types must provide instance")
+ .0;
+ if matches!(type_kind, TypeKind::NonPod) {
+ if let Item::Struct(ref mut s) = item {
+ // Retain generics and doc attrs.
+ make_non_pod(s, layout);
+ } else {
+ // enum
+ item = Item::Struct(new_non_pod_struct(id.clone()));
+ }
+ }
+ bindgen_mod_items.push(item);
+
+ if is_generic {
+ // Still generate the type as emitted by bindgen,
+ // but don't attempt to tell cxx about it
+ RsCodegenResult {
+ bindgen_mod_items,
+ materializations,
+ ..Default::default()
+ }
+ } else {
+ RsCodegenResult {
+ global_items: self.generate_extern_type_impl(type_kind, name),
+ bridge_items: create_impl_items(&id, movable, destroyable, self.config),
+ extern_c_mod_items: vec![
+ self.generate_cxxbridge_type(name, true, doc_attrs)
+ ],
+ bindgen_mod_items,
+ materializations,
+ ..Default::default()
+ }
+ }
+ }
+ TypeKind::Abstract => {
+ if is_generic {
+ RsCodegenResult::default()
+ } else {
+ // Feed cxx "type T;"
+ // We MUST do this because otherwise cxx assumes this can be
+ // instantiated using UniquePtr etc.
+ bindgen_mod_items.push(Item::Use(parse_quote! { pub use cxxbridge::#id; }));
+ RsCodegenResult {
+ extern_c_mod_items: vec![
+ self.generate_cxxbridge_type(name, false, doc_attrs)
+ ],
+ bindgen_mod_items,
+ materializations,
+ ..Default::default()
+ }
+ }
+ }
+ }
+ }
+
+ fn add_superclass_stuff_to_type(
+ name: &QualifiedName,
+ bindgen_mod_items: &mut Vec<Item>,
+ materializations: &mut Vec<Use>,
+ methods: Option<&Vec<SuperclassMethod>>,
+ ) {
+ if let Some(methods) = methods {
+ let (supers, mains): (Vec<_>, Vec<_>) = methods
+ .iter()
+ .map(|method| {
+ let id = &method.name;
+ let super_id =
+ SubclassName::get_super_fn_name(&Namespace::new(), &id.to_string())
+ .get_final_ident();
+ let param_names: Punctuated<Expr, Comma> =
+ Self::args_from_sig(&method.params).collect();
+ let mut params = method.params.clone();
+ *(params.iter_mut().next().unwrap()) = match method.receiver_mutability {
+ ReceiverMutability::Const => parse_quote!(&self),
+ ReceiverMutability::Mutable => parse_quote!(&mut self),
+ };
+ let ret_type = &method.ret_type;
+ let unsafe_token = method.requires_unsafe.wrapper_token();
+ if method.is_pure_virtual {
+ (
+ None,
+ parse_quote!(
+ #unsafe_token fn #id(#params) #ret_type;
+ ),
+ )
+ } else {
+ let a: Option<TraitItem> = Some(parse_quote!(
+ #unsafe_token fn #super_id(#params) #ret_type;
+ ));
+ let b: TraitItem = parse_quote!(
+ #unsafe_token fn #id(#params) #ret_type {
+ self.#super_id(#param_names)
+ }
+ );
+ (a, b)
+ }
+ })
+ .unzip();
+ let supers: Vec<_> = supers.into_iter().flatten().collect();
+ let supers_name = SubclassName::get_supers_trait_name(name).get_final_ident();
+ let methods_name = SubclassName::get_methods_trait_name(name).get_final_ident();
+ if !supers.is_empty() {
+ bindgen_mod_items.push(parse_quote! {
+ #[allow(non_snake_case)]
+ pub trait #supers_name {
+ #(#supers)*
+ }
+ });
+ bindgen_mod_items.push(parse_quote! {
+ #[allow(non_snake_case)]
+ pub trait #methods_name : #supers_name {
+ #(#mains)*
+ }
+ });
+ materializations.push(Use::SpecificNameFromBindgen(supers_name));
+ } else {
+ bindgen_mod_items.push(parse_quote! {
+ #[allow(non_snake_case)]
+ pub trait #methods_name {
+ #(#mains)*
+ }
+ });
+ }
+ materializations.push(Use::SpecificNameFromBindgen(methods_name));
+ }
+ }
+
+ fn generate_extern_cpp_type(
+ &self,
+ name: &QualifiedName,
+ rust_path: TypePath,
+ ns_depth: usize,
+ ) -> RsCodegenResult {
+ let id = name.get_final_ident();
+ let super_duper = std::iter::repeat(make_ident("super"));
+ let supers = super_duper.take(ns_depth + 2);
+ let use_statement = parse_quote! {
+ pub use #(#supers)::* :: #id;
+ };
+ RsCodegenResult {
+ bindgen_mod_items: vec![use_statement],
+ extern_c_mod_items: vec![self.generate_cxxbridge_type(name, true, Vec::new())],
+ materializations: vec![Use::Custom(Box::new(parse_quote! { pub use #rust_path; }))],
+ ..Default::default()
+ }
+ }
+
+ /// Generates something in the output mod that will carry a docstring
+ /// explaining why a given type or function couldn't have bindings
+ /// generated.
+ fn generate_error_entry(err: ConvertError, ctx: ErrorContext) -> RsCodegenResult {
+ let err = format!("autocxx bindings couldn't be generated: {}", err);
+ let (impl_entry, bindgen_mod_item, materialization) = match ctx.into_type() {
+ ErrorContextType::Item(id) => (
+ // Populate within bindgen mod because impl blocks may attach.
+ None,
+ Some(parse_quote! {
+ #[doc = #err]
+ pub struct #id;
+ }),
+ Some(Use::SpecificNameFromBindgen(id)),
+ ),
+ ErrorContextType::SanitizedItem(id) => (
+ // Guaranteed to be no impl blocks - populate directly in output mod.
+ None,
+ None,
+ Some(Use::Custom(Box::new(parse_quote! {
+ #[doc = #err]
+ pub struct #id;
+ }))),
+ ),
+ ErrorContextType::Method { self_ty, method } => (
+ Some(Box::new(ImplBlockDetails {
+ item: parse_quote! {
+ #[doc = #err]
+ fn #method(_uhoh: autocxx::BindingGenerationFailure) {
+ }
+ },
+ ty: self_ty,
+ })),
+ None,
+ None,
+ ),
+ };
+ RsCodegenResult {
+ impl_entry,
+ bindgen_mod_items: bindgen_mod_item.into_iter().collect(),
+ materializations: materialization.into_iter().collect(),
+ ..Default::default()
+ }
+ }
+
+ fn generate_cxx_use_stmt(name: &QualifiedName, alias: Option<&Ident>) -> Item {
+ let segs = Self::find_output_mod_root(name.get_namespace())
+ .chain(std::iter::once(make_ident("cxxbridge")))
+ .chain(std::iter::once(name.get_final_ident()));
+ Item::Use(match alias {
+ None => parse_quote! {
+ pub use #(#segs)::*;
+ },
+ Some(alias) => parse_quote! {
+ pub use #(#segs)::* as #alias;
+ },
+ })
+ }
+
+ fn generate_bindgen_use_stmt(name: &QualifiedName) -> Item {
+ let segs =
+ Self::find_output_mod_root(name.get_namespace()).chain(name.get_bindgen_path_idents());
+ Item::Use(parse_quote! {
+ pub use #(#segs)::*;
+ })
+ }
+
+ fn generate_extern_type_impl(&self, type_kind: TypeKind, tyname: &QualifiedName) -> Vec<Item> {
+ let tynamestring = namespaced_name_using_original_name_map(tyname, &self.original_name_map);
+ let fulltypath = tyname.get_bindgen_path_idents();
+ let kind_item = match type_kind {
+ TypeKind::Pod => "Trivial",
+ _ => "Opaque",
+ };
+ let kind_item = make_ident(kind_item);
+ vec![Item::Impl(parse_quote! {
+ unsafe impl cxx::ExternType for #(#fulltypath)::* {
+ type Id = cxx::type_id!(#tynamestring);
+ type Kind = cxx::kind::#kind_item;
+ }
+ })]
+ }
+
+ fn generate_cxxbridge_type(
+ &self,
+ name: &QualifiedName,
+ references_bindgen: bool,
+ doc_attrs: Vec<Attribute>,
+ ) -> ForeignItem {
+ let ns = name.get_namespace();
+ let id = name.get_final_ident();
+ // The following lines actually Tell A Lie.
+ // If we have a nested class, B::C, within namespace A,
+ // we actually have to tell cxx that we have nested class C
+ // within namespace A.
+ let mut ns_components: Vec<_> = ns.iter().cloned().collect();
+ let mut cxx_name = None;
+ if let Some(cpp_name) = self.original_name_map.get(name) {
+ let cpp_name = QualifiedName::new_from_cpp_name(cpp_name);
+ cxx_name = Some(cpp_name.get_final_item().to_string());
+ ns_components.extend(cpp_name.ns_segment_iter().cloned());
+ };
+
+ let mut for_extern_c_ts = if !ns_components.is_empty() {
+ let ns_string = ns_components.join("::");
+ quote! {
+ #[namespace = #ns_string]
+ }
+ } else {
+ TokenStream::new()
+ };
+
+ if let Some(n) = cxx_name {
+ for_extern_c_ts.extend(quote! {
+ #[cxx_name = #n]
+ });
+ }
+
+ for_extern_c_ts.extend(quote! {
+ #(#doc_attrs)*
+ });
+
+ if references_bindgen {
+ for_extern_c_ts.extend(quote! {
+ type #id = super::bindgen::root::
+ });
+ for_extern_c_ts.extend(ns.iter().map(make_ident).map(|id| {
+ quote! {
+ #id::
+ }
+ }));
+ for_extern_c_ts.extend(quote! {
+ #id;
+ });
+ } else {
+ for_extern_c_ts.extend(quote! {
+ type #id;
+ });
+ }
+ ForeignItem::Verbatim(for_extern_c_ts)
+ }
+
+ fn find_output_mod_root(ns: &Namespace) -> impl Iterator<Item = Ident> {
+ std::iter::repeat(make_ident("super")).take(ns.depth())
+ }
+}
+
+fn find_trivially_constructed_subclasses(apis: &ApiVec<FnPhase>) -> HashSet<QualifiedName> {
+ let (simple_constructors, complex_constructors): (Vec<_>, Vec<_>) = apis
+ .iter()
+ .filter_map(|api| match api {
+ Api::Function { fun, .. } => match &fun.provenance {
+ Provenance::SynthesizedSubclassConstructor(details) => {
+ Some((&details.subclass.0.name, details.is_trivial))
+ }
+ _ => None,
+ },
+ _ => None,
+ })
+ .partition(|(_, trivial)| *trivial);
+ let simple_constructors: HashSet<_> =
+ simple_constructors.into_iter().map(|(qn, _)| qn).collect();
+ let complex_constructors: HashSet<_> =
+ complex_constructors.into_iter().map(|(qn, _)| qn).collect();
+ (&simple_constructors - &complex_constructors)
+ .into_iter()
+ .cloned()
+ .collect()
+}
+
+fn find_non_pod_types(apis: &ApiVec<FnPhase>) -> HashSet<QualifiedName> {
+ apis.iter()
+ .filter_map(|api| match api {
+ Api::Struct {
+ name,
+ analysis:
+ PodAndDepAnalysis {
+ pod:
+ PodAnalysis {
+ kind: TypeKind::NonPod,
+ ..
+ },
+ ..
+ },
+ ..
+ } => Some(name.name.clone()),
+ _ => None,
+ })
+ .collect()
+}
+
+impl HasNs for (QualifiedName, RsCodegenResult) {
+ fn get_namespace(&self) -> &Namespace {
+ self.0.get_namespace()
+ }
+}
+
+impl<T: AnalysisPhase> HasNs for Api<T> {
+ fn get_namespace(&self) -> &Namespace {
+ self.name().get_namespace()
+ }
+}
+
+/// Snippets of code generated from a particular API.
+/// These are then concatenated together into the final generated code.
+#[derive(Default)]
+struct RsCodegenResult {
+ extern_c_mod_items: Vec<ForeignItem>,
+ extern_rust_mod_items: Vec<ForeignItem>,
+ bridge_items: Vec<Item>,
+ global_items: Vec<Item>,
+ bindgen_mod_items: Vec<Item>,
+ impl_entry: Option<Box<ImplBlockDetails>>,
+ trait_impl_entry: Option<Box<TraitImplBlockDetails>>,
+ materializations: Vec<Use>,
+}
+
+/// An [`Item`] that always needs to be in an unsafe block.
+#[derive(Clone)]
+enum MaybeUnsafeStmt {
+ // This could almost be a syn::Stmt, but that doesn't quite work
+ // because the last stmt in a function is actually an expression
+ // thus lacking a semicolon.
+ Normal(TokenStream),
+ NeedsUnsafe(TokenStream),
+ Binary {
+ in_safe_context: TokenStream,
+ in_unsafe_context: TokenStream,
+ },
+}
+
+impl MaybeUnsafeStmt {
+ fn new(stmt: TokenStream) -> Self {
+ Self::Normal(stmt)
+ }
+
+ fn needs_unsafe(stmt: TokenStream) -> Self {
+ Self::NeedsUnsafe(stmt)
+ }
+
+ fn maybe_unsafe(stmt: TokenStream, needs_unsafe: bool) -> Self {
+ if needs_unsafe {
+ Self::NeedsUnsafe(stmt)
+ } else {
+ Self::Normal(stmt)
+ }
+ }
+
+ fn binary(in_safe_context: TokenStream, in_unsafe_context: TokenStream) -> Self {
+ Self::Binary {
+ in_safe_context,
+ in_unsafe_context,
+ }
+ }
+}
+
+fn maybe_unsafes_to_tokens(
+ items: Vec<MaybeUnsafeStmt>,
+ context_is_already_unsafe: bool,
+) -> TokenStream {
+ if context_is_already_unsafe {
+ let items = items.into_iter().map(|item| match item {
+ MaybeUnsafeStmt::Normal(stmt)
+ | MaybeUnsafeStmt::NeedsUnsafe(stmt)
+ | MaybeUnsafeStmt::Binary {
+ in_unsafe_context: stmt,
+ ..
+ } => stmt,
+ });
+ quote! {
+ #(#items)*
+ }
+ } else {
+ let mut currently_unsafe_list = None;
+ let mut output = Vec::new();
+ for item in items {
+ match item {
+ MaybeUnsafeStmt::NeedsUnsafe(stmt) => {
+ if currently_unsafe_list.is_none() {
+ currently_unsafe_list = Some(Vec::new());
+ }
+ currently_unsafe_list.as_mut().unwrap().push(stmt);
+ }
+ MaybeUnsafeStmt::Normal(stmt)
+ | MaybeUnsafeStmt::Binary {
+ in_safe_context: stmt,
+ ..
+ } => {
+ if let Some(currently_unsafe_list) = currently_unsafe_list.take() {
+ output.push(quote! {
+ unsafe {
+ #(#currently_unsafe_list)*
+ }
+ })
+ }
+ output.push(stmt);
+ }
+ }
+ }
+ if let Some(currently_unsafe_list) = currently_unsafe_list.take() {
+ output.push(quote! {
+ unsafe {
+ #(#currently_unsafe_list)*
+ }
+ })
+ }
+ quote! {
+ #(#output)*
+ }
+ }
+}
+
+#[test]
+fn test_maybe_unsafes_to_tokens() {
+ let items = vec![
+ MaybeUnsafeStmt::new(quote! { use A; }),
+ MaybeUnsafeStmt::new(quote! { use B; }),
+ MaybeUnsafeStmt::needs_unsafe(quote! { use C; }),
+ MaybeUnsafeStmt::needs_unsafe(quote! { use D; }),
+ MaybeUnsafeStmt::new(quote! { use E; }),
+ MaybeUnsafeStmt::needs_unsafe(quote! { use F; }),
+ ];
+ assert_eq!(
+ maybe_unsafes_to_tokens(items.clone(), false).to_string(),
+ quote! {
+ use A;
+ use B;
+ unsafe {
+ use C;
+ use D;
+ }
+ use E;
+ unsafe {
+ use F;
+ }
+ }
+ .to_string()
+ );
+ assert_eq!(
+ maybe_unsafes_to_tokens(items, true).to_string(),
+ quote! {
+ use A;
+ use B;
+ use C;
+ use D;
+ use E;
+ use F;
+ }
+ .to_string()
+ );
+}
diff --git a/engine/src/conversion/codegen_rs/namespace_organizer.rs b/engine/src/conversion/codegen_rs/namespace_organizer.rs
new file mode 100644
index 0000000..886cb1c
--- /dev/null
+++ b/engine/src/conversion/codegen_rs/namespace_organizer.rs
@@ -0,0 +1,136 @@
+// 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 std::collections::BTreeMap;
+
+pub trait HasNs {
+ fn get_namespace(&self) -> &Namespace;
+}
+
+pub struct NamespaceEntries<'a, T: HasNs> {
+ entries: Vec<&'a T>,
+ children: BTreeMap<&'a String, NamespaceEntries<'a, T>>,
+}
+
+impl<'a, T: HasNs> NamespaceEntries<'a, T> {
+ pub(crate) fn new(apis: &'a [T]) -> Self {
+ let api_refs = apis.iter().collect::<Vec<_>>();
+ Self::sort_by_inner_namespace(api_refs, 0)
+ }
+
+ pub(crate) fn is_empty(&self) -> bool {
+ self.entries.is_empty() && self.children.iter().all(|(_, child)| child.is_empty())
+ }
+
+ pub(crate) fn entries(&self) -> &[&'a T] {
+ &self.entries
+ }
+
+ pub(crate) fn children(&self) -> impl Iterator<Item = (&&String, &NamespaceEntries<T>)> {
+ self.children.iter()
+ }
+
+ fn sort_by_inner_namespace(apis: Vec<&'a T>, depth: usize) -> Self {
+ let mut root = NamespaceEntries {
+ entries: Vec::new(),
+ children: BTreeMap::new(),
+ };
+
+ let mut kids_by_child_ns = BTreeMap::new();
+ for api in apis {
+ let first_ns_elem = api.get_namespace().iter().nth(depth);
+ if let Some(first_ns_elem) = first_ns_elem {
+ let list = kids_by_child_ns
+ .entry(first_ns_elem)
+ .or_insert_with(Vec::new);
+ list.push(api);
+ continue;
+ }
+ root.entries.push(api);
+ }
+
+ for (k, v) in kids_by_child_ns.into_iter() {
+ root.children
+ .insert(k, Self::sort_by_inner_namespace(v, depth + 1));
+ }
+
+ root
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{HasNs, NamespaceEntries};
+ use crate::types::Namespace;
+
+ struct TestApi(&'static str, Namespace);
+ impl HasNs for TestApi {
+ fn get_namespace(&self) -> &Namespace {
+ &self.1
+ }
+ }
+
+ #[test]
+ fn test_ns_entries_sort() {
+ let entries = vec![
+ make_api(None, "C"),
+ make_api(None, "A"),
+ make_api(Some("G"), "E"),
+ make_api(Some("D"), "F"),
+ make_api(Some("G"), "H"),
+ make_api(Some("D::K"), "L"),
+ make_api(Some("D::K"), "M"),
+ make_api(None, "B"),
+ make_api(Some("D"), "I"),
+ make_api(Some("D"), "J"),
+ ];
+ let ns = NamespaceEntries::new(&entries);
+ let root_entries = ns.entries();
+ assert_eq!(root_entries.len(), 3);
+ assert_ident(root_entries[0], "C");
+ assert_ident(root_entries[1], "A");
+ assert_ident(root_entries[2], "B");
+ let mut kids = ns.children();
+ let (d_id, d_nse) = kids.next().unwrap();
+ assert_eq!(d_id.to_string(), "D");
+ let (g_id, g_nse) = kids.next().unwrap();
+ assert_eq!(g_id.to_string(), "G");
+ assert!(kids.next().is_none());
+ let d_nse_entries = d_nse.entries();
+ assert_eq!(d_nse_entries.len(), 3);
+ assert_ident(d_nse_entries[0], "F");
+ assert_ident(d_nse_entries[1], "I");
+ assert_ident(d_nse_entries[2], "J");
+ let g_nse_entries = g_nse.entries();
+ assert_eq!(g_nse_entries.len(), 2);
+ assert_ident(g_nse_entries[0], "E");
+ assert_ident(g_nse_entries[1], "H");
+ let mut g_kids = g_nse.children();
+ assert!(g_kids.next().is_none());
+ let mut d_kids = d_nse.children();
+ let (k_id, k_nse) = d_kids.next().unwrap();
+ assert_eq!(k_id.to_string(), "K");
+ let k_nse_entries = k_nse.entries();
+ assert_eq!(k_nse_entries.len(), 2);
+ assert_ident(k_nse_entries[0], "L");
+ assert_ident(k_nse_entries[1], "M");
+ }
+
+ fn assert_ident(api: &TestApi, expected: &str) {
+ assert_eq!(api.0, expected);
+ }
+
+ fn make_api(ns: Option<&str>, id: &'static str) -> TestApi {
+ let ns = match ns {
+ Some(st) => Namespace::from_user_input(st),
+ None => Namespace::new(),
+ };
+ TestApi(id, ns)
+ }
+}
diff --git a/engine/src/conversion/codegen_rs/non_pod_struct.rs b/engine/src/conversion/codegen_rs/non_pod_struct.rs
new file mode 100644
index 0000000..e4bec2f
--- /dev/null
+++ b/engine/src/conversion/codegen_rs/non_pod_struct.rs
@@ -0,0 +1,132 @@
+// 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::Layout;
+use crate::types::make_ident;
+use proc_macro2::{Ident, Span};
+use quote::quote;
+use syn::parse::Parser;
+use syn::punctuated::Punctuated;
+use syn::{parse_quote, Field, Fields, GenericParam, ItemStruct, LitInt};
+
+pub(crate) fn new_non_pod_struct(id: Ident) -> ItemStruct {
+ let mut s = parse_quote! {
+ pub struct #id {
+ }
+ };
+ make_non_pod(&mut s, None);
+ s
+}
+
+pub(crate) fn make_non_pod(s: &mut ItemStruct, layout: Option<Layout>) {
+ // Make an opaque struct. If we have layout information, we pass
+ // that through to Rust. We keep only doc attrs, plus add a #[repr(C)]
+ // if necessary.
+ // Constraints here (thanks to dtolnay@ for this explanation of why the
+ // following is needed:)
+ // (1) If the real alignment of the C++ type is smaller and a reference
+ // is returned from C++ to Rust, mere existence of an insufficiently
+ // aligned reference in Rust causes UB even if never dereferenced
+ // by Rust code
+ // (see https://doc.rust-lang.org/1.47.0/reference/behavior-considered-undefined.html).
+ // Rustc can use least-significant bits of the reference for other storage.
+ // (if we have layout information from bindgen we use that instead)
+ // (2) We want to ensure the type is !Unpin
+ // (3) We want to ensure it's not Send or Sync
+ //
+ // For opaque types, the Rusty opaque structure could in fact be generated
+ // by three different things:
+ // a) bindgen, using its --opaque-type command line argument or the library
+ // equivalent;
+ // b) us (autocxx), which is what this code does
+ // c) cxx, using "type B;" in an "extern "C++"" section
+ // We never use (a) because bindgen requires an allowlist of opaque types.
+ // Furthermore, it sometimes then discards struct definitions entirely
+ // and says "type A = [u8;2];" or something else which makes our life
+ // much more difficult.
+ // We use (c) for abstract types. For everything else, we do it ourselves
+ // for maximal control. See codegen_rs/mod.rs generate_type for more notes.
+ // First work out attributes.
+ let doc_attr = s
+ .attrs
+ .iter()
+ .filter(|a| a.path.get_ident().iter().any(|p| *p == "doc"))
+ .cloned();
+ let repr_attr = if let Some(layout) = &layout {
+ let align = make_lit_int(layout.align);
+ if layout.packed {
+ parse_quote! {
+ #[repr(C,align(#align),packed)]
+ }
+ } else {
+ parse_quote! {
+ #[repr(C,align(#align))]
+ }
+ }
+ } else {
+ parse_quote! {
+ #[repr(C, packed)]
+ }
+ };
+ let attrs = doc_attr.chain(std::iter::once(repr_attr));
+ s.attrs = attrs.collect();
+ // Now fill in fields. Usually, we just want a single field
+ // but if this is a generic type we need to faff a bit.
+ let generic_type_fields = s
+ .generics
+ .params
+ .iter()
+ .enumerate()
+ .filter_map(|(counter, gp)| match gp {
+ GenericParam::Type(gpt) => {
+ let id = &gpt.ident;
+ let field_name = make_ident(&format!("_phantom_{}", counter));
+ let toks = quote! {
+ #field_name: ::std::marker::PhantomData<::std::cell::UnsafeCell< #id >>
+ };
+ Some(Field::parse_named.parse2(toks).unwrap())
+ }
+ _ => None,
+ });
+ let data_field = if let Some(layout) = layout {
+ let size = make_lit_int(layout.size);
+ Some(
+ syn::Field::parse_named
+ .parse2(quote! {
+ _data: [u8; #size]
+ })
+ .unwrap(),
+ )
+ } else {
+ None
+ }
+ .into_iter();
+ let pin_field = syn::Field::parse_named
+ .parse2(quote! {
+ _pinned: core::marker::PhantomData<core::marker::PhantomPinned>
+ })
+ .unwrap();
+
+ let non_send_sync_field = syn::Field::parse_named
+ .parse2(quote! {
+ _non_send_sync: core::marker::PhantomData<[*const u8;0]>
+ })
+ .unwrap();
+ let all_fields: Punctuated<_, syn::token::Comma> = std::iter::once(pin_field)
+ .chain(std::iter::once(non_send_sync_field))
+ .chain(generic_type_fields)
+ .chain(data_field)
+ .collect();
+ s.fields = Fields::Named(parse_quote! { {
+ #all_fields
+ } })
+}
+
+fn make_lit_int(val: usize) -> LitInt {
+ LitInt::new(&val.to_string(), Span::call_site())
+}
diff --git a/engine/src/conversion/codegen_rs/unqualify.rs b/engine/src/conversion/codegen_rs/unqualify.rs
new file mode 100644
index 0000000..6f245c2
--- /dev/null
+++ b/engine/src/conversion/codegen_rs/unqualify.rs
@@ -0,0 +1,94 @@
+// 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 syn::{
+ parse_quote, punctuated::Punctuated, FnArg, GenericArgument, PathArguments, PathSegment,
+ ReturnType, Token, Type, TypePath,
+};
+
+/// Mod to handle stripping paths off the front of types.
+
+fn unqualify_type_path(typ: TypePath) -> TypePath {
+ // If we've still got more than one
+ // path segment then this is referring to a type within
+ // C++ namespaces. Strip them off for now, until cxx supports
+ // nested mods within a cxx::bridge.
+ // This is 'safe' because earlier code will already have
+ // failed with 'DuplicateType' if we had several types called
+ // the same thing.
+ let last_seg = typ.path.segments.into_iter().last().unwrap();
+ let ident = &last_seg.ident;
+ let args = match last_seg.arguments {
+ PathArguments::AngleBracketed(mut ab) => {
+ ab.args = unqualify_punctuated(ab.args);
+ PathArguments::AngleBracketed(ab)
+ }
+ _ => last_seg.arguments.clone(),
+ };
+ let last_seg: PathSegment = parse_quote!( #ident #args );
+ parse_quote!(
+ #last_seg
+ )
+}
+
+fn unqualify_punctuated<P>(pun: Punctuated<GenericArgument, P>) -> Punctuated<GenericArgument, P>
+where
+ P: Default,
+{
+ let mut new_pun = Punctuated::new();
+ for arg in pun.into_iter() {
+ new_pun.push(match arg {
+ GenericArgument::Type(t) => GenericArgument::Type(unqualify_type(t)),
+ _ => arg,
+ });
+ }
+ new_pun
+}
+
+fn unqualify_type(typ: Type) -> Type {
+ match typ {
+ Type::Path(typ) => Type::Path(unqualify_type_path(typ)),
+ Type::Reference(mut typeref) => {
+ typeref.elem = unqualify_boxed_type(typeref.elem);
+ Type::Reference(typeref)
+ }
+ Type::Ptr(mut typeptr) => {
+ typeptr.elem = unqualify_boxed_type(typeptr.elem);
+ Type::Ptr(typeptr)
+ }
+ _ => typ,
+ }
+}
+
+fn unqualify_boxed_type(typ: Box<Type>) -> Box<Type> {
+ Box::new(unqualify_type(*typ))
+}
+
+pub(crate) fn unqualify_ret_type(ret_type: ReturnType) -> ReturnType {
+ match ret_type {
+ ReturnType::Type(tok, boxed_type) => {
+ ReturnType::Type(tok, unqualify_boxed_type(boxed_type))
+ }
+ _ => ret_type,
+ }
+}
+
+pub(crate) fn unqualify_params(
+ params: Punctuated<FnArg, Token![,]>,
+) -> Punctuated<FnArg, Token![,]> {
+ params
+ .into_iter()
+ .map(|p| match p {
+ FnArg::Typed(mut pt) => {
+ pt.ty = unqualify_boxed_type(pt.ty);
+ FnArg::Typed(pt)
+ }
+ _ => p,
+ })
+ .collect()
+}
diff --git a/engine/src/conversion/conversion_tests.rs b/engine/src/conversion/conversion_tests.rs
new file mode 100644
index 0000000..b0474d6
--- /dev/null
+++ b/engine/src/conversion/conversion_tests.rs
@@ -0,0 +1,46 @@
+// 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 autocxx_parser::UnsafePolicy;
+#[allow(unused_imports)]
+use syn::parse_quote;
+use syn::ItemMod;
+
+use crate::CppCodegenOptions;
+
+use super::BridgeConverter;
+
+// This mod is for tests which take bindgen output directly.
+// This should be avoided where possible, since these tests will
+// become obsolete or have to change if and when we update
+// bindgen. Instead, please add tests working directly from
+// the original C++ in integration_tests.rs if possible.
+// Also, if you're pasting in code from github issues, it's
+// important to make sure that the underlying code has an
+// acceptable license. That's why this file is currently blank.
+
+#[allow(dead_code)]
+fn do_test(input: ItemMod) {
+ let tc = parse_quote! {};
+ let bc = BridgeConverter::new(&[], &tc);
+ let inclusions = "".into();
+ bc.convert(
+ input,
+ UnsafePolicy::AllFunctionsSafe,
+ inclusions,
+ &CppCodegenOptions::default(),
+ )
+ .unwrap();
+}
+
+// How to add a test here
+//
+// #[test]
+// fn test_xyz() {
+// do_test(parse_quote!{ /* paste bindgen output here */})
+// }
diff --git a/engine/src/conversion/convert_error.rs b/engine/src/conversion/convert_error.rs
new file mode 100644
index 0000000..0de4f19
--- /dev/null
+++ b/engine/src/conversion/convert_error.rs
@@ -0,0 +1,218 @@
+// 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 itertools::Itertools;
+use syn::Ident;
+use thiserror::Error;
+
+use crate::{
+ known_types,
+ types::{make_ident, Namespace, QualifiedName},
+};
+
+#[derive(Debug, Clone, Error)]
+pub enum ConvertError {
+ #[error("The initial run of 'bindgen' did not generate any content. This might be because none of the requested items for generation could be converted.")]
+ NoContent,
+ #[error("An item was requested using 'generate_pod' which was not safe to hold by value in Rust. {0}")]
+ UnsafePodType(String),
+ #[error("Bindgen generated some unexpected code in a foreign mod section. You may have specified something in a 'generate' directive which is not currently compatible with autocxx.")]
+ UnexpectedForeignItem,
+ #[error("Bindgen generated some unexpected code in its outermost mod section. You may have specified something in a 'generate' directive which is not currently compatible with autocxx.")]
+ UnexpectedOuterItem,
+ #[error("Bindgen generated some unexpected code in an inner namespace mod. You may have specified something in a 'generate' directive which is not currently compatible with autocxx.")]
+ UnexpectedItemInMod,
+ #[error("autocxx was unable to produce a typdef pointing to the complex type {0}.")]
+ ComplexTypedefTarget(String),
+ #[error("Unexpected type for 'this' in the function {}.", .0.to_cpp_name())]
+ UnexpectedThisType(QualifiedName),
+ #[error("autocxx does not yet know how to support the built-in C++ type {} - please raise an issue on github", .0.to_cpp_name())]
+ UnsupportedBuiltInType(QualifiedName),
+ #[error("Type {} has templated arguments and so does the typedef to which it points", .0.to_cpp_name())]
+ ConflictingTemplatedArgsWithTypedef(QualifiedName),
+ #[error("Function {0} has a parameter or return type which is either on the blocklist or a forward declaration")]
+ UnacceptableParam(String),
+ #[error("Function {0} has a return reference parameter, but 0 or >1 input reference parameters, so the lifetime of the output reference cannot be deduced.")]
+ NotOneInputReference(String),
+ #[error("Encountered type not yet supported by autocxx: {0}")]
+ UnsupportedType(String),
+ #[error("Encountered type not yet known by autocxx: {0}")]
+ UnknownType(String),
+ #[error("Encountered mutable static data, not yet supported: {0}")]
+ StaticData(String),
+ #[error("Encountered typedef to itself - this is a known bindgen bug: {0}")]
+ InfinitelyRecursiveTypedef(QualifiedName),
+ #[error("Unexpected 'use' statement encountered: {}", .0.as_ref().map(|s| s.as_str()).unwrap_or("<unknown>"))]
+ UnexpectedUseStatement(Option<String>),
+ #[error("Type {} was parameterized over something complex which we don't yet support", .0.to_cpp_name())]
+ TemplatedTypeContainingNonPathArg(QualifiedName),
+ #[error("Pointer pointed to something unsupported")]
+ InvalidPointee,
+ #[error("The 'generate' or 'generate_pod' directive for '{0}' did not result in any code being generated. Perhaps this was mis-spelled or you didn't qualify the name with any namespaces? Otherwise please report a bug.")]
+ DidNotGenerateAnything(String),
+ #[error("Found an attempt at using a forward declaration ({}) inside a templated cxx type such as UniquePtr or CxxVector. If the forward declaration is a typedef, perhaps autocxx wasn't sure whether or not it involved a forward declaration. If you're sure it didn't, then you may be able to solve this by using instantiable!.", .0.to_cpp_name())]
+ TypeContainingForwardDeclaration(QualifiedName),
+ #[error("Found an attempt at using a type marked as blocked! ({})", .0.to_cpp_name())]
+ Blocked(QualifiedName),
+ #[error("This function or method uses a type where one of the template parameters was incomprehensible to bindgen/autocxx - probably because it uses template specialization.")]
+ UnusedTemplateParam,
+ #[error("Names containing __ are reserved by C++ so not acceptable to cxx")]
+ TooManyUnderscores,
+ #[error("This item relies on a type not known to autocxx ({})", .0.to_cpp_name())]
+ UnknownDependentType(QualifiedName),
+ #[error("This item depends on some other type(s) which autocxx could not generate, some of them are: {}", .0.iter().join(", "))]
+ IgnoredDependent(HashSet<QualifiedName>),
+ #[error("The item name '{0}' is a reserved word in Rust.")]
+ ReservedName(String),
+ #[error("This item name is used in multiple namespaces. At present, autocxx and cxx allow only one type of a given name. This limitation will be fixed in future. (Items found with this name: {})", .0.iter().join(", "))]
+ DuplicateCxxBridgeName(Vec<String>),
+ #[error("This is a method on a type which can't be used as the receiver in Rust (i.e. self/this). This is probably because some type involves template specialization.")]
+ UnsupportedReceiver,
+ #[error("A rust::Box<T> was encountered where T was not known to be a Rust type. Use rust_type!(T): {}", .0.to_cpp_name())]
+ BoxContainingNonRustType(QualifiedName),
+ #[error("A qualified Rust type was found (i.e. one containing ::): {}. Rust types must always be a simple identifier.", .0.to_cpp_name())]
+ RustTypeWithAPath(QualifiedName),
+ #[error("This type is nested within another struct/class, yet is abstract (or is not on the allowlist so we can't be sure). This is not yet supported by autocxx. If you don't believe this type is abstract, add it to the allowlist.")]
+ AbstractNestedType,
+ #[error("This typedef was nested within another struct/class. autocxx is unable to represent inner types if they might be abstract. Unfortunately, autocxx couldn't prove that this type isn't abstract, so it can't represent it.")]
+ NestedOpaqueTypedef,
+ #[error(
+ "This type is nested within another struct/class with protected or private visibility."
+ )]
+ NonPublicNestedType,
+ #[error("This function returns an rvalue reference (&&) which is not yet supported.")]
+ RValueReturn,
+ #[error("This method is private")]
+ PrivateMethod,
+ #[error("autocxx does not know how to generate bindings to operator=")]
+ AssignmentOperator,
+ #[error("This function was marked =delete")]
+ Deleted,
+ #[error("This structure has an rvalue reference field (&&) which is not yet supported.")]
+ RValueReferenceField,
+ #[error("This type was not on the allowlist, so we are not generating methods for it.")]
+ MethodOfNonAllowlistedType,
+ #[error("This type is templated, so we can't generate bindings. We will instead generate bindings for each instantiation.")]
+ MethodOfGenericType,
+ #[error("bindgen generated multiple different APIs (functions/types) with this name. autocxx doesn't know how to disambiguate them, so we won't generate bindings for any of them.")]
+ DuplicateItemsFoundInParsing,
+ #[error(
+ "bindgen generated a move or copy constructor with an unexpected number of parameters."
+ )]
+ ConstructorWithOnlyOneParam,
+ #[error("A copy or move constructor was found to take extra parameters. These are likely to be parameters with defaults, which are not yet supported by autocxx, so this constructor has been ignored.")]
+ ConstructorWithMultipleParams,
+ #[error("A C++ unique_ptr, shared_ptr or weak_ptr was found containing some type that cxx can't accommodate in that position ({})", .0.to_cpp_name())]
+ InvalidTypeForCppPtr(QualifiedName),
+ #[error("A C++ std::vector was found containing some type that cxx can't accommodate as a vector element ({})", .0.to_cpp_name())]
+ InvalidTypeForCppVector(QualifiedName),
+ #[error("Variadic functions are not supported by cxx or autocxx.")]
+ Variadic,
+ #[error("A type had a template inside a std::vector, which is not supported.")]
+ GenericsWithinVector,
+ #[error("This typedef takes generic parameters, not yet supported by autocxx.")]
+ TypedefTakesGenericParameters,
+ #[error("This method belonged to an item in an anonymous namespace, not currently supported.")]
+ MethodInAnonymousNamespace,
+ #[error("We're unable to make a concrete version of this template, because we found an error handling the template.")]
+ ConcreteVersionOfIgnoredTemplate,
+ #[error("bindgen decided to call this type _bindgen_ty_N because it couldn't deduce the correct name for it. That means we can't generate C++ bindings to it.")]
+ BindgenTy,
+}
+
+/// Ensures that error contexts are always created using the constructors in this
+/// mod, therefore undergoing identifier sanitation.
+#[derive(Clone)]
+struct PhantomSanitized;
+
+/// The context of an error, e.g. whether it applies to a function or a method.
+/// This is used to generate suitable rustdoc in the output codegen so that
+/// the errors can be revealed in rust-analyzer-based IDEs, etc.
+#[derive(Clone)]
+pub(crate) struct ErrorContext(ErrorContextType, PhantomSanitized);
+
+/// All idents in this structure are guaranteed to be something we can safely codegen for.
+#[derive(Clone)]
+pub(crate) enum ErrorContextType {
+ Item(Ident),
+ SanitizedItem(Ident),
+ Method { self_ty: Ident, method: Ident },
+}
+
+impl ErrorContext {
+ pub(crate) fn new_for_item(id: Ident) -> Self {
+ match Self::sanitize_error_ident(&id) {
+ None => Self(ErrorContextType::Item(id), PhantomSanitized),
+ Some(sanitized) => Self(ErrorContextType::SanitizedItem(sanitized), PhantomSanitized),
+ }
+ }
+
+ pub(crate) fn new_for_method(self_ty: Ident, method: Ident) -> Self {
+ // If this IgnoredItem relates to a method on a self_ty which we can't represent,
+ // e.g. u8, then forget about trying to attach this error text to something within
+ // an impl block.
+ match Self::sanitize_error_ident(&self_ty) {
+ None => Self(
+ ErrorContextType::Method {
+ self_ty,
+ method: Self::sanitize_error_ident(&method).unwrap_or(method),
+ },
+ PhantomSanitized,
+ ),
+ Some(_) => Self(
+ ErrorContextType::SanitizedItem(make_ident(format!("{}_{}", self_ty, method))),
+ PhantomSanitized,
+ ),
+ }
+ }
+
+ /// Because errors may be generated for invalid types or identifiers,
+ /// we may need to scrub the name
+ fn sanitize_error_ident(id: &Ident) -> Option<Ident> {
+ let qn = QualifiedName::new(&Namespace::new(), id.clone());
+ if known_types().conflicts_with_built_in_type(&qn) {
+ Some(make_ident(format!("{}_autocxx_error", qn.get_final_item())))
+ } else {
+ None
+ }
+ }
+
+ pub(crate) fn get_type(&self) -> &ErrorContextType {
+ &self.0
+ }
+
+ pub(crate) fn into_type(self) -> ErrorContextType {
+ self.0
+ }
+}
+
+impl std::fmt::Display for ErrorContext {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match &self.0 {
+ ErrorContextType::Item(id) | ErrorContextType::SanitizedItem(id) => write!(f, "{}", id),
+ ErrorContextType::Method { self_ty, method } => write!(f, "{}::{}", self_ty, method),
+ }
+ }
+}
+
+#[derive(Clone)]
+pub(crate) struct ConvertErrorWithContext(pub(crate) ConvertError, pub(crate) Option<ErrorContext>);
+
+impl std::fmt::Debug for ConvertErrorWithContext {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+impl std::fmt::Display for ConvertErrorWithContext {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
diff --git a/engine/src/conversion/doc_attr.rs b/engine/src/conversion/doc_attr.rs
new file mode 100644
index 0000000..8fe9d0b
--- /dev/null
+++ b/engine/src/conversion/doc_attr.rs
@@ -0,0 +1,18 @@
+// 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 syn::Attribute;
+
+/// Returns the attribute (if any) which contains a doc comment.
+pub(super) fn get_doc_attrs(attrs: &[Attribute]) -> Vec<Attribute> {
+ attrs
+ .iter()
+ .filter(|a| a.path.get_ident().iter().any(|p| *p == "doc"))
+ .cloned()
+ .collect()
+}
diff --git a/engine/src/conversion/error_reporter.rs b/engine/src/conversion/error_reporter.rs
new file mode 100644
index 0000000..7942a32
--- /dev/null
+++ b/engine/src/conversion/error_reporter.rs
@@ -0,0 +1,225 @@
+// 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 syn::ItemEnum;
+
+use super::{
+ api::{AnalysisPhase, Api, ApiName, FuncToConvert, StructDetails, TypedefKind},
+ apivec::ApiVec,
+ convert_error::{ConvertErrorWithContext, ErrorContext},
+ ConvertError,
+};
+use crate::{
+ conversion::convert_error::ErrorContextType,
+ types::{Namespace, QualifiedName},
+};
+
+/// Run some code which may generate a ConvertError.
+/// If it does, try to note the problem in our output APIs
+/// such that users will see documentation of the error.
+pub(crate) fn report_any_error<F, T>(
+ ns: &Namespace,
+ apis: &mut ApiVec<impl AnalysisPhase>,
+ fun: F,
+) -> Option<T>
+where
+ F: FnOnce() -> Result<T, ConvertErrorWithContext>,
+{
+ match fun() {
+ Ok(result) => Some(result),
+ Err(ConvertErrorWithContext(err, None)) => {
+ eprintln!("Ignored item: {}", err);
+ None
+ }
+ Err(ConvertErrorWithContext(err, Some(ctx))) => {
+ let id = match ctx.get_type() {
+ ErrorContextType::Item(id) | ErrorContextType::SanitizedItem(id) => id,
+ ErrorContextType::Method { self_ty, .. } => self_ty,
+ };
+ let name = ApiName::new_from_qualified_name(QualifiedName::new(ns, id.clone()));
+ apis.push(Api::IgnoredItem {
+ name,
+ err,
+ ctx: Some(ctx),
+ });
+ None
+ }
+ }
+}
+
+/// Run some code which generates an API. Add that API, or if
+/// anything goes wrong, instead add a note of the problem in our
+/// output API such that users will see documentation for the problem.
+pub(crate) fn convert_apis<FF, SF, EF, TF, A, B: 'static>(
+ in_apis: ApiVec<A>,
+ out_apis: &mut ApiVec<B>,
+ mut func_conversion: FF,
+ mut struct_conversion: SF,
+ mut enum_conversion: EF,
+ mut typedef_conversion: TF,
+) where
+ A: AnalysisPhase,
+ B: AnalysisPhase,
+ FF: FnMut(
+ ApiName,
+ Box<FuncToConvert>,
+ A::FunAnalysis,
+ ) -> Result<Box<dyn Iterator<Item = Api<B>>>, ConvertErrorWithContext>,
+ SF: FnMut(
+ ApiName,
+ Box<StructDetails>,
+ A::StructAnalysis,
+ ) -> Result<Box<dyn Iterator<Item = Api<B>>>, ConvertErrorWithContext>,
+ EF: FnMut(
+ ApiName,
+ ItemEnum,
+ ) -> Result<Box<dyn Iterator<Item = Api<B>>>, ConvertErrorWithContext>,
+ TF: FnMut(
+ ApiName,
+ TypedefKind,
+ Option<QualifiedName>,
+ A::TypedefAnalysis,
+ ) -> Result<Box<dyn Iterator<Item = Api<B>>>, ConvertErrorWithContext>,
+{
+ out_apis.extend(in_apis.into_iter().flat_map(|api| {
+ let fullname = api.name_info().clone();
+ let result: Result<Box<dyn Iterator<Item = Api<B>>>, ConvertErrorWithContext> = match api {
+ // No changes to any of these...
+ Api::ConcreteType {
+ name,
+ rs_definition,
+ cpp_definition,
+ } => Ok(Box::new(std::iter::once(Api::ConcreteType {
+ name,
+ rs_definition,
+ cpp_definition,
+ }))),
+ Api::ForwardDeclaration { name, err } => {
+ Ok(Box::new(std::iter::once(Api::ForwardDeclaration {
+ name,
+ err,
+ })))
+ }
+ Api::OpaqueTypedef {
+ name,
+ forward_declaration,
+ } => Ok(Box::new(std::iter::once(Api::OpaqueTypedef {
+ name,
+ forward_declaration,
+ }))),
+ Api::StringConstructor { name } => {
+ Ok(Box::new(std::iter::once(Api::StringConstructor { name })))
+ }
+ Api::Const { name, const_item } => {
+ Ok(Box::new(std::iter::once(Api::Const { name, const_item })))
+ }
+ Api::CType { name, typename } => {
+ Ok(Box::new(std::iter::once(Api::CType { name, typename })))
+ }
+ Api::RustType { name, path } => {
+ Ok(Box::new(std::iter::once(Api::RustType { name, path })))
+ }
+ Api::RustFn {
+ name,
+ details,
+ receiver,
+ } => Ok(Box::new(std::iter::once(Api::RustFn {
+ name,
+ details,
+ receiver,
+ }))),
+ Api::RustSubclassFn {
+ name,
+ subclass,
+ details,
+ } => Ok(Box::new(std::iter::once(Api::RustSubclassFn {
+ name,
+ subclass,
+ details,
+ }))),
+ Api::Subclass { name, superclass } => Ok(Box::new(std::iter::once(Api::Subclass {
+ name,
+ superclass,
+ }))),
+ Api::IgnoredItem { name, err, ctx } => {
+ Ok(Box::new(std::iter::once(Api::IgnoredItem {
+ name,
+ err,
+ ctx,
+ })))
+ }
+ Api::SubclassTraitItem { name, details } => {
+ Ok(Box::new(std::iter::once(Api::SubclassTraitItem {
+ name,
+ details,
+ })))
+ }
+ Api::ExternCppType { name, details, pod } => {
+ Ok(Box::new(std::iter::once(Api::ExternCppType {
+ name,
+ details,
+ pod,
+ })))
+ }
+ // Apply a mapping to the following
+ Api::Enum { name, item } => enum_conversion(name, item),
+ Api::Typedef {
+ name,
+ item,
+ old_tyname,
+ analysis,
+ } => typedef_conversion(name, item, old_tyname, analysis),
+ Api::Function {
+ name,
+ fun,
+ analysis,
+ } => func_conversion(name, fun, analysis),
+ Api::Struct {
+ name,
+ details,
+ analysis,
+ } => struct_conversion(name, details, analysis),
+ };
+ api_or_error(fullname, result)
+ }))
+}
+
+fn api_or_error<T: AnalysisPhase + 'static>(
+ name: ApiName,
+ api_or_error: Result<Box<dyn Iterator<Item = Api<T>>>, ConvertErrorWithContext>,
+) -> Box<dyn Iterator<Item = Api<T>>> {
+ match api_or_error {
+ Ok(opt) => opt,
+ Err(ConvertErrorWithContext(err, ctx)) => {
+ Box::new(std::iter::once(Api::IgnoredItem { name, err, ctx }))
+ }
+ }
+}
+
+/// Run some code which generates an API for an item (as opposed to
+/// a method). Add that API, or if
+/// anything goes wrong, instead add a note of the problem in our
+/// output API such that users will see documentation for the problem.
+pub(crate) fn convert_item_apis<F, A, B: 'static>(
+ in_apis: ApiVec<A>,
+ out_apis: &mut ApiVec<B>,
+ mut fun: F,
+) where
+ F: FnMut(Api<A>) -> Result<Box<dyn Iterator<Item = Api<B>>>, ConvertError>,
+ A: AnalysisPhase,
+ B: AnalysisPhase,
+{
+ out_apis.extend(in_apis.into_iter().flat_map(|api| {
+ let fullname = api.name_info().clone();
+ let tn = api.name().clone();
+ let result = fun(api).map_err(|e| {
+ ConvertErrorWithContext(e, Some(ErrorContext::new_for_item(tn.get_final_ident())))
+ });
+ api_or_error(fullname, result)
+ }))
+}
diff --git a/engine/src/conversion/mod.rs b/engine/src/conversion/mod.rs
new file mode 100644
index 0000000..3043dcf
--- /dev/null
+++ b/engine/src/conversion/mod.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.
+
+mod analysis;
+mod api;
+mod apivec;
+mod codegen_cpp;
+mod codegen_rs;
+#[cfg(test)]
+mod conversion_tests;
+mod convert_error;
+mod doc_attr;
+mod error_reporter;
+mod parse;
+mod utilities;
+
+use analysis::fun::FnAnalyzer;
+use autocxx_parser::IncludeCppConfig;
+pub(crate) use codegen_cpp::CppCodeGenerator;
+pub(crate) use convert_error::ConvertError;
+use itertools::Itertools;
+use syn::{Item, ItemMod};
+
+use crate::{
+ conversion::analysis::deps::HasDependencies, CppCodegenOptions, CppFilePair, UnsafePolicy,
+};
+
+use self::{
+ analysis::{
+ abstract_types::{discard_ignored_functions, mark_types_abstract},
+ allocators::create_alloc_and_frees,
+ casts::add_casts,
+ check_names,
+ constructor_deps::decorate_types_with_constructor_deps,
+ fun::FnPhase,
+ gc::filter_apis_by_following_edges_from_allowlist,
+ pod::analyze_pod_apis,
+ remove_ignored::filter_apis_by_ignored_dependents,
+ replace_hopeless_typedef_targets,
+ tdef::convert_typedef_targets,
+ },
+ api::AnalysisPhase,
+ apivec::ApiVec,
+ codegen_rs::RsCodeGenerator,
+ parse::ParseBindgen,
+};
+
+const LOG_APIS: bool = true;
+
+/// Converts the bindings generated by bindgen into a form suitable
+/// for use with `cxx`.
+/// In fact, most of the actual operation happens within an
+/// individual `BridgeConversion`.
+///
+/// # Flexibility in handling bindgen output
+///
+/// autocxx is inevitably tied to the details of the bindgen output;
+/// e.g. the creation of a 'root' mod when namespaces are enabled.
+/// At the moment this crate takes the view that it's OK to panic
+/// if the bindgen output is not as expected. It may be in future that
+/// we need to be a bit more graceful, but for now, that's OK.
+pub(crate) struct BridgeConverter<'a> {
+ include_list: &'a [String],
+ config: &'a IncludeCppConfig,
+}
+
+/// C++ and Rust code generation output.
+pub(crate) struct CodegenResults {
+ pub(crate) rs: Vec<Item>,
+ pub(crate) cpp: Option<CppFilePair>,
+ pub(crate) cxxgen_header_name: String,
+}
+
+impl<'a> BridgeConverter<'a> {
+ pub fn new(include_list: &'a [String], config: &'a IncludeCppConfig) -> Self {
+ Self {
+ include_list,
+ config,
+ }
+ }
+
+ fn dump_apis<T: AnalysisPhase>(label: &str, apis: &ApiVec<T>) {
+ if LOG_APIS {
+ log::info!(
+ "APIs after {}:\n{}",
+ label,
+ apis.iter()
+ .map(|api| { format!(" {:?}", api) })
+ .sorted()
+ .join("\n")
+ )
+ }
+ }
+
+ fn dump_apis_with_deps(label: &str, apis: &ApiVec<FnPhase>) {
+ if LOG_APIS {
+ log::info!(
+ "APIs after {}:\n{}",
+ label,
+ apis.iter()
+ .map(|api| { format!(" {:?}, deps={}", api, api.format_deps()) })
+ .sorted()
+ .join("\n")
+ )
+ }
+ }
+
+ /// Convert a TokenStream of bindgen-generated bindings to a form
+ /// suitable for cxx.
+ ///
+ /// This is really the heart of autocxx. It parses the output of `bindgen`
+ /// (although really by "parse" we mean to interpret the structures already built
+ /// up by the `syn` crate).
+ pub(crate) fn convert(
+ &self,
+ mut bindgen_mod: ItemMod,
+ unsafe_policy: UnsafePolicy,
+ inclusions: String,
+ cpp_codegen_options: &CppCodegenOptions,
+ ) -> Result<CodegenResults, ConvertError> {
+ match &mut bindgen_mod.content {
+ None => Err(ConvertError::NoContent),
+ Some((_, items)) => {
+ // Parse the bindgen mod.
+ let items_to_process = items.drain(..).collect();
+ let parser = ParseBindgen::new(self.config);
+ let apis = parser.parse_items(items_to_process)?;
+ Self::dump_apis("parsing", &apis);
+ // Inside parse_results, we now have a list of APIs.
+ // We now enter various analysis phases.
+ // Next, convert any typedefs.
+ // "Convert" means replacing bindgen-style type targets
+ // (e.g. root::std::unique_ptr) with cxx-style targets (e.g. UniquePtr).
+ let apis = convert_typedef_targets(self.config, apis);
+ Self::dump_apis("typedefs", &apis);
+ // Now analyze which of them can be POD (i.e. trivial, movable, pass-by-value
+ // versus which need to be opaque).
+ // Specifically, let's confirm that the items requested by the user to be
+ // POD really are POD, and duly mark any dependent types.
+ // This returns a new list of `Api`s, which will be parameterized with
+ // the analysis results. It also returns an object which can be used
+ // by subsequent phases to work out which objects are POD.
+ let analyzed_apis = analyze_pod_apis(apis, self.config)?;
+ Self::dump_apis("pod analysis", &analyzed_apis);
+ let analyzed_apis = replace_hopeless_typedef_targets(self.config, analyzed_apis);
+ let analyzed_apis = add_casts(analyzed_apis);
+ let analyzed_apis = create_alloc_and_frees(analyzed_apis);
+ // Next, figure out how we materialize different functions.
+ // Some will be simple entries in the cxx::bridge module; others will
+ // require C++ wrapper functions. This is probably the most complex
+ // part of `autocxx`. Again, this returns a new set of `Api`s, but
+ // parameterized by a richer set of metadata.
+ Self::dump_apis("adding casts", &analyzed_apis);
+ let analyzed_apis =
+ FnAnalyzer::analyze_functions(analyzed_apis, unsafe_policy, self.config);
+ // If any of those functions turned out to be pure virtual, don't attempt
+ // to generate UniquePtr implementations for the type, since it can't
+ // be instantiated.
+ Self::dump_apis("analyze fns", &analyzed_apis);
+ let analyzed_apis = mark_types_abstract(analyzed_apis);
+ Self::dump_apis("marking abstract", &analyzed_apis);
+ // Annotate structs with a note of any copy/move constructors which
+ // we may want to retain to avoid garbage collecting them later.
+ let analyzed_apis = decorate_types_with_constructor_deps(analyzed_apis);
+ Self::dump_apis_with_deps("adding constructor deps", &analyzed_apis);
+ let analyzed_apis = discard_ignored_functions(analyzed_apis);
+ Self::dump_apis_with_deps("ignoring ignorable fns", &analyzed_apis);
+ // Remove any APIs whose names are not compatible with cxx.
+ let analyzed_apis = check_names(analyzed_apis);
+ // During parsing or subsequent processing we might have encountered
+ // items which we couldn't process due to as-yet-unsupported features.
+ // There might be other items depending on such things. Let's remove them
+ // too.
+ let analyzed_apis = filter_apis_by_ignored_dependents(analyzed_apis);
+ Self::dump_apis_with_deps("removing ignored dependents", &analyzed_apis);
+
+ // We now garbage collect the ones we don't need...
+ let mut analyzed_apis =
+ filter_apis_by_following_edges_from_allowlist(analyzed_apis, self.config);
+ // Determine what variably-sized C types (e.g. int) we need to include
+ analysis::ctypes::append_ctype_information(&mut analyzed_apis);
+ Self::dump_apis_with_deps("GC", &analyzed_apis);
+ // And finally pass them to the code gen phases, which outputs
+ // code suitable for cxx to consume.
+ let cxxgen_header_name = cpp_codegen_options.cxxgen_header_namer.name_header();
+ let cpp = CppCodeGenerator::generate_cpp_code(
+ inclusions,
+ &analyzed_apis,
+ self.config,
+ cpp_codegen_options,
+ &cxxgen_header_name,
+ )?;
+ let rs = RsCodeGenerator::generate_rs_code(
+ analyzed_apis,
+ self.include_list,
+ bindgen_mod,
+ self.config,
+ cpp.as_ref().map(|file_pair| file_pair.header_name.clone()),
+ );
+ Ok(CodegenResults {
+ rs,
+ cpp,
+ cxxgen_header_name,
+ })
+ }
+ }
+ }
+}
diff --git a/engine/src/conversion/parse/bindgen_semantic_attributes.rs b/engine/src/conversion/parse/bindgen_semantic_attributes.rs
new file mode 100644
index 0000000..8b789ae
--- /dev/null
+++ b/engine/src/conversion/parse/bindgen_semantic_attributes.rs
@@ -0,0 +1,190 @@
+// 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::{Ident, TokenStream};
+use syn::{
+ parenthesized,
+ parse::{Parse, Parser},
+ Attribute, LitStr,
+};
+
+use crate::conversion::{
+ api::{CppVisibility, Layout, References, SpecialMemberKind, Virtualness},
+ convert_error::{ConvertErrorWithContext, ErrorContext},
+ ConvertError,
+};
+
+/// The set of all annotations that autocxx_bindgen has added
+/// for our benefit.
+#[derive(Debug)]
+pub(crate) struct BindgenSemanticAttributes(Vec<BindgenSemanticAttribute>);
+
+impl BindgenSemanticAttributes {
+ // Remove `bindgen_` attributes. They don't have a corresponding macro defined anywhere,
+ // so they will cause compilation errors if we leave them in.
+ // We may return an error if one of the bindgen attributes shows that the
+ // item can't be processed.
+ pub(crate) fn new_retaining_others(attrs: &mut Vec<Attribute>) -> Self {
+ let metadata = Self::new(attrs);
+ attrs.retain(|a| a.path.segments.last().unwrap().ident != "cpp_semantics");
+ metadata
+ }
+
+ pub(crate) fn new(attrs: &[Attribute]) -> Self {
+ Self(
+ attrs
+ .iter()
+ .filter_map(|attr| {
+ if attr.path.segments.last().unwrap().ident == "cpp_semantics" {
+ let r: Result<BindgenSemanticAttribute, syn::Error> = attr.parse_args();
+ r.ok()
+ } else {
+ None
+ }
+ })
+ .collect(),
+ )
+ }
+
+ /// Some attributes indicate we can never handle a given item. Check for those.
+ pub(crate) fn check_for_fatal_attrs(
+ &self,
+ id_for_context: &Ident,
+ ) -> Result<(), ConvertErrorWithContext> {
+ if self.has_attr("unused_template_param") {
+ Err(ConvertErrorWithContext(
+ ConvertError::UnusedTemplateParam,
+ Some(ErrorContext::new_for_item(id_for_context.clone())),
+ ))
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Whether the given attribute is present.
+ pub(super) fn has_attr(&self, attr_name: &str) -> bool {
+ self.0.iter().any(|a| a.is_ident(attr_name))
+ }
+
+ /// The C++ visibility of the item.
+ pub(super) fn get_cpp_visibility(&self) -> CppVisibility {
+ if self.has_attr("visibility_private") {
+ CppVisibility::Private
+ } else if self.has_attr("visibility_protected") {
+ CppVisibility::Protected
+ } else {
+ CppVisibility::Public
+ }
+ }
+
+ /// Whether the item is virtual.
+ pub(super) fn get_virtualness(&self) -> Virtualness {
+ if self.has_attr("pure_virtual") {
+ Virtualness::PureVirtual
+ } else if self.has_attr("bindgen_virtual") {
+ Virtualness::Virtual
+ } else {
+ Virtualness::None
+ }
+ }
+
+ fn parse_if_present<T: Parse>(&self, annotation: &str) -> Option<T> {
+ self.0
+ .iter()
+ .find(|a| a.is_ident(annotation))
+ .map(|a| a.parse_args().unwrap())
+ }
+
+ fn string_if_present(&self, annotation: &str) -> Option<String> {
+ let ls: Option<LitStr> = self.parse_if_present(annotation);
+ ls.map(|ls| ls.value())
+ }
+
+ /// The in-memory layout of the item.
+ pub(super) fn get_layout(&self) -> Option<Layout> {
+ self.parse_if_present("layout")
+ }
+
+ /// The original C++ name, which bindgen may have changed.
+ pub(super) fn get_original_name(&self) -> Option<String> {
+ self.string_if_present("original_name")
+ }
+
+ /// Whether this is a move constructor or other special member.
+ pub(super) fn special_member_kind(&self) -> Option<SpecialMemberKind> {
+ self.string_if_present("special_member")
+ .map(|kind| match kind.as_str() {
+ "default_ctor" => SpecialMemberKind::DefaultConstructor,
+ "copy_ctor" => SpecialMemberKind::CopyConstructor,
+ "move_ctor" => SpecialMemberKind::MoveConstructor,
+ "dtor" => SpecialMemberKind::Destructor,
+ "assignment_operator" => SpecialMemberKind::AssignmentOperator,
+ _ => panic!("unexpected special_member_kind"),
+ })
+ }
+
+ /// Any reference parameters or return values.
+ pub(super) fn get_reference_parameters_and_return(&self) -> References {
+ let mut results = References::default();
+ for a in &self.0 {
+ if a.is_ident("ret_type_reference") {
+ results.ref_return = true;
+ } else if a.is_ident("ret_type_rvalue_reference") {
+ results.rvalue_ref_return = true;
+ } else if a.is_ident("arg_type_reference") {
+ let r: Result<Ident, syn::Error> = a.parse_args();
+ if let Ok(ls) = r {
+ results.ref_params.insert(ls);
+ }
+ } else if a.is_ident("arg_type_rvalue_reference") {
+ let r: Result<Ident, syn::Error> = a.parse_args();
+ if let Ok(ls) = r {
+ results.rvalue_ref_params.insert(ls);
+ }
+ }
+ }
+ results
+ }
+}
+
+#[derive(Debug)]
+struct BindgenSemanticAttribute {
+ annotation_name: Ident,
+ body: Option<TokenStream>,
+}
+
+impl BindgenSemanticAttribute {
+ fn is_ident(&self, name: &str) -> bool {
+ self.annotation_name == name
+ }
+
+ fn parse_args<T: Parse>(&self) -> Result<T, syn::Error> {
+ T::parse.parse2(self.body.as_ref().unwrap().clone())
+ }
+}
+
+impl Parse for BindgenSemanticAttribute {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+ let annotation_name: Ident = input.parse()?;
+ if input.peek(syn::token::Paren) {
+ let body_contents;
+ parenthesized!(body_contents in input);
+ Ok(Self {
+ annotation_name,
+ body: Some(body_contents.parse()?),
+ })
+ } else if !input.is_empty() {
+ Err(input.error("expected nothing"))
+ } else {
+ Ok(Self {
+ annotation_name,
+ body: None,
+ })
+ }
+ }
+}
diff --git a/engine/src/conversion/parse/mod.rs b/engine/src/conversion/parse/mod.rs
new file mode 100644
index 0000000..3f42ce4
--- /dev/null
+++ b/engine/src/conversion/parse/mod.rs
@@ -0,0 +1,14 @@
+// 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 bindgen_semantic_attributes;
+mod parse_bindgen;
+mod parse_foreign_mod;
+
+pub(crate) use bindgen_semantic_attributes::BindgenSemanticAttributes;
+pub(crate) use parse_bindgen::ParseBindgen;
diff --git a/engine/src/conversion/parse/parse_bindgen.rs b/engine/src/conversion/parse/parse_bindgen.rs
new file mode 100644
index 0000000..0818aa5
--- /dev/null
+++ b/engine/src/conversion/parse/parse_bindgen.rs
@@ -0,0 +1,388 @@
+// 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 crate::{
+ conversion::{
+ api::{Api, ApiName, NullPhase, StructDetails, SubclassName, TypedefKind, UnanalyzedApi},
+ apivec::ApiVec,
+ ConvertError,
+ },
+ types::Namespace,
+ types::QualifiedName,
+};
+use crate::{
+ conversion::{
+ convert_error::{ConvertErrorWithContext, ErrorContext},
+ error_reporter::report_any_error,
+ },
+ types::validate_ident_ok_for_cxx,
+};
+use autocxx_parser::{IncludeCppConfig, RustPath};
+use syn::{parse_quote, Fields, Ident, Item, Type, TypePath, UseTree};
+
+use super::{
+ super::utilities::generate_utilities, bindgen_semantic_attributes::BindgenSemanticAttributes,
+};
+
+use super::parse_foreign_mod::ParseForeignMod;
+
+/// Parses a bindgen mod in order to understand the APIs within it.
+pub(crate) struct ParseBindgen<'a> {
+ config: &'a IncludeCppConfig,
+ apis: ApiVec<NullPhase>,
+}
+
+fn api_name(ns: &Namespace, id: Ident, attrs: &BindgenSemanticAttributes) -> ApiName {
+ ApiName::new_with_cpp_name(ns, id, attrs.get_original_name())
+}
+
+pub(crate) fn api_name_qualified(
+ ns: &Namespace,
+ id: Ident,
+ attrs: &BindgenSemanticAttributes,
+) -> Result<ApiName, ConvertErrorWithContext> {
+ match validate_ident_ok_for_cxx(&id.to_string()) {
+ Err(e) => {
+ let ctx = ErrorContext::new_for_item(id);
+ Err(ConvertErrorWithContext(e, Some(ctx)))
+ }
+ Ok(..) => Ok(api_name(ns, id, attrs)),
+ }
+}
+
+impl<'a> ParseBindgen<'a> {
+ pub(crate) fn new(config: &'a IncludeCppConfig) -> Self {
+ ParseBindgen {
+ config,
+ apis: ApiVec::new(),
+ }
+ }
+
+ /// Parses items found in the `bindgen` output and returns a set of
+ /// `Api`s together with some other data.
+ pub(crate) fn parse_items(
+ mut self,
+ items: Vec<Item>,
+ ) -> Result<ApiVec<NullPhase>, ConvertError> {
+ let items = Self::find_items_in_root(items)?;
+ if !self.config.exclude_utilities() {
+ generate_utilities(&mut self.apis, self.config);
+ }
+ self.add_apis_from_config();
+ let root_ns = Namespace::new();
+ self.parse_mod_items(items, root_ns);
+ self.confirm_all_generate_directives_obeyed()?;
+ self.replace_extern_cpp_types();
+ Ok(self.apis)
+ }
+
+ /// Some API items are not populated from bindgen output, but instead
+ /// directly from items in the config.
+ fn add_apis_from_config(&mut self) {
+ self.apis
+ .extend(self.config.subclasses.iter().map(|sc| Api::Subclass {
+ name: SubclassName::new(sc.subclass.clone()),
+ superclass: QualifiedName::new_from_cpp_name(&sc.superclass),
+ }));
+ self.apis
+ .extend(self.config.extern_rust_funs.iter().map(|fun| {
+ let id = fun.sig.ident.clone();
+ Api::RustFn {
+ name: ApiName::new_in_root_namespace(id),
+ details: fun.clone(),
+ receiver: fun.receiver.as_ref().map(|receiver_id| {
+ QualifiedName::new(&Namespace::new(), receiver_id.clone())
+ }),
+ }
+ }));
+ let unique_rust_types: HashSet<&RustPath> = self.config.rust_types.iter().collect();
+ self.apis.extend(unique_rust_types.into_iter().map(|path| {
+ let id = path.get_final_ident();
+ Api::RustType {
+ name: ApiName::new_in_root_namespace(id.clone()),
+ path: path.clone(),
+ }
+ }));
+ self.apis.extend(
+ self.config
+ .concretes
+ .0
+ .iter()
+ .map(|(cpp_definition, rust_id)| {
+ let name = ApiName::new_in_root_namespace(rust_id.clone());
+ Api::ConcreteType {
+ name,
+ cpp_definition: cpp_definition.clone(),
+ rs_definition: None,
+ }
+ }),
+ );
+ }
+
+ /// We do this last, _after_ we've parsed all the APIs, because we might want to actually
+ /// replace some of the existing APIs (structs/enums/etc.) with replacements.
+ fn replace_extern_cpp_types(&mut self) {
+ let pod_requests: HashSet<_> = self.config.get_pod_requests().iter().collect();
+ let replacements: HashMap<_, _> = self
+ .config
+ .externs
+ .0
+ .iter()
+ .map(|(cpp_definition, details)| {
+ let qn = QualifiedName::new_from_cpp_name(cpp_definition);
+ let pod = pod_requests.contains(&qn.to_cpp_name());
+ (
+ qn.clone(),
+ Api::ExternCppType {
+ name: ApiName::new_from_qualified_name(qn),
+ details: details.clone(),
+ pod,
+ },
+ )
+ })
+ .collect();
+ self.apis
+ .retain(|api| !replacements.contains_key(api.name()));
+ self.apis.extend(replacements.into_iter().map(|(_, v)| v));
+ }
+
+ fn find_items_in_root(items: Vec<Item>) -> Result<Vec<Item>, ConvertError> {
+ for item in items {
+ match item {
+ Item::Mod(root_mod) => {
+ // With namespaces enabled, bindgen always puts everything
+ // in a mod called 'root'. We don't want to pass that
+ // onto cxx, so jump right into it.
+ assert!(root_mod.ident == "root");
+ if let Some((_, items)) = root_mod.content {
+ return Ok(items);
+ }
+ }
+ _ => return Err(ConvertError::UnexpectedOuterItem),
+ }
+ }
+ Ok(Vec::new())
+ }
+
+ /// Interpret the bindgen-generated .rs for a particular
+ /// mod, which corresponds to a C++ namespace.
+ fn parse_mod_items(&mut self, items: Vec<Item>, ns: Namespace) {
+ // This object maintains some state specific to this namespace, i.e.
+ // this particular mod.
+ let mut mod_converter = ParseForeignMod::new(ns.clone());
+ let mut more_apis = ApiVec::new();
+ for item in items {
+ report_any_error(&ns, &mut more_apis, || {
+ self.parse_item(item, &mut mod_converter, &ns)
+ });
+ }
+ self.apis.append(&mut more_apis);
+ mod_converter.finished(&mut self.apis);
+ }
+
+ fn parse_item(
+ &mut self,
+ item: Item,
+ mod_converter: &mut ParseForeignMod,
+ ns: &Namespace,
+ ) -> Result<(), ConvertErrorWithContext> {
+ match item {
+ Item::ForeignMod(fm) => {
+ mod_converter.convert_foreign_mod_items(fm.items);
+ Ok(())
+ }
+ Item::Struct(s) => {
+ if s.ident.to_string().ends_with("__bindgen_vtable") {
+ return Ok(());
+ }
+ let annotations = BindgenSemanticAttributes::new(&s.attrs);
+ // cxx::bridge can't cope with type aliases to generic
+ // types at the moment.
+ let name = api_name_qualified(ns, s.ident.clone(), &annotations)?;
+ let err = annotations.check_for_fatal_attrs(&s.ident).err();
+ let api = if ns.is_empty() && self.config.is_rust_type(&s.ident) {
+ None
+ } else if Self::spot_forward_declaration(&s.fields)
+ || (Self::spot_zero_length_struct(&s.fields) && err.is_some())
+ {
+ // Forward declarations are recorded especially because we can't
+ // store them in UniquePtr or similar.
+ // Templated forward declarations don't appear with an _unused field (which is what
+ // we spot in the previous clause) but instead with an _address field.
+ // So, solely in the case where we're storing up an error about such
+ // a templated type, we'll also treat such cases as forward declarations.
+ Some(UnanalyzedApi::ForwardDeclaration { name, err })
+ } else {
+ let has_rvalue_reference_fields = s.fields.iter().any(|f| {
+ BindgenSemanticAttributes::new(&f.attrs).has_attr("rvalue_reference")
+ });
+ Some(UnanalyzedApi::Struct {
+ name,
+ details: Box::new(StructDetails {
+ vis: annotations.get_cpp_visibility(),
+ layout: annotations.get_layout(),
+ item: s,
+ has_rvalue_reference_fields,
+ }),
+ analysis: (),
+ })
+ };
+ if let Some(api) = api {
+ if !self.config.is_on_blocklist(&api.name().to_cpp_name()) {
+ self.apis.push(api);
+ }
+ }
+ Ok(())
+ }
+ Item::Enum(e) => {
+ let annotations = BindgenSemanticAttributes::new(&e.attrs);
+ let api = UnanalyzedApi::Enum {
+ name: api_name_qualified(ns, e.ident.clone(), &annotations)?,
+ item: e,
+ };
+ if !self.config.is_on_blocklist(&api.name().to_cpp_name()) {
+ self.apis.push(api);
+ }
+ Ok(())
+ }
+ Item::Impl(imp) => {
+ // We *mostly* ignore all impl blocks generated by bindgen.
+ // Methods also appear in 'extern "C"' blocks which
+ // we will convert instead. At that time we'll also construct
+ // synthetic impl blocks.
+ // We do however record which methods were spotted, since
+ // we have no other way of working out which functions are
+ // static methods vs plain functions.
+ mod_converter.convert_impl_items(imp);
+ Ok(())
+ }
+ Item::Mod(itm) => {
+ if let Some((_, items)) = itm.content {
+ let new_ns = ns.push(itm.ident.to_string());
+ self.parse_mod_items(items, new_ns);
+ }
+ Ok(())
+ }
+ Item::Use(use_item) => {
+ let mut segs = Vec::new();
+ let mut tree = &use_item.tree;
+ loop {
+ match tree {
+ UseTree::Path(up) => {
+ segs.push(up.ident.clone());
+ tree = &up.tree;
+ }
+ UseTree::Name(un) if un.ident == "root" => break, // we do not add this to any API since we generate equivalent
+ // use statements in our codegen phase.
+ UseTree::Rename(urn) => {
+ let old_id = &urn.ident;
+ let new_id = &urn.rename;
+ let new_tyname = QualifiedName::new(ns, new_id.clone());
+ assert!(segs.remove(0) == "self", "Path didn't start with self");
+ assert!(
+ segs.remove(0) == "super",
+ "Path didn't start with self::super"
+ );
+ // This is similar to the path encountered within 'tree'
+ // but without the self::super prefix which is unhelpful
+ // in our output mod, because we prefer relative paths
+ // (we're nested in another mod)
+ let old_path: TypePath = parse_quote! {
+ #(#segs)::* :: #old_id
+ };
+ let old_tyname = QualifiedName::from_type_path(&old_path);
+ if new_tyname == old_tyname {
+ return Err(ConvertErrorWithContext(
+ ConvertError::InfinitelyRecursiveTypedef(new_tyname),
+ Some(ErrorContext::new_for_item(new_id.clone())),
+ ));
+ }
+ let annotations = BindgenSemanticAttributes::new(&use_item.attrs);
+ self.apis.push(UnanalyzedApi::Typedef {
+ name: api_name(ns, new_id.clone(), &annotations),
+ item: TypedefKind::Use(
+ parse_quote! {
+ pub use #old_path as #new_id;
+ },
+ Box::new(Type::Path(old_path)),
+ ),
+ old_tyname: Some(old_tyname),
+ analysis: (),
+ });
+ break;
+ }
+ _ => {
+ return Err(ConvertErrorWithContext(
+ ConvertError::UnexpectedUseStatement(
+ segs.into_iter().last().map(|i| i.to_string()),
+ ),
+ None,
+ ))
+ }
+ }
+ }
+ Ok(())
+ }
+ Item::Const(const_item) => {
+ let annotations = BindgenSemanticAttributes::new(&const_item.attrs);
+ self.apis.push(UnanalyzedApi::Const {
+ name: api_name(ns, const_item.ident.clone(), &annotations),
+ const_item,
+ });
+ Ok(())
+ }
+ Item::Type(ity) => {
+ let annotations = BindgenSemanticAttributes::new(&ity.attrs);
+ // It's known that sometimes bindgen will give us duplicate typedefs with the
+ // same name - see test_issue_264.
+ self.apis.push(UnanalyzedApi::Typedef {
+ name: api_name(ns, ity.ident.clone(), &annotations),
+ item: TypedefKind::Type(ity),
+ old_tyname: None,
+ analysis: (),
+ });
+ Ok(())
+ }
+ _ => Err(ConvertErrorWithContext(
+ ConvertError::UnexpectedItemInMod,
+ None,
+ )),
+ }
+ }
+
+ fn spot_forward_declaration(s: &Fields) -> bool {
+ Self::spot_field(s, "_unused")
+ }
+
+ fn spot_zero_length_struct(s: &Fields) -> bool {
+ Self::spot_field(s, "_address")
+ }
+
+ fn spot_field(s: &Fields, desired_id: &str) -> bool {
+ s.iter()
+ .filter_map(|f| f.ident.as_ref())
+ .any(|id| id == desired_id)
+ }
+
+ fn confirm_all_generate_directives_obeyed(&self) -> Result<(), ConvertError> {
+ let api_names: HashSet<_> = self
+ .apis
+ .iter()
+ .map(|api| api.name().to_cpp_name())
+ .collect();
+ for generate_directive in self.config.must_generate_list() {
+ if !api_names.contains(&generate_directive) {
+ return Err(ConvertError::DidNotGenerateAnything(generate_directive));
+ }
+ }
+ Ok(())
+ }
+}
diff --git a/engine/src/conversion/parse/parse_foreign_mod.rs b/engine/src/conversion/parse/parse_foreign_mod.rs
new file mode 100644
index 0000000..08c1fd8
--- /dev/null
+++ b/engine/src/conversion/parse/parse_foreign_mod.rs
@@ -0,0 +1,177 @@
+// 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::{ApiName, NullPhase, Provenance};
+use crate::conversion::apivec::ApiVec;
+use crate::conversion::doc_attr::get_doc_attrs;
+use crate::conversion::error_reporter::report_any_error;
+use crate::conversion::{
+ api::{FuncToConvert, UnanalyzedApi},
+ convert_error::ConvertErrorWithContext,
+ convert_error::ErrorContext,
+};
+use crate::{
+ conversion::ConvertError,
+ types::{Namespace, QualifiedName},
+};
+use std::collections::HashMap;
+use syn::{Block, Expr, ExprCall, ForeignItem, Ident, ImplItem, ItemImpl, Stmt, Type};
+
+use super::bindgen_semantic_attributes::BindgenSemanticAttributes;
+
+/// Parses a given bindgen-generated 'mod' into suitable
+/// [Api]s. In bindgen output, a given mod concerns
+/// a specific C++ namespace.
+pub(crate) struct ParseForeignMod {
+ ns: Namespace,
+ // We mostly act upon the functions we see within the 'extern "C"'
+ // block of bindgen output, but we can't actually do this until
+ // we've seen the (possibly subsequent) 'impl' blocks so we can
+ // deduce which functions are actually static methods. Hence
+ // store them.
+ funcs_to_convert: Vec<FuncToConvert>,
+ // Evidence from 'impl' blocks about which of these items
+ // may actually be methods (static or otherwise). Mapping from
+ // function name to type name.
+ method_receivers: HashMap<Ident, QualifiedName>,
+ ignored_apis: ApiVec<NullPhase>,
+}
+
+impl ParseForeignMod {
+ pub(crate) fn new(ns: Namespace) -> Self {
+ Self {
+ ns,
+ funcs_to_convert: Vec::new(),
+ method_receivers: HashMap::new(),
+ ignored_apis: ApiVec::new(),
+ }
+ }
+
+ /// Record information from foreign mod items encountered
+ /// in bindgen output.
+ pub(crate) fn convert_foreign_mod_items(&mut self, foreign_mod_items: Vec<ForeignItem>) {
+ let mut extra_apis = ApiVec::new();
+ for i in foreign_mod_items {
+ report_any_error(&self.ns.clone(), &mut extra_apis, || {
+ self.parse_foreign_item(i)
+ });
+ }
+ self.ignored_apis.append(&mut extra_apis);
+ }
+
+ fn parse_foreign_item(&mut self, i: ForeignItem) -> Result<(), ConvertErrorWithContext> {
+ match i {
+ ForeignItem::Fn(item) => {
+ let annotations = BindgenSemanticAttributes::new(&item.attrs);
+ let doc_attrs = get_doc_attrs(&item.attrs);
+ self.funcs_to_convert.push(FuncToConvert {
+ provenance: Provenance::Bindgen,
+ self_ty: None,
+ ident: item.sig.ident,
+ doc_attrs,
+ inputs: item.sig.inputs,
+ output: item.sig.output,
+ vis: item.vis,
+ virtualness: annotations.get_virtualness(),
+ cpp_vis: annotations.get_cpp_visibility(),
+ special_member: annotations.special_member_kind(),
+ unused_template_param: annotations
+ .has_attr("incomprehensible_param_in_arg_or_return"),
+ references: annotations.get_reference_parameters_and_return(),
+ original_name: annotations.get_original_name(),
+ synthesized_this_type: None,
+ add_to_trait: None,
+ is_deleted: annotations.has_attr("deleted"),
+ synthetic_cpp: None,
+ variadic: item.sig.variadic.is_some(),
+ });
+ Ok(())
+ }
+ ForeignItem::Static(item) => Err(ConvertErrorWithContext(
+ ConvertError::StaticData(item.ident.to_string()),
+ Some(ErrorContext::new_for_item(item.ident)),
+ )),
+ _ => Err(ConvertErrorWithContext(
+ ConvertError::UnexpectedForeignItem,
+ None,
+ )),
+ }
+ }
+
+ /// Record information from impl blocks encountered in bindgen
+ /// output.
+ pub(crate) fn convert_impl_items(&mut self, imp: ItemImpl) {
+ let ty_id = match *imp.self_ty {
+ Type::Path(typ) => typ.path.segments.last().unwrap().ident.clone(),
+ _ => return,
+ };
+ for i in imp.items {
+ if let ImplItem::Method(itm) = i {
+ let effective_fun_name = match get_called_function(&itm.block) {
+ Some(id) => id.clone(),
+ None => itm.sig.ident,
+ };
+ self.method_receivers.insert(
+ effective_fun_name,
+ QualifiedName::new(&self.ns, ty_id.clone()),
+ );
+ }
+ }
+ }
+
+ /// Indicate that all foreign mods and all impl blocks have been
+ /// fed into us, and we should process that information to generate
+ /// the resulting APIs.
+ pub(crate) fn finished(mut self, apis: &mut ApiVec<NullPhase>) {
+ apis.append(&mut self.ignored_apis);
+ while !self.funcs_to_convert.is_empty() {
+ let mut fun = self.funcs_to_convert.remove(0);
+ fun.self_ty = self.method_receivers.get(&fun.ident).cloned();
+ apis.push(UnanalyzedApi::Function {
+ name: ApiName::new_with_cpp_name(
+ &self.ns,
+ fun.ident.clone(),
+ fun.original_name.clone(),
+ ),
+ fun: Box::new(fun),
+ analysis: (),
+ })
+ }
+ }
+}
+
+/// bindgen sometimes generates an impl fn called a which calls
+/// a function called a1(), if it's dealing with conflicting names.
+/// We actually care about the name a1, so we have to parse the
+/// name of the actual function call inside the block's body.
+fn get_called_function(block: &Block) -> Option<&Ident> {
+ match block.stmts.first() {
+ Some(Stmt::Expr(Expr::Call(ExprCall { func, .. }))) => match **func {
+ Expr::Path(ref exp) => exp.path.segments.first().map(|ps| &ps.ident),
+ _ => None,
+ },
+ _ => None,
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::get_called_function;
+ use syn::parse_quote;
+ use syn::Block;
+
+ #[test]
+ fn test_get_called_function() {
+ let b: Block = parse_quote! {
+ {
+ call_foo()
+ }
+ };
+ assert_eq!(get_called_function(&b).unwrap().to_string(), "call_foo");
+ }
+}
diff --git a/engine/src/conversion/utilities.rs b/engine/src/conversion/utilities.rs
new file mode 100644
index 0000000..b90b365
--- /dev/null
+++ b/engine/src/conversion/utilities.rs
@@ -0,0 +1,30 @@
+// 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 autocxx_parser::IncludeCppConfig;
+
+use super::{
+ api::{ApiName, NullPhase, UnanalyzedApi},
+ apivec::ApiVec,
+};
+use crate::types::{make_ident, Namespace};
+
+/// Adds items which we always add, cos they're useful.
+/// Any APIs or techniques which do not involve actual C++ interop
+/// shouldn't go here, but instead should go into the main autocxx
+/// src/lib.rs.
+pub(crate) fn generate_utilities(apis: &mut ApiVec<NullPhase>, config: &IncludeCppConfig) {
+ // Unless we've been specifically asked not to do so, we always
+ // generate a 'make_string' function. That pretty much *always* means
+ // we run two passes through bindgen. i.e. the next 'if' is always true,
+ // and we always generate an additional C++ file for our bindings additions,
+ // unless the include_cpp macro has specified ExcludeUtilities.
+ apis.push(UnanalyzedApi::StringConstructor {
+ name: ApiName::new(&Namespace::new(), make_ident(config.get_makestring_name())),
+ });
+}
diff --git a/engine/src/cxxbridge.rs b/engine/src/cxxbridge.rs
new file mode 100644
index 0000000..28163af
--- /dev/null
+++ b/engine/src/cxxbridge.rs
@@ -0,0 +1,45 @@
+// 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 proc_macro2::TokenStream;
+use quote::{ToTokens, TokenStreamExt};
+use syn::ItemMod;
+
+use crate::{do_cxx_cpp_generation, parse_file::CppBuildable, CppCodegenOptions, GeneratedCpp};
+
+/// A struct to represent a cxx::bridge (i.e. some manual bindings)
+/// found in a file. autocxx knows about them so that we can generate C++
+/// for both manual and automatic bindings using the same tooling.
+pub struct CxxBridge {
+ tokens: TokenStream,
+}
+
+impl From<ItemMod> for CxxBridge {
+ fn from(itm: ItemMod) -> Self {
+ Self {
+ tokens: itm.to_token_stream(),
+ }
+ }
+}
+
+impl ToTokens for CxxBridge {
+ fn to_tokens(&self, tokens: &mut TokenStream) {
+ tokens.append_all(self.tokens.clone());
+ }
+}
+
+impl CppBuildable for CxxBridge {
+ fn generate_h_and_cxx(
+ &self,
+ cpp_codegen_options: &CppCodegenOptions,
+ ) -> Result<GeneratedCpp, cxx_gen::Error> {
+ let header_name = cpp_codegen_options.cxxgen_header_namer.name_header();
+ let fp = do_cxx_cpp_generation(self.tokens.clone(), cpp_codegen_options, header_name)?;
+ Ok(GeneratedCpp(vec![fp]))
+ }
+}
diff --git a/engine/src/known_types.rs b/engine/src/known_types.rs
new file mode 100644
index 0000000..377101a
--- /dev/null
+++ b/engine/src/known_types.rs
@@ -0,0 +1,562 @@
+// 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::ConvertError,
+ types::{make_ident, QualifiedName},
+};
+use indexmap::map::IndexMap as HashMap;
+use indoc::indoc;
+use once_cell::sync::OnceCell;
+use syn::{parse_quote, Type, TypePath, TypePtr};
+
+//// The behavior of the type.
+#[derive(Debug)]
+enum Behavior {
+ CxxContainerPtr,
+ CxxContainerVector,
+ CxxString,
+ RustStr,
+ RustString,
+ RustByValue,
+ CByValue,
+ CByValueVecSafe,
+ CVariableLengthByValue,
+ CVoid,
+ CChar16,
+ RustContainerByValueSafe,
+}
+
+/// Details about known special types, mostly primitives.
+#[derive(Debug)]
+struct TypeDetails {
+ /// The name used by cxx (in Rust code) for this type.
+ rs_name: String,
+ /// C++ equivalent name for a Rust type.
+ cpp_name: String,
+ /// The behavior of the type.
+ behavior: Behavior,
+ /// Any extra non-canonical names
+ extra_non_canonical_name: Option<String>,
+ has_const_copy_constructor: bool,
+ has_move_constructor: bool,
+}
+
+impl TypeDetails {
+ fn new(
+ rs_name: impl Into<String>,
+ cpp_name: impl Into<String>,
+ behavior: Behavior,
+ extra_non_canonical_name: Option<String>,
+ has_const_copy_constructor: bool,
+ has_move_constructor: bool,
+ ) -> Self {
+ TypeDetails {
+ rs_name: rs_name.into(),
+ cpp_name: cpp_name.into(),
+ behavior,
+ extra_non_canonical_name,
+ has_const_copy_constructor,
+ has_move_constructor,
+ }
+ }
+
+ /// Whether and how to include this in the prelude given to bindgen.
+ fn get_prelude_entry(&self) -> Option<String> {
+ match self.behavior {
+ Behavior::RustString
+ | Behavior::RustStr
+ | Behavior::CxxString
+ | Behavior::CxxContainerPtr
+ | Behavior::CxxContainerVector
+ | Behavior::RustContainerByValueSafe => {
+ let tn = QualifiedName::new_from_cpp_name(&self.rs_name);
+ let cxx_name = tn.get_final_item();
+ let (templating, payload) = match self.behavior {
+ Behavior::CxxContainerPtr
+ | Behavior::CxxContainerVector
+ | Behavior::RustContainerByValueSafe => ("template<typename T> ", "T* ptr"),
+ _ => ("", "char* ptr"),
+ };
+ Some(format!(
+ indoc! {"
+ /**
+ * <div rustbindgen=\"true\" replaces=\"{}\">
+ */
+ {}class {} {{
+ {};
+ }};
+ "},
+ self.cpp_name, templating, cxx_name, payload
+ ))
+ }
+ _ => None,
+ }
+ }
+
+ fn to_type_path(&self) -> TypePath {
+ let mut segs = self.rs_name.split("::").peekable();
+ if segs.peek().map(|seg| seg.is_empty()).unwrap_or_default() {
+ segs.next();
+ let segs = segs.into_iter().map(make_ident);
+ parse_quote! {
+ ::#(#segs)::*
+ }
+ } else {
+ let segs = segs.into_iter().map(make_ident);
+ parse_quote! {
+ #(#segs)::*
+ }
+ }
+ }
+
+ fn to_typename(&self) -> QualifiedName {
+ QualifiedName::new_from_cpp_name(&self.rs_name)
+ }
+
+ fn get_generic_behavior(&self) -> CxxGenericType {
+ match self.behavior {
+ Behavior::CxxContainerPtr => CxxGenericType::CppPtr,
+ Behavior::CxxContainerVector => CxxGenericType::CppVector,
+ Behavior::RustContainerByValueSafe => CxxGenericType::Rust,
+ _ => CxxGenericType::Not,
+ }
+ }
+}
+
+/// Database of known types.
+#[derive(Default)]
+pub(crate) struct TypeDatabase {
+ by_rs_name: HashMap<QualifiedName, TypeDetails>,
+ canonical_names: HashMap<QualifiedName, QualifiedName>,
+}
+
+/// Returns a database of known types.
+pub(crate) fn known_types() -> &'static TypeDatabase {
+ static KNOWN_TYPES: OnceCell<TypeDatabase> = OnceCell::new();
+ KNOWN_TYPES.get_or_init(create_type_database)
+}
+
+/// The type of payload that a cxx generic can contain.
+#[derive(PartialEq, Clone, Copy)]
+pub enum CxxGenericType {
+ /// Not a generic at all
+ Not,
+ /// Some generic like cxx::UniquePtr where the contents must be a
+ /// complete type.
+ CppPtr,
+ /// Some generic like cxx::Vector where the contents must be a
+ /// complete type, and some types of int are allowed too.
+ CppVector,
+ /// Some generic like rust::Box where forward declarations are OK
+ Rust,
+}
+
+pub struct KnownTypeConstructorDetails {
+ pub has_move_constructor: bool,
+ pub has_const_copy_constructor: bool,
+}
+
+impl TypeDatabase {
+ fn get(&self, ty: &QualifiedName) -> Option<&TypeDetails> {
+ // The following line is important. It says that
+ // when we encounter something like 'std::unique_ptr'
+ // in the bindgen-generated bindings, we'll immediately
+ // start to refer to that as 'UniquePtr' henceforth.
+ let canonical_name = self.canonical_names.get(ty).unwrap_or(ty);
+ self.by_rs_name.get(canonical_name)
+ }
+
+ /// Prelude of C++ for squirting into bindgen. This configures
+ /// bindgen to output simpler types to replace some STL types
+ /// that bindgen just can't cope with. Although we then replace
+ /// those types with cxx types (e.g. UniquePtr), this intermediate
+ /// step is still necessary because bindgen can't otherwise
+ /// give us the templated types (e.g. when faced with the STL
+ /// unique_ptr, bindgen would normally give us std_unique_ptr
+ /// as opposed to std_unique_ptr<T>.)
+ pub(crate) fn get_prelude(&self) -> String {
+ itertools::join(
+ self.by_rs_name
+ .values()
+ .filter_map(|t| t.get_prelude_entry()),
+ "",
+ )
+ }
+
+ /// Returns all known types.
+ pub(crate) fn all_names(&self) -> impl Iterator<Item = &QualifiedName> {
+ self.canonical_names.keys().chain(self.by_rs_name.keys())
+ }
+
+ /// Types which are known to be safe (or unsafe) to hold and pass by
+ /// value in Rust.
+ pub(crate) fn get_pod_safe_types(&self) -> impl Iterator<Item = (QualifiedName, bool)> {
+ let pod_safety = self
+ .all_names()
+ .map(|tn| {
+ (
+ tn.clone(),
+ match self.get(tn).unwrap().behavior {
+ Behavior::CxxContainerPtr
+ | Behavior::RustStr
+ | Behavior::RustString
+ | Behavior::RustByValue
+ | Behavior::CByValueVecSafe
+ | Behavior::CByValue
+ | Behavior::CVariableLengthByValue
+ | Behavior::CChar16
+ | Behavior::RustContainerByValueSafe => true,
+ Behavior::CxxString | Behavior::CxxContainerVector | Behavior::CVoid => {
+ false
+ }
+ },
+ )
+ })
+ .collect::<HashMap<_, _>>();
+ pod_safety.into_iter()
+ }
+
+ pub(crate) fn get_constructor_details(
+ &self,
+ qn: &QualifiedName,
+ ) -> Option<KnownTypeConstructorDetails> {
+ self.get(qn).map(|x| KnownTypeConstructorDetails {
+ has_move_constructor: x.has_move_constructor,
+ has_const_copy_constructor: x.has_const_copy_constructor,
+ })
+ }
+
+ /// Whether this TypePath should be treated as a value in C++
+ /// but a reference in Rust. This only applies to rust::Str
+ /// (C++ name) which is &str in Rust.
+ pub(crate) fn should_dereference_in_cpp(&self, tn: &QualifiedName) -> bool {
+ self.get(tn)
+ .map(|td| matches!(td.behavior, Behavior::RustStr))
+ .unwrap_or(false)
+ }
+
+ /// Whether this can only be passed around using `std::move`
+ pub(crate) fn lacks_copy_constructor(&self, tn: &QualifiedName) -> bool {
+ self.get(tn)
+ .map(|td| {
+ matches!(
+ td.behavior,
+ Behavior::CxxContainerPtr | Behavior::CxxContainerVector
+ )
+ })
+ .unwrap_or(false)
+ }
+
+ /// Here we substitute any names which we know are Special from
+ /// our type database, e.g. std::unique_ptr -> UniquePtr.
+ /// We strip off and ignore
+ /// any PathArguments within this TypePath - callers should
+ /// put them back again if needs be.
+ pub(crate) fn consider_substitution(&self, tn: &QualifiedName) -> Option<TypePath> {
+ self.get(tn).map(|td| td.to_type_path())
+ }
+
+ pub(crate) fn special_cpp_name(&self, rs: &QualifiedName) -> Option<String> {
+ self.get(rs).map(|x| x.cpp_name.to_string())
+ }
+
+ pub(crate) fn is_known_type(&self, ty: &QualifiedName) -> bool {
+ self.get(ty).is_some()
+ }
+
+ pub(crate) fn known_type_type_path(&self, ty: &QualifiedName) -> Option<TypePath> {
+ self.get(ty).map(|td| td.to_type_path())
+ }
+
+ /// Get the list of types to give to bindgen to ask it _not_ to
+ /// generate code for.
+ pub(crate) fn get_initial_blocklist(&self) -> impl Iterator<Item = &str> + '_ {
+ self.by_rs_name
+ .iter()
+ .filter_map(|(_, td)| td.get_prelude_entry().map(|_| td.cpp_name.as_str()))
+ }
+
+ /// Whether this is one of the ctypes (mostly variable length integers)
+ /// which we need to wrap.
+ pub(crate) fn is_ctype(&self, ty: &QualifiedName) -> bool {
+ self.get(ty)
+ .map(|td| {
+ matches!(
+ td.behavior,
+ Behavior::CVariableLengthByValue | Behavior::CVoid | Behavior::CChar16
+ )
+ })
+ .unwrap_or(false)
+ }
+
+ /// Whether this is a generic type acceptable to cxx. Otherwise,
+ /// if we encounter a generic, we'll replace it with a synthesized concrete
+ /// type.
+ pub(crate) fn cxx_generic_behavior(&self, ty: &QualifiedName) -> CxxGenericType {
+ self.get(ty)
+ .map(|x| x.get_generic_behavior())
+ .unwrap_or(CxxGenericType::Not)
+ }
+
+ pub(crate) fn is_cxx_acceptable_receiver(&self, ty: &QualifiedName) -> bool {
+ self.get(ty).is_none() // at present, none of our known types can have
+ // methods attached.
+ }
+
+ pub(crate) fn permissible_within_vector(&self, ty: &QualifiedName) -> bool {
+ self.get(ty)
+ .map(|x| matches!(x.behavior, Behavior::CxxString | Behavior::CByValueVecSafe))
+ .unwrap_or(true)
+ }
+
+ pub(crate) fn permissible_within_unique_ptr(&self, ty: &QualifiedName) -> bool {
+ self.get(ty)
+ .map(|x| {
+ matches!(
+ x.behavior,
+ Behavior::CxxString | Behavior::CxxContainerVector
+ )
+ })
+ .unwrap_or(true)
+ }
+
+ pub(crate) fn conflicts_with_built_in_type(&self, ty: &QualifiedName) -> bool {
+ self.get(ty).is_some()
+ }
+
+ pub(crate) fn convertible_from_strs(&self, ty: &QualifiedName) -> bool {
+ self.get(ty)
+ .map(|x| matches!(x.behavior, Behavior::CxxString))
+ .unwrap_or(false)
+ }
+
+ fn insert(&mut self, td: TypeDetails) {
+ let rs_name = td.to_typename();
+ if let Some(extra_non_canonical_name) = &td.extra_non_canonical_name {
+ self.canonical_names.insert(
+ QualifiedName::new_from_cpp_name(extra_non_canonical_name),
+ rs_name.clone(),
+ );
+ }
+ self.canonical_names.insert(
+ QualifiedName::new_from_cpp_name(&td.cpp_name),
+ rs_name.clone(),
+ );
+ self.by_rs_name.insert(rs_name, td);
+ }
+
+ pub(crate) fn get_moveit_safe_types(&self) -> impl Iterator<Item = QualifiedName> + '_ {
+ self.all_names()
+ .filter(|tn| {
+ !matches!(
+ self.get(tn).unwrap().behavior,
+ Behavior::CxxString | Behavior::CxxContainerVector
+ )
+ })
+ .cloned()
+ }
+}
+
+fn create_type_database() -> TypeDatabase {
+ let mut db = TypeDatabase::default();
+ db.insert(TypeDetails::new(
+ "cxx::UniquePtr",
+ "std::unique_ptr",
+ Behavior::CxxContainerPtr,
+ None,
+ false,
+ true,
+ ));
+ db.insert(TypeDetails::new(
+ "cxx::CxxVector",
+ "std::vector",
+ Behavior::CxxContainerVector,
+ None,
+ false,
+ true,
+ ));
+ db.insert(TypeDetails::new(
+ "cxx::SharedPtr",
+ "std::shared_ptr",
+ Behavior::CxxContainerPtr,
+ None,
+ true,
+ true,
+ ));
+ db.insert(TypeDetails::new(
+ "cxx::WeakPtr",
+ "std::weak_ptr",
+ Behavior::CxxContainerPtr,
+ None,
+ true,
+ true,
+ ));
+ db.insert(TypeDetails::new(
+ "cxx::CxxString",
+ "std::string",
+ Behavior::CxxString,
+ None,
+ true,
+ true,
+ ));
+ db.insert(TypeDetails::new(
+ "str",
+ "rust::Str",
+ Behavior::RustStr,
+ None,
+ true,
+ false,
+ ));
+ db.insert(TypeDetails::new(
+ "String",
+ "rust::String",
+ Behavior::RustString,
+ None,
+ true,
+ true,
+ ));
+ db.insert(TypeDetails::new(
+ "std::boxed::Box",
+ "rust::Box",
+ Behavior::RustContainerByValueSafe,
+ None,
+ false,
+ true,
+ ));
+ db.insert(TypeDetails::new(
+ "i8",
+ "int8_t",
+ Behavior::CByValueVecSafe,
+ Some("std::os::raw::c_schar".into()),
+ true,
+ true,
+ ));
+ db.insert(TypeDetails::new(
+ "u8",
+ "uint8_t",
+ Behavior::CByValueVecSafe,
+ Some("std::os::raw::c_uchar".into()),
+ true,
+ true,
+ ));
+ for (cpp_type, rust_type) in (4..7).map(|x| 2i32.pow(x)).flat_map(|x| {
+ vec![
+ (format!("uint{}_t", x), format!("u{}", x)),
+ (format!("int{}_t", x), format!("i{}", x)),
+ ]
+ }) {
+ db.insert(TypeDetails::new(
+ rust_type,
+ cpp_type,
+ Behavior::CByValueVecSafe,
+ None,
+ true,
+ true,
+ ));
+ }
+ db.insert(TypeDetails::new(
+ "bool",
+ "bool",
+ Behavior::CByValue,
+ None,
+ true,
+ true,
+ ));
+
+ db.insert(TypeDetails::new(
+ "std::pin::Pin",
+ "Pin",
+ Behavior::RustByValue, // because this is actually Pin<&something>
+ None,
+ true,
+ false,
+ ));
+
+ let mut insert_ctype = |cname: &str| {
+ let concatenated_name = cname.replace(' ', "");
+ db.insert(TypeDetails::new(
+ format!("autocxx::c_{}", concatenated_name),
+ cname,
+ Behavior::CVariableLengthByValue,
+ Some(format!("std::os::raw::c_{}", concatenated_name)),
+ true,
+ true,
+ ));
+ db.insert(TypeDetails::new(
+ format!("autocxx::c_u{}", concatenated_name),
+ format!("unsigned {}", cname),
+ Behavior::CVariableLengthByValue,
+ Some(format!("std::os::raw::c_u{}", concatenated_name)),
+ true,
+ true,
+ ));
+ };
+
+ insert_ctype("long");
+ insert_ctype("int");
+ insert_ctype("short");
+ insert_ctype("long long");
+
+ db.insert(TypeDetails::new(
+ "f32",
+ "float",
+ Behavior::CByValueVecSafe,
+ None,
+ true,
+ true,
+ ));
+ db.insert(TypeDetails::new(
+ "f64",
+ "double",
+ Behavior::CByValueVecSafe,
+ None,
+ true,
+ true,
+ ));
+ db.insert(TypeDetails::new(
+ "::std::os::raw::c_char",
+ "char",
+ Behavior::CByValue,
+ None,
+ true,
+ true,
+ ));
+ db.insert(TypeDetails::new(
+ "usize",
+ "size_t",
+ Behavior::CByValueVecSafe,
+ None,
+ true,
+ true,
+ ));
+ db.insert(TypeDetails::new(
+ "autocxx::c_void",
+ "void",
+ Behavior::CVoid,
+ Some("std::os::raw::c_void".into()),
+ false,
+ false,
+ ));
+ db.insert(TypeDetails::new(
+ "autocxx::c_char16_t",
+ "char16_t",
+ Behavior::CChar16,
+ Some("c_char16_t".into()),
+ false,
+ false,
+ ));
+ db
+}
+
+pub(crate) fn ensure_pointee_is_valid(ptr: &TypePtr) -> Result<(), ConvertError> {
+ match *ptr.elem {
+ Type::Path(..) => Ok(()),
+ _ => Err(ConvertError::InvalidPointee),
+ }
+}
diff --git a/engine/src/lib.rs b/engine/src/lib.rs
new file mode 100644
index 0000000..4edc4a4
--- /dev/null
+++ b/engine/src/lib.rs
@@ -0,0 +1,735 @@
+//! The core of the `autocxx` engine, used by both the
+//! `autocxx_macro` and also code generators (e.g. `autocxx_build`).
+//! See [IncludeCppEngine] for general description of how this engine works.
+
+// 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.
+
+// This feature=nightly could be set by build.rs, but since we only care
+// about it for docs, we ask docs.rs to set it in the Cargo.toml.
+#![cfg_attr(feature = "nightly", feature(doc_cfg))]
+#![forbid(unsafe_code)]
+
+mod ast_discoverer;
+mod conversion;
+mod cxxbridge;
+mod known_types;
+mod output_generators;
+mod parse_callbacks;
+mod parse_file;
+mod rust_pretty_printer;
+mod types;
+
+#[cfg(any(test, feature = "build"))]
+mod builder;
+
+use autocxx_parser::{IncludeCppConfig, UnsafePolicy};
+use conversion::BridgeConverter;
+use miette::{SourceOffset, SourceSpan};
+use parse_callbacks::AutocxxParseCallbacks;
+use parse_file::CppBuildable;
+use proc_macro2::TokenStream as TokenStream2;
+use regex::Regex;
+use std::path::PathBuf;
+use std::{
+ fs::File,
+ io::prelude::*,
+ path::Path,
+ process::{Command, Stdio},
+};
+use tempfile::NamedTempFile;
+
+use quote::ToTokens;
+use syn::Result as ParseResult;
+use syn::{
+ parse::{Parse, ParseStream},
+ parse_quote, ItemMod, Macro,
+};
+use thiserror::Error;
+
+use itertools::{join, Itertools};
+use known_types::known_types;
+use log::info;
+use miette::Diagnostic;
+
+/// We use a forked version of bindgen - for now.
+/// We hope to unfork.
+use autocxx_bindgen as bindgen;
+
+#[cfg(any(test, feature = "build"))]
+pub use builder::{
+ Builder, BuilderBuild, BuilderContext, BuilderError, BuilderResult, BuilderSuccess,
+};
+pub use output_generators::{generate_rs_archive, generate_rs_single, RsOutput};
+pub use parse_file::{parse_file, ParseError, ParsedFile};
+
+pub use cxx_gen::HEADER;
+
+#[derive(Clone)]
+/// Some C++ content which should be written to disk and built.
+pub struct CppFilePair {
+ /// Declarations to go into a header file.
+ pub header: Vec<u8>,
+ /// Implementations to go into a .cpp file.
+ pub implementation: Option<Vec<u8>>,
+ /// The name which should be used for the header file
+ /// (important as it may be `#include`d elsewhere)
+ pub header_name: String,
+}
+
+/// All generated C++ content which should be written to disk.
+pub struct GeneratedCpp(pub Vec<CppFilePair>);
+
+/// A [`syn::Error`] which also implements [`miette::Diagnostic`] so can be pretty-printed
+/// to show the affected span of code.
+#[derive(Error, Debug, Diagnostic)]
+#[error("{err}")]
+pub struct LocatedSynError {
+ err: syn::Error,
+ #[source_code]
+ file: String,
+ #[label("error here")]
+ span: SourceSpan,
+}
+
+impl LocatedSynError {
+ fn new(err: syn::Error, file: &str) -> Self {
+ let span = proc_macro_span_to_miette_span(&err.span());
+ Self {
+ err,
+ file: file.to_string(),
+ span,
+ }
+ }
+}
+
+/// Errors which may occur in generating bindings for these C++
+/// functions.
+#[derive(Debug, Error, Diagnostic)]
+pub enum Error {
+ #[error("Bindgen was unable to generate the initial .rs bindings for this file. This may indicate a parsing problem with the C++ headers.")]
+ Bindgen(()),
+ #[error(transparent)]
+ #[diagnostic(transparent)]
+ MacroParsing(LocatedSynError),
+ #[error(transparent)]
+ #[diagnostic(transparent)]
+ BindingsParsing(LocatedSynError),
+ #[error("no C++ include directory was provided.")]
+ NoAutoCxxInc,
+ #[error(transparent)]
+ Conversion(conversion::ConvertError),
+}
+
+/// Result type.
+pub type Result<T, E = Error> = std::result::Result<T, E>;
+
+struct GenerationResults {
+ item_mod: ItemMod,
+ cpp: Option<CppFilePair>,
+ #[allow(dead_code)]
+ inc_dirs: Vec<PathBuf>,
+ cxxgen_header_name: String,
+}
+enum State {
+ NotGenerated,
+ ParseOnly,
+ Generated(Box<GenerationResults>),
+}
+
+const AUTOCXX_CLANG_ARGS: &[&str; 4] = &["-x", "c++", "-std=c++14", "-DBINDGEN"];
+
+/// Implement to learn of header files which get included
+/// by this build process, such that your build system can choose
+/// to rerun the build process if any such file changes in future.
+pub trait RebuildDependencyRecorder: std::fmt::Debug {
+ /// Records that this autocxx build depends on the given
+ /// header file. Full paths will be provided.
+ fn record_header_file_dependency(&self, filename: &str);
+}
+
+#[cfg_attr(doc, aquamarine::aquamarine)]
+/// Core of the autocxx engine.
+///
+/// The basic idea is this. We will run `bindgen` which will spit
+/// out a ton of Rust code corresponding to all the types and functions
+/// defined in C++. We'll then post-process that bindgen output
+/// into a form suitable for ingestion by `cxx`.
+/// (It's the `BridgeConverter` mod which does that.)
+/// Along the way, the `bridge_converter` might tell us of additional
+/// C++ code which we should generate, e.g. wrappers to move things
+/// into and out of `UniquePtr`s.
+///
+/// ```mermaid
+/// flowchart TB
+/// s[(C++ headers)]
+/// s --> lc
+/// rss[(.rs input)]
+/// rss --> parser
+/// parser --> include_cpp_conf
+/// cpp_output[(C++ output)]
+/// rs_output[(.rs output)]
+/// subgraph autocxx[autocxx_engine]
+/// parser[File parser]
+/// subgraph bindgen[autocxx_bindgen]
+/// lc[libclang parse]
+/// bir(bindgen IR)
+/// lc --> bir
+/// end
+/// bgo(bindgen generated bindings)
+/// bir --> bgo
+/// include_cpp_conf(Config from include_cpp)
+/// syn[Parse with syn]
+/// bgo --> syn
+/// conv[['conversion' mod: see below]]
+/// syn --> conv
+/// rsgen(Generated .rs TokenStream)
+/// conv --> rsgen
+/// subgraph cxx_gen
+/// cxx_codegen[cxx_gen C++ codegen]
+/// end
+/// rsgen --> cxx_codegen
+/// end
+/// conv -- autocxx C++ codegen --> cpp_output
+/// rsgen -- autocxx .rs codegen --> rs_output
+/// cxx_codegen -- cxx C++ codegen --> cpp_output
+/// subgraph rustc [rustc build]
+/// subgraph autocxx_macro
+/// include_cpp[autocxx include_cpp macro]
+/// end
+/// subgraph cxx
+/// cxxm[cxx procedural macro]
+/// end
+/// comprs(Fully expanded Rust code)
+/// end
+/// rs_output-. included .->include_cpp
+/// include_cpp --> cxxm
+/// cxxm --> comprs
+/// rss --> rustc
+/// include_cpp_conf -. used to configure .-> bindgen
+/// include_cpp_conf --> conv
+/// link[linker]
+/// cpp_output --> link
+/// comprs --> link
+/// ```
+///
+/// Here's a zoomed-in view of the "conversion" part:
+///
+/// ```mermaid
+/// flowchart TB
+/// syn[(syn parse)]
+/// apis(Unanalyzed APIs)
+/// subgraph parse
+/// syn ==> parse_bindgen
+/// end
+/// parse_bindgen ==> apis
+/// subgraph analysis
+/// typedef[typedef analysis]
+/// pod[POD analysis]
+/// apis ==> typedef
+/// typedef ==> pod
+/// podapis(APIs with POD analysis)
+/// pod ==> podapis
+/// fun[Function materialization analysis]
+/// podapis ==> fun
+/// funapis(APIs with function analysis)
+/// fun ==> funapis
+/// gc[Garbage collection]
+/// funapis ==> gc
+/// ctypes[C int analysis]
+/// gc ==> ctypes
+/// ctypes ==> finalapis
+/// end
+/// finalapis(Analyzed APIs)
+/// codegenrs(.rs codegen)
+/// codegencpp(.cpp codegen)
+/// finalapis ==> codegenrs
+/// finalapis ==> codegencpp
+/// ```
+pub struct IncludeCppEngine {
+ config: IncludeCppConfig,
+ state: State,
+}
+
+impl Parse for IncludeCppEngine {
+ fn parse(input: ParseStream) -> ParseResult<Self> {
+ let config = input.parse::<IncludeCppConfig>()?;
+ let state = if config.parse_only {
+ State::ParseOnly
+ } else {
+ State::NotGenerated
+ };
+ Ok(Self { config, state })
+ }
+}
+
+impl IncludeCppEngine {
+ pub fn new_from_syn(mac: Macro, file_contents: &str) -> Result<Self> {
+ mac.parse_body::<IncludeCppEngine>()
+ .map_err(|e| Error::MacroParsing(LocatedSynError::new(e, file_contents)))
+ }
+
+ pub fn config_mut(&mut self) -> &mut IncludeCppConfig {
+ assert!(
+ matches!(self.state, State::NotGenerated),
+ "Can't alter config after generation commenced"
+ );
+ &mut self.config
+ }
+
+ fn build_header(&self) -> String {
+ join(
+ self.config
+ .inclusions
+ .iter()
+ .map(|path| format!("#include \"{}\"\n", path)),
+ "",
+ )
+ }
+
+ fn make_bindgen_builder(
+ &self,
+ inc_dirs: &[PathBuf],
+ extra_clang_args: &[&str],
+ ) -> bindgen::Builder {
+ let mut builder = bindgen::builder()
+ .clang_args(make_clang_args(inc_dirs, extra_clang_args))
+ .derive_copy(false)
+ .derive_debug(false)
+ .default_enum_style(bindgen::EnumVariation::Rust {
+ non_exhaustive: false,
+ })
+ .size_t_is_usize(true)
+ .enable_cxx_namespaces()
+ .generate_inline_functions(true)
+ .respect_cxx_access_specs(true)
+ .use_specific_virtual_function_receiver(true)
+ .cpp_semantic_attributes(true)
+ .represent_cxx_operators(true)
+ .use_distinct_char16_t(true)
+ .layout_tests(false); // TODO revisit later
+ for item in known_types().get_initial_blocklist() {
+ builder = builder.blocklist_item(item);
+ }
+
+ // 3. Passes allowlist and other options to the bindgen::Builder equivalent
+ // to --output-style=cxx --allowlist=<as passed in>
+ if let Some(allowlist) = self.config.bindgen_allowlist() {
+ for a in allowlist {
+ // TODO - allowlist type/functions/separately
+ builder = builder
+ .allowlist_type(&a)
+ .allowlist_function(&a)
+ .allowlist_var(&a);
+ }
+ }
+
+ log::info!(
+ "Bindgen flags would be: {}",
+ builder
+ .command_line_flags()
+ .into_iter()
+ .map(|f| format!("\"{}\"", f))
+ .join(" ")
+ );
+ builder
+ }
+
+ pub fn get_rs_filename(&self) -> String {
+ self.config.get_rs_filename()
+ }
+
+ /// Generate the Rust bindings. Call `generate` first.
+ pub fn get_rs_output(&self) -> RsOutput {
+ RsOutput {
+ config: &self.config,
+ rs: match &self.state {
+ State::NotGenerated => panic!("Generate first"),
+ State::Generated(gen_results) => gen_results.item_mod.to_token_stream(),
+ State::ParseOnly => TokenStream2::new(),
+ },
+ }
+ }
+
+ /// Returns the name of the mod which this `include_cpp!` will generate.
+ /// Can and should be used to ensure multiple mods in a file don't conflict.
+ pub fn get_mod_name(&self) -> String {
+ self.config.get_mod_name().to_string()
+ }
+
+ fn parse_bindings(&self, bindings: bindgen::Bindings) -> Result<ItemMod> {
+ // This bindings object is actually a TokenStream internally and we're wasting
+ // effort converting to and from string. We could enhance the bindgen API
+ // in future.
+ let bindings = bindings.to_string();
+ // Manually add the mod ffi {} so that we can ask syn to parse
+ // into a single construct.
+ let bindings = format!("mod bindgen {{ {} }}", bindings);
+ info!("Bindings: {}", bindings);
+ syn::parse_str::<ItemMod>(&bindings)
+ .map_err(|e| Error::BindingsParsing(LocatedSynError::new(e, &bindings)))
+ }
+
+ /// Actually examine the headers to find out what needs generating.
+ /// Most errors occur at this stage as we fail to interpret the C++
+ /// headers properly.
+ ///
+ /// See documentation for this type for flow diagrams and more details.
+ pub fn generate(
+ &mut self,
+ inc_dirs: Vec<PathBuf>,
+ extra_clang_args: &[&str],
+ dep_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
+ cpp_codegen_options: &CppCodegenOptions,
+ ) -> Result<()> {
+ // If we are in parse only mode, do nothing. This is used for
+ // doc tests to ensure the parsing is valid, but we can't expect
+ // valid C++ header files or linkers to allow a complete build.
+ match self.state {
+ State::ParseOnly => return Ok(()),
+ State::NotGenerated => {}
+ State::Generated(_) => panic!("Only call generate once"),
+ }
+
+ let mod_name = self.config.get_mod_name();
+ let mut builder = self.make_bindgen_builder(&inc_dirs, extra_clang_args);
+ if let Some(dep_recorder) = dep_recorder {
+ builder = builder.parse_callbacks(Box::new(AutocxxParseCallbacks(dep_recorder)));
+ }
+ let header_contents = self.build_header();
+ self.dump_header_if_so_configured(&header_contents, &inc_dirs, extra_clang_args);
+ let header_and_prelude = format!("{}\n\n{}", known_types().get_prelude(), header_contents);
+ log::info!("Header and prelude for bindgen:\n{}", header_and_prelude);
+ builder = builder.header_contents("example.hpp", &header_and_prelude);
+
+ let bindings = builder.generate().map_err(Error::Bindgen)?;
+ let bindings = self.parse_bindings(bindings)?;
+
+ let converter = BridgeConverter::new(&self.config.inclusions, &self.config);
+
+ let conversion = converter
+ .convert(
+ bindings,
+ self.config.unsafe_policy.clone(),
+ header_contents,
+ cpp_codegen_options,
+ )
+ .map_err(Error::Conversion)?;
+ let mut items = conversion.rs;
+ let mut new_bindings: ItemMod = parse_quote! {
+ #[allow(non_snake_case)]
+ #[allow(dead_code)]
+ #[allow(non_upper_case_globals)]
+ #[allow(non_camel_case_types)]
+ mod #mod_name {
+ }
+ };
+ new_bindings.content.as_mut().unwrap().1.append(&mut items);
+ info!(
+ "New bindings:\n{}",
+ rust_pretty_printer::pretty_print(&new_bindings.to_token_stream())
+ );
+ self.state = State::Generated(Box::new(GenerationResults {
+ item_mod: new_bindings,
+ cpp: conversion.cpp,
+ inc_dirs,
+ cxxgen_header_name: conversion.cxxgen_header_name,
+ }));
+ Ok(())
+ }
+
+ /// Return the include directories used for this include_cpp invocation.
+ #[cfg(any(test, feature = "build"))]
+ fn include_dirs(&self) -> impl Iterator<Item = &PathBuf> {
+ match &self.state {
+ State::Generated(gen_results) => gen_results.inc_dirs.iter(),
+ _ => panic!("Must call generate() before include_dirs()"),
+ }
+ }
+
+ fn dump_header_if_so_configured(
+ &self,
+ header: &str,
+ inc_dirs: &[PathBuf],
+ extra_clang_args: &[&str],
+ ) {
+ if let Ok(output_path) = std::env::var("AUTOCXX_PREPROCESS") {
+ self.make_preprocessed_file(
+ &PathBuf::from(output_path),
+ header,
+ inc_dirs,
+ extra_clang_args,
+ );
+ }
+ #[cfg(feature = "reproduction_case")]
+ if let Ok(output_path) = std::env::var("AUTOCXX_REPRO_CASE") {
+ let tf = NamedTempFile::new().unwrap();
+ self.make_preprocessed_file(
+ &PathBuf::from(tf.path()),
+ header,
+ inc_dirs,
+ extra_clang_args,
+ );
+ let header = std::fs::read(tf.path()).unwrap();
+ let header = String::from_utf8_lossy(&header);
+ let output_path = PathBuf::from(output_path);
+ let config = self.config.to_token_stream().to_string();
+ let json = serde_json::json!({
+ "header": header,
+ "config": config
+ });
+ let f = File::create(&output_path).unwrap();
+ serde_json::to_writer(f, &json).unwrap();
+ }
+ }
+
+ fn make_preprocessed_file(
+ &self,
+ output_path: &Path,
+ header: &str,
+ inc_dirs: &[PathBuf],
+ extra_clang_args: &[&str],
+ ) {
+ // Include a load of system headers at the end of the preprocessed output,
+ // because we would like to be able to generate bindings from the
+ // preprocessed header, and then build those bindings. The C++ parts
+ // of those bindings might need things inside these various headers;
+ // we make sure all these definitions and declarations are inside
+ // this one header file so that the reduction process does not have
+ // to refer to local headers on the reduction machine too.
+ let suffix = ALL_KNOWN_SYSTEM_HEADERS
+ .iter()
+ .map(|hdr| format!("#include <{}>\n", hdr))
+ .join("\n");
+ let input = format!("/*\nautocxx config:\n\n{:?}\n\nend autocxx config.\nautocxx preprocessed input:\n*/\n\n{}\n\n/* autocxx: extra headers added below for completeness. */\n\n{}\n{}\n",
+ self.config, header, suffix, cxx_gen::HEADER);
+ let mut tf = NamedTempFile::new().unwrap();
+ write!(tf, "{}", input).unwrap();
+ let tp = tf.into_temp_path();
+ preprocess(&tp, &PathBuf::from(output_path), inc_dirs, extra_clang_args).unwrap();
+ }
+}
+
+/// This is a list of all the headers known to be included in generated
+/// C++ by cxx. We only use this when `AUTOCXX_PERPROCESS` is set to true,
+/// in an attempt to make the resulting preprocessed header more hermetic.
+/// We clearly should _not_ use this in any other circumstance; obviously
+/// we'd then want to add an API to cxx_gen such that we could retrieve
+/// that information from source.
+static ALL_KNOWN_SYSTEM_HEADERS: &[&str] = &[
+ "memory",
+ "string",
+ "algorithm",
+ "array",
+ "cassert",
+ "cstddef",
+ "cstdint",
+ "cstring",
+ "exception",
+ "functional",
+ "initializer_list",
+ "iterator",
+ "memory",
+ "new",
+ "stdexcept",
+ "type_traits",
+ "utility",
+ "vector",
+ "sys/types.h",
+];
+
+pub fn do_cxx_cpp_generation(
+ rs: TokenStream2,
+ cpp_codegen_options: &CppCodegenOptions,
+ cxxgen_header_name: String,
+) -> Result<CppFilePair, cxx_gen::Error> {
+ let mut opt = cxx_gen::Opt::default();
+ opt.cxx_impl_annotations = cpp_codegen_options.cxx_impl_annotations.clone();
+ let cxx_generated = cxx_gen::generate_header_and_cc(rs, &opt)?;
+ Ok(CppFilePair {
+ header: strip_system_headers(
+ cxx_generated.header,
+ cpp_codegen_options.suppress_system_headers,
+ ),
+ header_name: cxxgen_header_name,
+ implementation: Some(strip_system_headers(
+ cxx_generated.implementation,
+ cpp_codegen_options.suppress_system_headers,
+ )),
+ })
+}
+
+pub(crate) fn strip_system_headers(input: Vec<u8>, suppress_system_headers: bool) -> Vec<u8> {
+ if suppress_system_headers {
+ std::str::from_utf8(&input)
+ .unwrap()
+ .lines()
+ .filter(|l| !l.starts_with("#include <"))
+ .join("\n")
+ .as_bytes()
+ .to_vec()
+ } else {
+ input
+ }
+}
+
+impl CppBuildable for IncludeCppEngine {
+ /// Generate C++-side bindings for these APIs. Call `generate` first.
+ fn generate_h_and_cxx(
+ &self,
+ cpp_codegen_options: &CppCodegenOptions,
+ ) -> Result<GeneratedCpp, cxx_gen::Error> {
+ let mut files = Vec::new();
+ match &self.state {
+ State::ParseOnly => panic!("Cannot generate C++ in parse-only mode"),
+ State::NotGenerated => panic!("Call generate() first"),
+ State::Generated(gen_results) => {
+ let rs = gen_results.item_mod.to_token_stream();
+ files.push(do_cxx_cpp_generation(
+ rs,
+ cpp_codegen_options,
+ gen_results.cxxgen_header_name.clone(),
+ )?);
+ if let Some(cpp_file_pair) = &gen_results.cpp {
+ files.push(cpp_file_pair.clone());
+ }
+ }
+ };
+ Ok(GeneratedCpp(files))
+ }
+}
+
+/// Get clang args as if we were operating clang the same way as we operate
+/// bindgen.
+pub fn make_clang_args<'a>(
+ incs: &'a [PathBuf],
+ extra_args: &'a [&str],
+) -> impl Iterator<Item = String> + 'a {
+ // AUTOCXX_CLANG_ARGS come first so that any defaults defined there(e.g. for the `-std`
+ // argument) can be overridden by extra_args.
+ AUTOCXX_CLANG_ARGS
+ .iter()
+ .map(|s| s.to_string())
+ .chain(incs.iter().map(|i| format!("-I{}", i.to_str().unwrap())))
+ .chain(extra_args.iter().map(|s| s.to_string()))
+}
+
+/// Preprocess a file using the same options
+/// as is used by autocxx. Input: listing_path, output: preprocess_path.
+pub fn preprocess(
+ listing_path: &Path,
+ preprocess_path: &Path,
+ incs: &[PathBuf],
+ extra_clang_args: &[&str],
+) -> Result<(), std::io::Error> {
+ let mut cmd = Command::new(get_clang_path());
+ cmd.arg("-E");
+ cmd.arg("-C");
+ cmd.args(make_clang_args(incs, extra_clang_args));
+ cmd.arg(listing_path.to_str().unwrap());
+ cmd.stderr(Stdio::inherit());
+ let result = cmd.output().expect("failed to execute clang++");
+ assert!(result.status.success(), "failed to preprocess");
+ let mut file = File::create(preprocess_path)?;
+ file.write_all(&result.stdout)?;
+ Ok(())
+}
+
+/// Get the path to clang which is effective for any preprocessing
+/// operations done by autocxx.
+pub fn get_clang_path() -> String {
+ // `CLANG_PATH` is the environment variable that clang-sys uses to specify
+ // the path to Clang, so in most cases where someone is using a compiler
+ // that's not on the path, things should just work. We also check `CXX`,
+ // since some users may have set that.
+ std::env::var("CLANG_PATH")
+ .or_else(|_| std::env::var("CXX"))
+ .unwrap_or_else(|_| "clang++".to_string())
+}
+
+/// Function to generate the desired name of the header containing autocxx's
+/// extra generated C++.
+/// Newtype wrapper so we can give it a [`Default`].
+pub struct AutocxxgenHeaderNamer<'a>(pub Box<dyn 'a + Fn(String) -> String>);
+
+impl Default for AutocxxgenHeaderNamer<'static> {
+ fn default() -> Self {
+ Self(Box::new(|mod_name| format!("autocxxgen_{}.h", mod_name)))
+ }
+}
+
+impl AutocxxgenHeaderNamer<'_> {
+ fn name_header(&self, mod_name: String) -> String {
+ self.0(mod_name)
+ }
+}
+
+/// Function to generate the desired name of the header containing cxx's
+/// declarations.
+/// Newtype wrapper so we can give it a [`Default`].
+pub struct CxxgenHeaderNamer<'a>(pub Box<dyn 'a + Fn() -> String>);
+
+impl Default for CxxgenHeaderNamer<'static> {
+ fn default() -> Self {
+ Self(Box::new(|| "cxxgen.h".into()))
+ }
+}
+
+impl CxxgenHeaderNamer<'_> {
+ fn name_header(&self) -> String {
+ self.0()
+ }
+}
+
+/// Options for C++ codegen
+#[derive(Default)]
+pub struct CppCodegenOptions<'a> {
+ /// Whether to avoid generating `#include <some-system-header>`.
+ /// You may wish to do this to make a hermetic test case with no
+ /// external dependencies.
+ pub suppress_system_headers: bool,
+ /// Optionally, a prefix to go at `#include "<here>cxx.h". This is a header file from the `cxx`
+ /// crate.
+ pub path_to_cxx_h: Option<String>,
+ /// Optionally, a prefix to go at `#include "<here>cxxgen.h". This is a header file which we
+ /// generate.
+ pub path_to_cxxgen_h: Option<String>,
+ /// Optionally, a function called to determine the name that will be used
+ /// for the autocxxgen.h file.
+ /// The function is passed the name of the module generated by each `include_cpp`,
+ /// configured via `name`. These will be unique.
+ pub autocxxgen_header_namer: AutocxxgenHeaderNamer<'a>,
+ /// A function to generate the name of the cxxgen.h header that should be output.
+ pub cxxgen_header_namer: CxxgenHeaderNamer<'a>,
+ /// An annotation optionally to include on each C++ function.
+ /// For example to export the symbol from a library.
+ pub cxx_impl_annotations: Option<String>,
+}
+
+fn proc_macro_span_to_miette_span(span: &proc_macro2::Span) -> SourceSpan {
+ // A proc_macro2::Span stores its location as a byte offset. But there are
+ // no APIs to get that offset out.
+ // We could use `.start()` and `.end()` to get the line + column numbers, but it appears
+ // they're a little buggy. Hence we do this, to get the offsets directly across into
+ // miette.
+ struct Err;
+ let r: Result<(usize, usize), Err> = (|| {
+ let span_desc = format!("{:?}", span);
+ let re = Regex::new(r"(\d+)..(\d+)").unwrap();
+ let captures = re.captures(&span_desc).ok_or(Err)?;
+ let start = captures.get(1).ok_or(Err)?;
+ let start: usize = start.as_str().parse().map_err(|_| Err)?;
+ let start = start.saturating_sub(1); // proc_macro::Span offsets seem to be off-by-one
+ let end = captures.get(2).ok_or(Err)?;
+ let end: usize = end.as_str().parse().map_err(|_| Err)?;
+ let end = end.saturating_sub(1); // proc_macro::Span offsets seem to be off-by-one
+ Ok((start, end.saturating_sub(start)))
+ })();
+ let (start, end) = r.unwrap_or((0, 0));
+ SourceSpan::new(SourceOffset::from(start), SourceOffset::from(end))
+}
diff --git a/engine/src/output_generators.rs b/engine/src/output_generators.rs
new file mode 100644
index 0000000..a862558
--- /dev/null
+++ b/engine/src/output_generators.rs
@@ -0,0 +1,46 @@
+// 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, MultiBindings};
+use proc_macro2::TokenStream;
+
+/// Opaque structure representing the Rust which needs to be generated
+/// for a given `include_cpp!` macro. You will want to pass this into
+/// either [`generate_rs_single`] or [`generate_rs_archive`].
+pub struct RsOutput<'a> {
+ pub(crate) config: &'a IncludeCppConfig,
+ pub(crate) rs: TokenStream,
+}
+
+/// Creates an on-disk archive (actually a JSON file) of the Rust side of the bindings
+/// for multiple `include_cpp` macros. If you use this, you will want to tell
+/// `autocxx_macro` how to find this file using the `AUTOCXX_RS_ARCHIVE`
+/// environment variable.
+pub fn generate_rs_archive<'a>(rs_outputs: impl Iterator<Item = RsOutput<'a>>) -> String {
+ let mut multi_bindings = MultiBindings::default();
+ for rs in rs_outputs {
+ multi_bindings.insert(rs.config, rs.rs);
+ }
+ serde_json::to_string(&multi_bindings).expect("Unable to encode JSON archive")
+}
+
+/// A single Rust file to be written to disk.
+pub struct RsInclude {
+ pub code: String,
+ pub filename: String,
+}
+
+/// Gets the Rust code corresponding to a single [`RsOutput`]. You can write this
+/// to a file which can simply be `include!`ed by `autocxx_macro` when you give
+/// it the `AUTOCXX_RS_FILE` environment variable.
+pub fn generate_rs_single(rs_output: RsOutput) -> RsInclude {
+ RsInclude {
+ code: rs_output.rs.to_string(),
+ filename: rs_output.config.get_rs_filename(),
+ }
+}
diff --git a/engine/src/parse_callbacks.rs b/engine/src/parse_callbacks.rs
new file mode 100644
index 0000000..61098c0
--- /dev/null
+++ b/engine/src/parse_callbacks.rs
@@ -0,0 +1,23 @@
+// 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::panic::UnwindSafe;
+
+use crate::RebuildDependencyRecorder;
+use autocxx_bindgen::callbacks::ParseCallbacks;
+
+#[derive(Debug)]
+pub(crate) struct AutocxxParseCallbacks(pub(crate) Box<dyn RebuildDependencyRecorder>);
+
+impl UnwindSafe for AutocxxParseCallbacks {}
+
+impl ParseCallbacks for AutocxxParseCallbacks {
+ fn include_file(&self, filename: &str) {
+ self.0.record_header_file_dependency(filename);
+ }
+}
diff --git a/engine/src/parse_file.rs b/engine/src/parse_file.rs
new file mode 100644
index 0000000..3571d19
--- /dev/null
+++ b/engine/src/parse_file.rs
@@ -0,0 +1,414 @@
+// 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::ast_discoverer::{Discoveries, DiscoveryErr};
+use crate::output_generators::RsOutput;
+use crate::{
+ cxxbridge::CxxBridge, Error as EngineError, GeneratedCpp, IncludeCppEngine,
+ RebuildDependencyRecorder,
+};
+use crate::{CppCodegenOptions, LocatedSynError};
+use autocxx_parser::directive_names::SUBCLASS;
+use autocxx_parser::{AllowlistEntry, RustPath, Subclass, SubclassAttrs};
+use indexmap::set::IndexSet as HashSet;
+use miette::Diagnostic;
+use quote::ToTokens;
+use std::{io::Read, path::PathBuf};
+use std::{panic::UnwindSafe, path::Path, rc::Rc};
+use syn::{token::Brace, Item, ItemMod};
+use thiserror::Error;
+
+/// Errors which may occur when parsing a Rust source file to discover
+/// and interpret include_cxx macros.
+#[derive(Error, Diagnostic, Debug)]
+pub enum ParseError {
+ #[error("unable to open the source file: {0}")]
+ FileOpen(std::io::Error),
+ #[error("the .rs file couldn't be read: {0}")]
+ FileRead(std::io::Error),
+ #[error("syntax error interpreting Rust code: {0}")]
+ #[diagnostic(transparent)]
+ Syntax(LocatedSynError),
+ #[error("generate!/generate_ns! was used at the same time as generate_all!")]
+ ConflictingAllowlist,
+ #[error("the subclass attribute couldn't be parsed: {0}")]
+ #[diagnostic(transparent)]
+ SubclassSyntax(LocatedSynError),
+ /// The include CPP macro could not be expanded into
+ /// Rust bindings to C++, because of some problem during the conversion
+ /// process. This could be anything from a C++ parsing error to some
+ /// C++ feature that autocxx can't yet handle and isn't able to skip
+ /// over. It could also cover errors in your syntax of the `include_cpp`
+ /// macro or the directives inside.
+ #[error("the include_cpp! macro couldn't be expanded into Rust bindings to C++: {0}")]
+ #[diagnostic(transparent)]
+ AutocxxCodegenError(EngineError),
+ /// There are two or more `include_cpp` macros with the same
+ /// mod name.
+ #[error("there are two or more include_cpp! mods with the same mod name")]
+ ConflictingModNames,
+ #[error("dynamic discovery was enabled but no mod was found")]
+ ZeroModsForDynamicDiscovery,
+ #[error("dynamic discovery was enabled but multiple mods were found")]
+ MultipleModsForDynamicDiscovery,
+ #[error("a problem occurred while discovering C++ APIs used within the Rust: {0}")]
+ Discovery(DiscoveryErr),
+}
+
+/// Parse a Rust file, and spot any include_cpp macros within it.
+pub fn parse_file<P1: AsRef<Path>>(
+ rs_file: P1,
+ auto_allowlist: bool,
+) -> Result<ParsedFile, ParseError> {
+ let mut source_code = String::new();
+ let mut file = std::fs::File::open(rs_file).map_err(ParseError::FileOpen)?;
+ file.read_to_string(&mut source_code)
+ .map_err(ParseError::FileRead)?;
+ proc_macro2::fallback::force();
+ let source = syn::parse_file(&source_code)
+ .map_err(|e| ParseError::Syntax(LocatedSynError::new(e, &source_code)))?;
+ parse_file_contents(source, auto_allowlist, &source_code)
+}
+
+fn parse_file_contents(
+ source: syn::File,
+ auto_allowlist: bool,
+ file_contents: &str,
+) -> Result<ParsedFile, ParseError> {
+ #[derive(Default)]
+ struct State {
+ auto_allowlist: bool,
+ results: Vec<Segment>,
+ extra_superclasses: Vec<Subclass>,
+ discoveries: Discoveries,
+ }
+ impl State {
+ fn parse_item(
+ &mut self,
+ item: Item,
+ mod_path: Option<RustPath>,
+ file_contents: &str,
+ ) -> Result<(), ParseError> {
+ let result = match item {
+ Item::Macro(mac)
+ if mac
+ .mac
+ .path
+ .segments
+ .last()
+ .map(|s| s.ident == "include_cpp")
+ .unwrap_or(false) =>
+ {
+ Segment::Autocxx(
+ crate::IncludeCppEngine::new_from_syn(mac.mac, file_contents)
+ .map_err(ParseError::AutocxxCodegenError)?,
+ )
+ }
+ Item::Mod(itm)
+ if itm
+ .attrs
+ .iter()
+ .any(|attr| attr.path.to_token_stream().to_string() == "cxx :: bridge") =>
+ {
+ Segment::Cxx(CxxBridge::from(itm))
+ }
+ Item::Mod(itm) => {
+ if let Some((brace, items)) = itm.content {
+ let mut mod_state = State {
+ auto_allowlist: self.auto_allowlist,
+ ..Default::default()
+ };
+ let mod_path = match &mod_path {
+ None => RustPath::new_from_ident(itm.ident.clone()),
+ Some(mod_path) => mod_path.append(itm.ident.clone()),
+ };
+ for item in items {
+ mod_state.parse_item(item, Some(mod_path.clone()), file_contents)?
+ }
+ self.extra_superclasses.extend(mod_state.extra_superclasses);
+ self.discoveries.extend(mod_state.discoveries);
+ Segment::Mod(
+ mod_state.results,
+ (
+ brace,
+ ItemMod {
+ content: None,
+ ..itm
+ },
+ ),
+ )
+ } else {
+ Segment::Other(Item::Mod(itm))
+ }
+ }
+ Item::Struct(ref its) if self.auto_allowlist => {
+ let attrs = &its.attrs;
+ let is_superclass_attr = attrs.iter().find(|attr| {
+ attr.path
+ .segments
+ .last()
+ .map(|seg| seg.ident == "is_subclass" || seg.ident == SUBCLASS)
+ .unwrap_or(false)
+ });
+ if let Some(is_superclass_attr) = is_superclass_attr {
+ if !is_superclass_attr.tokens.is_empty() {
+ let subclass = its.ident.clone();
+ let args: SubclassAttrs =
+ is_superclass_attr.parse_args().map_err(|e| {
+ ParseError::SubclassSyntax(LocatedSynError::new(
+ e,
+ file_contents,
+ ))
+ })?;
+ if let Some(superclass) = args.superclass {
+ self.extra_superclasses.push(Subclass {
+ superclass,
+ subclass,
+ })
+ }
+ }
+ }
+ self.discoveries
+ .search_item(&item, mod_path)
+ .map_err(ParseError::Discovery)?;
+ Segment::Other(item)
+ }
+ _ => {
+ self.discoveries
+ .search_item(&item, mod_path)
+ .map_err(ParseError::Discovery)?;
+ Segment::Other(item)
+ }
+ };
+ self.results.push(result);
+ Ok(())
+ }
+ }
+ let mut state = State {
+ auto_allowlist,
+ ..Default::default()
+ };
+ for item in source.items {
+ state.parse_item(item, None, file_contents)?
+ }
+ let State {
+ auto_allowlist,
+ mut results,
+ mut extra_superclasses,
+ mut discoveries,
+ } = state;
+
+ let must_handle_discovered_things = discoveries.found_rust()
+ || !extra_superclasses.is_empty()
+ || (auto_allowlist && discoveries.found_allowlist());
+
+ // We do not want to enter this 'if' block unless the above conditions are true,
+ // since we may emit errors.
+ if must_handle_discovered_things {
+ let mut autocxx_seg_iterator = results.iter_mut().filter_map(|seg| match seg {
+ Segment::Autocxx(engine) => Some(engine),
+ _ => None,
+ });
+ let our_seg = autocxx_seg_iterator.next();
+ match our_seg {
+ None => return Err(ParseError::ZeroModsForDynamicDiscovery),
+ Some(engine) => {
+ engine
+ .config_mut()
+ .subclasses
+ .append(&mut extra_superclasses);
+ if auto_allowlist {
+ for cpp in discoveries.cpp_list {
+ engine
+ .config_mut()
+ .allowlist
+ .push(AllowlistEntry::Item(cpp))
+ .map_err(|_| ParseError::ConflictingAllowlist)?;
+ }
+ }
+ engine
+ .config_mut()
+ .extern_rust_funs
+ .append(&mut discoveries.extern_rust_funs);
+ engine
+ .config_mut()
+ .rust_types
+ .append(&mut discoveries.extern_rust_types);
+ }
+ }
+ if autocxx_seg_iterator.next().is_some() {
+ return Err(ParseError::MultipleModsForDynamicDiscovery);
+ }
+ }
+ let autocxx_seg_iterator = results.iter_mut().filter_map(|seg| match seg {
+ Segment::Autocxx(engine) => Some(engine),
+ _ => None,
+ });
+ for seg in autocxx_seg_iterator {
+ seg.config.confirm_complete();
+ }
+ Ok(ParsedFile(results))
+}
+
+/// A Rust file parsed by autocxx. May contain zero or more autocxx 'engines',
+/// i.e. the `IncludeCpp` class, corresponding to zero or more include_cpp
+/// macros within this file. Also contains `syn::Item` structures for all
+/// the rest of the Rust code, such that it can be reconstituted if necessary.
+pub struct ParsedFile(Vec<Segment>);
+
+#[allow(clippy::large_enum_variant)]
+enum Segment {
+ Autocxx(IncludeCppEngine),
+ Cxx(CxxBridge),
+ Mod(Vec<Segment>, (Brace, ItemMod)),
+ Other(Item),
+}
+
+pub trait CppBuildable {
+ fn generate_h_and_cxx(
+ &self,
+ cpp_codegen_options: &CppCodegenOptions,
+ ) -> Result<GeneratedCpp, cxx_gen::Error>;
+}
+
+impl ParsedFile {
+ /// Get all the autocxx `include_cpp` macros found in this file.
+ pub fn get_autocxxes(&self) -> impl Iterator<Item = &IncludeCppEngine> {
+ fn do_get_autocxxes(segments: &[Segment]) -> impl Iterator<Item = &IncludeCppEngine> {
+ segments
+ .iter()
+ .flat_map(|s| -> Box<dyn Iterator<Item = &IncludeCppEngine>> {
+ match s {
+ Segment::Autocxx(includecpp) => Box::new(std::iter::once(includecpp)),
+ Segment::Mod(segments, _) => Box::new(do_get_autocxxes(segments)),
+ _ => Box::new(std::iter::empty()),
+ }
+ })
+ }
+
+ do_get_autocxxes(&self.0)
+ }
+
+ /// Get all the areas of Rust code which need to be built for these bindings.
+ /// A shortcut for `get_autocxxes()` then calling `get_rs_output` on each.
+ pub fn get_rs_outputs(&self) -> impl Iterator<Item = RsOutput> {
+ self.get_autocxxes().map(|autocxx| autocxx.get_rs_output())
+ }
+
+ /// Get all items which can result in C++ code
+ pub fn get_cpp_buildables(&self) -> impl Iterator<Item = &dyn CppBuildable> {
+ fn do_get_cpp_buildables(segments: &[Segment]) -> impl Iterator<Item = &dyn CppBuildable> {
+ segments
+ .iter()
+ .flat_map(|s| -> Box<dyn Iterator<Item = &dyn CppBuildable>> {
+ match s {
+ Segment::Autocxx(includecpp) => {
+ Box::new(std::iter::once(includecpp as &dyn CppBuildable))
+ }
+ Segment::Cxx(cxxbridge) => {
+ Box::new(std::iter::once(cxxbridge as &dyn CppBuildable))
+ }
+ Segment::Mod(segments, _) => Box::new(do_get_cpp_buildables(segments)),
+ _ => Box::new(std::iter::empty()),
+ }
+ })
+ }
+
+ do_get_cpp_buildables(&self.0)
+ }
+
+ fn get_autocxxes_mut(&mut self) -> impl Iterator<Item = &mut IncludeCppEngine> {
+ fn do_get_autocxxes_mut(
+ segments: &mut [Segment],
+ ) -> impl Iterator<Item = &mut IncludeCppEngine> {
+ segments
+ .iter_mut()
+ .flat_map(|s| -> Box<dyn Iterator<Item = &mut IncludeCppEngine>> {
+ match s {
+ Segment::Autocxx(includecpp) => Box::new(std::iter::once(includecpp)),
+ Segment::Mod(segments, _) => Box::new(do_get_autocxxes_mut(segments)),
+ _ => Box::new(std::iter::empty()),
+ }
+ })
+ }
+
+ do_get_autocxxes_mut(&mut self.0)
+ }
+
+ /// Determines the include dirs that were set for each include_cpp, so they can be
+ /// used as input to a `cc::Build`.
+ #[cfg(any(test, feature = "build"))]
+ pub(crate) fn include_dirs(&self) -> impl Iterator<Item = &PathBuf> {
+ fn do_get_include_dirs(segments: &[Segment]) -> impl Iterator<Item = &PathBuf> {
+ segments
+ .iter()
+ .flat_map(|s| -> Box<dyn Iterator<Item = &PathBuf>> {
+ match s {
+ Segment::Autocxx(includecpp) => Box::new(includecpp.include_dirs()),
+ Segment::Mod(segments, _) => Box::new(do_get_include_dirs(segments)),
+ _ => Box::new(std::iter::empty()),
+ }
+ })
+ }
+
+ do_get_include_dirs(&self.0)
+ }
+
+ pub fn resolve_all(
+ &mut self,
+ autocxx_inc: Vec<PathBuf>,
+ extra_clang_args: &[&str],
+ dep_recorder: Option<Box<dyn RebuildDependencyRecorder>>,
+ cpp_codegen_options: &CppCodegenOptions,
+ ) -> Result<(), ParseError> {
+ let mut mods_found = HashSet::new();
+ let inner_dep_recorder: Option<Rc<dyn RebuildDependencyRecorder>> =
+ dep_recorder.map(Rc::from);
+ for include_cpp in self.get_autocxxes_mut() {
+ #[allow(clippy::manual_map)] // because of dyn shenanigans
+ let dep_recorder: Option<Box<dyn RebuildDependencyRecorder>> = match &inner_dep_recorder
+ {
+ None => None,
+ Some(inner_dep_recorder) => Some(Box::new(CompositeDepRecorder::new(
+ inner_dep_recorder.clone(),
+ ))),
+ };
+ if !mods_found.insert(include_cpp.get_mod_name()) {
+ return Err(ParseError::ConflictingModNames);
+ }
+ include_cpp
+ .generate(
+ autocxx_inc.clone(),
+ extra_clang_args,
+ dep_recorder,
+ cpp_codegen_options,
+ )
+ .map_err(ParseError::AutocxxCodegenError)?
+ }
+ Ok(())
+ }
+}
+
+/// Shenanigans required to share the same RebuildDependencyRecorder
+/// with all of the include_cpp instances in this one file.
+#[derive(Debug, Clone)]
+struct CompositeDepRecorder(Rc<dyn RebuildDependencyRecorder>);
+
+impl CompositeDepRecorder {
+ fn new(inner: Rc<dyn RebuildDependencyRecorder>) -> Self {
+ CompositeDepRecorder(inner)
+ }
+}
+
+impl UnwindSafe for CompositeDepRecorder {}
+
+impl RebuildDependencyRecorder for CompositeDepRecorder {
+ fn record_header_file_dependency(&self, filename: &str) {
+ self.0.record_header_file_dependency(filename);
+ }
+}
diff --git a/engine/src/rust_pretty_printer.rs b/engine/src/rust_pretty_printer.rs
new file mode 100644
index 0000000..9c23cbc
--- /dev/null
+++ b/engine/src/rust_pretty_printer.rs
@@ -0,0 +1,40 @@
+// 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 proc_macro2::TokenStream;
+use std::io::Write;
+use std::process::{Command, Stdio};
+
+enum Error {
+ Run(std::io::Error),
+ Write(std::io::Error),
+ Utf8(std::string::FromUtf8Error),
+ Wait(std::io::Error),
+}
+
+pub(crate) fn pretty_print(ts: &TokenStream) -> String {
+ reformat_or_else(ts.to_string())
+}
+
+fn reformat_or_else(text: impl std::fmt::Display) -> String {
+ match reformat(&text) {
+ Ok(s) => s,
+ Err(_) => text.to_string(),
+ }
+}
+
+fn reformat(text: impl std::fmt::Display) -> Result<String, Error> {
+ let mut rustfmt = Command::new("rustfmt")
+ .stdin(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn()
+ .map_err(Error::Run)?;
+ write!(rustfmt.stdin.take().unwrap(), "{}", text).map_err(Error::Write)?;
+ let output = rustfmt.wait_with_output().map_err(Error::Wait)?;
+ String::from_utf8(output.stdout).map_err(Error::Utf8)
+}
diff --git a/engine/src/types.rs b/engine/src/types.rs
new file mode 100644
index 0000000..0d11895
--- /dev/null
+++ b/engine/src/types.rs
@@ -0,0 +1,259 @@
+// 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 itertools::Itertools;
+use proc_macro2::Span;
+use quote::ToTokens;
+use std::iter::Peekable;
+use std::{fmt::Display, sync::Arc};
+use syn::{parse_quote, Ident, PathSegment, TypePath};
+
+use crate::{conversion::ConvertError, known_types::known_types};
+
+pub(crate) fn make_ident<S: AsRef<str>>(id: S) -> Ident {
+ Ident::new(id.as_ref(), Span::call_site())
+}
+
+/// Newtype wrapper for a C++ namespace.
+#[derive(Debug, PartialEq, PartialOrd, Eq, Hash, Clone)]
+#[allow(clippy::rc_buffer)]
+pub struct Namespace(Arc<Vec<String>>);
+
+impl Namespace {
+ pub(crate) fn new() -> Self {
+ Self(Arc::new(Vec::new()))
+ }
+
+ #[must_use]
+ pub(crate) fn push(&self, segment: String) -> Self {
+ let mut bigger = (*self.0).clone();
+ bigger.push(segment);
+ Namespace(Arc::new(bigger))
+ }
+
+ pub(crate) fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+
+ pub(crate) fn iter(&self) -> impl Iterator<Item = &String> {
+ self.0.iter()
+ }
+
+ #[cfg(test)]
+ pub(crate) fn from_user_input(input: &str) -> Self {
+ Self(Arc::new(input.split("::").map(|x| x.to_string()).collect()))
+ }
+
+ pub(crate) fn depth(&self) -> usize {
+ self.0.len()
+ }
+}
+
+impl Display for Namespace {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(&self.0.join("::"))
+ }
+}
+
+impl<'a> IntoIterator for &'a Namespace {
+ type Item = &'a String;
+
+ type IntoIter = std::slice::Iter<'a, String>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.0.iter()
+ }
+}
+
+/// Any time we store a qualified name, we should use this. Stores the type
+/// and its namespace. Namespaces should be stored without any
+/// 'bindgen::root' prefix; that means a type not in any C++
+/// namespace should have an empty namespace segment list.
+/// Some types have names that change as they flow through the
+/// autocxx pipeline. e.g. you start with std::string
+/// and end up with CxxString. This TypeName type can store
+/// either. It doesn't directly have functionality to convert
+/// from one to the other; `replace_type_path_without_arguments`
+/// does that.
+#[derive(Debug, PartialEq, PartialOrd, Eq, Hash, Clone)]
+pub struct QualifiedName(Namespace, String);
+
+impl QualifiedName {
+ /// From a TypePath which starts with 'root'
+ pub(crate) fn from_type_path(typ: &TypePath) -> Self {
+ let mut seg_iter = typ.path.segments.iter().peekable();
+ let first_seg = seg_iter.next().unwrap().ident.clone();
+ if first_seg == "root" {
+ // This is a C++ type prefixed with a namespace,
+ // e.g. std::string or something the user has defined.
+ Self::from_segments(seg_iter) // all but 'root'
+ } else {
+ // This is actually a Rust type e.g.
+ // std::os::raw::c_ulong. Start iterating from the beginning again.
+ Self::from_segments(typ.path.segments.iter().peekable())
+ }
+ }
+
+ fn from_segments<'a, T: Iterator<Item = &'a PathSegment>>(mut seg_iter: Peekable<T>) -> Self {
+ let mut ns = Namespace::new();
+ while let Some(seg) = seg_iter.next() {
+ if seg_iter.peek().is_some() {
+ ns = ns.push(seg.ident.to_string());
+ } else {
+ return Self(ns, seg.ident.to_string());
+ }
+ }
+ unreachable!()
+ }
+
+ /// Create from a type encountered in the code.
+ pub(crate) fn new(ns: &Namespace, id: Ident) -> Self {
+ Self(ns.clone(), id.to_string())
+ }
+
+ /// Create from user input, e.g. a name in an AllowPOD directive.
+ pub(crate) fn new_from_cpp_name(id: &str) -> Self {
+ let mut seg_iter = id.split("::").peekable();
+ let mut ns = Namespace::new();
+ while let Some(seg) = seg_iter.next() {
+ if seg_iter.peek().is_some() {
+ if !seg.to_string().is_empty() {
+ ns = ns.push(seg.to_string());
+ }
+ } else {
+ return Self(ns, seg.to_string());
+ }
+ }
+ unreachable!()
+ }
+
+ /// Return the actual type name, without any namespace
+ /// qualification. Avoid unless you have a good reason.
+ pub(crate) fn get_final_item(&self) -> &str {
+ &self.1
+ }
+
+ /// cxx doesn't accept names containing double underscores,
+ /// but these are OK elsewhere in our output mod.
+ pub(crate) fn validate_ok_for_cxx(&self) -> Result<(), ConvertError> {
+ validate_ident_ok_for_cxx(self.get_final_item())
+ }
+
+ /// Return the actual type name as an [Ident], without any namespace
+ /// qualification. Avoid unless you have a good reason.
+ pub(crate) fn get_final_ident(&self) -> Ident {
+ make_ident(self.get_final_item())
+ }
+
+ pub(crate) fn get_namespace(&self) -> &Namespace {
+ &self.0
+ }
+
+ pub(crate) fn get_bindgen_path_idents(&self) -> Vec<Ident> {
+ ["bindgen", "root"]
+ .iter()
+ .map(make_ident)
+ .chain(self.ns_segment_iter().map(make_ident))
+ .chain(std::iter::once(self.get_final_ident()))
+ .collect()
+ }
+
+ /// Output the fully-qualified C++ name of this type.
+ pub(crate) fn to_cpp_name(&self) -> String {
+ let special_cpp_name = known_types().special_cpp_name(self);
+ match special_cpp_name {
+ Some(name) => name,
+ None => self.0.iter().chain(std::iter::once(&self.1)).join("::"),
+ }
+ }
+
+ pub(crate) fn get_final_cpp_item(&self) -> String {
+ let special_cpp_name = known_types().special_cpp_name(self);
+ match special_cpp_name {
+ Some(name) => name,
+ None => self.1.to_string(),
+ }
+ }
+
+ pub(crate) fn to_type_path(&self) -> TypePath {
+ if let Some(known_type_path) = known_types().known_type_type_path(self) {
+ known_type_path
+ } else {
+ let root = "root".to_string();
+ let segs = std::iter::once(&root)
+ .chain(self.ns_segment_iter())
+ .chain(std::iter::once(&self.1))
+ .map(make_ident);
+ parse_quote! {
+ #(#segs)::*
+ }
+ }
+ }
+
+ /// Iterator over segments in the namespace of this name.
+ pub(crate) fn ns_segment_iter(&self) -> impl Iterator<Item = &String> {
+ self.0.iter()
+ }
+
+ /// Iterate over all segments of this name.
+ pub(crate) fn segment_iter(&self) -> impl Iterator<Item = String> + '_ {
+ self.ns_segment_iter()
+ .cloned()
+ .chain(std::iter::once(self.get_final_item().to_string()))
+ }
+}
+
+impl Display for QualifiedName {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ for seg in &self.0 {
+ f.write_str(seg)?;
+ f.write_str("::")?;
+ }
+ f.write_str(&self.1)
+ }
+}
+
+/// cxx doesn't allow identifiers containing __. These are OK elsewhere
+/// in our output mod. It would be nice in future to think of a way we
+/// can enforce this using the Rust type system, e.g. a newtype
+/// wrapper for a CxxCompatibleIdent which is used in any context
+/// where code will be output as part of the `#[cxx::bridge]` mod.
+pub fn validate_ident_ok_for_cxx(id: &str) -> Result<(), ConvertError> {
+ validate_ident_ok_for_rust(id)?;
+ if id.contains("__") {
+ Err(ConvertError::TooManyUnderscores)
+ } else if id.starts_with("_bindgen_ty_") {
+ Err(ConvertError::BindgenTy)
+ } else {
+ Ok(())
+ }
+}
+
+pub fn validate_ident_ok_for_rust(label: &str) -> Result<(), ConvertError> {
+ let id = make_ident(label);
+ syn::parse2::<syn::Ident>(id.into_token_stream())
+ .map_err(|_| ConvertError::ReservedName(label.to_string()))
+ .map(|_| ())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::QualifiedName;
+
+ #[test]
+ fn test_ints() {
+ assert_eq!(
+ QualifiedName::new_from_cpp_name("i8").to_cpp_name(),
+ "int8_t"
+ );
+ assert_eq!(
+ QualifiedName::new_from_cpp_name("u64").to_cpp_name(),
+ "uint64_t"
+ );
+ }
+}