Squashed 'third_party/autocxx/' content from commit 629e8fa53
git-subtree-dir: third_party/autocxx
git-subtree-split: 629e8fa531a633164c0b52e2a3cab536d4cd0849
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I62a03b0049f49adf029e0204639cdb5468dde1a1
diff --git a/engine/src/conversion/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()
+}