From 8943de26f6e995d1ba616f870561d0d253933f6d Mon Sep 17 00:00:00 2001 From: Brian Silverman Date: Sun, 20 Feb 2022 14:03:52 -0800 Subject: [PATCH] Implement most of the C++ special member function rules This now tracks most of the information about which C++ special member functions are implicit/explicit/deleted/etc for most of the common cases. This information was needed in several places, which were each using different approximations that failed in different ways, so unify those to get it all working. Also add a bunch of tests around the various cases to keep this working. This assumes that any non-analyzed types (except some built-in ones which are handled specially) have no implicit special member functions, instead of the previous behavior which assumed they all existed if the analyzed type had explicit declarations. This should generate functional code for more situations, but it will skip some optional things (such as moveit traits and make_unique) for additional types. If you run into issues with those things disappearing after this change, make sure all dependencies of the type (superclasses and member types) have a `generate!`/`generate_pod!`. Added TODOs for the following unhandled parts: * google/autocxx#815 (this is a Clang warning anyways, TODOs show all the places to change to fix it) * google/autocxx#816 (this just means we ignore some implicit constructors which do exist) Also added TODOs related to the followig issues, which limit what can be tested but aren't made better or worse by this change: * google/autocxx#832 (this one affects lots of areas) * google/autocxx#829 (this one's pretty prone to unexpected behavior) Also fixed some existing bugs which are surfaced by generating more trait implementations for types in the existing tests: * Use the correct C++ name for destructors of nested types * Handle trait methods for types that end up ignored --- .../src/conversion/analysis/abstract_types.rs | 19 +- .../conversion/analysis/constructor_deps.rs | 9 +- engine/src/conversion/analysis/deps.rs | 18 +- .../fun/implicit_constructor_rules.rs | 219 ++- .../analysis/fun/implicit_constructors.rs | 638 ++++++--- engine/src/conversion/analysis/fun/mod.rs | 404 ++++-- engine/src/conversion/analysis/pod/mod.rs | 63 +- .../src/conversion/analysis/remove_ignored.rs | 22 +- engine/src/conversion/api.rs | 2 +- engine/src/conversion/codegen_cpp/mod.rs | 5 + .../src/conversion/codegen_rs/fun_codegen.rs | 9 +- .../codegen_rs/impl_item_creator.rs | 24 +- engine/src/conversion/codegen_rs/mod.rs | 30 +- engine/src/known_types.rs | 31 +- integration-tests/tests/integration_test.rs | 1212 ++++++++++++++++- 15 files changed, 2077 insertions(+), 628 deletions(-) diff --git a/engine/src/conversion/analysis/abstract_types.rs b/engine/src/conversion/analysis/abstract_types.rs index 73af407f0..5f8f08ec3 100644 --- a/engine/src/conversion/analysis/abstract_types.rs +++ b/engine/src/conversion/analysis/abstract_types.rs @@ -18,6 +18,7 @@ use super::{ }; use crate::conversion::api::Api; use crate::conversion::{ + analysis::fun::FnStructAnalysis, api::TypeKind, error_reporter::{convert_apis, convert_item_apis}, ConvertError, @@ -44,7 +45,7 @@ pub(crate) fn mark_types_abstract(mut apis: Vec>) -> Vec { - analysis.kind = TypeKind::Abstract; + analysis.pod.kind = TypeKind::Abstract; } _ => {} } @@ -60,7 +61,11 @@ pub(crate) fn mark_types_abstract(mut apis: Vec>) -> Vec { *kind = TypeKind::Abstract; @@ -80,7 +85,7 @@ pub(crate) fn mark_types_abstract(mut apis: Vec>) -> Vec>) -> Vec, - pod: PodAnalysis, + fn_struct: FnStructAnalysis, constructors_and_allocators_by_type: &mut HashMap>, ) -> 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 { Vec::new() @@ -71,6 +71,7 @@ fn decorate_struct( analysis: PodAndDepAnalysis { pod, constructor_and_allocator_deps, + constructors: fn_struct.constructors, }, }))) } diff --git a/engine/src/conversion/analysis/deps.rs b/engine/src/conversion/analysis/deps.rs index c8c076e63..b886b7ce4 100644 --- a/engine/src/conversion/analysis/deps.rs +++ b/engine/src/conversion/analysis/deps.rs @@ -20,7 +20,7 @@ use crate::{ }; use super::{ - fun::{FnPhase, FnPrePhase, PodAndDepAnalysis}, + fun::{FnPhase, FnPrePhase, FnStructAnalysis, PodAndDepAnalysis}, pod::PodAnalysis, tdef::TypedefAnalysis, }; @@ -44,13 +44,18 @@ impl HasDependencies for Api { } => Box::new(old_tyname.iter().chain(deps.iter())), Api::Struct { analysis: - PodAnalysis { - kind: TypeKind::Pod, - field_types, + FnStructAnalysis { + pod: + PodAnalysis { + kind: TypeKind::Pod, + bases, + field_types, + .. + }, .. }, .. - } => Box::new(field_types.iter()), + } => Box::new(field_types.iter().chain(bases.iter())), Api::Function { analysis, .. } => Box::new(analysis.deps.iter()), Api::Subclass { name: _, @@ -81,15 +86,18 @@ impl HasDependencies for Api { pod: PodAnalysis { kind: TypeKind::Pod, + bases, field_types, .. }, constructor_and_allocator_deps, + .. }, .. } => Box::new( field_types .iter() + .chain(bases.iter()) .chain(constructor_and_allocator_deps.iter()), ), Api::Struct { diff --git a/engine/src/conversion/analysis/fun/implicit_constructor_rules.rs b/engine/src/conversion/analysis/fun/implicit_constructor_rules.rs index f112d9091..0bbeea234 100644 --- a/engine/src/conversion/analysis/fun/implicit_constructor_rules.rs +++ b/engine/src/conversion/analysis/fun/implicit_constructor_rules.rs @@ -14,164 +14,99 @@ //! Module which understands C++ constructor synthesis rules. -/// Output of the C++ rules about what implicit constructors should be generated. -#[cfg_attr(test, derive(Eq, PartialEq))] -#[derive(Debug)] -pub(super) struct ImplicitConstructorsNeeded { - pub(super) default_constructor: bool, - pub(super) copy_constructor_taking_t: bool, - pub(super) copy_constructor_taking_const_t: bool, - pub(super) move_constructor: bool, +use crate::conversion::api::CppVisibility; + +/// Indicates what we found out about a category of special member function. +/// +/// In the end, we only care whether it's public and exists, but we track a bit more information to +/// support determining the information for dependent classes. +#[derive(Debug, Copy, Clone)] +pub(super) enum SpecialMemberFound { + /// This covers being deleted in any way: + /// * Explicitly deleted + /// * Implicitly defaulted when that means being deleted + /// * Explicitly defaulted when that means being deleted + /// + /// It also covers not being either user declared or implicitly defaulted. + NotPresent, + /// Implicit special member functions, indicated by this, are always public. + Implicit, + /// This covers being explicitly defaulted (when that is not deleted) or being user-defined. + Explicit(CppVisibility), } -/// Input to the C++ rules about which implicit constructors are generated. -#[derive(Default, Debug)] -pub(super) struct ExplicitItemsFound { - pub(super) move_constructor: bool, - pub(super) copy_constructor: bool, - pub(super) any_other_constructor: bool, - pub(super) any_bases_or_fields_lack_const_copy_constructors: bool, - pub(super) any_bases_or_fields_have_deleted_or_inaccessible_copy_constructors: bool, - pub(super) destructor: bool, - pub(super) any_bases_have_deleted_or_inaccessible_destructors: bool, - pub(super) copy_assignment_operator: bool, - pub(super) move_assignment_operator: bool, - pub(super) has_rvalue_reference_fields: bool, - pub(super) any_field_or_base_not_understood: bool, -} - -pub(super) fn determine_implicit_constructors( - explicits: ExplicitItemsFound, -) -> ImplicitConstructorsNeeded { - if explicits.any_field_or_base_not_understood { - // Don't generate anything - return ImplicitConstructorsNeeded { - default_constructor: false, - copy_constructor_taking_t: false, - copy_constructor_taking_const_t: false, - move_constructor: false, - }; +impl SpecialMemberFound { + /// Returns whether code outside of subclasses can call this special member function. + pub fn callable_any(&self) -> bool { + matches!(self, Self::Explicit(CppVisibility::Public) | Self::Implicit) } - let any_constructor = - explicits.copy_constructor || explicits.move_constructor || explicits.any_other_constructor; - // If no user-declared constructors of any kind are provided for a class type (struct, class, or union), - // the compiler will always declare a default constructor as an inline public member of its class. - let default_constructor = !any_constructor; - - // If no user-defined copy constructors are provided for a class type (struct, class, or union), - // the compiler will always declare a copy constructor as a non-explicit inline public member of its class. - // This implicitly-declared copy constructor has the form T::T(const T&) if all of the following are true: - // each direct and virtual base B of T has a copy constructor whose parameters are const B& or const volatile B&; - // each non-static data member M of T of class type or array of class type has a copy constructor whose parameters are const M& or const volatile M&. - - // The implicitly-declared or defaulted copy constructor for class T is defined as deleted if any of the following conditions are true: - // T is a union-like class and has a variant member with non-trivial copy constructor; // we don't support unions anyway - // T has a user-defined move constructor or move assignment operator (this condition only causes the implicitly-declared, not the defaulted, copy constructor to be deleted). - // T has non-static data members that cannot be copied (have deleted, inaccessible, or ambiguous copy constructors); - // T has direct or virtual base class that cannot be copied (has deleted, inaccessible, or ambiguous copy constructors); - // T has direct or virtual base class with a deleted or inaccessible destructor; - // T has a data member of rvalue reference type; - let copy_constructor_is_deleted = explicits.move_constructor - || explicits.move_assignment_operator - || explicits.any_bases_or_fields_have_deleted_or_inaccessible_copy_constructors - || explicits.any_bases_have_deleted_or_inaccessible_destructors - || explicits.has_rvalue_reference_fields; + /// Returns whether code in a subclass can call this special member function. + pub fn callable_subclass(&self) -> bool { + matches!( + self, + Self::Explicit(CppVisibility::Public) + | Self::Explicit(CppVisibility::Protected) + | Self::Implicit + ) + } - let (copy_constructor_taking_const_t, copy_constructor_taking_t) = - if explicits.copy_constructor || copy_constructor_is_deleted { - (false, false) - } else if explicits.any_bases_or_fields_lack_const_copy_constructors { - (false, true) - } else { - (true, false) - }; + /// Returns whether this exists at all. Note that this will return true even if it's private, + /// which is generally not very useful, but does come into play for some rules around which + /// default special member functions are deleted vs don't exist. + pub fn exists(&self) -> bool { + matches!(self, Self::Explicit(_) | Self::Implicit) + } - // If no user-defined move constructors are provided for a class type (struct, class, or union), and all of the following is true: - // there are no user-declared copy constructors; - // there are no user-declared copy assignment operators; - // there are no user-declared move assignment operators; - // there is no user-declared destructor. - // then the compiler will declare a move constructor - let move_constructor = !(explicits.move_constructor - || explicits.copy_constructor - || explicits.destructor - || explicits.copy_assignment_operator - || explicits.move_assignment_operator); + pub fn exists_implicit(&self) -> bool { + matches!(self, Self::Implicit) + } - ImplicitConstructorsNeeded { - default_constructor, - copy_constructor_taking_t, - copy_constructor_taking_const_t, - move_constructor, + pub fn exists_explicit(&self) -> bool { + matches!(self, Self::Explicit(_)) } } -#[cfg(test)] -mod tests { - use super::determine_implicit_constructors; - - use super::ExplicitItemsFound; - - #[test] - fn test_simple() { - let inputs = ExplicitItemsFound::default(); - let outputs = determine_implicit_constructors(inputs); - assert!(outputs.default_constructor); - assert!(outputs.copy_constructor_taking_const_t); - assert!(!outputs.copy_constructor_taking_t); - assert!(outputs.move_constructor); - } +/// Information about which special member functions exist based on the C++ rules. +/// +/// Not all of this information is used directly, but we need to track it to determine the +/// information we do need for classes which are used as members or base classes. +#[derive(Debug, Copy, Clone)] +pub(super) struct ItemsFound { + pub(super) default_constructor: SpecialMemberFound, + pub(super) destructor: SpecialMemberFound, + pub(super) const_copy_constructor: SpecialMemberFound, + /// Remember that [`const_copy_constructor`] may be used in place of this if it exists. + pub(super) non_const_copy_constructor: SpecialMemberFound, + pub(super) move_constructor: SpecialMemberFound, +} - #[test] - fn test_with_destructor() { - let inputs = ExplicitItemsFound { - destructor: true, - ..Default::default() - }; - let outputs = determine_implicit_constructors(inputs); - assert!(outputs.default_constructor); - assert!(outputs.copy_constructor_taking_const_t); - assert!(!outputs.copy_constructor_taking_t); - assert!(!outputs.move_constructor); +impl ItemsFound { + /// Returns whether we should generate a default constructor wrapper, because bindgen won't do + /// one for the implicit default constructor which exists. + pub(super) fn implicit_default_constructor_needed(&self) -> bool { + self.default_constructor.exists_implicit() } - #[test] - fn test_with_pesky_base() { - let inputs = ExplicitItemsFound { - any_bases_or_fields_lack_const_copy_constructors: true, - ..Default::default() - }; - let outputs = determine_implicit_constructors(inputs); - assert!(outputs.default_constructor); - assert!(!outputs.copy_constructor_taking_const_t); - assert!(outputs.copy_constructor_taking_t); - assert!(outputs.move_constructor); + /// Returns whether we should generate a copy constructor wrapper, because bindgen won't do one + /// for the implicit copy constructor which exists. + pub(super) fn implicit_copy_constructor_needed(&self) -> bool { + let any_implicit_copy = self.const_copy_constructor.exists_implicit() + || self.non_const_copy_constructor.exists_implicit(); + let no_explicit_copy = !(self.const_copy_constructor.exists_explicit() + || self.non_const_copy_constructor.exists_explicit()); + any_implicit_copy && no_explicit_copy } - #[test] - fn test_with_user_defined_move_constructor() { - let inputs = ExplicitItemsFound { - move_constructor: true, - ..Default::default() - }; - let outputs = determine_implicit_constructors(inputs); - assert!(!outputs.default_constructor); - assert!(!outputs.copy_constructor_taking_const_t); - assert!(!outputs.copy_constructor_taking_t); - assert!(!outputs.move_constructor); + /// Returns whether we should generate a move constructor wrapper, because bindgen won't do one + /// for the implicit move constructor which exists. + pub(super) fn implicit_move_constructor_needed(&self) -> bool { + self.move_constructor.exists_implicit() } - #[test] - fn test_with_user_defined_misc_constructor() { - let inputs = ExplicitItemsFound { - any_other_constructor: true, - ..Default::default() - }; - let outputs = determine_implicit_constructors(inputs); - assert!(!outputs.default_constructor); - assert!(outputs.copy_constructor_taking_const_t); - assert!(!outputs.copy_constructor_taking_t); - assert!(outputs.move_constructor); + /// Returns whether we should generate a destructor wrapper, because bindgen won't do one for + /// the implicit destructor which exists. + pub(super) fn implicit_destructor_needed(&self) -> bool { + self.destructor.exists_implicit() } } diff --git a/engine/src/conversion/analysis/fun/implicit_constructors.rs b/engine/src/conversion/analysis/fun/implicit_constructors.rs index c6f3a99af..75d94be1b 100644 --- a/engine/src/conversion/analysis/fun/implicit_constructors.rs +++ b/engine/src/conversion/analysis/fun/implicit_constructors.rs @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::{HashMap, HashSet}; +use std::collections::{hash_map, HashMap}; use crate::{ conversion::{ - analysis::{depth_first::depth_first, pod::PodAnalysis, type_converter::find_types}, + analysis::{depth_first::depth_first, fun::FnStructAnalysis, pod::PodAnalysis}, api::{Api, CppVisibility, FuncToConvert, SpecialMemberKind}, }, known_types::known_types, @@ -24,31 +24,44 @@ use crate::{ }; use super::{ - implicit_constructor_rules::{ - determine_implicit_constructors, ExplicitItemsFound, ImplicitConstructorsNeeded, - }, + implicit_constructor_rules::{ItemsFound, SpecialMemberFound}, FnAnalysis, FnKind, FnPrePhase, MethodKind, ReceiverMutability, TraitMethodKind, }; #[derive(Hash, Eq, PartialEq)] enum ExplicitKind { - MoveConstructor, + DefaultConstructor, ConstCopyConstructor, NonConstCopyConstructor, + MoveConstructor, OtherConstructor, Destructor, - CopyAssignmentOperator, + ConstCopyAssignmentOperator, + NonConstCopyAssignmentOperator, MoveAssignmentOperator, - DeletedOrInaccessibleCopyConstructor, - DeletedOrInaccessibleDestructor, } +/// Denotes a specific kind of explicit member function that we found. #[derive(Hash, Eq, PartialEq)] -struct ExplicitFound { +struct ExplicitType { ty: QualifiedName, kind: ExplicitKind, } +/// Includes information about an explicit special member function which was found. +// TODO: Add Defaulted(CppVisibility) for https://github.com/google/autocxx/issues/815. +#[derive(Copy, Clone, Debug)] +enum ExplicitFound { + UserDefined(CppVisibility), + /// Note that this always means explicitly deleted, because this enum only represents + /// explicit declarations. + Deleted, + /// Indicates that we found more than one explicit of this kind. This is possible with most of + /// them, and we just bail and mostly act as if they're deleted. We'd have to decide whether + /// they're ambiguous to use them, which is really complicated. + Multiple, +} + /// If a type has explicit constructors, bindgen will generate corresponding /// constructor functions, which we'll have already converted to make_unique methods. /// For types with implicit constructors, we synthesize them here. @@ -59,261 +72,444 @@ struct ExplicitFound { /// the existing code in this phase to figure out what to do with it. pub(super) fn find_missing_constructors( apis: &[Api], -) -> HashMap { - let mut all_known_types = find_types(apis); - all_known_types.extend(known_types().all_names().cloned()); +) -> HashMap { let explicits = find_explicit_items(apis); - let mut implicit_constructors_needed = HashMap::new(); + + // These contain all the classes we've seen so far with the relevant properties on their + // constructors of each kind. We iterate via [`depth_first`], so analyzing later classes + // just needs to check these. + // // Important only to ask for a depth-first analysis of structs, because // when all APIs are considered there may be reference loops and that would // panic. - for api in depth_first(apis.iter().filter(|api| matches!(api, Api::Struct { .. }))) { + // + // These analyses include all bases and members of each class. + let mut all_items_found: HashMap = HashMap::new(); + + for api in depth_first(apis.iter()) { if let Api::Struct { name, - analysis: PodAnalysis { - bases, field_types, .. - }, + analysis: + FnStructAnalysis { + pod: + PodAnalysis { + bases, field_types, .. + }, + .. + }, details, .. } = api { let name = &name.name; - let find = |kind: ExplicitKind| -> bool { - explicits.contains(&ExplicitFound { + let find_explicit = |kind: ExplicitKind| -> Option<&ExplicitFound> { + explicits.get(&ExplicitType { ty: name.clone(), kind, }) }; - let any_bases_or_fields_lack_const_copy_constructors = - bases.iter().chain(field_types.iter()).any(|qn| { - let has_explicit = explicits.contains(&ExplicitFound { - ty: qn.clone(), - kind: ExplicitKind::ConstCopyConstructor, - }); - let has_implicit = implicit_constructors_needed - .get(qn) - .map(|imp: &ImplicitConstructorsNeeded| imp.copy_constructor_taking_const_t) - .unwrap_or_default(); - !has_explicit && !has_implicit - }); - let any_bases_or_fields_have_deleted_or_inaccessible_copy_constructors = - bases.iter().chain(field_types.iter()).any(|qn| { - explicits.contains(&ExplicitFound { - ty: qn.clone(), - kind: ExplicitKind::DeletedOrInaccessibleCopyConstructor, - }) - }); - let any_bases_have_deleted_or_inaccessible_destructors = bases.iter().any(|qn| { - explicits.contains(&ExplicitFound { - ty: qn.clone(), - kind: ExplicitKind::DeletedOrInaccessibleDestructor, - }) - }); - // Conservatively, we will not generate implicit constructors for any struct/class - // where we don't fully understand all field types. We need to extend our knowledge - // to understand the constructor behavior of things in known_types.rs, then we'll - // be able to cope with types which contain strings, unique_ptrs etc. - let any_field_or_base_not_understood = bases - .iter() - .chain(field_types.iter()) - .any(|qn| !all_known_types.contains(qn)); - let explicit_items_found = ExplicitItemsFound { - move_constructor: find(ExplicitKind::MoveConstructor), - copy_constructor: find(ExplicitKind::ConstCopyConstructor) - || find(ExplicitKind::NonConstCopyConstructor) - || find(ExplicitKind::DeletedOrInaccessibleCopyConstructor), - any_other_constructor: find(ExplicitKind::OtherConstructor), - any_bases_or_fields_lack_const_copy_constructors, - any_bases_or_fields_have_deleted_or_inaccessible_copy_constructors, - any_bases_have_deleted_or_inaccessible_destructors, - destructor: find(ExplicitKind::Destructor) - || find(ExplicitKind::DeletedOrInaccessibleDestructor), - copy_assignment_operator: find(ExplicitKind::CopyAssignmentOperator), - move_assignment_operator: find(ExplicitKind::MoveAssignmentOperator), - has_rvalue_reference_fields: details.has_rvalue_reference_fields, - any_field_or_base_not_understood, + let get_items_found = |qn: &QualifiedName| -> Option { + if known_types().is_known(qn) { + Some(known_type_items_found(qn)) + } else { + all_items_found.get(qn).copied() + } }; - log::info!( - "Explicit items found for {:?}: {:?}", - name, - explicit_items_found - ); - let implicits = determine_implicit_constructors(explicit_items_found); - implicit_constructors_needed.insert(name.clone(), implicits); + let bases_items_found: Vec<_> = bases.iter().map_while(get_items_found).collect(); + let field_types_items_found: Vec<_> = + field_types.iter().map_while(get_items_found).collect(); + let has_rvalue_reference_fields = details.has_rvalue_reference_fields; + + // Check that all the bases and field types are known first. This combined with + // iterating via [`depth_first`] means we can safely search in `items_found` for all of + // them. + // + // Conservatively, we will not acknowledge the existence of defaulted special member + // functions for any struct/class where we don't fully understand all field types. + // However, we can still look for explictly declared versions and use those. + // + // We need to extend our knowledge to understand the constructor behavior of things in + // known_types.rs, then we'll be able to cope with types which contain strings, + // unique_ptrs etc. + // + // We also need to learn to follow typedefs. + if bases_items_found.len() != bases.len() + || field_types_items_found.len() != field_types.len() + { + let is_explicit = |kind: ExplicitKind| -> SpecialMemberFound { + // TODO: For https://github.com/google/autocxx/issues/815, map + // ExplicitFound::Defaulted(_) to NotPresent. + match find_explicit(kind) { + None => SpecialMemberFound::NotPresent, + Some(ExplicitFound::Deleted | ExplicitFound::Multiple) => { + SpecialMemberFound::NotPresent + } + Some(ExplicitFound::UserDefined(visibility)) => { + SpecialMemberFound::Explicit(*visibility) + } + } + }; + let items_found = ItemsFound { + default_constructor: is_explicit(ExplicitKind::DefaultConstructor), + destructor: is_explicit(ExplicitKind::Destructor), + const_copy_constructor: is_explicit(ExplicitKind::ConstCopyConstructor), + non_const_copy_constructor: is_explicit(ExplicitKind::NonConstCopyConstructor), + move_constructor: is_explicit(ExplicitKind::MoveConstructor), + }; + log::info!( + "Special member functions found for {:?}: {:?}", + name, + items_found + ); + assert!( + all_items_found.insert(name.clone(), items_found).is_none(), + "Duplicate struct: {:?}", + name + ); + } else { + // If no user-declared constructors of any kind are provided for a class type (struct, class, or union), + // the compiler will always declare a default constructor as an inline public member of its class. + // + // The implicitly-declared or defaulted default constructor for class T is defined as deleted if any of the following is true: + // T has a member of reference type without a default initializer. + // T has a non-const-default-constructible const member without a default member initializer. + // T has a member (without a default member initializer) which has a deleted default constructor, or its default constructor is ambiguous or inaccessible from this constructor. + // T has a direct or virtual base which has a deleted default constructor, or it is ambiguous or inaccessible from this constructor. + // T has a direct or virtual base or a non-static data member which has a deleted destructor, or a destructor that is inaccessible from this constructor. + // T is a union with at least one variant member with non-trivial default constructor, and no variant member of T has a default member initializer. // we don't support unions anyway + // T is a non-union class with a variant member M with a non-trivial default constructor, and no variant member of the anonymous union containing M has a default member initializer. + // T is a union and all of its variant members are const. // we don't support unions anyway + // + // Variant members are the members of anonymous unions. + let default_constructor = { + let explicit = find_explicit(ExplicitKind::DefaultConstructor); + // TODO: For https://github.com/google/autocxx/issues/815, replace the first term with: + // explicit.map_or(true, |explicit_found| matches!(explicit_found, ExplicitFound::Defaulted(_))) + let have_defaulted = explicit.is_none() + && !explicits.iter().any(|(ExplicitType { ty, kind }, _)| { + ty == name + && match *kind { + ExplicitKind::DefaultConstructor => false, + ExplicitKind::ConstCopyConstructor => true, + ExplicitKind::NonConstCopyConstructor => true, + ExplicitKind::MoveConstructor => true, + ExplicitKind::OtherConstructor => true, + ExplicitKind::Destructor => false, + ExplicitKind::ConstCopyAssignmentOperator => false, + ExplicitKind::NonConstCopyAssignmentOperator => false, + ExplicitKind::MoveAssignmentOperator => false, + } + }); + if have_defaulted { + let bases_allow = bases_items_found.iter().all(|items_found| { + items_found.destructor.callable_subclass() + && items_found.default_constructor.callable_subclass() + }); + // TODO: Allow member initializers for + // https://github.com/google/autocxx/issues/816. + let members_allow = field_types_items_found.iter().all(|items_found| { + items_found.destructor.callable_any() + && items_found.default_constructor.callable_any() + }); + if bases_allow && members_allow { + // TODO: For https://github.com/google/autocxx/issues/815, grab the + // visibility from an explicit default if present. + SpecialMemberFound::Implicit + } else { + SpecialMemberFound::NotPresent + } + } else if let Some(ExplicitFound::UserDefined(visibility)) = explicit { + SpecialMemberFound::Explicit(*visibility) + } else { + SpecialMemberFound::NotPresent + } + }; + + // If no user-declared prospective destructor is provided for a class type (struct, class, or union), the compiler will always declare a destructor as an inline public member of its class. + // + // The implicitly-declared or explicitly defaulted destructor for class T is defined as deleted if any of the following is true: + // T has a non-static data member that cannot be destructed (has deleted or inaccessible destructor) + // T has direct or virtual base class that cannot be destructed (has deleted or inaccessible destructors) + // T is a union and has a variant member with non-trivial destructor. // we don't support unions anyway + // The implicitly-declared destructor is virtual (because the base class has a virtual destructor) and the lookup for the deallocation function (operator delete()) results in a call to ambiguous, deleted, or inaccessible function. + let destructor = { + let explicit = find_explicit(ExplicitKind::Destructor); + // TODO: For https://github.com/google/autocxx/issues/815, replace the condition with: + // explicit.map_or(true, |explicit_found| matches!(explicit_found, ExplicitFound::Defaulted(_))) + if explicit.is_none() { + let bases_allow = bases_items_found + .iter() + .all(|items_found| items_found.destructor.callable_subclass()); + let members_allow = field_types_items_found + .iter() + .all(|items_found| items_found.destructor.callable_any()); + if bases_allow && members_allow { + // TODO: For https://github.com/google/autocxx/issues/815, grab the + // visibility from an explicit default if present. + SpecialMemberFound::Implicit + } else { + SpecialMemberFound::NotPresent + } + } else if let Some(ExplicitFound::UserDefined(visibility)) = explicit { + SpecialMemberFound::Explicit(*visibility) + } else { + SpecialMemberFound::NotPresent + } + }; + + // If no user-defined copy constructors are provided for a class type (struct, class, or union), + // the compiler will always declare a copy constructor as a non-explicit inline public member of its class. + // This implicitly-declared copy constructor has the form T::T(const T&) if all of the following are true: + // each direct and virtual base B of T has a copy constructor whose parameters are const B& or const volatile B&; + // each non-static data member M of T of class type or array of class type has a copy constructor whose parameters are const M& or const volatile M&. + // + // The implicitly-declared or defaulted copy constructor for class T is defined as deleted if any of the following conditions are true: + // T is a union-like class and has a variant member with non-trivial copy constructor; // we don't support unions anyway + // T has a user-defined move constructor or move assignment operator (this condition only causes the implicitly-declared, not the defaulted, copy constructor to be deleted). + // T has non-static data members that cannot be copied (have deleted, inaccessible, or ambiguous copy constructors); + // T has direct or virtual base class that cannot be copied (has deleted, inaccessible, or ambiguous copy constructors); + // T has direct or virtual base class or a non-static data member with a deleted or inaccessible destructor; + // T has a data member of rvalue reference type; + let (const_copy_constructor, non_const_copy_constructor) = { + let explicit_const = find_explicit(ExplicitKind::ConstCopyConstructor); + let explicit_non_const = find_explicit(ExplicitKind::NonConstCopyConstructor); + let explicit_move = find_explicit(ExplicitKind::MoveConstructor); + + // TODO: For https://github.com/google/autocxx/issues/815, replace both terms with something like: + // explicit.map_or(true, |explicit_found| matches!(explicit_found, ExplicitFound::Defaulted(_))) + let have_defaulted = explicit_const.is_none() && explicit_non_const.is_none(); + if have_defaulted { + // TODO: For https://github.com/google/autocxx/issues/815, ignore this if + // the relevant (based on bases_are_const) copy constructor is explicitly defaulted. + let class_allows = explicit_move.is_none() && !has_rvalue_reference_fields; + let bases_allow = bases_items_found.iter().all(|items_found| { + items_found.destructor.callable_subclass() + && (items_found.const_copy_constructor.callable_subclass() + || items_found.non_const_copy_constructor.callable_subclass()) + }); + let members_allow = field_types_items_found.iter().all(|items_found| { + items_found.destructor.callable_any() + && (items_found.const_copy_constructor.callable_any() + || items_found.non_const_copy_constructor.callable_any()) + }); + if class_allows && bases_allow && members_allow { + // TODO: For https://github.com/google/autocxx/issues/815, grab the + // visibility and existence of const and non-const from an explicit default if present. + let dependencies_are_const = bases_items_found + .iter() + .chain(field_types_items_found.iter()) + .all(|items_found| items_found.const_copy_constructor.exists()); + if dependencies_are_const { + (SpecialMemberFound::Implicit, SpecialMemberFound::NotPresent) + } else { + (SpecialMemberFound::NotPresent, SpecialMemberFound::Implicit) + } + } else { + ( + SpecialMemberFound::NotPresent, + SpecialMemberFound::NotPresent, + ) + } + } else { + ( + if let Some(ExplicitFound::UserDefined(visibility)) = explicit_const { + SpecialMemberFound::Explicit(*visibility) + } else { + SpecialMemberFound::NotPresent + }, + if let Some(ExplicitFound::UserDefined(visibility)) = explicit_non_const + { + SpecialMemberFound::Explicit(*visibility) + } else { + SpecialMemberFound::NotPresent + }, + ) + } + }; + + // If no user-defined move constructors are provided for a class type (struct, class, or union), and all of the following is true: + // there are no user-declared copy constructors; + // there are no user-declared copy assignment operators; + // there are no user-declared move assignment operators; + // there is no user-declared destructor. + // then the compiler will declare a move constructor as a non-explicit inline public member of its class with the signature T::T(T&&). + // + // A class can have multiple move constructors, e.g. both T::T(const T&&) and T::T(T&&). If some user-defined move constructors are present, the user may still force the generation of the implicitly declared move constructor with the keyword default. + // + // The implicitly-declared or defaulted move constructor for class T is defined as deleted if any of the following is true: + // T has non-static data members that cannot be moved (have deleted, inaccessible, or ambiguous move constructors); + // T has direct or virtual base class that cannot be moved (has deleted, inaccessible, or ambiguous move constructors); + // T has direct or virtual base class with a deleted or inaccessible destructor; + // T is a union-like class and has a variant member with non-trivial move constructor. // we don't support unions anyway + let move_constructor = { + let explicit = find_explicit(ExplicitKind::MoveConstructor); + // TODO: For https://github.com/google/autocxx/issues/815, replace relevant terms with something like: + // explicit.map_or(true, |explicit_found| matches!(explicit_found, ExplicitFound::Defaulted(_))) + let have_defaulted = !(explicit.is_some() + || find_explicit(ExplicitKind::ConstCopyConstructor).is_some() + || find_explicit(ExplicitKind::NonConstCopyConstructor).is_some() + || find_explicit(ExplicitKind::ConstCopyAssignmentOperator).is_some() + || find_explicit(ExplicitKind::NonConstCopyAssignmentOperator).is_some() + || find_explicit(ExplicitKind::MoveAssignmentOperator).is_some() + || find_explicit(ExplicitKind::Destructor).is_some()); + if have_defaulted { + let bases_allow = bases_items_found.iter().all(|items_found| { + items_found.destructor.callable_subclass() + && items_found.move_constructor.callable_subclass() + }); + let members_allow = field_types_items_found + .iter() + .all(|items_found| items_found.move_constructor.callable_any()); + if bases_allow && members_allow { + // TODO: For https://github.com/google/autocxx/issues/815, grab the + // visibility from an explicit default if present. + SpecialMemberFound::Implicit + } else { + SpecialMemberFound::NotPresent + } + } else if let Some(ExplicitFound::UserDefined(visibility)) = explicit { + SpecialMemberFound::Explicit(*visibility) + } else { + SpecialMemberFound::NotPresent + } + }; + + let items_found = ItemsFound { + default_constructor, + destructor, + const_copy_constructor, + non_const_copy_constructor, + move_constructor, + }; + log::info!( + "Special member items found for {:?}: {:?}", + name, + items_found + ); + assert!( + all_items_found.insert(name.clone(), items_found).is_none(), + "Duplicate struct: {:?}", + name + ); + } } } - log::info!( - "Implicit constructors needed: {:?}", - implicit_constructors_needed - ); - implicit_constructors_needed + + all_items_found } -fn find_explicit_items(apis: &[Api]) -> HashSet { - apis.iter() - .filter_map(|api| match api { - Api::Function { - analysis: - FnAnalysis { - kind: FnKind::Method(self_ty, MethodKind::Constructor), - .. - }, - .. - } => Some(ExplicitFound { - ty: self_ty.clone(), - kind: ExplicitKind::OtherConstructor, - }), - Api::Function { - analysis: - FnAnalysis { - kind: - FnKind::TraitMethod { - kind: TraitMethodKind::MoveConstructor, - impl_for, - .. - }, - .. - }, - .. - } => Some(ExplicitFound { - ty: impl_for.clone(), - kind: ExplicitKind::MoveConstructor, - }), +fn find_explicit_items(apis: &[Api]) -> HashMap { + let mut result = HashMap::new(); + let mut merge_fun = |ty: QualifiedName, kind: ExplicitKind, fun: &FuncToConvert| match result + .entry(ExplicitType { ty, kind }) + { + hash_map::Entry::Vacant(entry) => { + entry.insert(if fun.is_deleted { + ExplicitFound::Deleted + } else { + ExplicitFound::UserDefined(fun.cpp_vis) + }); + } + hash_map::Entry::Occupied(mut entry) => { + entry.insert(ExplicitFound::Multiple); + } + }; + for api in apis.iter() { + match api { Api::Function { analysis: FnAnalysis { - kind: - FnKind::TraitMethod { - kind: TraitMethodKind::Destructor, - impl_for, - .. - }, + kind: FnKind::Method(impl_for, ..), + param_details, .. }, fun, .. - } if is_deleted_or_inaccessible(fun) => Some(ExplicitFound { - ty: impl_for.clone(), - kind: ExplicitKind::DeletedOrInaccessibleDestructor, - }), - Api::Function { - analysis: - FnAnalysis { - kind: - FnKind::TraitMethod { - kind: TraitMethodKind::Destructor, - impl_for, - .. - }, - .. + } if matches!( + fun.special_member, + Some(SpecialMemberKind::AssignmentOperator) + ) => + { + let is_move_assignment_operator = !fun.references.rvalue_ref_params.is_empty(); + merge_fun( + impl_for.clone(), + if is_move_assignment_operator { + ExplicitKind::MoveAssignmentOperator + } else { + let receiver_mutability = ¶m_details + .iter() + .next() + .unwrap() + .self_type + .as_ref() + .unwrap() + .1; + match receiver_mutability { + ReceiverMutability::Const => ExplicitKind::ConstCopyAssignmentOperator, + ReceiverMutability::Mutable => { + ExplicitKind::NonConstCopyAssignmentOperator + } + } }, - .. - } => Some(ExplicitFound { - ty: impl_for.clone(), - kind: ExplicitKind::Destructor, - }), + fun, + ) + } Api::Function { analysis: FnAnalysis { - kind: - FnKind::TraitMethod { - kind: TraitMethodKind::CopyConstructor, - impl_for, - .. - }, + kind: FnKind::Method(impl_for, method_kind, ..), .. }, fun, .. - } if is_deleted_or_inaccessible(fun) => Some(ExplicitFound { - ty: impl_for.clone(), - kind: ExplicitKind::DeletedOrInaccessibleCopyConstructor, + } => match method_kind { + MethodKind::DefaultConstructor => Some(ExplicitKind::DefaultConstructor), + MethodKind::Constructor => Some(ExplicitKind::OtherConstructor), + _ => None, + } + .map_or((), |explicit_kind| { + merge_fun(impl_for.clone(), explicit_kind, fun) }), Api::Function { analysis: FnAnalysis { - kind: - FnKind::TraitMethod { - kind: TraitMethodKind::CopyConstructor, - impl_for, - .. - }, - param_details, + kind: FnKind::TraitMethod { impl_for, kind, .. }, .. }, - .. - } => { - let receiver_mutability = ¶m_details - .iter() - .next() - .unwrap() - .self_type - .as_ref() - .unwrap() - .1; - let kind = match receiver_mutability { - ReceiverMutability::Const => ExplicitKind::ConstCopyConstructor, - ReceiverMutability::Mutable => ExplicitKind::NonConstCopyConstructor, - }; - Some(ExplicitFound { - ty: impl_for.clone(), - kind, - }) - } - Api::Function { fun, - analysis: - FnAnalysis { - kind: FnKind::Method(self_ty, ..), - .. - }, .. - } if matches!( - fun.special_member, - Some(SpecialMemberKind::AssignmentOperator) - ) => - { - let is_move_assignment_operator = !fun.references.rvalue_ref_params.is_empty(); - Some(ExplicitFound { - ty: self_ty.clone(), - kind: if is_move_assignment_operator { - ExplicitKind::MoveAssignmentOperator - } else { - ExplicitKind::CopyAssignmentOperator - }, - }) + } => match kind { + TraitMethodKind::Destructor => Some(ExplicitKind::Destructor), + // In `analyze_foreign_fn` we mark non-const copy constructors as not being copy + // constructors for now, so we don't have to worry about them. + TraitMethodKind::CopyConstructor => Some(ExplicitKind::ConstCopyConstructor), + TraitMethodKind::MoveConstructor => Some(ExplicitKind::MoveConstructor), + _ => None, } - _ => None, - }) - .chain(known_type_constructors()) - .collect() -} - -fn known_type_constructors() -> impl Iterator { - known_types() - .all_types_with_move_constructors() - .map(|ty| ExplicitFound { - ty, - kind: ExplicitKind::MoveConstructor, - }) - .chain( - known_types() - .all_types_with_const_copy_constructors() - .map(|ty| ExplicitFound { - ty, - kind: ExplicitKind::ConstCopyConstructor, - }), - ) - .chain( - known_types() - .all_types_without_copy_constructors() - .map(|ty| ExplicitFound { - ty, - kind: ExplicitKind::DeletedOrInaccessibleCopyConstructor, - }), - ) + .map_or((), |explicit_kind| { + merge_fun(impl_for.clone(), explicit_kind, fun) + }), + _ => (), + } + } + result } -fn is_deleted_or_inaccessible(fun: &FuncToConvert) -> bool { - fun.cpp_vis == CppVisibility::Private || fun.is_deleted +/// Returns the information for a given known type. +fn known_type_items_found(ty: &QualifiedName) -> ItemsFound { + let exists_public = SpecialMemberFound::Explicit(CppVisibility::Public); + let exists_public_if = |exists| { + if exists { + exists_public + } else { + SpecialMemberFound::NotPresent + } + }; + ItemsFound { + default_constructor: exists_public, + destructor: exists_public, + const_copy_constructor: exists_public_if(known_types().has_const_copy_constructor(ty)), + non_const_copy_constructor: SpecialMemberFound::NotPresent, + move_constructor: exists_public_if(known_types().has_move_constructor(ty)), + } } diff --git a/engine/src/conversion/analysis/fun/mod.rs b/engine/src/conversion/analysis/fun/mod.rs index 14194e9e5..6614e169b 100644 --- a/engine/src/conversion/analysis/fun/mod.rs +++ b/engine/src/conversion/analysis/fun/mod.rs @@ -27,8 +27,8 @@ use crate::{ }, api::{ ApiName, CastMutability, CppVisibility, FuncToConvert, Provenance, References, - SpecialMemberKind, SubclassName, TraitImplSignature, TraitSynthesis, UnsafetyNeeded, - Virtualness, + SpecialMemberKind, StructDetails, SubclassName, TraitImplSignature, TraitSynthesis, + UnsafetyNeeded, Virtualness, }, convert_error::ConvertErrorWithContext, convert_error::ErrorContext, @@ -80,10 +80,11 @@ pub(crate) enum ReceiverMutability { Mutable, } -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) enum MethodKind { Normal(ReceiverMutability), Constructor, + DefaultConstructor, MakeUnique, Static, Virtual(ReceiverMutability), @@ -198,25 +199,40 @@ impl Default for ReturnTypeAnalysis { } } +pub(crate) struct FnStructAnalysis { + pub(crate) pod: PodAnalysis, + pub(crate) constructors: PublicConstructors, +} + /// An analysis phase where we've analyzed each function, but /// haven't yet determined which constructors/etc. belong to each type. pub(crate) struct FnPrePhase; impl AnalysisPhase for FnPrePhase { type TypedefAnalysis = TypedefAnalysis; - type StructAnalysis = PodAnalysis; + type StructAnalysis = FnStructAnalysis; type FunAnalysis = FnAnalysis; } pub(crate) struct PodAndDepAnalysis { pub(crate) pod: PodAnalysis, pub(crate) constructor_and_allocator_deps: Vec, + pub(crate) constructors: PublicConstructors, } /// Analysis phase after we've finished analyzing functions and determined /// which constructors etc. belong to them. pub(crate) struct FnPhase; +/// Indicates which kinds of public constructors are known to exist for a type. +#[derive(Debug, Default, Copy, Clone)] +pub(crate) struct PublicConstructors { + pub(crate) default_constructor: bool, + pub(crate) copy_constructor: bool, + pub(crate) move_constructor: bool, + pub(crate) destructor: bool, +} + impl AnalysisPhase for FnPhase { type TypedefAnalysis = TypedefAnalysis; type StructAnalysis = PodAndDepAnalysis; @@ -246,6 +262,23 @@ pub(crate) struct FnAnalyzer<'a> { existing_superclass_trait_api_names: HashSet, } +/// Leaves the [`FnStructAnalysis::constructors`] at its default for [`add_missing_constructors`] +/// to fill out. +fn convert_struct( + name: ApiName, + details: Box, + analysis: PodAnalysis, +) -> Result>>, ConvertErrorWithContext> { + Ok(Box::new(std::iter::once(Api::Struct { + name, + details, + analysis: FnStructAnalysis { + pod: analysis, + constructors: Default::default(), + }, + }))) +} + impl<'a> FnAnalyzer<'a> { pub(crate) fn analyze_functions( apis: Vec>, @@ -270,11 +303,12 @@ impl<'a> FnAnalyzer<'a> { apis, &mut results, |name, fun, _, _| me.analyze_foreign_fn_and_subclasses(name, fun), - Api::struct_unchanged, + convert_struct, Api::enum_unchanged, Api::typedef_unchanged, ); me.add_missing_constructors(&mut results); + me.add_make_uniques(&mut results); results.extend(me.extra_apis.into_iter().map(add_analysis)); results } @@ -414,6 +448,62 @@ impl<'a> FnAnalyzer<'a> { } } + fn add_make_uniques(&mut self, apis: &mut Vec>) { + let mut results = Vec::new(); + 'outer: for api in apis.iter() { + if let Api::Function { + name, + fun, + analysis, + .. + } = api + { + let initial_name = name.clone(); + if let FnKind::Method( + sup, + MethodKind::Constructor | MethodKind::DefaultConstructor, + ) = &analysis.kind + { + // Now go find the Api::Struct for our type, and verify it has an accessible + // destructor. + for api in apis.iter() { + match api { + Api::Struct { name, analysis, .. } if &name.name == sup => { + if !analysis.constructors.destructor { + continue 'outer; + } + } + _ => (), + } + } + + // Create a make_unique too + self.create_make_unique(fun, initial_name, &mut results); + + for sub in self.subclasses_by_superclass(sup) { + // Create a subclass constructor. This is a synthesized function + // which didn't exist in the original C++. + let (subclass_constructor_func, subclass_constructor_name) = + create_subclass_constructor(sub, analysis, sup, fun); + self.analyze_and_add( + subclass_constructor_name.clone(), + subclass_constructor_func.clone(), + &mut results, + TypeConversionSophistication::Regular, + ); + // and its corresponding make_unique + self.create_make_unique( + &subclass_constructor_func, + subclass_constructor_name, + &mut results, + ); + } + } + } + } + apis.extend(results.into_iter()); + } + /// Analyze a given function, and any permutations of that function which /// we might additionally generate (e.g. for subclasses.) fn analyze_foreign_fn_and_subclasses( @@ -421,109 +511,82 @@ impl<'a> FnAnalyzer<'a> { name: ApiName, fun: Box, ) -> Result>>, ConvertErrorWithContext> { - let initial_name = name.clone(); let (analysis, name) = self.analyze_foreign_fn(name, &fun, TypeConversionSophistication::Regular, None); let mut results = Vec::new(); // Consider whether we need to synthesize subclass items. - match &analysis.kind { - FnKind::Method(sup, MethodKind::Constructor) => { - // Create a make_unique too - self.create_make_unique(&fun, initial_name, &mut results); - - for sub in self.subclasses_by_superclass(sup) { - // Create a subclass constructor. This is a synthesized function - // which didn't exist in the original C++. - let (subclass_constructor_func, subclass_constructor_name) = - create_subclass_constructor(sub, &analysis, sup, &fun); - self.analyze_and_add( - subclass_constructor_name.clone(), - subclass_constructor_func.clone(), - &mut results, - TypeConversionSophistication::Regular, - ); - // and its corresponding make_unique - self.create_make_unique( - &subclass_constructor_func, - subclass_constructor_name, - &mut results, - ); - } - } - FnKind::Method( - sup, - MethodKind::Virtual(receiver_mutability) - | MethodKind::PureVirtual(receiver_mutability), - ) => { - let (simpler_analysis, _) = self.analyze_foreign_fn( - name.clone(), - &fun, - TypeConversionSophistication::SimpleForSubclasses, - Some(analysis.rust_name.clone()), + if let FnKind::Method( + sup, + MethodKind::Virtual(receiver_mutability) | MethodKind::PureVirtual(receiver_mutability), + ) = &analysis.kind + { + let (simpler_analysis, _) = self.analyze_foreign_fn( + name.clone(), + &fun, + TypeConversionSophistication::SimpleForSubclasses, + Some(analysis.rust_name.clone()), + ); + for sub in self.subclasses_by_superclass(sup) { + // For each subclass, we need to create a plain-C++ method to call its superclass + // and a Rust/C++ bridge API to call _that_. + // What we're generating here is entirely about the subclass, so the + // superclass's namespace is irrelevant. We generate + // all subclasses in the root namespace. + let is_pure_virtual = matches!( + &simpler_analysis.kind, + FnKind::Method(_, MethodKind::PureVirtual(..)) ); - for sub in self.subclasses_by_superclass(sup) { - // For each subclass, we need to create a plain-C++ method to call its superclass - // and a Rust/C++ bridge API to call _that_. - // What we're generating here is entirely about the subclass, so the - // superclass's namespace is irrelevant. We generate - // all subclasses in the root namespace. - let is_pure_virtual = matches!( - &simpler_analysis.kind, - FnKind::Method(_, MethodKind::PureVirtual(..)) - ); - let super_fn_name = - SubclassName::get_super_fn_name(&Namespace::new(), &analysis.rust_name); - let trait_api_name = SubclassName::get_trait_api_name(sup, &analysis.rust_name); - - // Create the Rust API representing the subclass implementation (allowing calls - // from C++ -> Rust) - results.push(create_subclass_function( - &sub, + let super_fn_name = + SubclassName::get_super_fn_name(&Namespace::new(), &analysis.rust_name); + let trait_api_name = SubclassName::get_trait_api_name(sup, &analysis.rust_name); + + // Create the Rust API representing the subclass implementation (allowing calls + // from C++ -> Rust) + results.push(create_subclass_function( + &sub, + &simpler_analysis, + &name, + receiver_mutability, + sup, + if is_pure_virtual { + vec![trait_api_name.clone()] + } else { + vec![trait_api_name.clone(), super_fn_name.clone()] + }, + )); + + // Create the trait item for the _methods and _supers + // traits. This is required per-superclass, not per-subclass, so don't + // create it if it already exists. + if !self + .existing_superclass_trait_api_names + .contains(&trait_api_name) + { + self.existing_superclass_trait_api_names + .insert(trait_api_name.clone()); + results.push(create_subclass_trait_item( + ApiName::new_from_qualified_name(trait_api_name), &simpler_analysis, - &name, receiver_mutability, - sup, - if is_pure_virtual { - vec![trait_api_name.clone()] - } else { - vec![trait_api_name.clone(), super_fn_name.clone()] - }, + sup.clone(), + is_pure_virtual, )); + } - // Create the trait item for the _methods and _supers - // traits. This is required per-superclass, not per-subclass, so don't - // create it if it already exists. - if !self - .existing_superclass_trait_api_names - .contains(&trait_api_name) - { - self.existing_superclass_trait_api_names - .insert(trait_api_name.clone()); - results.push(create_subclass_trait_item( - ApiName::new_from_qualified_name(trait_api_name), - &simpler_analysis, - receiver_mutability, - sup.clone(), - is_pure_virtual, - )); - } - - if !is_pure_virtual { - // Create a C++ API representing the superclass implementation (allowing - // calls from Rust->C++) - let maybe_wrap = create_subclass_fn_wrapper(sub, &super_fn_name, &fun); - let super_fn_name = ApiName::new_from_qualified_name(super_fn_name); - self.analyze_and_add( - super_fn_name, - maybe_wrap, - &mut results, - TypeConversionSophistication::SimpleForSubclasses, - ); - } + if !is_pure_virtual { + // Create a C++ API representing the superclass implementation (allowing + // calls from Rust->C++) + let maybe_wrap = create_subclass_fn_wrapper(sub, &super_fn_name, &fun); + let super_fn_name = ApiName::new_from_qualified_name(super_fn_name); + self.analyze_and_add( + super_fn_name, + maybe_wrap, + &mut results, + TypeConversionSophistication::SimpleForSubclasses, + ); } } - _ => {} } results.push(Api::Function { @@ -706,47 +769,64 @@ impl<'a> FnAnalyzer<'a> { ) { let is_move = matches!(fun.special_member, Some(SpecialMemberKind::MoveConstructor)); - let (kind, method_name, trait_id) = if is_move { - ( - TraitMethodKind::MoveConstructor, - "move_new", - quote! { MoveNew }, - ) - } else { - ( - TraitMethodKind::CopyConstructor, - "copy_new", - quote! { CopyNew }, - ) - }; if let Some(constructor_suffix) = rust_name.strip_prefix(nested_type_ident) { rust_name = format!("new{}", constructor_suffix); } rust_name = predetermined_rust_name .unwrap_or_else(|| self.get_overload_name(ns, type_ident, rust_name)); let error_context = error_context_for_method(&self_ty, &rust_name); - let ty = Type::Path(self_ty.to_type_path()); - ( - FnKind::TraitMethod { - kind, - impl_for: self_ty, - details: Box::new(TraitMethodDetails { - trt: TraitImplSignature { - ty, - trait_signature: parse_quote! { - autocxx::moveit::new:: #trait_id + + let arg_is_reference = matches!( + param_details[1].conversion.unwrapped_type, + Type::Reference(_) + ); + // Some exotic forms of copy constructor have const and/or volatile qualifiers. + // These are not sufficient to implement CopyNew, so we just treat them as regular + // constructors. We detect them by their argument being translated to Pin at this + // point. + if is_move || arg_is_reference { + let (kind, method_name, trait_id) = if is_move { + ( + TraitMethodKind::MoveConstructor, + "move_new", + quote! { MoveNew }, + ) + } else { + ( + TraitMethodKind::CopyConstructor, + "copy_new", + quote! { CopyNew }, + ) + }; + let ty = Type::Path(self_ty.to_type_path()); + ( + FnKind::TraitMethod { + kind, + impl_for: self_ty, + details: Box::new(TraitMethodDetails { + trt: TraitImplSignature { + ty, + trait_signature: parse_quote! { + autocxx::moveit::new:: #trait_id + }, + unsafety: Some(parse_quote! { unsafe }), }, - unsafety: Some(parse_quote! { unsafe }), - }, - avoid_self: true, - method_name: make_ident(method_name), - parameter_reordering: Some(vec![1, 0]), - trait_call_is_unsafe: false, - }), - }, - error_context, - rust_name, - ) + avoid_self: true, + method_name: make_ident(method_name), + parameter_reordering: Some(vec![1, 0]), + trait_call_is_unsafe: false, + }), + }, + error_context, + rust_name, + ) + } else { + ( + FnKind::Method(self_ty, MethodKind::Constructor), + error_context, + rust_name, + ) + } } else if matches!(fun.special_member, Some(SpecialMemberKind::Destructor)) { rust_name = predetermined_rust_name .unwrap_or_else(|| self.get_overload_name(ns, type_ident, rust_name)); @@ -778,7 +858,10 @@ impl<'a> FnAnalyzer<'a> { // We're re-running this routine for a function we already analyzed. // Previously we made a placement "new" (MethodKind::Constructor). // This time we've asked ourselves to synthesize a make_unique. - let constructor_suffix = rust_name.strip_prefix(nested_type_ident).unwrap(); + let constructor_suffix = rust_name + .strip_prefix(nested_type_ident) + .or_else(|| rust_name.strip_prefix("new")) + .unwrap(); rust_name = format!("make_unique{}", constructor_suffix); // Strip off the 'this' arg. params = params.into_iter().skip(1).collect(); @@ -797,7 +880,14 @@ impl<'a> FnAnalyzer<'a> { // If there are multiple constructors, bindgen generates // new, new1, new2 etc. and we'll keep those suffixes. rust_name = format!("new{}", constructor_suffix); - MethodKind::Constructor + if matches!( + fun.special_member, + Some(SpecialMemberKind::DefaultConstructor) + ) { + MethodKind::DefaultConstructor + } else { + MethodKind::Constructor + } } else if is_static_method { MethodKind::Static } else { @@ -845,7 +935,7 @@ impl<'a> FnAnalyzer<'a> { // pointer not a reference. For copy + move constructors, we also // enforce Rust-side conversions to comply with moveit traits. match kind { - FnKind::Method(_, MethodKind::Constructor) => { + FnKind::Method(_, MethodKind::Constructor | MethodKind::DefaultConstructor) => { self.reanalyze_parameter( 0, fun, @@ -969,6 +1059,7 @@ impl<'a> FnAnalyzer<'a> { FnKind::Method( ref self_ty, MethodKind::Constructor + | MethodKind::DefaultConstructor | MethodKind::MakeUnique | MethodKind::Normal(..) | MethodKind::PureVirtual(..) @@ -1062,6 +1153,7 @@ impl<'a> FnAnalyzer<'a> { let wrapper_function_needed = match kind { FnKind::Method(_, MethodKind::Static) | FnKind::Method(_, MethodKind::Constructor) + | FnKind::Method(_, MethodKind::DefaultConstructor) | FnKind::Method(_, MethodKind::Virtual(_)) | FnKind::Method(_, MethodKind::PureVirtual(_)) | FnKind::TraitMethod { @@ -1096,6 +1188,7 @@ impl<'a> FnAnalyzer<'a> { (CppFunctionBody::MakeUnique, CppFunctionKind::Function) } FnKind::Method(ref self_ty, MethodKind::Constructor) + | FnKind::Method(ref self_ty, MethodKind::DefaultConstructor) | FnKind::TraitMethod { kind: TraitMethodKind::CopyConstructor | TraitMethodKind::MoveConstructor, impl_for: ref self_ty, @@ -1616,18 +1709,31 @@ impl<'a> FnAnalyzer<'a> { /// If a type has explicit constructors, bindgen will generate corresponding /// constructor functions, which we'll have already converted to make_unique methods. /// C++ mandates the synthesis of certain implicit constructors, to which we - /// need ro create bindings too. We do that here. + /// need to create bindings too. We do that here. /// It is tempting to make this a separate analysis phase, to be run later than /// the function analysis; but that would make the code much more complex as it /// would need to output a `FnAnalysisBody`. By running it as part of this phase /// we can simply generate the sort of thing bindgen generates, then ask /// the existing code in this phase to figure out what to do with it. + /// + /// Also fills out the [`FnStructAnalysis::constructors`] fields with information useful + /// for further analysis phases. fn add_missing_constructors(&mut self, apis: &mut Vec>) { - if self.config.exclude_impls { - return; - } - let implicit_constructors_needed = find_missing_constructors(apis); - for (self_ty, implicit_constructors_needed) in implicit_constructors_needed { + for (self_ty, items_found) in find_missing_constructors(apis).iter() { + if let Some(Api::Struct { + analysis: FnStructAnalysis { constructors, .. }, + .. + }) = apis.iter_mut().find(|api| api.name() == self_ty) + { + constructors.default_constructor = items_found.default_constructor.callable_any(); + constructors.copy_constructor = items_found.const_copy_constructor.callable_any() + || items_found.non_const_copy_constructor.callable_any(); + constructors.move_constructor = items_found.move_constructor.callable_any(); + constructors.destructor = items_found.destructor.callable_any(); + } + if self.config.exclude_impls { + continue; + } if self .config .is_on_constructor_blocklist(&self_ty.to_cpp_name()) @@ -1635,7 +1741,7 @@ impl<'a> FnAnalyzer<'a> { continue; } let path = self_ty.to_type_path(); - if implicit_constructors_needed.default_constructor { + if items_found.implicit_default_constructor_needed() { self.synthesize_constructor( self_ty.clone(), None, @@ -1645,7 +1751,7 @@ impl<'a> FnAnalyzer<'a> { References::default(), ); } - if implicit_constructors_needed.move_constructor { + if items_found.implicit_move_constructor_needed() { self.synthesize_constructor( self_ty.clone(), Some("move"), @@ -1658,11 +1764,7 @@ impl<'a> FnAnalyzer<'a> { }, ) } - // C++ synthesizes two different implicit copy constructors, but moveit - // supports only one, so we'll always synthesize that one. - if implicit_constructors_needed.copy_constructor_taking_const_t - || implicit_constructors_needed.copy_constructor_taking_t - { + if items_found.implicit_copy_constructor_needed() { self.synthesize_constructor( self_ty.clone(), Some("const_copy"), @@ -1675,6 +1777,16 @@ impl<'a> FnAnalyzer<'a> { }, ) } + if items_found.implicit_destructor_needed() { + self.synthesize_constructor( + self_ty.clone(), + None, + apis, + SpecialMemberKind::Destructor, + parse_quote! { this: *mut #path }, + References::default(), + ); + } } } diff --git a/engine/src/conversion/analysis/pod/mod.rs b/engine/src/conversion/analysis/pod/mod.rs index bd0d404d6..2ddb05107 100644 --- a/engine/src/conversion/analysis/pod/mod.rs +++ b/engine/src/conversion/analysis/pod/mod.rs @@ -18,15 +18,12 @@ use std::collections::{HashMap, HashSet}; use autocxx_parser::IncludeCppConfig; use byvalue_checker::ByValueChecker; -use syn::{FnArg, ItemEnum, ItemStruct, Type, TypePtr, Visibility}; +use syn::{ItemEnum, ItemStruct, Type, Visibility}; use crate::{ conversion::{ analysis::type_converter::{add_analysis, TypeConversionContext, TypeConverter}, - api::{ - AnalysisPhase, Api, ApiName, CppVisibility, FuncToConvert, SpecialMemberKind, - StructDetails, TypeKind, UnanalyzedApi, - }, + api::{AnalysisPhase, Api, ApiName, CppVisibility, StructDetails, TypeKind, UnanalyzedApi}, convert_error::{ConvertErrorWithContext, ErrorContext}, error_reporter::convert_apis, parse::BindgenSemanticAttributes, @@ -46,7 +43,6 @@ pub(crate) struct PodAnalysis { /// abstract or not. pub(crate) castable_bases: HashSet, pub(crate) field_types: HashSet, - pub(crate) movable: bool, pub(crate) is_generic: bool, } @@ -72,8 +68,6 @@ pub(crate) fn analyze_pod_apis( // a type contains a std::string or some other type which can't be // held safely by value in Rust. let byvalue_checker = ByValueChecker::new_from_apis(&apis, config)?; - // We'll also note which types have deleted move constructors. - let deleted_move_constructors = find_deleted_move_and_copy_constructors(&apis); let mut extra_apis = Vec::new(); let mut type_converter = TypeConverter::new(config, &apis); let mut results = Vec::new(); @@ -89,7 +83,6 @@ pub(crate) fn analyze_pod_apis( name, details, config, - &deleted_move_constructors, ) }, analyze_enum, @@ -111,7 +104,6 @@ pub(crate) fn analyze_pod_apis( name, details, config, - &deleted_move_constructors, ) }, analyze_enum, @@ -137,9 +129,7 @@ fn analyze_struct( name: ApiName, mut details: Box, config: &IncludeCppConfig, - deleted_move_constructors: &HashSet, ) -> Result>>, ConvertErrorWithContext> { - let movable = !deleted_move_constructors.contains(&name.name); let id = name.name.get_final_ident(); if details.vis != CppVisibility::Public { return Err(ConvertErrorWithContext( @@ -190,7 +180,6 @@ fn analyze_struct( bases: bases.into_keys().collect(), castable_bases, field_types, - movable, is_generic, }, }))) @@ -210,7 +199,16 @@ fn get_struct_field_types( match annotated { Ok(r) => { extra_apis.extend(r.extra_apis); - deps.extend(r.types_encountered); + // Skip base classes represented as fields. Anything which wants to include bases can chain + // those to the list we're building. + if !f + .ident + .as_ref() + .map(|id| id.to_string().starts_with("_base")) + .unwrap_or(false) + { + deps.extend(r.types_encountered); + } } Err(e) => convert_errors.push(e), }; @@ -235,40 +233,3 @@ fn get_bases(item: &ItemStruct) -> HashMap { }) .collect() } - -fn find_deleted_move_and_copy_constructors(apis: &[Api]) -> HashSet { - // Remove any deleted move + copy constructors from the API list and list the types - // that they construct. - apis.iter().filter_map(|api| match api { - Api::Function { ref fun, .. } => match &**fun { - FuncToConvert { - special_member: - Some(SpecialMemberKind::MoveConstructor | SpecialMemberKind::CopyConstructor), - is_deleted: true, - inputs, - .. - } => match is_a_pointer_arg(inputs.iter().next()) { - Some(ty) => Some(ty), - _ => panic!("found special constructor member with something other than a pointer first arg"), - }, - _ => None - }, - _ => None, - }).collect() -} - -/// Determine if a function argument is a pointer, and if so, to what. -/// It's unfortunate that we need to do this during the POD analysis but -/// for now, it's the best way to identify special constructor members. -fn is_a_pointer_arg(arg: Option<&FnArg>) -> Option { - arg.and_then(|arg| match arg { - FnArg::Receiver(..) => None, - FnArg::Typed(pt) => match &*pt.ty { - Type::Ptr(TypePtr { elem, .. }) => match &**elem { - Type::Path(typ) => Some(QualifiedName::from_type_path(typ)), - _ => None, - }, - _ => None, - }, - }) -} diff --git a/engine/src/conversion/analysis/remove_ignored.rs b/engine/src/conversion/analysis/remove_ignored.rs index 06378d7d0..432a53661 100644 --- a/engine/src/conversion/analysis/remove_ignored.rs +++ b/engine/src/conversion/analysis/remove_ignored.rs @@ -46,7 +46,7 @@ pub(crate) fn filter_apis_by_ignored_dependents(mut apis: Vec>) -> iterate_again = false; apis = apis .into_iter() - .map(|api| { + .filter_map(|api| { if api.deps().any(|dep| ignored_items.contains(dep)) { iterate_again = true; ignored_items.insert(api.name().clone()); @@ -60,7 +60,7 @@ pub(crate) fn filter_apis_by_ignored_dependents(mut apis: Vec>) -> if let Some(missing_dep) = first.cloned() { create_ignore_item(api, ConvertError::UnknownDependentType(missing_dep)) } else { - api + Some(api) } } }) @@ -69,13 +69,25 @@ pub(crate) fn filter_apis_by_ignored_dependents(mut apis: Vec>) -> apis } -fn create_ignore_item(api: Api, err: ConvertError) -> Api { +fn create_ignore_item(api: Api, err: ConvertError) -> Option> { let id = api.name().get_final_ident(); log::info!("Marking as ignored: {} because {}", id.to_string(), err); - Api::IgnoredItem { + Some(Api::IgnoredItem { name: api.name_info().clone(), err, ctx: match api { + Api::Function { + analysis: + FnAnalysis { + kind: FnKind::TraitMethod { .. }, + .. + }, + .. + } => { + // We're not going to generate the impl block for the trait, which means there's no + // place to attach an error. + return None; + } Api::Function { analysis: FnAnalysis { @@ -89,5 +101,5 @@ fn create_ignore_item(api: Api, err: ConvertError) -> Api { }, _ => ErrorContext::Item(id), }, - } + }) } diff --git a/engine/src/conversion/api.rs b/engine/src/conversion/api.rs index a73645c2d..d63370cb3 100644 --- a/engine/src/conversion/api.rs +++ b/engine/src/conversion/api.rs @@ -198,7 +198,7 @@ impl std::hash::Hash for TraitImplSignature { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) enum SpecialMemberKind { DefaultConstructor, CopyConstructor, diff --git a/engine/src/conversion/codegen_cpp/mod.rs b/engine/src/conversion/codegen_cpp/mod.rs index fef2fe7cb..7293e862a 100644 --- a/engine/src/conversion/codegen_cpp/mod.rs +++ b/engine/src/conversion/codegen_cpp/mod.rs @@ -432,6 +432,11 @@ impl<'a> CppCodeGenerator<'a> { CppFunctionBody::Destructor(ns, id) => { let ty_id = QualifiedName::new(ns, id.clone()); let ty_id = identifier_using_original_name_map(&ty_id, &self.original_name_map); + // If the name refers to a nested identifier, then we only want the last part + // of it to name the destructor. + let ty_id = ty_id + .rsplit_once("::") + .map_or(ty_id.as_str(), |(_, ty_id)| ty_id); (format!("{}->~{}()", arg_list, ty_id), "".to_string(), false) } CppFunctionBody::FunctionCall(ns, id) => match receiver { diff --git a/engine/src/conversion/codegen_rs/fun_codegen.rs b/engine/src/conversion/codegen_rs/fun_codegen.rs index 45a07be7a..766709a8a 100644 --- a/engine/src/conversion/codegen_rs/fun_codegen.rs +++ b/engine/src/conversion/codegen_rs/fun_codegen.rs @@ -124,7 +124,10 @@ pub(super) fn gen_function( if analysis.rust_wrapper_needed { match kind { - FnKind::Method(ref type_name, MethodKind::Constructor) => { + FnKind::Method( + ref type_name, + MethodKind::Constructor | MethodKind::DefaultConstructor, + ) => { // Constructor. impl_entry = Some(fn_generator.generate_constructor_impl(type_name)); } @@ -133,7 +136,9 @@ pub(super) fn gen_function( impl_entry = Some(fn_generator.generate_method_impl( matches!( method_kind, - MethodKind::MakeUnique | MethodKind::Constructor + MethodKind::MakeUnique + | MethodKind::Constructor + | MethodKind::DefaultConstructor ), type_name, &ret_type, diff --git a/engine/src/conversion/codegen_rs/impl_item_creator.rs b/engine/src/conversion/codegen_rs/impl_item_creator.rs index db6e1d0a9..c3501be76 100644 --- a/engine/src/conversion/codegen_rs/impl_item_creator.rs +++ b/engine/src/conversion/codegen_rs/impl_item_creator.rs @@ -15,21 +15,27 @@ use autocxx_parser::IncludeCppConfig; use syn::{parse_quote, Ident, Item}; -pub(crate) fn create_impl_items(id: &Ident, movable: bool, config: &IncludeCppConfig) -> Vec { +pub(crate) fn create_impl_items( + id: &Ident, + movable: bool, + destroyable: bool, + config: &IncludeCppConfig, +) -> Vec { if config.exclude_impls { return vec![]; } - let mut results = vec![ - Item::Impl(parse_quote! { + let mut results = Vec::new(); + if destroyable { + results.push(Item::Impl(parse_quote! { impl UniquePtr<#id> {} - }), - Item::Impl(parse_quote! { + })); + results.push(Item::Impl(parse_quote! { impl SharedPtr<#id> {} - }), - Item::Impl(parse_quote! { + })); + results.push(Item::Impl(parse_quote! { impl WeakPtr<#id> {} - }), - ]; + })); + } if movable { results.push(Item::Impl(parse_quote! { impl CxxVector<#id> {} diff --git a/engine/src/conversion/codegen_rs/mod.rs b/engine/src/conversion/codegen_rs/mod.rs index e57402bca..edd6f910f 100644 --- a/engine/src/conversion/codegen_rs/mod.rs +++ b/engine/src/conversion/codegen_rs/mod.rs @@ -49,16 +49,15 @@ use self::{ }; use super::{ - analysis::fun::PodAndDepAnalysis, + analysis::fun::{FnPhase, ReceiverMutability}, + api::{AnalysisPhase, Api, SubclassName, TypeKind, TypedefKind}, +}; +use super::{ api::{Layout, Provenance, RustSubclassFnDetails, SuperclassMethod, TraitImplSignature}, codegen_cpp::type_to_cpp::{ namespaced_name_using_original_name_map, original_name_map_from_apis, CppNameMap, }, }; -use super::{ - analysis::fun::{FnPhase, ReceiverMutability}, - api::{AnalysisPhase, Api, SubclassName, TypeKind, TypedefKind}, -}; use super::{convert_error::ErrorContext, ConvertError}; use quote::{quote, ToTokens}; @@ -491,17 +490,16 @@ impl<'a> RsCodeGenerator<'a> { ..Default::default() }, Api::Struct { - details, - analysis: PodAndDepAnalysis { pod, .. }, - .. + details, analysis, .. } => { let doc_attr = get_doc_attr(&details.item.attrs); let layout = details.layout.clone(); self.generate_type( &name, id, - pod.kind, - pod.movable, + analysis.pod.kind, + analysis.constructors.move_constructor, + analysis.constructors.destructor, || Some((Item::Struct(details.item), doc_attr)), associated_methods, layout, @@ -514,6 +512,7 @@ impl<'a> RsCodeGenerator<'a> { id, TypeKind::Pod, true, + true, || Some((Item::Enum(item), doc_attr)), associated_methods, None, @@ -524,6 +523,7 @@ impl<'a> RsCodeGenerator<'a> { 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, @@ -690,9 +690,10 @@ impl<'a> RsCodeGenerator<'a> { }); RsCodegenResult { extern_c_mod_items, - // For now we just assume we can't keep subclasses in vectors. - // That's the reason for the 'false' - bridge_items: create_impl_items(&cpp_id, false, self.config), + // 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; @@ -781,6 +782,7 @@ impl<'a> RsCodeGenerator<'a> { id: Ident, type_kind: TypeKind, movable: bool, + destroyable: bool, item_creator: F, associated_methods: &HashMap>, layout: Option, @@ -827,7 +829,7 @@ impl<'a> RsCodeGenerator<'a> { bindgen_mod_items.push(item); RsCodegenResult { global_items: self.generate_extern_type_impl(type_kind, name), - bridge_items: create_impl_items(&id, movable, self.config), + bridge_items: create_impl_items(&id, movable, destroyable, self.config), extern_c_mod_items: vec![self.generate_cxxbridge_type(name, true, None)], bindgen_mod_items, materializations, diff --git a/engine/src/known_types.rs b/engine/src/known_types.rs index 73452bad9..9b2937c54 100644 --- a/engine/src/known_types.rs +++ b/engine/src/known_types.rs @@ -43,7 +43,7 @@ struct TypeDetails { rs_name: String, /// C++ equivalent name for a Rust type. cpp_name: String, - //// The behavior of the type. + /// The behavior of the type. behavior: Behavior, /// Any extra non-canonical names extra_non_canonical_name: Option, @@ -191,6 +191,11 @@ impl TypeDatabase { self.canonical_names.keys().chain(self.by_rs_name.keys()) } + /// Returns whether the given type is a known type. + pub(crate) fn is_known(&self, qn: &QualifiedName) -> bool { + self.canonical_names.contains_key(qn) || self.by_rs_name.contains_key(qn) + } + /// Types which are known to be safe (or unsafe) to hold and pass by /// value in Rust. pub(crate) fn get_pod_safe_types(&self) -> impl Iterator { @@ -217,28 +222,12 @@ impl TypeDatabase { pod_safety.into_iter() } - pub(crate) fn all_types_with_move_constructors( - &self, - ) -> impl Iterator + '_ { - self.all_names() - .filter(|qn| self.get(qn).unwrap().has_move_constructor) - .cloned() - } - - pub(crate) fn all_types_with_const_copy_constructors( - &self, - ) -> impl Iterator + '_ { - self.all_names() - .filter(|qn| self.get(qn).unwrap().has_const_copy_constructor) - .cloned() + pub(crate) fn has_move_constructor(&self, qn: &QualifiedName) -> bool { + self.get(qn).unwrap().has_move_constructor } - pub(crate) fn all_types_without_copy_constructors( - &self, - ) -> impl Iterator + '_ { - self.all_names() - .filter(|qn| !self.get(qn).unwrap().has_const_copy_constructor) - .cloned() + pub(crate) fn has_const_copy_constructor(&self, qn: &QualifiedName) -> bool { + self.get(qn).unwrap().has_const_copy_constructor } /// Whether this TypePath should be treated as a value in C++ diff --git a/integration-tests/tests/integration_test.rs b/integration-tests/tests/integration_test.rs index da2f366cc..d12205404 100644 --- a/integration-tests/tests/integration_test.rs +++ b/integration-tests/tests/integration_test.rs @@ -2325,6 +2325,23 @@ fn test_destructor() { run_test(cxx, hdr, rs, &["WithDtor", "make_with_dtor"], &[]); } +#[test] +fn test_nested_with_destructor() { + // Regression test, naming the destructor in the generated C++ is a bit tricky. + let hdr = indoc! {" + struct A { + struct B { + B() = default; + ~B() = default; + }; + }; + "}; + let rs = quote! { + ffi::A_B::make_unique(); + }; + run_test("", hdr, rs, &["A", "A_B"], &[]); +} + // Even without a `safety!`, we still need to generate a safe `fn drop`. #[test] fn test_destructor_no_safety() { @@ -2896,6 +2913,7 @@ fn test_templated_typedef() { struct Origin { Origin() {} + ~Origin() {} StringPiece host; }; "}; @@ -2923,6 +2941,7 @@ fn test_struct_templated_typedef() { struct Origin { Origin() {} + ~Origin() {} StringPiece host; }; "}; @@ -4057,6 +4076,9 @@ fn test_typedef_to_std_in_struct() { #include typedef std::string my_string; struct A { + A() {} + ~A() {} + A(A&&) = default; my_string a; }; inline A make_a(std::string b) { @@ -4083,6 +4105,9 @@ fn test_typedef_to_up_in_struct() { #include typedef std::unique_ptr my_string; struct A { + A() {} + ~A() {} + A(A&&) = default; my_string a; }; inline A make_a(std::string b) { @@ -4627,7 +4652,8 @@ fn test_unexpected_use() { class RenderFrameHost { public: RenderFrameHost() {} - d e; + ~RenderFrameHost() {} + d e; }; } // namespace content "}; @@ -5585,7 +5611,7 @@ fn test_ignore_function_with_rvalue_ref() { fn test_overloaded_ignored_function() { // When overloaded functions are ignored during import, the placeholder // functions generated for them should have unique names, just as they - // would have if they had ben imported successfully. + // would have if they had been imported successfully. // The test is successful if the bindings compile. let hdr = indoc! {" struct Blocked {}; @@ -8351,6 +8377,1188 @@ fn test_skip_cxx_gen() { ); } +#[test] +/// Tests types with various forms of copy, move, and default constructors. Calls the things which +/// should be generated, and will produce C++ compile failures if other wrappers are generated. +/// +/// Specifically, we can have the cross product of any of these: +/// * Explicitly deleted +/// * Implicitly defaulted +/// * User declared +/// * Explicitly defaulted +/// Not handled yet: https://github.com/google/autocxx/issues/815. +/// Once this is handled, add equivalents of all the implicitly defaulted cases, at all +/// visibility levels. +/// applied to each of these: +/// * Default constructor +/// * Copy constructor +/// * Move constructor +/// in any of these: +/// * The class itself +/// * A base class +/// * A field of the class +/// * A field of a base class +/// with any of these access modifiers: +/// * private (impossible for implicitly defaulted) +/// * protected (impossible for implicitly defaulted) +/// * public +/// +/// Various combinations of these lead to the default versions being deleted. The move and copy +/// ones also interact with each other in various ways. +/// +/// TODO: Remove all the `int x` members after https://github.com/google/autocxx/issues/832 is +/// fixed. +fn test_implicit_constructor_rules() { + let cxx = ""; + let hdr = indoc! {" + struct AllImplicitlyDefaulted { + void a() const {} + }; + + struct PublicDeleted { + PublicDeleted() = delete; + PublicDeleted(const PublicDeleted&) = delete; + PublicDeleted(PublicDeleted&&) = delete; + + void a() const {} + + int x; + }; + struct PublicDeletedDefault { + PublicDeletedDefault() = delete; + + void a() const {} + + int x; + }; + struct PublicDeletedCopy { + PublicDeletedCopy() = default; + PublicDeletedCopy(const PublicDeletedCopy&) = delete; + + void a() const {} + + int x; + }; + struct PublicDeletedCopyNoDefault { + PublicDeletedCopyNoDefault(const PublicDeletedCopyNoDefault&) = delete; + + void a() const {} + + int x; + }; + struct PublicMoveDeletedCopy { + PublicMoveDeletedCopy() = default; + PublicMoveDeletedCopy(const PublicMoveDeletedCopy&) = delete; + PublicMoveDeletedCopy(PublicMoveDeletedCopy&&) = default; + + void a() const {} + + int x; + }; + struct PublicDeletedMove { + PublicDeletedMove() = default; + PublicDeletedMove(PublicDeletedMove&&) = delete; + + void a() const {} + + int x; + }; + struct PublicDeletedDestructor { + PublicDeletedDestructor() = default; + ~PublicDeletedDestructor() = delete; + + void a() const {} + + int x; + }; + struct PublicDestructor { + PublicDestructor() = default; + ~PublicDestructor() = default; + + void a() const {} + + int x; + }; + + struct ProtectedDeleted { + void a() const {} + + int x; + + protected: + ProtectedDeleted() = delete; + ProtectedDeleted(const ProtectedDeleted&) = delete; + ProtectedDeleted(ProtectedDeleted&&) = delete; + }; + struct ProtectedDeletedDefault { + void a() const {} + + int x; + + protected: + ProtectedDeletedDefault() = delete; + }; + struct ProtectedDeletedCopy { + ProtectedDeletedCopy() = default; + + void a() const {} + + int x; + + protected: + ProtectedDeletedCopy(const ProtectedDeletedCopy&) = delete; + }; + struct ProtectedDeletedCopyNoDefault { + void a() const {} + + int x; + + protected: + ProtectedDeletedCopyNoDefault(const ProtectedDeletedCopyNoDefault&) = delete; + }; + struct ProtectedMoveDeletedCopy { + ProtectedMoveDeletedCopy() = default; + + void a() const {} + + int x; + + protected: + ProtectedMoveDeletedCopy(const ProtectedMoveDeletedCopy&) = delete; + ProtectedMoveDeletedCopy(ProtectedMoveDeletedCopy&&) = default; + }; + struct ProtectedDeletedMove { + ProtectedDeletedMove() = default; + + void a() const {} + + int x; + + protected: + ProtectedDeletedMove(ProtectedDeletedMove&&) = delete; + }; + struct ProtectedDeletedDestructor { + ProtectedDeletedDestructor() = default; + + void a() const {} + + int x; + + protected: + ~ProtectedDeletedDestructor() = delete; + }; + struct ProtectedDestructor { + ProtectedDestructor() = default; + + void a() const {} + + int x; + + protected: + ~ProtectedDestructor() = default; + }; + + struct PrivateDeleted { + void a() const {} + + int x; + + private: + PrivateDeleted() = delete; + PrivateDeleted(const PrivateDeleted&) = delete; + PrivateDeleted(PrivateDeleted&&) = delete; + }; + struct PrivateDeletedDefault { + void a() const {} + + int x; + + private: + PrivateDeletedDefault() = delete; + }; + struct PrivateDeletedCopy { + PrivateDeletedCopy() = default; + + void a() const {} + + int x; + + private: + PrivateDeletedCopy(const PrivateDeletedCopy&) = delete; + }; + struct PrivateDeletedCopyNoDefault { + void a() const {} + + int x; + + private: + PrivateDeletedCopyNoDefault(const PrivateDeletedCopyNoDefault&) = delete; + }; + struct PrivateMoveDeletedCopy { + PrivateMoveDeletedCopy() = default; + + void a() const {} + + int x; + + private: + PrivateMoveDeletedCopy(const PrivateMoveDeletedCopy&) = delete; + PrivateMoveDeletedCopy(PrivateMoveDeletedCopy&&) = default; + }; + struct PrivateDeletedMove { + PrivateDeletedMove() = default; + + void a() const {} + + int x; + + private: + PrivateDeletedMove(PrivateDeletedMove&&) = delete; + }; + struct PrivateDeletedDestructor { + PrivateDeletedDestructor() = default; + + void a() const {} + + int x; + + private: + ~PrivateDeletedDestructor() = delete; + }; + struct PrivateDestructor { + PrivateDestructor() = default; + + void a() const {} + + int x; + + private: + ~PrivateDestructor() = default; + }; + + struct NonConstCopy { + NonConstCopy() = default; + + NonConstCopy(NonConstCopy&) {} + NonConstCopy(NonConstCopy&&) = default; + + void a() const {} + }; + struct TwoCopy { + TwoCopy() = default; + + TwoCopy(TwoCopy&) {} + TwoCopy(const TwoCopy&) {} + TwoCopy(TwoCopy&&) = default; + + void a() const {} + }; + + struct MemberPointerDeleted { + PublicDeleted *x; + + void a() const {} + }; + + struct MemberConstPointerDeleted { + PublicDeleted *const x; + + void a() const {} + }; + + struct MemberConst { + const int x; + + void a() const {} + }; + + struct MemberReferenceDeleted { + PublicDeleted &x; + + void a() const {} + }; + + struct MemberConstReferenceDeleted { + const PublicDeleted &x; + + void a() const {} + }; + + struct MemberReference { + int &x; + + void a() const {} + }; + + struct MemberConstReference { + const int &x; + + void a() const {} + }; + + struct MemberRvalueReferenceDeleted { + PublicDeleted &&x; + + void a() const {} + }; + + struct MemberRvalueReference { + int &&x; + + void a() const {} + }; + + struct BasePublicDeleted : public PublicDeleted {}; + struct BasePublicDeletedDefault : public PublicDeletedDefault {}; + struct BasePublicDeletedCopy : public PublicDeletedCopy {}; + struct BasePublicDeletedCopyNoDefault : public PublicDeletedCopyNoDefault { }; + struct BasePublicMoveDeletedCopy : public PublicMoveDeletedCopy {}; + struct BasePublicDeletedMove : public PublicDeletedMove {}; + struct BasePublicDeletedDestructor : public PublicDeletedDestructor {}; + struct BasePublicDestructor : public PublicDestructor {}; + + struct MemberPublicDeleted { + void a() const {} + + PublicDeleted member; + }; + struct MemberPublicDeletedDefault { + void a() const {} + + PublicDeletedDefault member; + }; + struct MemberPublicDeletedCopy { + void a() const {} + + PublicDeletedCopy member; + }; + struct MemberPublicDeletedCopyNoDefault { + void a() const {} + + PublicDeletedCopyNoDefault member; + }; + struct MemberPublicMoveDeletedCopy { + void a() const {} + + PublicMoveDeletedCopy member; + }; + struct MemberPublicDeletedMove { + void a() const {} + + PublicDeletedMove member; + }; + struct MemberPublicDeletedDestructor { + void a() const {} + + PublicDeletedDestructor member; + }; + struct MemberPublicDestructor { + void a() const {} + + PublicDestructor member; + }; + + struct BaseMemberPublicDeleted : public MemberPublicDeleted {}; + struct BaseMemberPublicDeletedDefault : public MemberPublicDeletedDefault {}; + struct BaseMemberPublicDeletedCopy : public MemberPublicDeletedCopy {}; + struct BaseMemberPublicDeletedCopyNoDefault : public MemberPublicDeletedCopyNoDefault {}; + struct BaseMemberPublicMoveDeletedCopy : public MemberPublicMoveDeletedCopy {}; + struct BaseMemberPublicDeletedMove : public MemberPublicDeletedMove {}; + struct BaseMemberPublicDeletedDestructor : public MemberPublicDeletedDestructor {}; + struct BaseMemberPublicDestructor : public MemberPublicDestructor {}; + + struct BaseProtectedDeleted : public ProtectedDeleted {}; + struct BaseProtectedDeletedDefault : public ProtectedDeletedDefault {}; + struct BaseProtectedDeletedCopy : public ProtectedDeletedCopy {}; + struct BaseProtectedDeletedCopyNoDefault : public ProtectedDeletedCopyNoDefault {}; + struct BaseProtectedMoveDeletedCopy : public ProtectedMoveDeletedCopy {}; + struct BaseProtectedDeletedMove : public ProtectedDeletedMove {}; + struct BaseProtectedDeletedDestructor : public ProtectedDeletedDestructor {}; + struct BaseProtectedDestructor : public ProtectedDestructor {}; + + struct MemberProtectedDeleted { + void a() const {} + + ProtectedDeleted member; + }; + struct MemberProtectedDeletedDefault { + void a() const {} + + ProtectedDeletedDefault member; + }; + struct MemberProtectedDeletedCopy { + void a() const {} + + ProtectedDeletedCopy member; + }; + struct MemberProtectedDeletedCopyNoDefault { + void a() const {} + + ProtectedDeletedCopyNoDefault member; + }; + struct MemberProtectedMoveDeletedCopy { + void a() const {} + + ProtectedMoveDeletedCopy member; + }; + struct MemberProtectedDeletedMove { + void a() const {} + + ProtectedDeletedMove member; + }; + struct MemberProtectedDeletedDestructor { + void a() const {} + + ProtectedDeletedDestructor member; + }; + struct MemberProtectedDestructor { + void a() const {} + + ProtectedDestructor member; + }; + + struct BaseMemberProtectedDeleted : public MemberProtectedDeleted {}; + struct BaseMemberProtectedDeletedDefault : public MemberProtectedDeletedDefault {}; + struct BaseMemberProtectedDeletedCopy : public MemberProtectedDeletedCopy {}; + struct BaseMemberProtectedDeletedCopyNoDefault : public MemberProtectedDeletedCopyNoDefault {}; + struct BaseMemberProtectedMoveDeletedCopy : public MemberProtectedMoveDeletedCopy {}; + struct BaseMemberProtectedDeletedMove : public MemberProtectedDeletedMove {}; + struct BaseMemberProtectedDeletedDestructor : public MemberProtectedDeletedDestructor {}; + struct BaseMemberProtectedDestructor : public MemberProtectedDestructor {}; + + struct BasePrivateDeleted : public PrivateDeleted {}; + struct BasePrivateDeletedDefault : public PrivateDeletedDefault {}; + struct BasePrivateDeletedCopy : public PrivateDeletedCopy {}; + struct BasePrivateDeletedCopyNoDefault : public PrivateDeletedCopyNoDefault {}; + struct BasePrivateMoveDeletedCopy : public PrivateMoveDeletedCopy {}; + struct BasePrivateDeletedMove : public PrivateDeletedMove {}; + struct BasePrivateDeletedDestructor : public PrivateDeletedDestructor {}; + struct BasePrivateDestructor : public PrivateDestructor {}; + + struct MemberPrivateDeleted { + void a() const {} + + PrivateDeleted member; + }; + struct MemberPrivateDeletedDefault { + void a() const {} + + PrivateDeletedDefault member; + }; + struct MemberPrivateDeletedCopy { + void a() const {} + + PrivateDeletedCopy member; + }; + struct MemberPrivateDeletedCopyNoDefault { + void a() const {} + + PrivateDeletedCopyNoDefault member; + }; + struct MemberPrivateMoveDeletedCopy { + void a() const {} + + PrivateMoveDeletedCopy member; + }; + struct MemberPrivateDeletedMove { + void a() const {} + + PrivateDeletedMove member; + }; + struct MemberPrivateDeletedDestructor { + void a() const {} + + PrivateDeletedDestructor member; + }; + struct MemberPrivateDestructor { + void a() const {} + + PrivateDestructor member; + }; + + struct BaseMemberPrivateDeleted : public MemberPrivateDeleted {}; + struct BaseMemberPrivateDeletedDefault : public MemberPrivateDeletedDefault {}; + struct BaseMemberPrivateDeletedCopy : public MemberPrivateDeletedCopy {}; + struct BaseMemberPrivateDeletedCopyNoDefault : public MemberPrivateDeletedCopyNoDefault {}; + struct BaseMemberPrivateMoveDeletedCopy : public MemberPrivateMoveDeletedCopy {}; + struct BaseMemberPrivateDeletedMove : public MemberPrivateDeletedMove {}; + struct BaseMemberPrivateDeletedDestructor : public MemberPrivateDeletedDestructor {}; + struct BaseMemberPrivateDestructor : public MemberPrivateDestructor {}; + "}; + let rs = quote! { + // Some macros to test various operations on our types. Note that some of them define + // functions which take arguments that the APIs defined in this test have no way to + // produce, because we have C++ types which can't be constructed (for example). In a real + // program, there might be other C++ APIs which can instantiate these types. + + // TODO: https://github.com/google/autocxx/issues/829: Should this be merged with + // `test_make_unique`? Currently types where the Rust wrappers permit this but not that + // aren't running C++ destructors. + macro_rules! test_constructible { + [$t:ty] => { + moveit! { + let _moveit_t = <$t>::new(); + } + } + } + macro_rules! test_make_unique { + [$t:ty] => { + let _unique_t = <$t>::make_unique(); + } + } + macro_rules! test_copyable { + [$t:ty] => { + { + fn test_copyable(moveit_t: impl autocxx::moveit::new::New) { + moveit! { + let moveit_t = moveit_t; + let _copied_t = autocxx::moveit::new::copy(moveit_t); + } + } + } + } + } + macro_rules! test_movable { + [$t:ty] => { + { + fn test_movable(moveit_t: impl autocxx::moveit::new::New) { + moveit! { + let moveit_t = moveit_t; + let _moved_t = autocxx::moveit::new::mov(moveit_t); + } + } + } + } + } + macro_rules! test_call_a { + [$t:ty] => { + { + fn test_call_a(t: &$t) { + t.a(); + } + } + } + } + macro_rules! test_call_a_as { + [$t:ty, $parent:ty] => { + { + fn test_call_a(t: &$t) { + let t: &$parent = t.as_ref(); + t.a(); + } + } + } + } + + test_constructible![ffi::AllImplicitlyDefaulted]; + test_make_unique![ffi::AllImplicitlyDefaulted]; + test_copyable![ffi::AllImplicitlyDefaulted]; + test_movable![ffi::AllImplicitlyDefaulted]; + test_call_a![ffi::AllImplicitlyDefaulted]; + + test_call_a![ffi::PublicDeleted]; + + test_copyable![ffi::PublicDeletedDefault]; + test_movable![ffi::PublicDeletedDefault]; + test_call_a![ffi::PublicDeletedDefault]; + + test_constructible![ffi::PublicDeletedCopy]; + test_make_unique![ffi::PublicDeletedCopy]; + test_call_a![ffi::PublicDeletedCopy]; + + test_call_a![ffi::PublicDeletedCopyNoDefault]; + + test_constructible![ffi::PublicMoveDeletedCopy]; + test_make_unique![ffi::PublicMoveDeletedCopy]; + test_movable![ffi::PublicMoveDeletedCopy]; + test_call_a![ffi::PublicMoveDeletedCopy]; + + test_constructible![ffi::PublicDeletedMove]; + test_make_unique![ffi::PublicDeletedMove]; + test_call_a![ffi::PublicDeletedMove]; + + test_constructible![ffi::PublicDeletedDestructor]; + test_copyable![ffi::PublicDeletedDestructor]; + test_call_a![ffi::PublicDeletedDestructor]; + + test_constructible![ffi::PublicDestructor]; + test_make_unique![ffi::PublicDestructor]; + test_copyable![ffi::PublicDestructor]; + test_call_a![ffi::PublicDestructor]; + + test_call_a![ffi::ProtectedDeleted]; + + test_copyable![ffi::ProtectedDeletedDefault]; + test_movable![ffi::ProtectedDeletedDefault]; + test_call_a![ffi::ProtectedDeletedDefault]; + + test_constructible![ffi::ProtectedDeletedCopy]; + test_make_unique![ffi::ProtectedDeletedCopy]; + test_call_a![ffi::ProtectedDeletedCopy]; + + test_call_a![ffi::ProtectedDeletedCopyNoDefault]; + + test_constructible![ffi::ProtectedMoveDeletedCopy]; + test_make_unique![ffi::ProtectedMoveDeletedCopy]; + test_call_a![ffi::ProtectedMoveDeletedCopy]; + + test_constructible![ffi::ProtectedDeletedMove]; + test_make_unique![ffi::ProtectedDeletedMove]; + test_call_a![ffi::ProtectedDeletedMove]; + + test_constructible![ffi::ProtectedDeletedDestructor]; + test_copyable![ffi::ProtectedDeletedDestructor]; + test_call_a![ffi::ProtectedDeletedDestructor]; + + test_constructible![ffi::ProtectedDestructor]; + test_copyable![ffi::ProtectedDestructor]; + test_call_a![ffi::ProtectedDestructor]; + + test_call_a![ffi::PrivateDeleted]; + + test_copyable![ffi::PrivateDeletedDefault]; + test_movable![ffi::PrivateDeletedDefault]; + test_call_a![ffi::PrivateDeletedDefault]; + + test_constructible![ffi::PrivateDeletedCopy]; + test_make_unique![ffi::PrivateDeletedCopy]; + test_call_a![ffi::PrivateDeletedCopy]; + + test_call_a![ffi::PrivateDeletedCopyNoDefault]; + + test_constructible![ffi::PrivateMoveDeletedCopy]; + test_make_unique![ffi::PrivateMoveDeletedCopy]; + test_call_a![ffi::PrivateMoveDeletedCopy]; + + test_constructible![ffi::PrivateDeletedMove]; + test_make_unique![ffi::PrivateDeletedMove]; + test_call_a![ffi::PrivateDeletedMove]; + + test_constructible![ffi::PrivateDeletedDestructor]; + test_copyable![ffi::PrivateDeletedDestructor]; + test_call_a![ffi::PrivateDeletedDestructor]; + + test_constructible![ffi::PrivateDestructor]; + test_copyable![ffi::PrivateDestructor]; + test_call_a![ffi::PrivateDestructor]; + + test_constructible![ffi::NonConstCopy]; + test_make_unique![ffi::NonConstCopy]; + test_movable![ffi::NonConstCopy]; + test_call_a![ffi::NonConstCopy]; + + test_constructible![ffi::TwoCopy]; + test_make_unique![ffi::TwoCopy]; + test_copyable![ffi::TwoCopy]; + test_movable![ffi::TwoCopy]; + test_call_a![ffi::TwoCopy]; + + // TODO: Need to differentiate pointers from the types they point to. + //test_constructible![ffi::MemberPointerDeleted]; + //test_make_unique![ffi::MemberPointerDeleted]; + //test_copyable![ffi::MemberPointerDeleted]; + //test_movable![ffi::MemberPointerDeleted]; + test_call_a![ffi::MemberPointerDeleted]; + + test_call_a_as![ffi::BasePublicDeleted, ffi::PublicDeleted]; + + test_copyable![ffi::BasePublicDeletedDefault]; + test_movable![ffi::BasePublicDeletedDefault]; + test_call_a_as![ffi::BasePublicDeletedDefault, ffi::PublicDeletedDefault]; + + test_constructible![ffi::BasePublicDeletedCopy]; + test_make_unique![ffi::BasePublicDeletedCopy]; + test_call_a_as![ffi::BasePublicDeletedCopy, ffi::PublicDeletedCopy]; + + test_call_a_as![ffi::BasePublicDeletedCopyNoDefault, ffi::PublicDeletedCopyNoDefault]; + + test_constructible![ffi::BasePublicMoveDeletedCopy]; + test_make_unique![ffi::BasePublicMoveDeletedCopy]; + test_movable![ffi::BasePublicMoveDeletedCopy]; + test_call_a_as![ffi::BasePublicMoveDeletedCopy, ffi::PublicMoveDeletedCopy]; + + test_constructible![ffi::BasePublicDeletedMove]; + test_make_unique![ffi::BasePublicDeletedMove]; + test_call_a_as![ffi::BasePublicDeletedMove, ffi::PublicDeletedMove]; + + test_call_a_as![ffi::BasePublicDeletedDestructor, ffi::PublicDeletedDestructor]; + + test_constructible![ffi::BasePublicDestructor]; + test_make_unique![ffi::BasePublicDestructor]; + test_copyable![ffi::BasePublicDestructor]; + test_call_a_as![ffi::BasePublicDestructor, ffi::PublicDestructor]; + + test_call_a![ffi::MemberPublicDeleted]; + + test_copyable![ffi::MemberPublicDeletedDefault]; + test_movable![ffi::MemberPublicDeletedDefault]; + test_call_a![ffi::MemberPublicDeletedDefault]; + + test_constructible![ffi::MemberPublicDeletedCopy]; + test_make_unique![ffi::MemberPublicDeletedCopy]; + test_call_a![ffi::MemberPublicDeletedCopy]; + + test_call_a![ffi::MemberPublicDeletedCopyNoDefault]; + + test_constructible![ffi::MemberPublicMoveDeletedCopy]; + test_make_unique![ffi::MemberPublicMoveDeletedCopy]; + test_movable![ffi::MemberPublicMoveDeletedCopy]; + test_call_a![ffi::MemberPublicMoveDeletedCopy]; + + test_constructible![ffi::MemberPublicDeletedMove]; + test_make_unique![ffi::MemberPublicDeletedMove]; + test_call_a![ffi::MemberPublicDeletedMove]; + + test_call_a![ffi::MemberPublicDeletedDestructor]; + + test_constructible![ffi::MemberPublicDestructor]; + test_make_unique![ffi::MemberPublicDestructor]; + test_copyable![ffi::MemberPublicDestructor]; + test_call_a![ffi::MemberPublicDestructor]; + + test_call_a_as![ffi::BaseMemberPublicDeleted, ffi::MemberPublicDeleted]; + + test_copyable![ffi::BaseMemberPublicDeletedDefault]; + test_movable![ffi::BaseMemberPublicDeletedDefault]; + test_call_a_as![ffi::BaseMemberPublicDeletedDefault, ffi::MemberPublicDeletedDefault]; + + test_constructible![ffi::BaseMemberPublicDeletedCopy]; + test_make_unique![ffi::BaseMemberPublicDeletedCopy]; + test_call_a_as![ffi::BaseMemberPublicDeletedCopy, ffi::MemberPublicDeletedCopy]; + + test_call_a_as![ffi::BaseMemberPublicDeletedCopyNoDefault, ffi::MemberPublicDeletedCopyNoDefault]; + + test_constructible![ffi::BaseMemberPublicMoveDeletedCopy]; + test_make_unique![ffi::BaseMemberPublicMoveDeletedCopy]; + test_movable![ffi::BaseMemberPublicMoveDeletedCopy]; + test_call_a_as![ffi::BaseMemberPublicMoveDeletedCopy, ffi::MemberPublicMoveDeletedCopy]; + + test_constructible![ffi::BaseMemberPublicDeletedMove]; + test_make_unique![ffi::BaseMemberPublicDeletedMove]; + test_call_a_as![ffi::BaseMemberPublicDeletedMove, ffi::MemberPublicDeletedMove]; + + test_call_a_as![ffi::BaseMemberPublicDeletedDestructor, ffi::MemberPublicDeletedDestructor]; + + test_constructible![ffi::BaseMemberPublicDestructor]; + test_make_unique![ffi::BaseMemberPublicDestructor]; + test_copyable![ffi::BaseMemberPublicDestructor]; + test_call_a_as![ffi::BaseMemberPublicDestructor, ffi::MemberPublicDestructor]; + + test_call_a_as![ffi::BaseProtectedDeleted, ffi::ProtectedDeleted]; + + test_copyable![ffi::BaseProtectedDeletedDefault]; + test_movable![ffi::BaseProtectedDeletedDefault]; + test_call_a_as![ffi::BaseProtectedDeletedDefault, ffi::ProtectedDeletedDefault]; + + test_constructible![ffi::BaseProtectedDeletedCopy]; + test_make_unique![ffi::BaseProtectedDeletedCopy]; + test_call_a_as![ffi::BaseProtectedDeletedCopy, ffi::ProtectedDeletedCopy]; + + test_call_a_as![ffi::BaseProtectedDeletedCopyNoDefault, ffi::ProtectedDeletedCopyNoDefault]; + + test_constructible![ffi::BaseProtectedMoveDeletedCopy]; + test_make_unique![ffi::BaseProtectedMoveDeletedCopy]; + test_movable![ffi::BaseProtectedMoveDeletedCopy]; + test_call_a_as![ffi::BaseProtectedMoveDeletedCopy, ffi::ProtectedMoveDeletedCopy]; + + test_constructible![ffi::BaseProtectedDeletedMove]; + test_make_unique![ffi::BaseProtectedDeletedMove]; + test_call_a_as![ffi::BaseProtectedDeletedMove, ffi::ProtectedDeletedMove]; + + test_call_a_as![ffi::BaseProtectedDeletedDestructor, ffi::ProtectedDeletedDestructor]; + + test_constructible![ffi::BaseProtectedDestructor]; + test_make_unique![ffi::BaseProtectedDestructor]; + test_copyable![ffi::BaseProtectedDestructor]; + test_call_a_as![ffi::BaseProtectedDestructor, ffi::ProtectedDestructor]; + + test_call_a![ffi::MemberProtectedDeleted]; + + test_copyable![ffi::MemberProtectedDeletedDefault]; + test_movable![ffi::MemberProtectedDeletedDefault]; + test_call_a![ffi::MemberProtectedDeletedDefault]; + + test_constructible![ffi::MemberProtectedDeletedCopy]; + test_make_unique![ffi::MemberProtectedDeletedCopy]; + test_call_a![ffi::MemberProtectedDeletedCopy]; + + test_call_a![ffi::MemberProtectedDeletedCopyNoDefault]; + + test_constructible![ffi::MemberProtectedMoveDeletedCopy]; + test_make_unique![ffi::MemberProtectedMoveDeletedCopy]; + test_call_a![ffi::MemberProtectedMoveDeletedCopy]; + + test_constructible![ffi::MemberProtectedDeletedMove]; + test_make_unique![ffi::MemberProtectedDeletedMove]; + test_call_a![ffi::MemberProtectedDeletedMove]; + + test_call_a![ffi::MemberProtectedDeletedDestructor]; + + test_call_a![ffi::MemberProtectedDestructor]; + + test_call_a_as![ffi::BaseMemberProtectedDeleted, ffi::MemberProtectedDeleted]; + + test_copyable![ffi::BaseMemberProtectedDeletedDefault]; + test_movable![ffi::BaseMemberProtectedDeletedDefault]; + test_call_a_as![ffi::BaseMemberProtectedDeletedDefault, ffi::MemberProtectedDeletedDefault]; + + test_constructible![ffi::BaseMemberProtectedDeletedCopy]; + test_make_unique![ffi::BaseMemberProtectedDeletedCopy]; + test_call_a_as![ffi::BaseMemberProtectedDeletedCopy, ffi::MemberProtectedDeletedCopy]; + + test_call_a_as![ffi::BaseMemberProtectedDeletedCopyNoDefault, ffi::MemberProtectedDeletedCopyNoDefault]; + + test_constructible![ffi::BaseMemberProtectedMoveDeletedCopy]; + test_make_unique![ffi::BaseMemberProtectedMoveDeletedCopy]; + test_call_a_as![ffi::BaseMemberProtectedMoveDeletedCopy, ffi::MemberProtectedMoveDeletedCopy]; + + test_constructible![ffi::BaseMemberProtectedDeletedMove]; + test_make_unique![ffi::BaseMemberProtectedDeletedMove]; + test_call_a_as![ffi::BaseMemberProtectedDeletedMove, ffi::MemberProtectedDeletedMove]; + + test_call_a_as![ffi::BaseMemberProtectedDeletedDestructor, ffi::MemberProtectedDeletedDestructor]; + + test_call_a_as![ffi::BaseMemberProtectedDestructor, ffi::MemberProtectedDestructor]; + + test_call_a_as![ffi::BasePrivateDeleted, ffi::PrivateDeleted]; + + test_copyable![ffi::BasePrivateDeletedDefault]; + test_movable![ffi::BasePrivateDeletedDefault]; + test_call_a_as![ffi::BasePrivateDeletedDefault, ffi::PrivateDeletedDefault]; + + test_constructible![ffi::BasePrivateDeletedCopy]; + test_make_unique![ffi::BasePrivateDeletedCopy]; + test_call_a_as![ffi::BasePrivateDeletedCopy, ffi::PrivateDeletedCopy]; + + test_call_a_as![ffi::BasePrivateDeletedCopyNoDefault, ffi::PrivateDeletedCopyNoDefault]; + + test_constructible![ffi::BasePrivateMoveDeletedCopy]; + test_make_unique![ffi::BasePrivateMoveDeletedCopy]; + test_call_a_as![ffi::BasePrivateMoveDeletedCopy, ffi::PrivateMoveDeletedCopy]; + + test_constructible![ffi::BasePrivateDeletedMove]; + test_make_unique![ffi::BasePrivateDeletedMove]; + test_call_a_as![ffi::BasePrivateDeletedMove, ffi::PrivateDeletedMove]; + + test_call_a_as![ffi::BasePrivateDeletedDestructor, ffi::PrivateDeletedDestructor]; + + test_call_a_as![ffi::BasePrivateDestructor, ffi::PrivateDestructor]; + + test_call_a![ffi::MemberPrivateDeleted]; + + test_copyable![ffi::MemberPrivateDeletedDefault]; + test_movable![ffi::MemberPrivateDeletedDefault]; + test_call_a![ffi::MemberPrivateDeletedDefault]; + + test_constructible![ffi::MemberPrivateDeletedCopy]; + test_make_unique![ffi::MemberPrivateDeletedCopy]; + test_call_a![ffi::MemberPrivateDeletedCopy]; + + test_call_a![ffi::MemberPrivateDeletedCopyNoDefault]; + + test_constructible![ffi::MemberPrivateMoveDeletedCopy]; + test_make_unique![ffi::MemberPrivateMoveDeletedCopy]; + test_call_a![ffi::MemberPrivateMoveDeletedCopy]; + + test_constructible![ffi::MemberPrivateDeletedMove]; + test_make_unique![ffi::MemberPrivateDeletedMove]; + test_call_a![ffi::MemberPrivateDeletedMove]; + + test_call_a![ffi::MemberPrivateDeletedDestructor]; + + test_call_a![ffi::MemberPrivateDestructor]; + + test_call_a_as![ffi::BaseMemberPrivateDeleted, ffi::MemberPrivateDeleted]; + + test_copyable![ffi::BaseMemberPrivateDeletedDefault]; + test_movable![ffi::BaseMemberPrivateDeletedDefault]; + test_call_a_as![ffi::BaseMemberPrivateDeletedDefault, ffi::MemberPrivateDeletedDefault]; + + test_constructible![ffi::BaseMemberPrivateDeletedCopy]; + test_make_unique![ffi::BaseMemberPrivateDeletedCopy]; + test_call_a_as![ffi::BaseMemberPrivateDeletedCopy, ffi::MemberPrivateDeletedCopy]; + + test_call_a_as![ffi::BaseMemberPrivateDeletedCopyNoDefault, ffi::MemberPrivateDeletedCopyNoDefault]; + + test_constructible![ffi::BaseMemberPrivateMoveDeletedCopy]; + test_make_unique![ffi::BaseMemberPrivateMoveDeletedCopy]; + test_call_a_as![ffi::BaseMemberPrivateMoveDeletedCopy, ffi::MemberPrivateMoveDeletedCopy]; + + test_constructible![ffi::BaseMemberPrivateDeletedMove]; + test_make_unique![ffi::BaseMemberPrivateDeletedMove]; + test_call_a_as![ffi::BaseMemberPrivateDeletedMove, ffi::MemberPrivateDeletedMove]; + + test_call_a_as![ffi::BaseMemberPrivateDeletedDestructor, ffi::MemberPrivateDeletedDestructor]; + + test_call_a_as![ffi::BaseMemberPrivateDestructor, ffi::MemberPrivateDestructor]; + }; + run_test( + cxx, + hdr, + rs, + &[ + "AllImplicitlyDefaulted", + "PublicDeleted", + "PublicDeletedDefault", + "PublicDeletedCopy", + "PublicDeletedCopyNoDefault", + "PublicMoveDeletedCopy", + "PublicDeletedMove", + "PublicDeletedDestructor", + "PublicDestructor", + "ProtectedDeleted", + "ProtectedDeletedDefault", + "ProtectedDeletedCopy", + "ProtectedDeletedCopyNoDefault", + "ProtectedMoveDeletedCopy", + "ProtectedDeletedMove", + "ProtectedDeletedDestructor", + "ProtectedDestructor", + "PrivateDeleted", + "PrivateDeletedDefault", + "PrivateDeletedCopy", + "PrivateDeletedCopyNoDefault", + "PrivateMoveDeletedCopy", + "PrivateDeletedMove", + "PrivateDeletedDestructor", + "PrivateDestructor", + "NonConstCopy", + "TwoCopy", + "MemberPointerDeleted", + // TODO: Need to handle const and/or reference members without + // default initializers. + //"MemberConstPointerDeleted", + //"MemberConst", + //"MemberReferenceDeleted", + //"MemberConstReferenceDeleted", + //"MemberReference", + //"MemberConstReference", + //"MemberRvalueReferenceDeleted", + //"MemberRvalueReference", + "BasePublicDeleted", + "BasePublicDeletedDefault", + "BasePublicDeletedCopy", + "BasePublicDeletedCopyNoDefault", + "BasePublicMoveDeletedCopy", + "BasePublicDeletedMove", + "BasePublicDeletedDestructor", + "BasePublicDestructor", + "MemberPublicDeleted", + "MemberPublicDeletedDefault", + "MemberPublicDeletedCopy", + "MemberPublicDeletedCopyNoDefault", + "MemberPublicMoveDeletedCopy", + "MemberPublicDeletedMove", + "MemberPublicDeletedDestructor", + "MemberPublicDestructor", + "BaseMemberPublicDeleted", + "BaseMemberPublicDeletedDefault", + "BaseMemberPublicDeletedCopy", + "BaseMemberPublicDeletedCopyNoDefault", + "BaseMemberPublicMoveDeletedCopy", + "BaseMemberPublicDeletedMove", + "BaseMemberPublicDeletedDestructor", + "BaseMemberPublicDestructor", + "BaseProtectedDeleted", + "BaseProtectedDeletedDefault", + "BaseProtectedDeletedCopy", + "BaseProtectedDeletedCopyNoDefault", + "BaseProtectedMoveDeletedCopy", + "BaseProtectedDeletedMove", + "BaseProtectedDeletedDestructor", + "BaseProtectedDestructor", + "MemberProtectedDeleted", + "MemberProtectedDeletedDefault", + "MemberProtectedDeletedCopy", + "MemberProtectedDeletedCopyNoDefault", + "MemberProtectedMoveDeletedCopy", + "MemberProtectedDeletedMove", + "MemberProtectedDeletedDestructor", + "MemberProtectedDestructor", + "BaseMemberProtectedDeleted", + "BaseMemberProtectedDeletedDefault", + "BaseMemberProtectedDeletedCopy", + "BaseMemberProtectedDeletedCopyNoDefault", + "BaseMemberProtectedMoveDeletedCopy", + "BaseMemberProtectedDeletedMove", + "BaseMemberProtectedDeletedDestructor", + "BaseMemberProtectedDestructor", + "BasePrivateDeleted", + "BasePrivateDeletedDefault", + "BasePrivateDeletedCopy", + "BasePrivateDeletedCopyNoDefault", + "BasePrivateMoveDeletedCopy", + "BasePrivateDeletedMove", + "BasePrivateDeletedDestructor", + "BasePrivateDestructor", + "MemberPrivateDeleted", + "MemberPrivateDeletedDefault", + "MemberPrivateDeletedCopy", + "MemberPrivateDeletedCopyNoDefault", + "MemberPrivateMoveDeletedCopy", + "MemberPrivateDeletedMove", + "MemberPrivateDeletedDestructor", + "MemberPrivateDestructor", + "BaseMemberPrivateDeleted", + "BaseMemberPrivateDeletedDefault", + "BaseMemberPrivateDeletedCopy", + "BaseMemberPrivateDeletedCopyNoDefault", + "BaseMemberPrivateMoveDeletedCopy", + "BaseMemberPrivateDeletedMove", + "BaseMemberPrivateDeletedDestructor", + "BaseMemberPrivateDestructor", + ], + &[], + ); +} + +#[test] +/// Test that destructors hidden in various places are correctly called. +/// +/// Some types are excluded because we know they behave poorly due to +/// https://github.com/google/autocxx/issues/829. +fn test_tricky_destructors() { + let cxx = ""; + let hdr = indoc! {" + #include + #include + // A simple type to let Rust verify the destructor is run. + struct DestructorFlag { + DestructorFlag() = default; + DestructorFlag(const DestructorFlag&) = default; + DestructorFlag(DestructorFlag&&) = default; + + ~DestructorFlag() { + if (!flag) return; + if (*flag) { + fprintf(stderr, \"DestructorFlag is already set\\n\"); + abort(); + } + *flag = true; + // Note we deliberately do NOT clear the value of `flag`, to catch Rust calling + // this destructor twice. + } + + bool *flag = nullptr; + }; + + struct ImplicitlyDefaulted { + DestructorFlag flag; + + void set_flag(bool *flag_pointer) { flag.flag = flag_pointer; } + }; + struct ExplicitlyDefaulted { + ExplicitlyDefaulted() = default; + ~ExplicitlyDefaulted() = default; + + DestructorFlag flag; + + void set_flag(bool *flag_pointer) { flag.flag = flag_pointer; } + }; + struct Explicit { + Explicit() = default; + ~Explicit() {} + + DestructorFlag flag; + + void set_flag(bool *flag_pointer) { flag.flag = flag_pointer; } + }; + + struct BaseImplicitlyDefaulted : public ImplicitlyDefaulted { + void set_flag(bool *flag_pointer) { ImplicitlyDefaulted::set_flag(flag_pointer); } + }; + struct BaseExplicitlyDefaulted : public ExplicitlyDefaulted { + void set_flag(bool *flag_pointer) { ExplicitlyDefaulted::set_flag(flag_pointer); } + }; + struct BaseExplicit : public Explicit { + void set_flag(bool *flag_pointer) { Explicit::set_flag(flag_pointer); } + }; + + struct MemberImplicitlyDefaulted { + ImplicitlyDefaulted member; + + void set_flag(bool *flag_pointer) { member.set_flag(flag_pointer); } + }; + struct MemberExplicitlyDefaulted { + ExplicitlyDefaulted member; + + void set_flag(bool *flag_pointer) { member.set_flag(flag_pointer); } + }; + struct MemberExplicit { + Explicit member; + + void set_flag(bool *flag_pointer) { member.set_flag(flag_pointer); } + }; + + struct BaseMemberImplicitlyDefaulted : public MemberImplicitlyDefaulted { + void set_flag(bool *flag_pointer) { MemberImplicitlyDefaulted::set_flag(flag_pointer); } + }; + struct BaseMemberExplicitlyDefaulted : public MemberExplicitlyDefaulted { + void set_flag(bool *flag_pointer) { MemberExplicitlyDefaulted::set_flag(flag_pointer); } + }; + struct BaseMemberExplicit : public MemberExplicit { + void set_flag(bool *flag_pointer) { MemberExplicit::set_flag(flag_pointer); } + }; + "}; + let rs = quote! { + macro_rules! test_type { + [$t:ty] => { + let mut unique_t = <$t>::make_unique(); + let mut destructor_flag = false; + unsafe { + unique_t.pin_mut().set_flag(&mut destructor_flag); + } + std::mem::drop(unique_t); + assert!(destructor_flag, "Destructor did not run with make_unique for {}", quote::quote!{$t}); + + moveit! { + let mut moveit_t = <$t>::new(); + } + let mut destructor_flag = false; + unsafe { + moveit_t.as_mut().set_flag(&mut destructor_flag); + } + std::mem::drop(moveit_t); + assert!(destructor_flag, "Destructor did not run with moveit for {}", quote::quote!{$t}); + } + } + + test_type![ffi::ImplicitlyDefaulted]; + test_type![ffi::ExplicitlyDefaulted]; + test_type![ffi::Explicit]; + test_type![ffi::BaseImplicitlyDefaulted]; + test_type![ffi::BaseExplicitlyDefaulted]; + test_type![ffi::BaseExplicit]; + test_type![ffi::MemberImplicitlyDefaulted]; + test_type![ffi::MemberExplicitlyDefaulted]; + test_type![ffi::MemberExplicit]; + test_type![ffi::BaseMemberImplicitlyDefaulted]; + test_type![ffi::BaseMemberExplicitlyDefaulted]; + test_type![ffi::BaseMemberExplicit]; + }; + run_test( + cxx, + hdr, + rs, + &[ + "DestructorFlag", + "ImplicitlyDefaulted", + "ExplicitlyDefaulted", + "Explicit", + "BaseImplicitlyDefaulted", + "BaseExplicitlyDefaulted", + "BaseExplicit", + "MemberImplicitlyDefaulted", + "MemberExplicitlyDefaulted", + "MemberExplicit", + "BaseMemberImplicitlyDefaulted", + "BaseMemberExplicitlyDefaulted", + "BaseMemberExplicit", + ], + &[], + ); +} + // Yet to test: // - Ifdef // - Out param pointers