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