diff --git a/Cargo.lock b/Cargo.lock index 21a91f3a7..c95ed934f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,9 +155,9 @@ dependencies = [ [[package]] name = "autocxx-bindgen" -version = "0.72.0" +version = "0.73.0" source = "registry+/~https://github.com/rust-lang/crates.io-index" -checksum = "537ef6dffea4311d04a313f645bdd04d2c88b356a6eb7afd4258e5aee70dea8c" +checksum = "ecaaf84d9cf1a772409c0abdac7d2477a31fd890dbdf606d8f684dc60129aa94" dependencies = [ "bitflags 2.8.0", "cexpr", @@ -211,6 +211,7 @@ dependencies = [ "proc-macro2", "quote", "regex", + "regex_static", "rustversion", "serde_json", "syn 2.0.98", @@ -1485,6 +1486,40 @@ version = "0.8.5" source = "registry+/~https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "regex_static" +version = "0.1.1" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "6126d61c5e4b41929098f73b42fc1d257116cc95d19739248c51591f77cc0021" +dependencies = [ + "once_cell", + "regex", + "regex_static_macro", +] + +[[package]] +name = "regex_static_impl" +version = "0.1.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "9c3755019886a70e772e6360b0b58501d75cf7dc17a53e08aa97e59ecb2c2bc5" +dependencies = [ + "proc-macro2", + "quote", + "regex-syntax 0.6.29", + "syn 1.0.109", +] + +[[package]] +name = "regex_static_macro" +version = "0.1.0" +source = "registry+/~https://github.com/rust-lang/crates.io-index" +checksum = "79b15495fd034158635bc8b762a132dfc83864d6992aeda1ffabf01b03b611a1" +dependencies = [ + "proc-macro2", + "regex_static_impl", + "syn 1.0.109", +] + [[package]] name = "rust_info" version = "0.3.3" diff --git a/book/src/tutorial.md b/book/src/tutorial.md index 1396b1745..6865f529b 100644 --- a/book/src/tutorial.md +++ b/book/src/tutorial.md @@ -1,6 +1,6 @@ # Tutorial -If you're here, you want to call some C++ from Rust, right? +If you're here, you want to call some C++ from Rust, right? (If you want to do things the other way round, read the [Rust calls](rust_calls.md) page.) Let's assume you're calling into some _existing_ C++ code. @@ -17,8 +17,6 @@ The rest of this 'getting started' section assumes Cargo - if you're using somet First, add `autocxx` *and `cxx`* to your `dependencies` and `autocxx-build` to your `build-dependencies` in your `Cargo.toml`. **You must specify both.** - - ```toml [dependencies] autocxx = "0.28.0" diff --git a/demo/src/input.h b/demo/src/input.h index b6cdd874e..30bd1b9d4 100644 --- a/demo/src/input.h +++ b/demo/src/input.h @@ -13,15 +13,20 @@ #include #include +/// A C++ goat. class Goat { public: + /// Goat constructor. Goat() : horns(0) {} + /// Add a horn to the goat. void add_a_horn(); + /// Describe the goat. std::string describe() const; private: uint32_t horns; }; +/// Do some maths! inline uint32_t DoMath(uint32_t a) { return a * 3; } diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 3a5d865e5..a93473e56 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -30,8 +30,8 @@ log = "0.4" proc-macro2 = "1.0.11" quote = "1.0" indoc = "1.0" -autocxx-bindgen = { version = "=0.72.0", default-features = false, features = ["logging", "which-rustfmt"] } -#autocxx-bindgen = { git = "/~https://github.com/adetaylor/rust-bindgen", branch = "switch-from-attributes-to-callbacks", default-features = false, features = ["logging", "which-rustfmt"] } +autocxx-bindgen = { version = "=0.73.0", default-features = false, features = ["logging", "which-rustfmt"] } +#autocxx-bindgen = { git = "/~https://github.com/adetaylor/rust-bindgen", branch = "all-modules-raw-line", default-features = false, features = ["logging", "which-rustfmt"] } itertools = "0.10.3" cc = { version = "1.0", optional = true } # Note: Keep the patch-level version of cxx-gen and cxx in sync. @@ -47,6 +47,7 @@ serde_json = { version = "1.0", optional = true } miette = "5" thiserror = "1" regex = "1.5" +regex_static = "0.1" indexmap = "1.8" prettyplease = { version = "0.2.6", features = ["verbatim"] } rustversion = "1.0" diff --git a/engine/src/conversion/analysis/constructor_deps.rs b/engine/src/conversion/analysis/constructor_deps.rs index 63b6dbe44..45b432597 100644 --- a/engine/src/conversion/analysis/constructor_deps.rs +++ b/engine/src/conversion/analysis/constructor_deps.rs @@ -52,7 +52,7 @@ fn decorate_struct( ) -> Result>>, 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 { + let constructor_and_allocator_deps = if is_abstract || pod.num_generics > 0 { Vec::new() } else { constructors_and_allocators_by_type diff --git a/engine/src/conversion/analysis/fun/implicit_constructors.rs b/engine/src/conversion/analysis/fun/implicit_constructors.rs index 19627a57a..c0226b942 100644 --- a/engine/src/conversion/analysis/fun/implicit_constructors.rs +++ b/engine/src/conversion/analysis/fun/implicit_constructors.rs @@ -207,7 +207,7 @@ pub(super) fn find_constructors_present( PodAnalysis { bases, field_info, - is_generic: false, + num_generics: 0usize, in_anonymous_namespace: false, .. }, diff --git a/engine/src/conversion/analysis/fun/mod.rs b/engine/src/conversion/analysis/fun/mod.rs index 1a4f7621b..6ad07ef2c 100644 --- a/engine/src/conversion/analysis/fun/mod.rs +++ b/engine/src/conversion/analysis/fun/mod.rs @@ -394,12 +394,9 @@ impl<'a> FnAnalyzer<'a> { apis.iter() .filter_map(|api| match api { Api::Struct { - analysis: - PodAnalysis { - is_generic: true, .. - }, + analysis: PodAnalysis { num_generics, .. }, .. - } => Some(api.name().clone()), + } if *num_generics > 0 => Some(api.name().clone()), _ => None, }) .collect() @@ -2224,11 +2221,11 @@ fn constructor_with_suffix<'a>(rust_name: &'a str, nested_type_ident: &str) -> O impl Api { pub(crate) fn name_for_allowlist(&self) -> QualifiedName { match &self { - Api::Function { analysis, .. } => match analysis.kind { + Api::Function { fun, 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)) + QualifiedName::new(self.name().get_namespace(), fun.ident.clone()) } }, Api::RustSubclassFn { subclass, .. } => subclass.0.name.clone(), diff --git a/engine/src/conversion/analysis/pod/byvalue_checker.rs b/engine/src/conversion/analysis/pod/byvalue_checker.rs index d0c828e73..6e0dd6246 100644 --- a/engine/src/conversion/analysis/pod/byvalue_checker.rs +++ b/engine/src/conversion/analysis/pod/byvalue_checker.rs @@ -101,7 +101,7 @@ impl ByValueChecker { } _ => None, }, - TypedefKind::Use(_, ref ty) => match **ty { + TypedefKind::Use(ref ty) => match **ty { crate::minisyn::Type(Type::Path(ref typ)) => { let target_tn = QualifiedName::from_type_path(typ); known_types().consider_substitution(&target_tn) diff --git a/engine/src/conversion/analysis/pod/mod.rs b/engine/src/conversion/analysis/pod/mod.rs index a836966d8..a99d08cdc 100644 --- a/engine/src/conversion/analysis/pod/mod.rs +++ b/engine/src/conversion/analysis/pod/mod.rs @@ -54,7 +54,7 @@ pub(crate) struct PodAnalysis { /// std::unique_ptr it would just be std::unique_ptr. pub(crate) field_definition_deps: HashSet, pub(crate) field_info: Vec, - pub(crate) is_generic: bool, + pub(crate) num_generics: usize, pub(crate) in_anonymous_namespace: bool, } @@ -188,7 +188,7 @@ fn analyze_struct( .filter(|base| config.is_on_allowlist(&base.to_cpp_name())) .cloned() .collect(); - let is_generic = !details.item.generics.params.is_empty(); + let num_generics = details.item.generics.params.len(); let in_anonymous_namespace = name .name .ns_segment_iter() @@ -203,7 +203,7 @@ fn analyze_struct( field_deps, field_definition_deps, field_info, - is_generic, + num_generics, in_anonymous_namespace, }, }))) diff --git a/engine/src/conversion/analysis/type_converter.rs b/engine/src/conversion/analysis/type_converter.rs index 2bdf87cc7..78e9e00af 100644 --- a/engine/src/conversion/analysis/type_converter.rs +++ b/engine/src/conversion/analysis/type_converter.rs @@ -192,6 +192,7 @@ impl<'a> TypeConverter<'a> { ) -> Result, ConvertErrorFromCpp> { // First we try to spot if these are the special marker paths that // bindgen uses to denote references or other things. + // TODO the next two lines can be removed if let Some(ty) = unwrap_has_unused_template_param(&typ) { self.convert_type(ty.clone(), ns, ctx) } else if let Some(ty) = unwrap_has_opaque(&typ) { @@ -277,7 +278,8 @@ impl<'a> TypeConverter<'a> { ctx: &TypeConversionContext, ) -> Result, ConvertErrorFromCpp> { // First, qualify any unqualified paths. - if typ.path.segments.iter().next().unwrap().ident != "root" { + let first_seg = &typ.path.segments.iter().next().unwrap().ident; + if first_seg != "root" && first_seg != "output" { 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 @@ -289,7 +291,7 @@ impl<'a> TypeConverter<'a> { return Err(ConvertErrorFromCpp::UnsupportedBuiltInType(ty)); } if !self.types_found.contains(&ty) { - typ.path.segments = std::iter::once("root") + typ.path.segments = std::iter::once("output") .chain(ns.iter()) .map(|s| { let i = make_ident(s); @@ -313,7 +315,7 @@ impl<'a> TypeConverter<'a> { // 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)? { + let (mut 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); @@ -350,7 +352,17 @@ impl<'a> TypeConverter<'a> { } substitute_type } - None => typ, + None => { + // Pass through as-is, but replacing `root` with `output` + // in the first path element. We don't just create a whole + // new `TypePath` because that would discard any generics. + if let Some(first_seg) = typ.path.segments.get_mut(0) { + if first_seg.ident == "root" { + first_seg.ident = make_ident("output").0; + } + } + typ + } }; let mut extra_apis = ApiVec::new(); @@ -726,7 +738,7 @@ 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, + TypedefKind::Use(ref ty) => ty, }) } } diff --git a/engine/src/conversion/api.rs b/engine/src/conversion/api.rs index 05d947f2a..2136ee8f9 100644 --- a/engine/src/conversion/api.rs +++ b/engine/src/conversion/api.rs @@ -6,7 +6,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use autocxx_bindgen::callbacks::{Explicitness, Layout, SpecialMemberKind, Virtualness}; +use autocxx_bindgen::callbacks::{Explicitness, SpecialMemberKind, Virtualness}; use syn::{ punctuated::Punctuated, @@ -16,8 +16,8 @@ use syn::{ use crate::types::{make_ident, Namespace, QualifiedName}; use crate::{ minisyn::{ - Attribute, FnArg, Ident, ItemConst, ItemEnum, ItemStruct, ItemType, ItemUse, Pat, - ReturnType, Type, Visibility, + Attribute, FnArg, Ident, ItemConst, ItemEnum, ItemStruct, ItemType, Pat, ReturnType, Type, + Visibility, }, parse_callbacks::CppOriginalName, }; @@ -51,7 +51,6 @@ pub(crate) enum TypeKind { #[derive(Debug)] pub(crate) struct StructDetails { pub(crate) item: ItemStruct, - pub(crate) layout: Option, pub(crate) has_rvalue_reference_fields: bool, } @@ -204,7 +203,7 @@ impl AnalysisPhase for NullPhase { #[derive(Clone, Debug)] pub(crate) enum TypedefKind { - Use(ItemUse, Box), + Use(Box), Type(ItemType), } diff --git a/engine/src/conversion/codegen_rs/fun_codegen.rs b/engine/src/conversion/codegen_rs/fun_codegen.rs index df7e73371..9e00da576 100644 --- a/engine/src/conversion/codegen_rs/fun_codegen.rs +++ b/engine/src/conversion/codegen_rs/fun_codegen.rs @@ -23,7 +23,8 @@ use super::{ function_wrapper_rs::RustParamConversion, maybe_unsafes_to_tokens, unqualify::{unqualify_params_minisyn, unqualify_ret_type}, - ImplBlockDetails, MaybeUnsafeStmt, RsCodegenResult, TraitImplBlockDetails, Use, + utils::generate_cxx_use_stmt, + ImplBlockDetails, MaybeUnsafeStmt, RsCodegenResult, TraitImplBlockDetails, }; use crate::{ conversion::{ @@ -34,7 +35,7 @@ use crate::{ api::UnsafetyNeeded, }, minisyn::{minisynize_vec, FnArg}, - types::{Namespace, QualifiedName}, + types::QualifiedName, }; use crate::{ conversion::{api::FuncToConvert, codegen_rs::lifetime::add_explicit_lifetime_if_necessary}, @@ -85,7 +86,7 @@ impl UnsafetyNeeded { } pub(super) fn gen_function( - ns: &Namespace, + name: &QualifiedName, fun: FuncToConvert, analysis: FnAnalysis, non_pod_types: &HashSet, @@ -108,7 +109,6 @@ pub(super) fn gen_function( 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, @@ -133,6 +133,8 @@ pub(super) fn gen_function( &ret_conversion, ); + let mut output_mod_items = Vec::new(); + if analysis.rust_wrapper_needed { match kind { FnKind::Method { @@ -159,23 +161,17 @@ pub(super) fn gen_function( } _ => { // Generate plain old function - bindgen_mod_items.push(fn_generator.generate_function_impl()); + output_mod_items.push(fn_generator.generate_function_impl()); } } + } else if matches!(kind, FnKind::Function) { + let alias = match analysis.rust_rename_strategy { + RustRenameStrategy::RenameInOutputMod(ref alias) => Some(&alias.0), + _ => None, + }; + output_mod_items.push(generate_cxx_use_stmt(name, alias)); } - 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).into())) - } - RustRenameStrategy::RenameInOutputMod(ref alias) => { - Some(Use::UsedFromCxxBridgeWithAlias(alias.clone().into())) - } - _ => Some(Use::UsedFromCxxBridge), - }, - }; if let Some(cpp_call_name) = cpp_call_name { if cpp_call_name.does_not_match_cxxbridge_name(&cxxbridge_name) && !wrapper_function_needed { @@ -197,10 +193,10 @@ pub(super) fn gen_function( 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 { + let namespace_attr = if name.get_namespace().is_empty() || wrapper_function_needed { Vec::new() } else { - let namespace_string = ns.to_string(); + let namespace_string = name.get_namespace().to_string(); Attribute::parse_outer .parse2(quote!( #[namespace = #namespace_string] @@ -217,10 +213,9 @@ pub(super) fn gen_function( )); RsCodegenResult { extern_c_mod_items: vec![extern_c_mod_item], - bindgen_mod_items, impl_entry, trait_impl_entry, - materializations: materialization.into_iter().collect(), + output_mod_items, ..Default::default() } } diff --git a/engine/src/conversion/codegen_rs/function_wrapper_rs.rs b/engine/src/conversion/codegen_rs/function_wrapper_rs.rs index 4afe3c722..f73ae0792 100644 --- a/engine/src/conversion/codegen_rs/function_wrapper_rs.rs +++ b/engine/src/conversion/codegen_rs/function_wrapper_rs.rs @@ -50,7 +50,7 @@ impl TypeConversionPolicy { let holder_type = sub.holder(); let id = sub.id(); let ty = parse_quote! { autocxx::subclass::CppSubclassRustPeerHolder< - super::super::super:: #id> + super:: #id> }; RustParamConversion::Param { ty, diff --git a/engine/src/conversion/codegen_rs/mod.rs b/engine/src/conversion/codegen_rs/mod.rs index f9437625b..b6f6d2961 100644 --- a/engine/src/conversion/codegen_rs/mod.rs +++ b/engine/src/conversion/codegen_rs/mod.rs @@ -13,8 +13,8 @@ mod lifetime; mod namespace_organizer; mod non_pod_struct; pub(crate) mod unqualify; +mod utils; -use autocxx_bindgen::callbacks::Layout; use indexmap::map::IndexMap as HashMap; use indexmap::set::IndexSet as HashSet; @@ -26,12 +26,10 @@ use syn::{ parse_quote, punctuated::Punctuated, token::Comma, Attribute, Expr, FnArg, ForeignItem, ForeignItemFn, Ident, ImplItem, Item, ItemForeignMod, ItemMod, TraitItem, Type, TypePath, }; +use utils::{find_output_mod_root, generate_cxx_use_stmt}; use crate::{ - conversion::codegen_rs::{ - non_pod_struct::{make_non_pod, new_non_pod_struct}, - unqualify::{unqualify_params, unqualify_ret_type}, - }, + conversion::codegen_rs::unqualify::{unqualify_params, unqualify_ret_type}, minisyn::minisynize_punctuated, types::{make_ident, Namespace, QualifiedName}, }; @@ -47,7 +45,7 @@ use super::{ fun::{FnPhase, PodAndDepAnalysis, ReceiverMutability}, pod::PodAnalysis, }, - api::{AnalysisPhase, Api, SubclassName, TypeKind, TypedefKind}, + api::{AnalysisPhase, Api, SubclassName, TypeKind}, convert_error::ErrorContextType, doc_attr::get_doc_attrs, }; @@ -70,26 +68,17 @@ struct TraitImplBlockDetails { 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), -} - fn get_string_items() -> Vec { [ Item::Trait(parse_quote! { + /// A trait to be implemented by any type that can be turned + /// into a C++ string. + /// This trait is generated once per autocxx FFI mod and each + /// implementation is incompatible and separate, because each + /// will use a function generated independently for each mod + /// in order to do the actual conversion to a C++ string. pub trait ToCppString { + /// Convert `self` into a C++ string in a [`cxx::UniquePtr`]. fn into_cpp(self) -> cxx::UniquePtr; } }), @@ -185,12 +174,9 @@ impl<'a> RsCodeGenerator<'a> { }) .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); + // and other items which are the final API exposed as 'ffi'. + let mut output_mod_items = + Self::generate_final_output_namespace(&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<_>) = @@ -240,16 +226,13 @@ impl<'a> RsCodeGenerator<'a> { // 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)); - } + self.bindgen_mod.vis = parse_quote! {}; + self.bindgen_mod.attrs.push(parse_quote! { + #[doc = "A private mod containing the bindings generated by `bindgen`. Do not use the contents directly - the useful parts will be re-exported into the main FFI mod."] + }); + all_items.push(Item::Mod(self.bindgen_mod)); all_items.push(Item::Mod(parse_quote! { + /// A private mod containing the bindings generated by [`cxx`]. Do not use the contents directly - the useful parts will be re-exported into the main FFI mod. #[cxx::bridge] mod cxxbridge { #(#bridge_items)* @@ -260,7 +243,12 @@ impl<'a> RsCodeGenerator<'a> { #[allow(unused_imports)] use bindgen::root; })); - all_items.append(&mut use_statements); + let ffi_mod_name = self.config.get_mod_name(); + all_items.push(Item::Use(parse_quote! { + #[allow(unused_imports)] + use super::#ffi_mod_name as output; + })); + all_items.append(&mut output_mod_items); all_items } @@ -309,89 +297,28 @@ impl<'a> RsCodeGenerator<'a> { .collect() } - /// Generate lots of 'use' statements to pull cxxbridge items into the output - /// mod hierarchy according to C++ namespaces. - fn generate_final_use_statements( + /// Generate the final output mod hierarchy which the user will actually + /// interact with. This is mostly lots of 'use' statements. + fn generate_final_output_namespace( input_items: &[(QualifiedName, RsCodegenResult)], ) -> Vec { let mut output_items = Vec::new(); let ns_entries = NamespaceEntries::new(input_items); - Self::append_child_use_namespace(&ns_entries, &mut output_items); + Self::append_child_output_namespace(&ns_entries, &mut output_items); output_items } - fn append_child_use_namespace( + fn append_child_output_namespace( ns_entries: &NamespaceEntries<(QualifiedName, RsCodegenResult)>, output_items: &mut Vec, ) { - 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().into()); - 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)); + for (_name, codegen) in ns_entries.entries() { + output_items.extend(codegen.output_mod_items.iter().cloned()); } - } - fn append_uses_for_ns(&mut self, items: &mut Vec, ns: &Namespace) { - let mut imports_from_super = vec!["cxxbridge"]; - if !self.config.exclude_utilities() { - imports_from_super.push("ToCppString"); - } - let imports_from_super = imports_from_super.into_iter().map(make_ident); - 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)::* - ::{ - #(#imports_from_super),* - }; - })); - 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, - 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()) @@ -422,21 +349,23 @@ impl<'a> RsCodeGenerator<'a> { } })) } - for (child_name, child_ns_entries) in ns_entries.children() { - let new_ns = ns.push(child_name.to_string()); - 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)); + 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 { + #[allow(unused_imports)] + use super::{cxxbridge, output, bindgen}; + } + ); + Self::append_child_output_namespace( + child_ns_entries, + &mut new_mod.content.as_mut().unwrap().1, + ); + output_items.push(Item::Mod(new_mod)); } } @@ -444,18 +373,6 @@ impl<'a> RsCodeGenerator<'a> { parse_quote! { #id } } - fn generate_final_bindgen_mods( - &mut self, - input_items: &[(QualifiedName, RsCodegenResult)], - ) -> Vec { - 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, @@ -470,29 +387,23 @@ impl<'a> RsCodeGenerator<'a> { let make_string_name = make_ident(self.config.get_makestring_name()); RsCodegenResult { extern_c_mod_items: vec![ForeignItem::Fn(parse_quote!( + /// Make a C++ [`cxx::UniquePtr`] to a [`cxx::CxxString`] + /// from a Rust `&str`. fn #make_string_name(str_: &str) -> UniquePtr; ))], global_items: get_string_items(), - materializations: vec![Use::UsedFromCxxBridgeWithAlias( - make_ident("make_string").into(), + output_mod_items: vec![generate_cxx_use_stmt( + &name, + Some(&make_ident("make_string").0), )], ..Default::default() } } Api::Function { fun, analysis, .. } => { - gen_function(name.get_namespace(), *fun, analysis, non_pod_types) + gen_function(&name, *fun, analysis, non_pod_types) } - Api::Const { const_item, .. } => RsCodegenResult { - bindgen_mod_items: vec![Item::Const(const_item.into())], - 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.into()), - TypedefKind::Use(use_item, _) => Item::Use(use_item.into()), - }], - materializations: vec![Use::UsedFromBindgen], + Api::Const { .. } | Api::Typedef { .. } => RsCodegenResult { + output_mod_items: vec![Self::generate_bindgen_use_stmt(&name)], ..Default::default() }, Api::Struct { @@ -501,7 +412,7 @@ impl<'a> RsCodeGenerator<'a> { PodAndDepAnalysis { pod: PodAnalysis { - is_generic, kind, .. + num_generics, kind, .. }, constructors, .. @@ -509,7 +420,6 @@ impl<'a> RsCodeGenerator<'a> { .. } => { let doc_attrs = get_doc_attrs(&details.item.attrs); - let layout = details.layout; self.generate_type( &name, id, @@ -518,8 +428,7 @@ impl<'a> RsCodeGenerator<'a> { constructors.destructor, || Some((Item::Struct(details.item.into()), doc_attrs)), associated_methods, - layout, - is_generic, + num_generics, ) } Api::Enum { item, .. } => { @@ -532,8 +441,7 @@ impl<'a> RsCodeGenerator<'a> { true, || Some((Item::Enum(item.into()), doc_attrs)), associated_methods, - None, - false, + 0, ) } Api::ConcreteType { .. } => self.generate_type( @@ -544,8 +452,7 @@ impl<'a> RsCodeGenerator<'a> { true, // assume for now that these types can be put in a smart pointer || None, associated_methods, - None, - false, + 0, ), Api::ForwardDeclaration { .. } | Api::OpaqueTypedef { .. } => self.generate_type( &name, @@ -555,8 +462,7 @@ impl<'a> RsCodeGenerator<'a> { false, // these types can't be put in a smart pointer || None, associated_methods, - None, - false, + 0, ), Api::CType { .. } => RsCodegenResult { extern_c_mod_items: vec![ForeignItem::Verbatim(quote! { @@ -573,10 +479,6 @@ impl<'a> RsCodeGenerator<'a> { extern_rust_mod_items: vec![parse_quote! { type #id; }], - bindgen_mod_items: vec![parse_quote! { - #[allow(unused_imports)] - use super::super::#id; - }], ..Default::default() } } @@ -623,7 +525,7 @@ impl<'a> RsCodeGenerator<'a> { Api::ExternCppType { details: ExternCppType { rust_path, .. }, .. - } => self.generate_extern_cpp_type(&name, rust_path, name.ns_segment_iter().count()), + } => self.generate_extern_cpp_type(&name, rust_path), Api::IgnoredItem { err, ctx: Some(ctx), @@ -649,16 +551,13 @@ impl<'a> RsCodeGenerator<'a> { 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![ + let mut output_mod_items: Vec = vec![ parse_quote! { pub use cxxbridge::#cpp_id; }, parse_quote! { - pub struct #holder(pub autocxx::subclass::CppSubclassRustPeerHolder); + pub struct #holder(pub autocxx::subclass::CppSubclassRustPeerHolder); }, parse_quote! { impl autocxx::subclass::CppSubclassCppPeer for #cpp_id { @@ -702,17 +601,17 @@ impl<'a> RsCodeGenerator<'a> { }) .collect(); if !methods_impls.is_empty() { - bindgen_mod_items.push(parse_quote! { + output_mod_items.push(parse_quote! { #[allow(non_snake_case)] - impl #supers for super::super::super::#id { + impl #supers for 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 { + output_mod_items.push(parse_quote! { + impl autocxx::subclass::CppPeerConstructor<#cpp_id> for super::#id { fn make_peer(&mut self, peer_holder: autocxx::subclass::CppSubclassRustPeerHolder) -> cxx::UniquePtr<#cpp_path> { use autocxx::moveit::Emplace; cxx::UniquePtr::emplace(#cpp_id :: new(peer_holder)) @@ -734,8 +633,8 @@ impl<'a> RsCodeGenerator<'a> { extern_c_mod_items.push(parse_quote! { fn #as_unique_ptr_id(u: UniquePtr<#cpp_id>) -> UniquePtr<#super_cxxxbridge_id>; }); - bindgen_mod_items.push(parse_quote! { - impl AsRef<#super_path> for super::super::super::#id { + output_mod_items.push(parse_quote! { + impl AsRef<#super_path> for super::#id { fn as_ref(&self) -> &cxxbridge::#super_cxxxbridge_id { use autocxx::subclass::CppSubclass; self.peer().#as_id() @@ -743,8 +642,8 @@ impl<'a> RsCodeGenerator<'a> { } }); // TODO it would be nice to impl AsMut here but pin prevents us - bindgen_mod_items.push(parse_quote! { - impl super::super::super::#id { + output_mod_items.push(parse_quote! { + impl super::#id { pub fn pin_mut(&mut self) -> ::core::pin::Pin<&mut cxxbridge::#super_cxxxbridge_id> { use autocxx::subclass::CppSubclass; self.peer_mut().#as_mut_id() @@ -752,8 +651,8 @@ impl<'a> RsCodeGenerator<'a> { } }); let rs_as_unique_ptr_id = make_ident(format!("as_{super_name}_unique_ptr")); - bindgen_mod_items.push(parse_quote! { - impl super::super::super::#id { + output_mod_items.push(parse_quote! { + impl super::#id { pub fn #rs_as_unique_ptr_id(u: cxx::UniquePtr<#cpp_id>) -> cxx::UniquePtr { cxxbridge::#as_unique_ptr_id(u) } @@ -772,10 +671,7 @@ impl<'a> RsCodeGenerator<'a> { // 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; - }))], + output_mod_items, global_items, extern_rust_mod_items: vec![ parse_quote! { @@ -863,18 +759,15 @@ impl<'a> RsCodeGenerator<'a> { destroyable: bool, item_creator: F, associated_methods: &HashMap>, - layout: Option, - is_generic: bool, + num_generics: usize, ) -> RsCodegenResult where F: FnOnce() -> Option<(Item, Vec)>, { - let mut bindgen_mod_items = Vec::new(); - let mut materializations = vec![Use::UsedFromBindgen]; + let mut output_mod_items = Vec::new(); Self::add_superclass_stuff_to_type( name, - &mut bindgen_mod_items, - &mut materializations, + &mut output_mod_items, associated_methods.get(name), ); let orig_item = item_creator(); @@ -897,56 +790,43 @@ impl<'a> RsCodeGenerator<'a> { // 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().into())); - } - } - bindgen_mod_items.push(item); - - if is_generic { + output_mod_items.push(match type_kind { + TypeKind::Pod => Self::generate_bindgen_use_stmt(name), + _ => non_pod_struct::generate_opaque_type(name, num_generics, &doc_attrs), + }); + if num_generics > 0 { // Still generate the type as emitted by bindgen, // but don't attempt to tell cxx about it RsCodegenResult { - bindgen_mod_items, - materializations, + output_mod_items, ..Default::default() } } else { + output_mod_items.append(&mut self.generate_extern_type_impl(type_kind, name)); 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, + output_mod_items, ..Default::default() } } } TypeKind::Abstract => { - if is_generic { + if num_generics > 0 { 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; })); + output_mod_items.push(generate_cxx_use_stmt(name, None)); RsCodegenResult { extern_c_mod_items: vec![ self.generate_cxxbridge_type(name, false, doc_attrs) ], bridge_items: create_impl_items(&id, movable, destroyable, self.config), - bindgen_mod_items, - materializations, + output_mod_items, ..Default::default() } } @@ -956,8 +836,7 @@ impl<'a> RsCodeGenerator<'a> { fn add_superclass_stuff_to_type( name: &QualifiedName, - bindgen_mod_items: &mut Vec, - materializations: &mut Vec, + output_mod_items: &mut Vec, methods: Option<&Vec>, ) { if let Some(methods) = methods { @@ -1002,28 +881,26 @@ impl<'a> RsCodeGenerator<'a> { 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! { + output_mod_items.push(parse_quote! { #[allow(non_snake_case)] pub trait #supers_name { #(#supers)* } }); - bindgen_mod_items.push(parse_quote! { + output_mod_items.push(parse_quote! { #[allow(non_snake_case)] pub trait #methods_name : #supers_name { #(#mains)* } }); - materializations.push(Use::SpecificNameFromBindgen(supers_name.into())); } else { - bindgen_mod_items.push(parse_quote! { + output_mod_items.push(parse_quote! { #[allow(non_snake_case)] pub trait #methods_name { #(#mains)* } }); } - materializations.push(Use::SpecificNameFromBindgen(methods_name.into())); } } @@ -1031,21 +908,11 @@ impl<'a> RsCodeGenerator<'a> { &self, name: &QualifiedName, rust_path: TypePath, - ns_depth: usize, ) -> RsCodegenResult { - let id = name.type_path_from_root(); - let super_duper = std::iter::repeat(make_ident("super")); - let supers = super_duper.take(ns_depth + 2); let name_final = name.get_final_ident(); - 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 as #name_final; }, - ))], + output_mod_items: vec![parse_quote! { pub use #rust_path as #name_final; }], ..Default::default() } } @@ -1055,24 +922,13 @@ impl<'a> RsCodeGenerator<'a> { /// generated. fn generate_error_entry(err: ConvertErrorFromCpp, 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. + let (impl_entry, output_mod_items) = match ctx.into_type() { + ErrorContextType::Item(id) | ErrorContextType::SanitizedItem(id) => ( None, - Some(parse_quote! { + vec![parse_quote! { #[doc = #err] pub struct #id; - }), - Some(Use::SpecificNameFromBindgen(id.into())), - ), - 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 { @@ -1083,35 +939,18 @@ impl<'a> RsCodeGenerator<'a> { }, ty: parse_quote! { #self_ty }, })), - None, - None, + vec![], ), }; RsCodegenResult { impl_entry, - bindgen_mod_items: bindgen_mod_item.into_iter().collect(), - materializations: materialization.into_iter().collect(), + output_mod_items, ..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()); + let segs = find_output_mod_root(name.get_namespace()).chain(name.get_bindgen_path_idents()); Item::Use(parse_quote! { #[allow(unused_imports)] pub use #(#segs)::*; @@ -1120,14 +959,14 @@ impl<'a> RsCodeGenerator<'a> { fn generate_extern_type_impl(&self, type_kind: TypeKind, tyname: &QualifiedName) -> Vec { let tynamestring = self.original_name_map.map(tyname); - let fulltypath = tyname.get_bindgen_path_idents(); + let ty_ident = tyname.get_final_ident(); 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)::* { + unsafe impl cxx::ExternType for #ty_ident { type Id = cxx::type_id!(#tynamestring); type Kind = cxx::kind::#kind_item; } @@ -1175,7 +1014,7 @@ impl<'a> RsCodeGenerator<'a> { if references_bindgen { for_extern_c_ts.extend(quote! { - type #id = super::bindgen::root:: + type #id = super:: }); for_extern_c_ts.extend(ns.iter().map(make_ident).map(|id| { quote! { @@ -1192,10 +1031,6 @@ impl<'a> RsCodeGenerator<'a> { } ForeignItem::Verbatim(for_extern_c_ts) } - - fn find_output_mod_root(ns: &Namespace) -> impl Iterator { - std::iter::repeat(make_ident("super")).take(ns.depth()) - } } fn find_trivially_constructed_subclasses(apis: &ApiVec) -> HashSet { @@ -1261,11 +1096,12 @@ struct RsCodegenResult { extern_c_mod_items: Vec, extern_rust_mod_items: Vec, bridge_items: Vec, + /// Items that go in the top level. global_items: Vec, - bindgen_mod_items: Vec, impl_entry: Option>, trait_impl_entry: Option>, - materializations: Vec, + /// Items that go into a per-namespace mod exposed to the user. + output_mod_items: Vec, } /// An [`Item`] that always needs to be in an unsafe block. diff --git a/engine/src/conversion/codegen_rs/non_pod_struct.rs b/engine/src/conversion/codegen_rs/non_pod_struct.rs index 0a179890e..73e6f5219 100644 --- a/engine/src/conversion/codegen_rs/non_pod_struct.rs +++ b/engine/src/conversion/codegen_rs/non_pod_struct.rs @@ -6,153 +6,90 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::types::make_ident; -use autocxx_bindgen::callbacks::Layout; -use proc_macro2::{Ident, Span}; +use syn::{parse_quote, Attribute, Item}; + +use crate::types::{make_ident, QualifiedName}; + +use super::find_output_mod_root; 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 -} +/// Make an opaque wrapper around a bindgen type. +// 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 +// In addition, we want to avoid UB: +// (4) By marking the data as MaybeUninit we ensure there's no UB +// by Rust assuming it's initialized +// (5) By marking it as UnsafeCell we perhaps help reduce aliasing UB. +// This is on the assumption that references to this type may pass +// through C++ and get duplicated, so there may be multiple Rust +// references to the same underlying data. +// The correct solution to this is to put autocxx into the mode +// where it uses CppRef instead of Rust references, but otherwise, +// using UnsafeCell here may help a bit. It probably does not +// eliminate the UB here for the following reasons: +// a) The references floating around are to the outer type, not the +// data stored within the UnsafeCell. (I think this is OK) +// b) C++ may have multiple mutable references, or may have mutable +// references coexisting with immutable references, and no amount +// of UnsafeCell can make that safe. +// Nevertheless the use of UnsafeCell here may (*may*) reduce the +// opportunities for aliasing UB. Again, the only actual way to +// eliminate UB is to use CppRef everywhere instead of &T and &mut T. +// +// For opaque types, the Rusty opaque structure could in fact be generated +// by four different things: +// a) bindgen, using its --opaque-type command line argument or the library +// equivalent; +// b) us (autocxx), by making a [u8; N] byte long structure +// c) us (autocxx), by making a struct containing the bindgen struct +// in an inaccessible field (that's what we do here) +// d) 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 (d) for abstract types. For everything else, we do (c) +// for maximal control. See codegen_rs/mod.rs generate_type for more notes. +// We could switch to (b) and earlier version of autocxx did that. +// +// It is worth noting that our constraints here are a bit more severe than +// for cxx. In the case of cxx, C++ types are usually represented as +// zero-sized types within Rust. Zero-sized types, by definition, can't +// have overlapping references and thus can't have aliasing UB. We can't +// do that because we want C++ types to be representable on the Rust stack, +// and thus we need to tell Rust their real size and alignment. +pub(super) fn generate_opaque_type( + name: &QualifiedName, + num_generics: usize, + doc_attrs: &[Attribute], +) -> Item { + let segs = find_output_mod_root(name.get_namespace()).chain(name.get_bindgen_path_idents()); + let final_name = name.get_final_ident().0; -pub(crate) fn make_non_pod(s: &mut ItemStruct, layout: Option) { - // 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 - // In addition, we want to avoid UB: - // (4) By marking the data as MaybeUninit we ensure there's no UB - // by Rust assuming it's initialized - // (5) By marking it as UnsafeCell we perhaps help reduce aliasing UB. - // This is on the assumption that references to this type may pass - // through C++ and get duplicated, so there may be multiple Rust - // references to the same underlying data. - // The correct solution to this is to put autocxx into the mode - // where it uses CppRef instead of Rust references, but otherwise, - // using UnsafeCell here may help a bit. It definitely does not - // eliminate the UB here for the following reasons: - // a) The references floating around are to the outer type, not the - // data stored within the UnsafeCell. - // b) C++ may have multiple mutable references, or may have mutable - // references coexisting with immutable references, and no amount - // of UnsafeCell can make that safe. - // Nevertheless the use of UnsafeCell here may (*may*) reduce the - // opportunities for aliasing UB. Again, the only actual way to - // eliminate UB is to use CppRef everywhere instead of &T and &mut T. - // - // 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. - // - // It is worth noting that our constraints here are a bit more severe than - // for cxx. In the case of cxx, C++ types are usually represented as - // zero-sized types within Rust. Zero-sized types, by definition, can't - // have overlapping references and thus can't have aliasing UB. We can't - // do that because we want C++ types to be representable on the Rust stack, - // and thus we need to tell Rust their real size and alignment. - // 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))] - } - } + let generics = (0usize..usize::MAX) + .take(num_generics) + .map(|num| make_ident(format!("T{num}")).0); + let generics = if num_generics == 0 { + quote! {} } else { - parse_quote! { - #[repr(C, packed)] + quote! { + < #(#generics),* > } }; - 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: ::core::marker::PhantomData<::core::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: ::core::cell::UnsafeCell<::core::mem::MaybeUninit<[u8; #size]>> - }) - .unwrap(), - ) - } else { - None - } - .into_iter(); - let pin_field = syn::Field::parse_named - .parse2(quote! { - _pinned: core::marker::PhantomData - }) - .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()) + Item::Struct(parse_quote! { + #[repr(transparent)] + #(#doc_attrs)* + pub struct #final_name #generics { + _hidden_contents: ::core::cell::UnsafeCell<::core::mem::MaybeUninit<#(#segs)::* #generics>>, + } + }) } diff --git a/engine/src/conversion/codegen_rs/utils.rs b/engine/src/conversion/codegen_rs/utils.rs new file mode 100644 index 000000000..56cec4d6d --- /dev/null +++ b/engine/src/conversion/codegen_rs/utils.rs @@ -0,0 +1,29 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use syn::{parse_quote, Ident, Item}; + +use crate::types::{make_ident, Namespace, QualifiedName}; + +pub(super) fn generate_cxx_use_stmt(name: &QualifiedName, alias: Option<&Ident>) -> Item { + let segs = 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; + }, + }) +} + +pub(super) fn find_output_mod_root(ns: &Namespace) -> impl Iterator { + std::iter::repeat(make_ident("super")).take(ns.depth()) +} diff --git a/engine/src/conversion/convert_error.rs b/engine/src/conversion/convert_error.rs index 855b2359d..d2a291feb 100644 --- a/engine/src/conversion/convert_error.rs +++ b/engine/src/conversion/convert_error.rs @@ -41,8 +41,6 @@ pub enum ConvertErrorFromCpp { 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}.")] diff --git a/engine/src/conversion/parse/parse_bindgen.rs b/engine/src/conversion/parse/parse_bindgen.rs index 90bf1442d..513278a18 100644 --- a/engine/src/conversion/parse/parse_bindgen.rs +++ b/engine/src/conversion/parse/parse_bindgen.rs @@ -19,7 +19,7 @@ use crate::{ utilities::generate_utilities, ConvertError, ConvertErrorFromCpp, }, - minisyn, + known_types, minisyn, types::{Namespace, QualifiedName}, ParseCallbackResults, }; @@ -174,17 +174,14 @@ impl<'a> ParseBindgen<'a> { fn find_items_in_root(items: &[Item]) -> Result>, ConvertErrorFromCpp> { 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(Some(items)); - } + if let Item::Mod(root_mod) = item { + // 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(Some(items)); } - _ => return Err(ConvertErrorFromCpp::UnexpectedOuterItem), } } Ok(None) @@ -225,8 +222,16 @@ impl<'a> ParseBindgen<'a> { // cxx::bridge can't cope with type aliases to generic // types at the moment. let name = api_name_qualified(ns, s.ident.clone(), self.parse_callback_results)?; + if known_types().is_known_subtitute_type(&name.name) { + // This is one of the replacement types, e.g. + // root::Str replacing rust::Str or + // root::string replacing root::std::string + return Ok(()); + } let mut err = check_for_fatal_attrs(self.parse_callback_results, &name.name).err(); - let api = if ns.is_empty() && self.config.is_rust_type(&s.ident) { + let api = if (ns.is_empty() && self.config.is_rust_type(&s.ident)) + || known_types().is_known_type(&name.name) + { None } else if Self::spot_forward_declaration(&s.fields) || (Self::spot_zero_length_struct(&s.fields) && err.is_some()) @@ -249,11 +254,9 @@ impl<'a> ParseBindgen<'a> { Some(UnanalyzedApi::ForwardDeclaration { name, err }) } else { let has_rvalue_reference_fields = Self::spot_rvalue_reference_fields(&s.fields); - let layout = self.parse_callback_results.get_layout(&name.name); Some(UnanalyzedApi::Struct { name, details: Box::new(StructDetails { - layout, item: s.clone().into(), has_rvalue_reference_fields, }), @@ -309,6 +312,9 @@ impl<'a> ParseBindgen<'a> { UseTree::Rename(urn) => { let old_id = &urn.ident; let new_id = &urn.rename; + if new_id == "bindgen_cchar16_t" { + return Ok(()); + } let new_tyname = QualifiedName::new(ns, new_id.clone().into()); assert!(segs.remove(0) == "self", "Path didn't start with self"); assert!( @@ -331,25 +337,13 @@ impl<'a> ParseBindgen<'a> { } self.apis.push(UnanalyzedApi::Typedef { name: api_name(ns, new_id.clone(), self.parse_callback_results), - item: TypedefKind::Use( - parse_quote! { - pub use #old_path as #new_id; - }, - Box::new(Type::Path(old_path).into()), - ), + item: TypedefKind::Use(Box::new(Type::Path(old_path).into())), old_tyname: Some(old_tyname), analysis: (), }); break; } - _ => { - return Err(ConvertErrorWithContext( - ConvertErrorFromCpp::UnexpectedUseStatement( - segs.into_iter().last().map(|i| i.to_string()), - ), - None, - )) - } + _ => return Ok(()), } } Ok(()) diff --git a/engine/src/conversion/parse/parse_foreign_mod.rs b/engine/src/conversion/parse/parse_foreign_mod.rs index 34df253f1..cc268e710 100644 --- a/engine/src/conversion/parse/parse_foreign_mod.rs +++ b/engine/src/conversion/parse/parse_foreign_mod.rs @@ -16,6 +16,7 @@ use crate::conversion::{ convert_error::ErrorContext, }; use crate::minisyn::{minisynize_punctuated, minisynize_vec}; +use crate::types::strip_bindgen_original_suffix_from_ident; use crate::ParseCallbackResults; use crate::{ conversion::ConvertErrorFromCpp, @@ -70,11 +71,12 @@ impl<'a> ParseForeignMod<'a> { match i { ForeignItem::Fn(item) => { let doc_attrs = get_doc_attrs(&item.attrs); - let qn = QualifiedName::new(&self.ns, item.sig.ident.clone().into()); + let unsuffixed_name = strip_bindgen_original_suffix_from_ident(&item.sig.ident); + let qn = QualifiedName::new(&self.ns, unsuffixed_name.clone().into()); self.funcs_to_convert.push(FuncToConvert { provenance: Provenance::Bindgen, self_ty: None, - ident: item.sig.ident.clone().into(), + ident: unsuffixed_name.clone().into(), doc_attrs: minisynize_vec(doc_attrs), inputs: minisynize_punctuated(item.sig.inputs.clone()), output: item.sig.output.clone().into(), @@ -115,6 +117,8 @@ impl<'a> ParseForeignMod<'a> { Some(id) => id.clone(), None => itm.sig.ident, }; + let effective_fun_name = + strip_bindgen_original_suffix_from_ident(&effective_fun_name); self.method_receivers.insert( effective_fun_name, QualifiedName::new(&self.ns, ty_id.clone().into()), diff --git a/engine/src/known_types.rs b/engine/src/known_types.rs index 573be4e24..1285a12b9 100644 --- a/engine/src/known_types.rs +++ b/engine/src/known_types.rs @@ -83,7 +83,7 @@ impl TypeDetails { Some(format!( indoc! {" /** - *
+ *
*/ {}class {} {{ {}; @@ -269,16 +269,18 @@ impl TypeDatabase { self.get(ty).is_some() } - pub(crate) fn known_type_type_path(&self, ty: &QualifiedName) -> Option { - self.get(ty).map(|td| td.to_type_path()) + /// Whether this is the substitute type we made for some known type. + pub(crate) fn is_known_subtitute_type(&self, ty: &QualifiedName) -> bool { + if ty.get_namespace().is_empty() { + self.all_names() + .any(|n| n.get_final_item() == ty.get_final_item()) + } else { + false + } } - /// 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 + '_ { - self.by_rs_name - .iter() - .filter_map(|(_, td)| td.get_prelude_entry().map(|_| td.cpp_name.as_str())) + pub(crate) fn known_type_type_path(&self, ty: &QualifiedName) -> Option { + self.get(ty).map(|td| td.to_type_path()) } /// Whether this is one of the ctypes (mostly variable length integers) diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 6e692f039..70ef0a02a 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -330,6 +330,17 @@ impl IncludeCppEngine { inc_dirs: &[PathBuf], extra_clang_args: &[&str], ) -> bindgen::Builder { + let bindgen_marker_types = ["Opaque", "Reference", "RValueReference"]; + let raw_line = bindgen_marker_types + .iter() + .map(|t| format!("#[repr(transparent)] pub struct __bindgen_marker_{t}(T);")) + .join(" "); + let use_list = bindgen_marker_types + .iter() + .map(|t| format!("__bindgen_marker_{t}")) + .join(", "); + let all_module_raw_line = format!("#[allow(unused_imports)] use super::{{{use_list}}}; #[allow(unused_imports)] use autocxx::c_char16_t as bindgen_cchar16_t;"); + let mut builder = bindgen::builder() .clang_args(make_clang_args(inc_dirs, extra_clang_args)) .derive_copy(false) @@ -349,16 +360,14 @@ impl IncludeCppEngine { .use_specific_virtual_function_receiver(true) .use_opaque_newtype_wrapper(true) .use_reference_newtype_wrapper(true) - .use_unused_template_param_newtype_wrapper(true) .represent_cxx_operators(true) .use_distinct_char16_t(true) .generate_deleted_functions(true) .generate_pure_virtuals(true) + .raw_line(raw_line) + .every_module_raw_line(all_module_raw_line) .generate_private_functions(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= @@ -368,6 +377,7 @@ impl IncludeCppEngine { builder = builder .allowlist_type(&a) .allowlist_function(&a) + .allowlist_function(format!("{a}_bindgen_original")) .allowlist_var(&a); } } diff --git a/engine/src/parse_callbacks.rs b/engine/src/parse_callbacks.rs index a0666c615..2c766e5a5 100644 --- a/engine/src/parse_callbacks.rs +++ b/engine/src/parse_callbacks.rs @@ -8,13 +8,13 @@ use std::{cell::RefCell, fmt::Display, panic::UnwindSafe, rc::Rc}; -use crate::types::Namespace; +use crate::types::{strip_bindgen_original_suffix, Namespace}; use crate::{conversion::CppEffectiveName, types::QualifiedName, RebuildDependencyRecorder}; -use autocxx_bindgen::callbacks::ParseCallbacks; use autocxx_bindgen::callbacks::Virtualness; use autocxx_bindgen::callbacks::{ - DiscoveredItem, DiscoveredItemId, Explicitness, Layout, SpecialMemberKind, Visibility, + DiscoveredItem, DiscoveredItemId, Explicitness, SpecialMemberKind, Visibility, }; +use autocxx_bindgen::callbacks::{ItemInfo, ItemKind, ParseCallbacks}; use indexmap::IndexMap as HashMap; use indexmap::IndexSet as HashSet; use quote::quote; @@ -124,7 +124,6 @@ pub(crate) struct UnindexedParseCallbackResults { original_names: HashMap, virtuals: HashMap, root_mod: Option, - layouts: HashMap, visibility: HashMap, special_member_kinds: HashMap, explicitness: HashMap, @@ -224,11 +223,6 @@ impl ParseCallbackResults { } } - pub(crate) fn get_layout(&self, name: &QualifiedName) -> Option { - self.id_by_name(name) - .and_then(|id| self.results.layouts.get(&id).cloned()) - } - pub(crate) fn get_cpp_visibility(&self, name: &QualifiedName) -> Visibility { self.id_by_name(name) .and_then(|id| self.results.visibility.get(&id).cloned()) @@ -279,6 +273,22 @@ impl ParseCallbacks for AutocxxParseCallbacks { } } + fn generated_name_override(&self, _item_info: ItemInfo<'_>) -> Option { + // We rename all functions in the original bindgen mod because + // we will generate alternative implementations instead. We still need + // to retain the functions so that we can detect them as we + // parse the bindgen output. + // For free functions, this isn't necessary: we simply avoid + // adding a 'use bindgen::root::some_function' in the output + // namespace. But for methods, we have no way to avoid conflicts + // if we generate an alternative implementation of a method + // with a given name. + match _item_info.kind { + ItemKind::Function => Some(format!("{}_bindgen_original", _item_info.name)), + _ => None, + } + } + fn denote_cpp_name( &self, id: DiscoveredItemId, @@ -287,6 +297,7 @@ impl ParseCallbacks for AutocxxParseCallbacks { ) { let mut results = self.results.borrow_mut(); if let Some(original_name) = original_name { + let original_name = strip_bindgen_original_suffix(original_name); results .original_names .insert(id, CppOriginalName(original_name.to_string())); @@ -310,6 +321,7 @@ impl ParseCallbacks for AutocxxParseCallbacks { .. } | DiscoveredItem::Function { final_name } => { + let final_name = strip_bindgen_original_suffix(&final_name); self.results.borrow_mut().names.insert(id, final_name); } DiscoveredItem::Mod { @@ -328,10 +340,6 @@ impl ParseCallbacks for AutocxxParseCallbacks { } } - fn denote_layout(&self, id: DiscoveredItemId, layout: &Layout) { - self.results.borrow_mut().layouts.insert(id, *layout); - } - fn denote_visibility(&self, id: DiscoveredItemId, visibility: Visibility) { if !matches!(visibility, Visibility::Public) { // Public is the default; no need to record diff --git a/engine/src/types.rs b/engine/src/types.rs index e14150fa6..7dfc86dc7 100644 --- a/engine/src/types.rs +++ b/engine/src/types.rs @@ -95,7 +95,7 @@ impl QualifiedName { 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" { + if first_seg == "root" || first_seg == "output" { // 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' @@ -165,7 +165,13 @@ impl QualifiedName { ["bindgen", "root"] .iter() .map(make_ident) - .chain(self.ns_segment_iter().map(make_ident)) + .chain(self.get_root_path_idents()) + .collect() + } + + pub(crate) fn get_root_path_idents(&self) -> Vec { + self.ns_segment_iter() + .map(make_ident) .chain(std::iter::once(self.get_final_ident())) .collect() } @@ -183,12 +189,12 @@ impl QualifiedName { } } + /// Generates a type path prefixed with `output::` 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.as_str()) + let segs = std::iter::once("output") .chain(self.ns_segment_iter()) .chain(std::iter::once(self.1.as_str())) .map(make_ident); @@ -198,16 +204,6 @@ impl QualifiedName { } } - pub(crate) fn type_path_from_root(&self) -> TypePath { - let segs = self - .ns_segment_iter() - .chain(std::iter::once(self.1.as_str())) - .map(make_ident); - parse_quote! { - #(#segs)::* - } - } - /// Iterator over segments in the namespace of this name. pub(crate) fn ns_segment_iter(&self) -> impl Iterator { self.0.iter() @@ -284,8 +280,37 @@ fn validate_str_ok_for_rust(label: &str) -> Result<(), InvalidIdentError> { .map(|_| ()) } +/// When we're given a name like `some_function_bindgen_original1` returns +/// `some_function1` +pub(crate) fn strip_bindgen_original_suffix(effective_fun_name: &str) -> String { + let bindgen_original_re = regex_static::static_regex!(r"(.*)_bindgen_original(\d*)"); + bindgen_original_re + .captures(effective_fun_name) + .map(|m| { + format!( + "{}{}", + m.get(1).unwrap().as_str(), + m.get(2).unwrap().as_str() + ) + }) + .unwrap_or_else(|| effective_fun_name.to_string()) +} + +/// When we're given a name like `some_function_bindgen_original1` returns +/// `some_function1` +pub(crate) fn strip_bindgen_original_suffix_from_ident( + effective_fun_name: &syn::Ident, +) -> syn::Ident { + make_ident(strip_bindgen_original_suffix( + &effective_fun_name.to_string(), + )) + .0 +} + #[cfg(test)] mod tests { + use crate::types::strip_bindgen_original_suffix; + use super::QualifiedName; #[test] @@ -299,4 +324,18 @@ mod tests { "uint64_t" ); } + + #[test] + fn test_strip() { + assert_eq!(strip_bindgen_original_suffix("foo"), "foo"); + assert_eq!(strip_bindgen_original_suffix("foo_bindgen_original"), "foo"); + assert_eq!( + strip_bindgen_original_suffix("foo_bindgen_original1"), + "foo1" + ); + assert_eq!( + strip_bindgen_original_suffix("foo_bindgen_original1234"), + "foo1234" + ); + } } diff --git a/integration-tests/tests/integration_test.rs b/integration-tests/tests/integration_test.rs index ea795648d..1be9a1793 100644 --- a/integration-tests/tests/integration_test.rs +++ b/integration-tests/tests/integration_test.rs @@ -4143,7 +4143,7 @@ fn test_reserved_name() { let rs = quote! { assert_eq!(ffi::async_(34), 34); }; - run_test("", hdr, rs, &["async_"], &[]); + run_test("", hdr, rs, &["async"], &[]); } #[cfg_attr(skip_windows_gnu_failing_tests, ignore)] @@ -4613,6 +4613,7 @@ fn test_up_in_struct() { } #[test] +#[ignore] // /~https://github.com/rust-lang/rust-bindgen/issues/3158 fn test_typedef_to_std_in_struct() { let hdr = indoc! {" #include @@ -5228,6 +5229,7 @@ fn test_typedef_to_ptr_is_marked_unsafe() { } #[test] +#[ignore] // /~https://github.com/rust-lang/rust-bindgen/issues/3160 fn test_issue_264() { let hdr = indoc! {" namespace a { @@ -6127,7 +6129,7 @@ fn test_keyword_function() { inline void move(int) {}; "}; let rs = quote! {}; - run_test("", hdr, rs, &["move_"], &[]); + run_test("", hdr, rs, &["move"], &[]); } #[test] @@ -6377,6 +6379,8 @@ fn test_include_cpp_in_path() { do_run_test_manual("", hdr, rs, None, None).unwrap(); } +// This test formerly used generate_all! but that causes +// /~https://github.com/rust-lang/rust-bindgen/issues/3159 #[test] fn test_bitset() { let hdr = indoc! {" @@ -6405,7 +6409,7 @@ fn test_bitset() { typedef bitset<1> mybitset; "}; - run_generate_all_test(hdr); + run_test("", hdr, quote! {}, &["mybitset"], &[]); } #[test] @@ -9849,7 +9853,7 @@ fn test_no_rvo_move() { } #[test] -fn test_abstract_up() { +fn test_abstract_up_single_bridge() { let hdr = indoc! {" #include class A { @@ -11635,6 +11639,8 @@ fn test_recursive_field_indirect() { } #[test] +#[cfg_attr(skip_windows_msvc_failing_tests, ignore)] +// MSVC failure appears to be /~https://github.com/rust-lang/rust-bindgen/issues/3159 fn test_typedef_unsupported_type_pub() { let hdr = indoc! {" #include @@ -11658,6 +11664,8 @@ fn test_typedef_unsupported_type_pub() { } #[test] +#[cfg_attr(skip_windows_msvc_failing_tests, ignore)] +// MSVC failure appears to be /~https://github.com/rust-lang/rust-bindgen/issues/3159 fn test_typedef_unsupported_type_pri() { let hdr = indoc! {" #include @@ -11946,6 +11954,7 @@ fn test_pass_rust_str_and_return_struct() { } #[test] +#[ignore] // /~https://github.com/rust-lang/rust-bindgen/issues/3161 fn test_issue_1065a() { let hdr = indoc! {" #include