| // Copyright 2020 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| |
| mod bridge_name_tracker; |
| pub(crate) mod function_wrapper; |
| mod implicit_constructors; |
| mod overload_tracker; |
| mod subclass; |
| |
| use crate::{ |
| conversion::{ |
| analysis::{ |
| fun::function_wrapper::{CppConversionType, CppFunctionKind}, |
| type_converter::{self, add_analysis, TypeConversionContext, TypeConverter}, |
| }, |
| api::{ |
| ApiName, CastMutability, CppVisibility, FuncToConvert, NullPhase, Provenance, |
| References, SpecialMemberKind, SubclassName, TraitImplSignature, TraitSynthesis, |
| UnsafetyNeeded, Virtualness, |
| }, |
| apivec::ApiVec, |
| convert_error::ErrorContext, |
| convert_error::{ConvertErrorWithContext, ErrorContextType}, |
| error_reporter::{convert_apis, report_any_error}, |
| }, |
| known_types::known_types, |
| types::validate_ident_ok_for_rust, |
| }; |
| use indexmap::map::IndexMap as HashMap; |
| use indexmap::set::IndexSet as HashSet; |
| |
| use autocxx_parser::{ExternCppType, IncludeCppConfig, UnsafePolicy}; |
| use function_wrapper::{CppFunction, CppFunctionBody, TypeConversionPolicy}; |
| use itertools::Itertools; |
| use proc_macro2::Span; |
| use quote::quote; |
| use syn::{ |
| parse_quote, punctuated::Punctuated, token::Comma, FnArg, Ident, Pat, ReturnType, Type, |
| TypePtr, Visibility, |
| }; |
| |
| use crate::{ |
| conversion::{ |
| api::{AnalysisPhase, Api, TypeKind}, |
| ConvertError, |
| }, |
| types::{make_ident, validate_ident_ok_for_cxx, Namespace, QualifiedName}, |
| }; |
| |
| use self::{ |
| bridge_name_tracker::BridgeNameTracker, |
| function_wrapper::RustConversionType, |
| implicit_constructors::{find_constructors_present, ItemsFound}, |
| overload_tracker::OverloadTracker, |
| subclass::{ |
| create_subclass_constructor, create_subclass_fn_wrapper, create_subclass_function, |
| create_subclass_trait_item, |
| }, |
| }; |
| |
| use super::{ |
| doc_label::make_doc_attrs, |
| pod::{PodAnalysis, PodPhase}, |
| tdef::TypedefAnalysis, |
| type_converter::{Annotated, PointerTreatment}, |
| }; |
| |
| #[derive(Clone, Debug)] |
| pub(crate) enum ReceiverMutability { |
| Const, |
| Mutable, |
| } |
| |
| #[derive(Clone, Debug)] |
| pub(crate) enum MethodKind { |
| Normal(ReceiverMutability), |
| Constructor { is_default: bool }, |
| Static, |
| Virtual(ReceiverMutability), |
| PureVirtual(ReceiverMutability), |
| } |
| |
| #[derive(Clone)] |
| pub(crate) enum TraitMethodKind { |
| CopyConstructor, |
| MoveConstructor, |
| Cast, |
| Destructor, |
| Alloc, |
| Dealloc, |
| } |
| |
| #[derive(Clone)] |
| pub(crate) struct TraitMethodDetails { |
| pub(crate) trt: TraitImplSignature, |
| pub(crate) avoid_self: bool, |
| pub(crate) method_name: Ident, |
| /// For traits, where we're trying to implement a specific existing |
| /// interface, we may need to reorder the parameters to fit that |
| /// interface. |
| pub(crate) parameter_reordering: Option<Vec<usize>>, |
| /// The function we're calling from the trait requires unsafe even |
| /// though the trait and its function aren't. |
| pub(crate) trait_call_is_unsafe: bool, |
| } |
| |
| #[derive(Clone)] |
| pub(crate) enum FnKind { |
| Function, |
| Method { |
| method_kind: MethodKind, |
| impl_for: QualifiedName, |
| }, |
| TraitMethod { |
| kind: TraitMethodKind, |
| /// The name of the type T for which we're implementing a trait, |
| /// though we may be actually implementing the trait for &mut T or |
| /// similar, so we store more details of both the type and the |
| /// method in `details` |
| impl_for: QualifiedName, |
| details: Box<TraitMethodDetails>, |
| }, |
| } |
| |
| /// Strategy for ensuring that the final, callable, Rust name |
| /// is what the user originally expected. |
| #[derive(Clone)] |
| |
| pub(crate) enum RustRenameStrategy { |
| /// cxx::bridge name matches user expectations |
| None, |
| /// Even the #[rust_name] attribute would cause conflicts, and we need |
| /// to use a 'use XYZ as ABC' |
| RenameInOutputMod(Ident), |
| /// This function requires us to generate a Rust function to do |
| /// parameter conversion. |
| RenameUsingWrapperFunction, |
| } |
| |
| #[derive(Clone)] |
| pub(crate) struct FnAnalysis { |
| /// Each entry in the cxx::bridge needs to have a unique name, even if |
| /// (from the perspective of Rust and C++) things are in different |
| /// namespaces/mods. |
| pub(crate) cxxbridge_name: Ident, |
| /// ... so record also the name under which we wish to expose it in Rust. |
| pub(crate) rust_name: String, |
| pub(crate) rust_rename_strategy: RustRenameStrategy, |
| pub(crate) params: Punctuated<FnArg, Comma>, |
| pub(crate) kind: FnKind, |
| pub(crate) ret_type: ReturnType, |
| pub(crate) param_details: Vec<ArgumentAnalysis>, |
| pub(crate) ret_conversion: Option<TypeConversionPolicy>, |
| pub(crate) requires_unsafe: UnsafetyNeeded, |
| pub(crate) vis: Visibility, |
| pub(crate) cpp_wrapper: Option<CppFunction>, |
| pub(crate) deps: HashSet<QualifiedName>, |
| /// Some methods still need to be recorded because we want |
| /// to (a) generate the ability to call superclasses, (b) create |
| /// subclass entries for them. But we do not want to have them |
| /// be externally callable. |
| pub(crate) ignore_reason: Result<(), ConvertErrorWithContext>, |
| /// Whether this can be called by external code. Not so for |
| /// protected methods. |
| pub(crate) externally_callable: bool, |
| /// Whether we need to generate a Rust-side calling function |
| pub(crate) rust_wrapper_needed: bool, |
| } |
| |
| #[derive(Clone)] |
| pub(crate) struct ArgumentAnalysis { |
| pub(crate) conversion: TypeConversionPolicy, |
| pub(crate) name: Pat, |
| pub(crate) self_type: Option<(QualifiedName, ReceiverMutability)>, |
| pub(crate) has_lifetime: bool, |
| pub(crate) deps: HashSet<QualifiedName>, |
| pub(crate) requires_unsafe: UnsafetyNeeded, |
| pub(crate) is_placement_return_destination: bool, |
| } |
| |
| struct ReturnTypeAnalysis { |
| rt: ReturnType, |
| conversion: Option<TypeConversionPolicy>, |
| was_reference: bool, |
| deps: HashSet<QualifiedName>, |
| placement_param_needed: Option<(FnArg, ArgumentAnalysis)>, |
| } |
| |
| impl Default for ReturnTypeAnalysis { |
| fn default() -> Self { |
| Self { |
| rt: parse_quote! {}, |
| conversion: None, |
| was_reference: false, |
| deps: Default::default(), |
| placement_param_needed: None, |
| } |
| } |
| } |
| |
| pub(crate) struct PodAndConstructorAnalysis { |
| pub(crate) pod: PodAnalysis, |
| pub(crate) constructors: PublicConstructors, |
| } |
| |
| /// An analysis phase where we've analyzed each function, but |
| /// haven't yet determined which constructors/etc. belong to each type. |
| pub(crate) struct FnPrePhase1; |
| |
| impl AnalysisPhase for FnPrePhase1 { |
| type TypedefAnalysis = TypedefAnalysis; |
| type StructAnalysis = PodAnalysis; |
| type FunAnalysis = FnAnalysis; |
| } |
| |
| /// An analysis phase where we've analyzed each function, and identified |
| /// what implicit constructors/destructors are present in each type. |
| pub(crate) struct FnPrePhase2; |
| |
| impl AnalysisPhase for FnPrePhase2 { |
| type TypedefAnalysis = TypedefAnalysis; |
| type StructAnalysis = PodAndConstructorAnalysis; |
| type FunAnalysis = FnAnalysis; |
| } |
| |
| pub(crate) struct PodAndDepAnalysis { |
| pub(crate) pod: PodAnalysis, |
| pub(crate) constructor_and_allocator_deps: Vec<QualifiedName>, |
| pub(crate) constructors: PublicConstructors, |
| } |
| |
| /// Analysis phase after we've finished analyzing functions and determined |
| /// which constructors etc. belong to them. |
| pub(crate) struct FnPhase; |
| |
| /// Indicates which kinds of public constructors are known to exist for a type. |
| #[derive(Debug, Default, Copy, Clone)] |
| pub(crate) struct PublicConstructors { |
| pub(crate) move_constructor: bool, |
| pub(crate) destructor: bool, |
| } |
| |
| impl PublicConstructors { |
| fn from_items_found(items_found: &ItemsFound) -> Self { |
| Self { |
| move_constructor: items_found.move_constructor.callable_any(), |
| destructor: items_found.destructor.callable_any(), |
| } |
| } |
| } |
| |
| impl AnalysisPhase for FnPhase { |
| type TypedefAnalysis = TypedefAnalysis; |
| type StructAnalysis = PodAndDepAnalysis; |
| type FunAnalysis = FnAnalysis; |
| } |
| |
| /// Whether to allow highly optimized calls because this is a simple Rust->C++ call, |
| /// or to use a simpler set of policies because this is a subclass call where |
| /// we may have C++->Rust->C++ etc. |
| #[derive(Copy, Clone)] |
| enum TypeConversionSophistication { |
| Regular, |
| SimpleForSubclasses, |
| } |
| |
| pub(crate) struct FnAnalyzer<'a> { |
| unsafe_policy: UnsafePolicy, |
| extra_apis: ApiVec<NullPhase>, |
| type_converter: TypeConverter<'a>, |
| bridge_name_tracker: BridgeNameTracker, |
| pod_safe_types: HashSet<QualifiedName>, |
| moveit_safe_types: HashSet<QualifiedName>, |
| config: &'a IncludeCppConfig, |
| overload_trackers_by_mod: HashMap<Namespace, OverloadTracker>, |
| subclasses_by_superclass: HashMap<QualifiedName, Vec<SubclassName>>, |
| nested_type_name_map: HashMap<QualifiedName, String>, |
| generic_types: HashSet<QualifiedName>, |
| types_in_anonymous_namespace: HashSet<QualifiedName>, |
| existing_superclass_trait_api_names: HashSet<QualifiedName>, |
| } |
| |
| impl<'a> FnAnalyzer<'a> { |
| pub(crate) fn analyze_functions( |
| apis: ApiVec<PodPhase>, |
| unsafe_policy: UnsafePolicy, |
| config: &'a IncludeCppConfig, |
| ) -> ApiVec<FnPrePhase2> { |
| let mut me = Self { |
| unsafe_policy, |
| extra_apis: ApiVec::new(), |
| type_converter: TypeConverter::new(config, &apis), |
| bridge_name_tracker: BridgeNameTracker::new(), |
| config, |
| overload_trackers_by_mod: HashMap::new(), |
| pod_safe_types: Self::build_pod_safe_type_set(&apis), |
| moveit_safe_types: Self::build_correctly_sized_type_set(&apis), |
| subclasses_by_superclass: subclass::subclasses_by_superclass(&apis), |
| nested_type_name_map: Self::build_nested_type_map(&apis), |
| generic_types: Self::build_generic_type_set(&apis), |
| existing_superclass_trait_api_names: HashSet::new(), |
| types_in_anonymous_namespace: Self::build_types_in_anonymous_namespace(&apis), |
| }; |
| let mut results = ApiVec::new(); |
| convert_apis( |
| apis, |
| &mut results, |
| |name, fun, _| me.analyze_foreign_fn_and_subclasses(name, fun), |
| Api::struct_unchanged, |
| Api::enum_unchanged, |
| Api::typedef_unchanged, |
| ); |
| let mut results = me.add_constructors_present(results); |
| me.add_subclass_constructors(&mut results); |
| results.extend(me.extra_apis.into_iter().map(add_analysis)); |
| results |
| } |
| |
| fn build_pod_safe_type_set(apis: &ApiVec<PodPhase>) -> HashSet<QualifiedName> { |
| apis.iter() |
| .filter_map(|api| match api { |
| Api::Struct { |
| analysis: |
| PodAnalysis { |
| kind: TypeKind::Pod, |
| .. |
| }, |
| .. |
| } => Some(api.name().clone()), |
| Api::Enum { .. } => Some(api.name().clone()), |
| Api::ExternCppType { pod: true, .. } => Some(api.name().clone()), |
| _ => None, |
| }) |
| .chain( |
| known_types() |
| .get_pod_safe_types() |
| .filter_map( |
| |(tn, is_pod_safe)| { |
| if is_pod_safe { |
| Some(tn) |
| } else { |
| None |
| } |
| }, |
| ), |
| ) |
| .collect() |
| } |
| |
| /// Return the set of 'moveit safe' types. That must include only types where |
| /// the size is known to be correct. |
| fn build_correctly_sized_type_set(apis: &ApiVec<PodPhase>) -> HashSet<QualifiedName> { |
| apis.iter() |
| .filter(|api| { |
| matches!( |
| api, |
| Api::Struct { .. } |
| | Api::Enum { .. } |
| | Api::ExternCppType { |
| details: ExternCppType { opaque: false, .. }, |
| .. |
| } |
| ) |
| }) |
| .map(|api| api.name().clone()) |
| .chain(known_types().get_moveit_safe_types()) |
| .collect() |
| } |
| |
| fn build_generic_type_set(apis: &ApiVec<PodPhase>) -> HashSet<QualifiedName> { |
| apis.iter() |
| .filter_map(|api| match api { |
| Api::Struct { |
| analysis: |
| PodAnalysis { |
| is_generic: true, .. |
| }, |
| .. |
| } => Some(api.name().clone()), |
| _ => None, |
| }) |
| .collect() |
| } |
| |
| fn build_types_in_anonymous_namespace(apis: &ApiVec<PodPhase>) -> HashSet<QualifiedName> { |
| apis.iter() |
| .filter_map(|api| match api { |
| Api::Struct { |
| analysis: |
| PodAnalysis { |
| in_anonymous_namespace: true, |
| .. |
| }, |
| .. |
| } => Some(api.name().clone()), |
| _ => None, |
| }) |
| .collect() |
| } |
| |
| /// Builds a mapping from a qualified type name to the last 'nest' |
| /// of its name, if it has multiple elements. |
| fn build_nested_type_map(apis: &ApiVec<PodPhase>) -> HashMap<QualifiedName, String> { |
| apis.iter() |
| .filter_map(|api| match api { |
| Api::Struct { name, .. } | Api::Enum { name, .. } => { |
| let cpp_name = name |
| .cpp_name_if_present() |
| .cloned() |
| .unwrap_or_else(|| name.name.get_final_item().to_string()); |
| cpp_name |
| .rsplit_once("::") |
| .map(|(_, suffix)| (name.name.clone(), suffix.to_string())) |
| } |
| _ => None, |
| }) |
| .collect() |
| } |
| |
| fn convert_boxed_type( |
| &mut self, |
| ty: Box<Type>, |
| ns: &Namespace, |
| pointer_treatment: PointerTreatment, |
| ) -> Result<Annotated<Box<Type>>, ConvertError> { |
| let ctx = TypeConversionContext::OuterType { pointer_treatment }; |
| let mut annotated = self.type_converter.convert_boxed_type(ty, ns, &ctx)?; |
| self.extra_apis.append(&mut annotated.extra_apis); |
| Ok(annotated) |
| } |
| |
| fn get_cxx_bridge_name( |
| &mut self, |
| type_name: Option<&str>, |
| found_name: &str, |
| ns: &Namespace, |
| ) -> String { |
| self.bridge_name_tracker |
| .get_unique_cxx_bridge_name(type_name, found_name, ns) |
| } |
| |
| fn is_on_allowlist(&self, type_name: &QualifiedName) -> bool { |
| self.config.is_on_allowlist(&type_name.to_cpp_name()) |
| } |
| |
| fn is_generic_type(&self, type_name: &QualifiedName) -> bool { |
| self.generic_types.contains(type_name) |
| } |
| |
| #[allow(clippy::if_same_then_else)] // clippy bug doesn't notice the two |
| // closures below are different. |
| fn should_be_unsafe( |
| &self, |
| param_details: &[ArgumentAnalysis], |
| kind: &FnKind, |
| ) -> UnsafetyNeeded { |
| let unsafest_non_placement_param = UnsafetyNeeded::from_param_details(param_details, true); |
| let unsafest_param = UnsafetyNeeded::from_param_details(param_details, false); |
| match kind { |
| // Trait unsafety must always correspond to the norms for the |
| // trait we're implementing. |
| FnKind::TraitMethod { |
| kind: |
| TraitMethodKind::CopyConstructor |
| | TraitMethodKind::MoveConstructor |
| | TraitMethodKind::Alloc |
| | TraitMethodKind::Dealloc, |
| .. |
| } => UnsafetyNeeded::Always, |
| FnKind::TraitMethod { .. } => match unsafest_param { |
| UnsafetyNeeded::Always => UnsafetyNeeded::JustBridge, |
| _ => unsafest_param, |
| }, |
| _ if self.unsafe_policy == UnsafePolicy::AllFunctionsUnsafe => UnsafetyNeeded::Always, |
| _ => match unsafest_non_placement_param { |
| UnsafetyNeeded::Always => UnsafetyNeeded::Always, |
| UnsafetyNeeded::JustBridge => match unsafest_param { |
| UnsafetyNeeded::Always => UnsafetyNeeded::JustBridge, |
| _ => unsafest_non_placement_param, |
| }, |
| UnsafetyNeeded::None => match unsafest_param { |
| UnsafetyNeeded::Always => UnsafetyNeeded::JustBridge, |
| _ => unsafest_param, |
| }, |
| }, |
| } |
| } |
| |
| fn add_subclass_constructors(&mut self, apis: &mut ApiVec<FnPrePhase2>) { |
| let mut results = ApiVec::new(); |
| |
| // Pre-assemble a list of types with known destructors, to avoid having to |
| // do a O(n^2) nested loop. |
| let types_with_destructors: HashSet<_> = apis |
| .iter() |
| .filter_map(|api| match api { |
| Api::Function { |
| fun, |
| analysis: |
| FnAnalysis { |
| kind: FnKind::TraitMethod { impl_for, .. }, |
| .. |
| }, |
| .. |
| } if matches!( |
| **fun, |
| FuncToConvert { |
| special_member: Some(SpecialMemberKind::Destructor), |
| is_deleted: false, |
| cpp_vis: CppVisibility::Public, |
| .. |
| } |
| ) => |
| { |
| Some(impl_for) |
| } |
| _ => None, |
| }) |
| .cloned() |
| .collect(); |
| |
| for api in apis.iter() { |
| if let Api::Function { |
| fun, |
| analysis: |
| analysis @ FnAnalysis { |
| kind: |
| FnKind::Method { |
| impl_for: sup, |
| method_kind: MethodKind::Constructor { .. }, |
| .. |
| }, |
| .. |
| }, |
| .. |
| } = api |
| { |
| // If we don't have an accessible destructor, then std::unique_ptr cannot be |
| // instantiated for this C++ type. |
| if !types_with_destructors.contains(sup) { |
| continue; |
| } |
| |
| for sub in self.subclasses_by_superclass(sup) { |
| // Create a subclass constructor. This is a synthesized function |
| // which didn't exist in the original C++. |
| let (subclass_constructor_func, subclass_constructor_name) = |
| create_subclass_constructor(sub, analysis, sup, fun); |
| self.analyze_and_add( |
| subclass_constructor_name.clone(), |
| subclass_constructor_func.clone(), |
| &mut results, |
| TypeConversionSophistication::Regular, |
| ); |
| } |
| } |
| } |
| apis.extend(results.into_iter()); |
| } |
| |
| /// Analyze a given function, and any permutations of that function which |
| /// we might additionally generate (e.g. for subclasses.) |
| /// |
| /// Leaves the [`FnKind::Method::type_constructors`] at its default for [`add_constructors_present`] |
| /// to fill out. |
| fn analyze_foreign_fn_and_subclasses( |
| &mut self, |
| name: ApiName, |
| fun: Box<FuncToConvert>, |
| ) -> Result<Box<dyn Iterator<Item = Api<FnPrePhase1>>>, ConvertErrorWithContext> { |
| let (analysis, name) = |
| self.analyze_foreign_fn(name, &fun, TypeConversionSophistication::Regular, None); |
| let mut results = ApiVec::new(); |
| |
| // Consider whether we need to synthesize subclass items. |
| if let FnKind::Method { |
| impl_for: sup, |
| method_kind: |
| MethodKind::Virtual(receiver_mutability) | MethodKind::PureVirtual(receiver_mutability), |
| .. |
| } = &analysis.kind |
| { |
| let (simpler_analysis, _) = self.analyze_foreign_fn( |
| name.clone(), |
| &fun, |
| TypeConversionSophistication::SimpleForSubclasses, |
| Some(analysis.rust_name.clone()), |
| ); |
| for sub in self.subclasses_by_superclass(sup) { |
| // For each subclass, we need to create a plain-C++ method to call its superclass |
| // and a Rust/C++ bridge API to call _that_. |
| // What we're generating here is entirely about the subclass, so the |
| // superclass's namespace is irrelevant. We generate |
| // all subclasses in the root namespace. |
| let is_pure_virtual = matches!( |
| &simpler_analysis.kind, |
| FnKind::Method { |
| method_kind: MethodKind::PureVirtual(..), |
| .. |
| } |
| ); |
| |
| let super_fn_call_name = |
| SubclassName::get_super_fn_name(&Namespace::new(), &analysis.rust_name); |
| let super_fn_api_name = SubclassName::get_super_fn_name( |
| &Namespace::new(), |
| &analysis.cxxbridge_name.to_string(), |
| ); |
| let trait_api_name = SubclassName::get_trait_api_name(sup, &analysis.rust_name); |
| |
| let mut subclass_fn_deps = vec![trait_api_name.clone()]; |
| if !is_pure_virtual { |
| // Create a C++ API representing the superclass implementation (allowing |
| // calls from Rust->C++) |
| let maybe_wrap = create_subclass_fn_wrapper(&sub, &super_fn_call_name, &fun); |
| let super_fn_name = ApiName::new_from_qualified_name(super_fn_api_name); |
| let super_fn_call_api_name = self.analyze_and_add( |
| super_fn_name, |
| maybe_wrap, |
| &mut results, |
| TypeConversionSophistication::SimpleForSubclasses, |
| ); |
| subclass_fn_deps.push(super_fn_call_api_name); |
| } |
| |
| // Create the Rust API representing the subclass implementation (allowing calls |
| // from C++ -> Rust) |
| results.push(create_subclass_function( |
| // RustSubclassFn |
| &sub, |
| &simpler_analysis, |
| &name, |
| receiver_mutability, |
| sup, |
| subclass_fn_deps, |
| )); |
| |
| // Create the trait item for the <superclass>_methods and <superclass>_supers |
| // traits. This is required per-superclass, not per-subclass, so don't |
| // create it if it already exists. |
| if !self |
| .existing_superclass_trait_api_names |
| .contains(&trait_api_name) |
| { |
| self.existing_superclass_trait_api_names |
| .insert(trait_api_name.clone()); |
| results.push(create_subclass_trait_item( |
| ApiName::new_from_qualified_name(trait_api_name), |
| &simpler_analysis, |
| receiver_mutability, |
| sup.clone(), |
| is_pure_virtual, |
| )); |
| } |
| } |
| } |
| |
| results.push(Api::Function { |
| fun, |
| analysis, |
| name, |
| }); |
| |
| Ok(Box::new(results.into_iter())) |
| } |
| |
| /// Adds an API, usually a synthesized API. Returns the final calculated API name, which can be used |
| /// for others to depend on this. |
| fn analyze_and_add<P: AnalysisPhase<FunAnalysis = FnAnalysis>>( |
| &mut self, |
| name: ApiName, |
| new_func: Box<FuncToConvert>, |
| results: &mut ApiVec<P>, |
| sophistication: TypeConversionSophistication, |
| ) -> QualifiedName { |
| let (analysis, name) = self.analyze_foreign_fn(name, &new_func, sophistication, None); |
| results.push(Api::Function { |
| fun: new_func, |
| analysis, |
| name: name.clone(), |
| }); |
| name.name |
| } |
| |
| /// Determine how to materialize a function. |
| /// |
| /// The main job here is to determine whether a function can simply be noted |
| /// in the [cxx::bridge] mod and passed directly to cxx, or if it needs a Rust-side |
| /// wrapper function, or if it needs a C++-side wrapper function, or both. |
| /// We aim for the simplest case but, for example: |
| /// * We'll need a C++ wrapper for static methods |
| /// * We'll need a C++ wrapper for parameters which need to be wrapped and unwrapped |
| /// to [cxx::UniquePtr] |
| /// * We'll need a Rust wrapper if we've got a C++ wrapper and it's a method. |
| /// * We may need wrappers if names conflict. |
| /// etc. |
| /// The other major thing we do here is figure out naming for the function. |
| /// This depends on overloads, and what other functions are floating around. |
| /// The output of this analysis phase is used by both Rust and C++ codegen. |
| fn analyze_foreign_fn( |
| &mut self, |
| name: ApiName, |
| fun: &FuncToConvert, |
| sophistication: TypeConversionSophistication, |
| predetermined_rust_name: Option<String>, |
| ) -> (FnAnalysis, ApiName) { |
| let mut cpp_name = name.cpp_name_if_present().cloned(); |
| let ns = name.name.get_namespace(); |
| |
| // Let's gather some pre-wisdom about the name of the function. |
| // We're shortly going to plunge into analyzing the parameters, |
| // and it would be nice to have some idea of the function name |
| // for diagnostics whilst we do that. |
| let initial_rust_name = fun.ident.to_string(); |
| let diagnostic_display_name = cpp_name.as_ref().unwrap_or(&initial_rust_name); |
| |
| // Now let's analyze all the parameters. |
| // See if any have annotations which our fork of bindgen has craftily inserted... |
| let (param_details, bads): (Vec<_>, Vec<_>) = fun |
| .inputs |
| .iter() |
| .map(|i| { |
| self.convert_fn_arg( |
| i, |
| ns, |
| diagnostic_display_name, |
| &fun.synthesized_this_type, |
| &fun.references, |
| true, |
| false, |
| None, |
| sophistication, |
| false, |
| ) |
| }) |
| .partition(Result::is_ok); |
| let (mut params, mut param_details): (Punctuated<_, Comma>, Vec<_>) = |
| param_details.into_iter().map(Result::unwrap).unzip(); |
| |
| let params_deps: HashSet<_> = param_details |
| .iter() |
| .flat_map(|p| p.deps.iter().cloned()) |
| .collect(); |
| let self_ty = param_details |
| .iter() |
| .filter_map(|pd| pd.self_type.as_ref()) |
| .next() |
| .cloned(); |
| |
| // End of parameter processing. |
| // Work out naming, part one. |
| // bindgen may have mangled the name either because it's invalid Rust |
| // syntax (e.g. a keyword like 'async') or it's an overload. |
| // If the former, we respect that mangling. If the latter, we don't, |
| // because we'll add our own overload counting mangling later. |
| // Cases: |
| // function, IRN=foo, CN=<none> output: foo case 1 |
| // function, IRN=move_, CN=move (keyword problem) output: move_ case 2 |
| // function, IRN=foo1, CN=foo (overload) output: foo case 3 |
| // method, IRN=A_foo, CN=foo output: foo case 4 |
| // method, IRN=A_move, CN=move (keyword problem) output: move_ case 5 |
| // method, IRN=A_foo1, CN=foo (overload) output: foo case 6 |
| let ideal_rust_name = match &cpp_name { |
| None => initial_rust_name, // case 1 |
| Some(cpp_name) => { |
| if initial_rust_name.ends_with('_') { |
| initial_rust_name // case 2 |
| } else if validate_ident_ok_for_rust(cpp_name).is_err() { |
| format!("{}_", cpp_name) // case 5 |
| } else { |
| cpp_name.to_string() // cases 3, 4, 6 |
| } |
| } |
| }; |
| |
| // Let's spend some time figuring out the kind of this function (i.e. method, |
| // virtual function, etc.) |
| // Part one, work out if this is a static method. |
| let (is_static_method, self_ty, receiver_mutability) = match self_ty { |
| None => { |
| // Even if we can't find a 'self' parameter this could conceivably |
| // be a static method. |
| let self_ty = fun.self_ty.clone(); |
| (self_ty.is_some(), self_ty, None) |
| } |
| Some((self_ty, receiver_mutability)) => { |
| (false, Some(self_ty), Some(receiver_mutability)) |
| } |
| }; |
| |
| // Part two, work out if this is a function, or method, or whatever. |
| // First determine if this is actually a trait implementation. |
| let trait_details = self.trait_creation_details_for_synthetic_function( |
| &fun.add_to_trait, |
| ns, |
| &ideal_rust_name, |
| &self_ty, |
| ); |
| let (kind, error_context, rust_name) = if let Some(trait_details) = trait_details { |
| trait_details |
| } else if let Some(self_ty) = self_ty { |
| // Some kind of method or static method. |
| let type_ident = self_ty.get_final_item(); |
| // bindgen generates methods with the name: |
| // {class}_{method name} |
| // It then generates an impl section for the Rust type |
| // with the original name, but we currently discard that impl section. |
| // We want to feed cxx methods with just the method name, so let's |
| // strip off the class name. |
| let mut rust_name = ideal_rust_name; |
| let nested_type_ident = self |
| .nested_type_name_map |
| .get(&self_ty) |
| .map(|s| s.as_str()) |
| .unwrap_or_else(|| self_ty.get_final_item()); |
| if matches!( |
| fun.special_member, |
| Some(SpecialMemberKind::CopyConstructor | SpecialMemberKind::MoveConstructor) |
| ) { |
| let is_move = |
| matches!(fun.special_member, Some(SpecialMemberKind::MoveConstructor)); |
| if let Some(constructor_suffix) = rust_name.strip_prefix(nested_type_ident) { |
| rust_name = format!("new{}", constructor_suffix); |
| } |
| rust_name = predetermined_rust_name |
| .unwrap_or_else(|| self.get_overload_name(ns, type_ident, rust_name)); |
| let error_context = self.error_context_for_method(&self_ty, &rust_name); |
| |
| // If this is 'None', then something weird is going on. We'll check for that |
| // later when we have enough context to generate useful errors. |
| let arg_is_reference = matches!( |
| param_details |
| .get(1) |
| .map(|param| ¶m.conversion.unwrapped_type), |
| Some(Type::Reference(_)) |
| ); |
| // Some exotic forms of copy constructor have const and/or volatile qualifiers. |
| // These are not sufficient to implement CopyNew, so we just treat them as regular |
| // constructors. We detect them by their argument being translated to Pin at this |
| // point. |
| if is_move || arg_is_reference { |
| let (kind, method_name, trait_id) = if is_move { |
| ( |
| TraitMethodKind::MoveConstructor, |
| "move_new", |
| quote! { MoveNew }, |
| ) |
| } else { |
| ( |
| TraitMethodKind::CopyConstructor, |
| "copy_new", |
| quote! { CopyNew }, |
| ) |
| }; |
| let ty = Type::Path(self_ty.to_type_path()); |
| ( |
| FnKind::TraitMethod { |
| kind, |
| impl_for: self_ty, |
| details: Box::new(TraitMethodDetails { |
| trt: TraitImplSignature { |
| ty, |
| trait_signature: parse_quote! { |
| autocxx::moveit::new:: #trait_id |
| }, |
| unsafety: Some(parse_quote! { unsafe }), |
| }, |
| avoid_self: true, |
| method_name: make_ident(method_name), |
| parameter_reordering: Some(vec![1, 0]), |
| trait_call_is_unsafe: false, |
| }), |
| }, |
| error_context, |
| rust_name, |
| ) |
| } else { |
| ( |
| FnKind::Method { |
| impl_for: self_ty, |
| method_kind: MethodKind::Constructor { is_default: false }, |
| }, |
| error_context, |
| rust_name, |
| ) |
| } |
| } else if matches!(fun.special_member, Some(SpecialMemberKind::Destructor)) { |
| rust_name = predetermined_rust_name |
| .unwrap_or_else(|| self.get_overload_name(ns, type_ident, rust_name)); |
| let error_context = self.error_context_for_method(&self_ty, &rust_name); |
| let ty = Type::Path(self_ty.to_type_path()); |
| ( |
| FnKind::TraitMethod { |
| kind: TraitMethodKind::Destructor, |
| impl_for: self_ty, |
| details: Box::new(TraitMethodDetails { |
| trt: TraitImplSignature { |
| ty, |
| trait_signature: parse_quote! { |
| Drop |
| }, |
| unsafety: None, |
| }, |
| avoid_self: false, |
| method_name: make_ident("drop"), |
| parameter_reordering: None, |
| trait_call_is_unsafe: false, |
| }), |
| }, |
| error_context, |
| rust_name, |
| ) |
| } else { |
| let method_kind = if let Some(constructor_suffix) = |
| constructor_with_suffix(&rust_name, nested_type_ident) |
| { |
| // It's a constructor. bindgen generates |
| // fn Type(this: *mut Type, ...args) |
| // We want |
| // fn new(this: *mut Type, ...args) |
| // Later code will spot this and re-enter us, and we'll make |
| // a duplicate function in the above 'if' clause like this: |
| // fn make_unique(...args) -> Type |
| // which later code will convert to |
| // fn make_unique(...args) -> UniquePtr<Type> |
| // If there are multiple constructors, bindgen generates |
| // new, new1, new2 etc. and we'll keep those suffixes. |
| rust_name = format!("new{}", constructor_suffix); |
| MethodKind::Constructor { |
| is_default: matches!( |
| fun.special_member, |
| Some(SpecialMemberKind::DefaultConstructor) |
| ), |
| } |
| } else if is_static_method { |
| MethodKind::Static |
| } else { |
| let receiver_mutability = |
| receiver_mutability.expect("Failed to find receiver details"); |
| match fun.virtualness { |
| Virtualness::None => MethodKind::Normal(receiver_mutability), |
| Virtualness::Virtual => MethodKind::Virtual(receiver_mutability), |
| Virtualness::PureVirtual => MethodKind::PureVirtual(receiver_mutability), |
| } |
| }; |
| // Disambiguate overloads. |
| let rust_name = predetermined_rust_name |
| .unwrap_or_else(|| self.get_overload_name(ns, type_ident, rust_name)); |
| let error_context = self.error_context_for_method(&self_ty, &rust_name); |
| ( |
| FnKind::Method { |
| impl_for: self_ty, |
| method_kind, |
| }, |
| error_context, |
| rust_name, |
| ) |
| } |
| } else { |
| // Not a method. |
| // What shall we call this function? It may be overloaded. |
| let rust_name = self.get_function_overload_name(ns, ideal_rust_name); |
| ( |
| FnKind::Function, |
| ErrorContext::new_for_item(make_ident(&rust_name)), |
| rust_name, |
| ) |
| }; |
| |
| // If we encounter errors from here on, we can give some context around |
| // where the error occurred such that we can put a marker in the output |
| // Rust code to indicate that a problem occurred (benefiting people using |
| // rust-analyzer or similar). Make a closure to make this easy. |
| let mut ignore_reason = Ok(()); |
| let mut set_ignore_reason = |
| |err| ignore_reason = Err(ConvertErrorWithContext(err, Some(error_context.clone()))); |
| |
| // Now we have figured out the type of function (from its parameters) |
| // we might have determined that we have a constructor. If so, |
| // annoyingly, we need to go back and fiddle with the parameters in a |
| // different way. This is because we want the first parameter to be a |
| // pointer not a reference. For copy + move constructors, we also |
| // enforce Rust-side conversions to comply with moveit traits. |
| match kind { |
| FnKind::Method { |
| method_kind: MethodKind::Constructor { .. }, |
| .. |
| } => { |
| self.reanalyze_parameter( |
| 0, |
| fun, |
| ns, |
| &rust_name, |
| &mut params, |
| &mut param_details, |
| None, |
| sophistication, |
| true, |
| false, |
| ) |
| .unwrap_or_else(&mut set_ignore_reason); |
| } |
| |
| FnKind::TraitMethod { |
| kind: TraitMethodKind::Destructor, |
| .. |
| } => { |
| self.reanalyze_parameter( |
| 0, |
| fun, |
| ns, |
| &rust_name, |
| &mut params, |
| &mut param_details, |
| Some(RustConversionType::FromTypeToPtr), |
| sophistication, |
| false, |
| false, |
| ) |
| .unwrap_or_else(&mut set_ignore_reason); |
| } |
| FnKind::TraitMethod { |
| kind: TraitMethodKind::CopyConstructor, |
| .. |
| } => { |
| if param_details.len() < 2 { |
| set_ignore_reason(ConvertError::ConstructorWithOnlyOneParam); |
| } |
| if param_details.len() > 2 { |
| set_ignore_reason(ConvertError::ConstructorWithMultipleParams); |
| } |
| self.reanalyze_parameter( |
| 0, |
| fun, |
| ns, |
| &rust_name, |
| &mut params, |
| &mut param_details, |
| Some(RustConversionType::FromPinMaybeUninitToPtr), |
| sophistication, |
| false, |
| false, |
| ) |
| .unwrap_or_else(&mut set_ignore_reason); |
| } |
| |
| FnKind::TraitMethod { |
| kind: TraitMethodKind::MoveConstructor, |
| .. |
| } => { |
| if param_details.len() < 2 { |
| set_ignore_reason(ConvertError::ConstructorWithOnlyOneParam); |
| } |
| if param_details.len() > 2 { |
| set_ignore_reason(ConvertError::ConstructorWithMultipleParams); |
| } |
| self.reanalyze_parameter( |
| 0, |
| fun, |
| ns, |
| &rust_name, |
| &mut params, |
| &mut param_details, |
| Some(RustConversionType::FromPinMaybeUninitToPtr), |
| sophistication, |
| false, |
| false, |
| ) |
| .unwrap_or_else(&mut set_ignore_reason); |
| self.reanalyze_parameter( |
| 1, |
| fun, |
| ns, |
| &rust_name, |
| &mut params, |
| &mut param_details, |
| Some(RustConversionType::FromPinMoveRefToPtr), |
| sophistication, |
| false, |
| true, |
| ) |
| .unwrap_or_else(&mut set_ignore_reason); |
| } |
| _ => {} |
| } |
| |
| // Now we can add context to the error, check for a variety of error |
| // cases. In each case, we continue to record the API, because it might |
| // influence our later decisions to generate synthetic constructors |
| // or note whether the type is abstract. |
| let externally_callable = match fun.cpp_vis { |
| CppVisibility::Private => { |
| set_ignore_reason(ConvertError::PrivateMethod); |
| false |
| } |
| CppVisibility::Protected => false, |
| CppVisibility::Public => true, |
| }; |
| if fun.variadic { |
| set_ignore_reason(ConvertError::Variadic); |
| } |
| if let Some(problem) = bads.into_iter().next() { |
| match problem { |
| Ok(_) => panic!("No error in the error"), |
| Err(problem) => set_ignore_reason(problem), |
| } |
| } else if fun.unused_template_param { |
| // This indicates that bindgen essentially flaked out because templates |
| // were too complex. |
| set_ignore_reason(ConvertError::UnusedTemplateParam) |
| } else if matches!( |
| fun.special_member, |
| Some(SpecialMemberKind::AssignmentOperator) |
| ) { |
| // Be careful with the order of this if-else tree. Anything above here means we won't |
| // treat it as an assignment operator, but anything below we still consider when |
| // deciding which other C++ special member functions are implicitly defined. |
| set_ignore_reason(ConvertError::AssignmentOperator) |
| } else if fun.references.rvalue_ref_return { |
| set_ignore_reason(ConvertError::RValueReturn) |
| } else if fun.is_deleted { |
| set_ignore_reason(ConvertError::Deleted) |
| } else { |
| match kind { |
| FnKind::Method { |
| ref impl_for, |
| method_kind: |
| MethodKind::Constructor { .. } |
| | MethodKind::Normal(..) |
| | MethodKind::PureVirtual(..) |
| | MethodKind::Virtual(..), |
| .. |
| } if !known_types().is_cxx_acceptable_receiver(impl_for) => { |
| set_ignore_reason(ConvertError::UnsupportedReceiver); |
| } |
| FnKind::Method { ref impl_for, .. } if !self.is_on_allowlist(impl_for) => { |
| // Bindgen will output methods for types which have been encountered |
| // virally as arguments on other allowlisted types. But we don't want |
| // to generate methods unless the user has specifically asked us to. |
| // It may, for instance, be a private type. |
| set_ignore_reason(ConvertError::MethodOfNonAllowlistedType); |
| } |
| FnKind::Method { ref impl_for, .. } | FnKind::TraitMethod { ref impl_for, .. } => { |
| if self.is_generic_type(impl_for) { |
| set_ignore_reason(ConvertError::MethodOfGenericType); |
| } |
| if self.types_in_anonymous_namespace.contains(impl_for) { |
| set_ignore_reason(ConvertError::MethodInAnonymousNamespace); |
| } |
| } |
| _ => {} |
| } |
| }; |
| |
| // The name we use within the cxx::bridge mod may be different |
| // from both the C++ name and the Rust name, because it's a flat |
| // namespace so we might need to prepend some stuff to make it unique. |
| let cxxbridge_name = self.get_cxx_bridge_name( |
| match kind { |
| FnKind::Method { ref impl_for, .. } => Some(impl_for.get_final_item()), |
| FnKind::Function => None, |
| FnKind::TraitMethod { ref impl_for, .. } => Some(impl_for.get_final_item()), |
| }, |
| &rust_name, |
| ns, |
| ); |
| if cxxbridge_name != rust_name && cpp_name.is_none() { |
| cpp_name = Some(rust_name.clone()); |
| } |
| let mut cxxbridge_name = make_ident(&cxxbridge_name); |
| |
| // Analyze the return type, just as we previously did for the |
| // parameters. |
| let mut return_analysis = self |
| .convert_return_type(&fun.output, ns, &fun.references, sophistication) |
| .unwrap_or_else(|err| { |
| set_ignore_reason(err); |
| ReturnTypeAnalysis::default() |
| }); |
| let mut deps = params_deps; |
| deps.extend(return_analysis.deps.drain(..)); |
| |
| // Sometimes, the return type will actually be a value type |
| // for which we instead want to _pass_ a pointer into which the value |
| // can be constructed. Handle that case here. |
| if let Some((extra_param, extra_param_details)) = return_analysis.placement_param_needed { |
| param_details.push(extra_param_details); |
| params.push(extra_param); |
| } |
| |
| let requires_unsafe = self.should_be_unsafe(¶m_details, &kind); |
| |
| let num_input_references = param_details.iter().filter(|pd| pd.has_lifetime).count(); |
| if num_input_references != 1 && return_analysis.was_reference { |
| // cxx only allows functions to return a reference if they take exactly |
| // one reference as a parameter. Let's see... |
| set_ignore_reason(ConvertError::NotOneInputReference(rust_name.clone())); |
| } |
| let mut ret_type = return_analysis.rt; |
| let ret_type_conversion = return_analysis.conversion; |
| |
| // Do we need to convert either parameters or return type? |
| let param_conversion_needed = param_details.iter().any(|b| b.conversion.cpp_work_needed()); |
| let ret_type_conversion_needed = ret_type_conversion |
| .as_ref() |
| .map_or(false, |x| x.cpp_work_needed()); |
| // See https://github.com/dtolnay/cxx/issues/878 for the reason for this next line. |
| let effective_cpp_name = cpp_name.as_ref().unwrap_or(&rust_name); |
| let cpp_name_incompatible_with_cxx = |
| validate_ident_ok_for_rust(effective_cpp_name).is_err(); |
| // If possible, we'll put knowledge of the C++ API directly into the cxx::bridge |
| // mod. However, there are various circumstances where cxx can't work with the existing |
| // C++ API and we need to create a C++ wrapper function which is more cxx-compliant. |
| // That wrapper function is included in the cxx::bridge, and calls through to the |
| // original function. |
| let wrapper_function_needed = match kind { |
| FnKind::Method { |
| method_kind: |
| MethodKind::Static |
| | MethodKind::Constructor { .. } |
| | MethodKind::Virtual(_) |
| | MethodKind::PureVirtual(_), |
| .. |
| } |
| | FnKind::TraitMethod { |
| kind: |
| TraitMethodKind::CopyConstructor |
| | TraitMethodKind::MoveConstructor |
| | TraitMethodKind::Destructor, |
| .. |
| } => true, |
| FnKind::Method { .. } if cxxbridge_name != rust_name => true, |
| _ if param_conversion_needed => true, |
| _ if ret_type_conversion_needed => true, |
| _ if cpp_name_incompatible_with_cxx => true, |
| _ if fun.synthetic_cpp.is_some() => true, |
| _ => false, |
| }; |
| |
| let cpp_wrapper = if wrapper_function_needed { |
| // Generate a new layer of C++ code to wrap/unwrap parameters |
| // and return values into/out of std::unique_ptrs. |
| let cpp_construction_ident = make_ident(&effective_cpp_name); |
| let joiner = if cxxbridge_name.to_string().ends_with('_') { |
| "" |
| } else { |
| "_" |
| }; |
| cxxbridge_name = make_ident(&format!("{}{}autocxx_wrapper", cxxbridge_name, joiner)); |
| let (payload, cpp_function_kind) = match fun.synthetic_cpp.as_ref().cloned() { |
| Some((payload, cpp_function_kind)) => (payload, cpp_function_kind), |
| None => match kind { |
| FnKind::Method { |
| ref impl_for, |
| method_kind: MethodKind::Constructor { .. }, |
| .. |
| } |
| | FnKind::TraitMethod { |
| kind: TraitMethodKind::CopyConstructor | TraitMethodKind::MoveConstructor, |
| ref impl_for, |
| .. |
| } => ( |
| CppFunctionBody::PlacementNew(ns.clone(), impl_for.get_final_ident()), |
| CppFunctionKind::Constructor, |
| ), |
| FnKind::TraitMethod { |
| kind: TraitMethodKind::Destructor, |
| ref impl_for, |
| .. |
| } => ( |
| CppFunctionBody::Destructor(ns.clone(), impl_for.get_final_ident()), |
| CppFunctionKind::Function, |
| ), |
| FnKind::Method { |
| ref impl_for, |
| method_kind: MethodKind::Static, |
| .. |
| } => ( |
| CppFunctionBody::StaticMethodCall( |
| ns.clone(), |
| impl_for.get_final_ident(), |
| cpp_construction_ident, |
| ), |
| CppFunctionKind::Function, |
| ), |
| FnKind::Method { .. } => ( |
| CppFunctionBody::FunctionCall(ns.clone(), cpp_construction_ident), |
| CppFunctionKind::Method, |
| ), |
| _ => ( |
| CppFunctionBody::FunctionCall(ns.clone(), cpp_construction_ident), |
| CppFunctionKind::Function, |
| ), |
| }, |
| }; |
| // Now modify the cxx::bridge entry we're going to make. |
| if let Some(ref conversion) = ret_type_conversion { |
| if conversion.populate_return_value() { |
| let new_ret_type = conversion.unconverted_rust_type(); |
| ret_type = parse_quote!( |
| -> #new_ret_type |
| ); |
| } |
| } |
| |
| // Amend parameters for the function which we're asking cxx to generate. |
| params.clear(); |
| for pd in ¶m_details { |
| let type_name = pd.conversion.converted_rust_type(); |
| let arg_name = if pd.self_type.is_some() { |
| parse_quote!(autocxx_gen_this) |
| } else { |
| pd.name.clone() |
| }; |
| params.push(parse_quote!( |
| #arg_name: #type_name |
| )); |
| } |
| |
| Some(CppFunction { |
| payload, |
| wrapper_function_name: cxxbridge_name.clone(), |
| original_cpp_name: cpp_name |
| .as_ref() |
| .cloned() |
| .unwrap_or_else(|| cxxbridge_name.to_string()), |
| return_conversion: ret_type_conversion.clone(), |
| argument_conversion: param_details.iter().map(|d| d.conversion.clone()).collect(), |
| kind: cpp_function_kind, |
| pass_obs_field: false, |
| qualification: None, |
| }) |
| } else { |
| None |
| }; |
| |
| let vis = fun.vis.clone(); |
| |
| let any_param_needs_rust_conversion = param_details |
| .iter() |
| .any(|pd| pd.conversion.rust_work_needed()); |
| |
| let rust_wrapper_needed = match kind { |
| FnKind::TraitMethod { .. } => true, |
| FnKind::Method { .. } => any_param_needs_rust_conversion || cxxbridge_name != rust_name, |
| _ => any_param_needs_rust_conversion, |
| }; |
| |
| // Naming, part two. |
| // Work out our final naming strategy. |
| validate_ident_ok_for_cxx(&cxxbridge_name.to_string()).unwrap_or_else(set_ignore_reason); |
| let rust_name_ident = make_ident(&rust_name); |
| let rust_rename_strategy = match kind { |
| _ if rust_wrapper_needed => RustRenameStrategy::RenameUsingWrapperFunction, |
| FnKind::Function if cxxbridge_name != rust_name => { |
| RustRenameStrategy::RenameInOutputMod(rust_name_ident) |
| } |
| _ => RustRenameStrategy::None, |
| }; |
| |
| let analysis = FnAnalysis { |
| cxxbridge_name: cxxbridge_name.clone(), |
| rust_name: rust_name.clone(), |
| rust_rename_strategy, |
| params, |
| ret_conversion: ret_type_conversion, |
| kind, |
| ret_type, |
| param_details, |
| requires_unsafe, |
| vis, |
| cpp_wrapper, |
| deps, |
| ignore_reason, |
| externally_callable, |
| rust_wrapper_needed, |
| }; |
| let name = ApiName::new_with_cpp_name(ns, cxxbridge_name, cpp_name); |
| (analysis, name) |
| } |
| |
| fn error_context_for_method(&self, self_ty: &QualifiedName, rust_name: &str) -> ErrorContext { |
| if self.is_generic_type(self_ty) { |
| // A 'method' error context would end up in an |
| // impl A { |
| // fn error_thingy |
| // } |
| // block. We can't impl A if it would need to be impl A<B> |
| ErrorContext::new_for_item(make_ident(rust_name)) |
| } else { |
| ErrorContext::new_for_method(self_ty.get_final_ident(), make_ident(rust_name)) |
| } |
| } |
| |
| /// Applies a specific `force_rust_conversion` to the parameter at index |
| /// `param_idx`. Modifies `param_details` and `params` in place. |
| #[allow(clippy::too_many_arguments)] // it's true, but sticking with it for now |
| fn reanalyze_parameter( |
| &mut self, |
| param_idx: usize, |
| fun: &FuncToConvert, |
| ns: &Namespace, |
| rust_name: &str, |
| params: &mut Punctuated<FnArg, Comma>, |
| param_details: &mut [ArgumentAnalysis], |
| force_rust_conversion: Option<RustConversionType>, |
| sophistication: TypeConversionSophistication, |
| construct_into_self: bool, |
| is_move_constructor: bool, |
| ) -> Result<(), ConvertError> { |
| self.convert_fn_arg( |
| fun.inputs.iter().nth(param_idx).unwrap(), |
| ns, |
| rust_name, |
| &fun.synthesized_this_type, |
| &fun.references, |
| false, |
| is_move_constructor, |
| force_rust_conversion, |
| sophistication, |
| construct_into_self, |
| ) |
| .map(|(new_arg, new_analysis)| { |
| param_details[param_idx] = new_analysis; |
| let mut params_before = params.clone().into_iter(); |
| let prefix = params_before |
| .by_ref() |
| .take(param_idx) |
| .collect_vec() |
| .into_iter(); |
| let suffix = params_before.skip(1); |
| *params = prefix |
| .chain(std::iter::once(new_arg)) |
| .chain(suffix) |
| .collect() |
| }) |
| } |
| |
| fn get_overload_name(&mut self, ns: &Namespace, type_ident: &str, rust_name: String) -> String { |
| let overload_tracker = self.overload_trackers_by_mod.entry(ns.clone()).or_default(); |
| overload_tracker.get_method_real_name(type_ident, rust_name) |
| } |
| |
| /// Determine if this synthetic function should actually result in the implementation |
| /// of a trait, rather than a function/method. |
| fn trait_creation_details_for_synthetic_function( |
| &mut self, |
| synthesis: &Option<TraitSynthesis>, |
| ns: &Namespace, |
| ideal_rust_name: &str, |
| self_ty: &Option<QualifiedName>, |
| ) -> Option<(FnKind, ErrorContext, String)> { |
| synthesis.as_ref().and_then(|synthesis| match synthesis { |
| TraitSynthesis::Cast { to_type, mutable } => { |
| let rust_name = self.get_function_overload_name(ns, ideal_rust_name.to_string()); |
| let from_type = self_ty.as_ref().unwrap(); |
| let from_type_path = from_type.to_type_path(); |
| let to_type = to_type.to_type_path(); |
| let (trait_signature, ty, method_name) = match *mutable { |
| CastMutability::ConstToConst => ( |
| parse_quote! { |
| AsRef < #to_type > |
| }, |
| Type::Path(from_type_path), |
| "as_ref", |
| ), |
| CastMutability::MutToConst => ( |
| parse_quote! { |
| AsRef < #to_type > |
| }, |
| parse_quote! { |
| &'a mut ::std::pin::Pin < &'a mut #from_type_path > |
| }, |
| "as_ref", |
| ), |
| CastMutability::MutToMut => ( |
| parse_quote! { |
| autocxx::PinMut < #to_type > |
| }, |
| parse_quote! { |
| ::std::pin::Pin < &'a mut #from_type_path > |
| }, |
| "pin_mut", |
| ), |
| }; |
| let method_name = make_ident(method_name); |
| Some(( |
| FnKind::TraitMethod { |
| kind: TraitMethodKind::Cast, |
| impl_for: from_type.clone(), |
| details: Box::new(TraitMethodDetails { |
| trt: TraitImplSignature { |
| ty, |
| trait_signature, |
| unsafety: None, |
| }, |
| avoid_self: false, |
| method_name, |
| parameter_reordering: None, |
| trait_call_is_unsafe: false, |
| }), |
| }, |
| ErrorContext::new_for_item(make_ident(&rust_name)), |
| rust_name, |
| )) |
| } |
| TraitSynthesis::AllocUninitialized(ty) => self.generate_alloc_or_deallocate( |
| ideal_rust_name, |
| ty, |
| "allocate_uninitialized_cpp_storage", |
| TraitMethodKind::Alloc, |
| ), |
| TraitSynthesis::FreeUninitialized(ty) => self.generate_alloc_or_deallocate( |
| ideal_rust_name, |
| ty, |
| "free_uninitialized_cpp_storage", |
| TraitMethodKind::Dealloc, |
| ), |
| }) |
| } |
| |
| fn generate_alloc_or_deallocate( |
| &mut self, |
| ideal_rust_name: &str, |
| ty: &QualifiedName, |
| method_name: &str, |
| kind: TraitMethodKind, |
| ) -> Option<(FnKind, ErrorContext, String)> { |
| let rust_name = |
| self.get_function_overload_name(ty.get_namespace(), ideal_rust_name.to_string()); |
| let typ = ty.to_type_path(); |
| Some(( |
| FnKind::TraitMethod { |
| impl_for: ty.clone(), |
| details: Box::new(TraitMethodDetails { |
| trt: TraitImplSignature { |
| ty: Type::Path(typ), |
| trait_signature: parse_quote! { autocxx::moveit::MakeCppStorage }, |
| unsafety: Some(parse_quote! { unsafe }), |
| }, |
| avoid_self: false, |
| method_name: make_ident(method_name), |
| parameter_reordering: None, |
| trait_call_is_unsafe: false, |
| }), |
| kind, |
| }, |
| ErrorContext::new_for_item(make_ident(&rust_name)), |
| rust_name, |
| )) |
| } |
| |
| fn get_function_overload_name(&mut self, ns: &Namespace, ideal_rust_name: String) -> String { |
| let overload_tracker = self.overload_trackers_by_mod.entry(ns.clone()).or_default(); |
| overload_tracker.get_function_real_name(ideal_rust_name) |
| } |
| |
| fn subclasses_by_superclass(&self, sup: &QualifiedName) -> impl Iterator<Item = SubclassName> { |
| match self.subclasses_by_superclass.get(sup) { |
| Some(subs) => subs.clone().into_iter(), |
| None => Vec::new().into_iter(), |
| } |
| } |
| |
| #[allow(clippy::too_many_arguments)] // currently reasonably clear |
| fn convert_fn_arg( |
| &mut self, |
| arg: &FnArg, |
| ns: &Namespace, |
| fn_name: &str, |
| virtual_this: &Option<QualifiedName>, |
| references: &References, |
| treat_this_as_reference: bool, |
| is_move_constructor: bool, |
| force_rust_conversion: Option<RustConversionType>, |
| sophistication: TypeConversionSophistication, |
| construct_into_self: bool, |
| ) -> Result<(FnArg, ArgumentAnalysis), ConvertError> { |
| Ok(match arg { |
| FnArg::Typed(pt) => { |
| let mut pt = pt.clone(); |
| let mut self_type = None; |
| let old_pat = *pt.pat; |
| let mut pointer_treatment = PointerTreatment::Pointer; |
| let mut is_placement_return_destination = false; |
| let new_pat = match old_pat { |
| syn::Pat::Ident(mut pp) if pp.ident == "this" => { |
| let this_type = match pt.ty.as_ref() { |
| Type::Ptr(TypePtr { |
| elem, mutability, .. |
| }) => match elem.as_ref() { |
| Type::Path(typ) => { |
| let receiver_mutability = if mutability.is_some() { |
| ReceiverMutability::Mutable |
| } else { |
| ReceiverMutability::Const |
| }; |
| |
| let this_type = if let Some(virtual_this) = virtual_this { |
| let this_type_path = virtual_this.to_type_path(); |
| let const_token = if mutability.is_some() { |
| None |
| } else { |
| Some(syn::Token)) |
| }; |
| pt.ty = Box::new(parse_quote! { |
| * #mutability #const_token #this_type_path |
| }); |
| virtual_this.clone() |
| } else { |
| QualifiedName::from_type_path(typ) |
| }; |
| Ok((this_type, receiver_mutability)) |
| } |
| _ => Err(ConvertError::UnexpectedThisType(QualifiedName::new( |
| ns, |
| make_ident(fn_name), |
| ))), |
| }, |
| _ => Err(ConvertError::UnexpectedThisType(QualifiedName::new( |
| ns, |
| make_ident(fn_name), |
| ))), |
| }?; |
| self_type = Some(this_type); |
| is_placement_return_destination = construct_into_self; |
| if treat_this_as_reference { |
| pp.ident = Ident::new("self", pp.ident.span()); |
| pointer_treatment = PointerTreatment::Reference; |
| } |
| syn::Pat::Ident(pp) |
| } |
| syn::Pat::Ident(pp) => { |
| validate_ident_ok_for_cxx(&pp.ident.to_string())?; |
| pointer_treatment = references.param_treatment(&pp.ident); |
| syn::Pat::Ident(pp) |
| } |
| _ => old_pat, |
| }; |
| let is_placement_return_destination = is_placement_return_destination |
| || matches!( |
| force_rust_conversion, |
| Some(RustConversionType::FromPlacementParamToNewReturn) |
| ); |
| let annotated_type = self.convert_boxed_type(pt.ty, ns, pointer_treatment)?; |
| let conversion = self.argument_conversion_details( |
| &annotated_type, |
| is_move_constructor, |
| force_rust_conversion, |
| sophistication, |
| ); |
| let new_ty = annotated_type.ty; |
| pt.pat = Box::new(new_pat.clone()); |
| pt.ty = new_ty; |
| let requires_unsafe = |
| if matches!(annotated_type.kind, type_converter::TypeKind::Pointer) |
| && !is_placement_return_destination |
| { |
| UnsafetyNeeded::Always |
| } else if conversion.bridge_unsafe_needed() || is_placement_return_destination { |
| UnsafetyNeeded::JustBridge |
| } else { |
| UnsafetyNeeded::None |
| }; |
| ( |
| FnArg::Typed(pt), |
| ArgumentAnalysis { |
| self_type, |
| name: new_pat, |
| conversion, |
| has_lifetime: matches!( |
| annotated_type.kind, |
| type_converter::TypeKind::Reference |
| | type_converter::TypeKind::MutableReference |
| ), |
| deps: annotated_type.types_encountered, |
| requires_unsafe, |
| is_placement_return_destination, |
| }, |
| ) |
| } |
| _ => panic!("Did not expect FnArg::Receiver to be generated by bindgen"), |
| }) |
| } |
| |
| fn argument_conversion_details( |
| &self, |
| annotated_type: &Annotated<Box<Type>>, |
| is_move_constructor: bool, |
| force_rust_conversion: Option<RustConversionType>, |
| sophistication: TypeConversionSophistication, |
| ) -> TypeConversionPolicy { |
| let is_subclass_holder = match &annotated_type.kind { |
| type_converter::TypeKind::SubclassHolder(holder) => Some(holder), |
| _ => None, |
| }; |
| let is_rvalue_ref = matches!( |
| annotated_type.kind, |
| type_converter::TypeKind::RValueReference |
| ); |
| let ty = &*annotated_type.ty; |
| if let Some(holder_id) = is_subclass_holder { |
| let subclass = SubclassName::from_holder_name(holder_id); |
| return { |
| let ty = parse_quote! { |
| rust::Box<#holder_id> |
| }; |
| TypeConversionPolicy { |
| unwrapped_type: ty, |
| cpp_conversion: CppConversionType::Move, |
| rust_conversion: RustConversionType::ToBoxedUpHolder(subclass), |
| } |
| }; |
| } else if matches!( |
| force_rust_conversion, |
| Some(RustConversionType::FromPlacementParamToNewReturn) |
| ) && matches!(sophistication, TypeConversionSophistication::Regular) |
| { |
| return TypeConversionPolicy { |
| unwrapped_type: ty.clone(), |
| cpp_conversion: CppConversionType::IgnoredPlacementPtrParameter, |
| rust_conversion: RustConversionType::FromPlacementParamToNewReturn, |
| }; |
| } |
| match ty { |
| Type::Path(p) => { |
| let ty = ty.clone(); |
| let tn = QualifiedName::from_type_path(p); |
| if self.pod_safe_types.contains(&tn) { |
| if known_types().lacks_copy_constructor(&tn) { |
| TypeConversionPolicy { |
| unwrapped_type: ty, |
| cpp_conversion: CppConversionType::Move, |
| rust_conversion: RustConversionType::None, |
| } |
| } else { |
| TypeConversionPolicy::new_unconverted(ty) |
| } |
| } else if known_types().convertible_from_strs(&tn) |
| && !self.config.exclude_utilities() |
| { |
| TypeConversionPolicy { |
| unwrapped_type: ty, |
| cpp_conversion: CppConversionType::FromUniquePtrToValue, |
| rust_conversion: RustConversionType::FromStr, |
| } |
| } else if matches!( |
| sophistication, |
| TypeConversionSophistication::SimpleForSubclasses |
| ) { |
| TypeConversionPolicy { |
| unwrapped_type: ty, |
| cpp_conversion: CppConversionType::FromUniquePtrToValue, |
| rust_conversion: RustConversionType::None, |
| } |
| } else { |
| TypeConversionPolicy { |
| unwrapped_type: ty, |
| cpp_conversion: CppConversionType::FromPtrToValue, |
| rust_conversion: RustConversionType::FromValueParamToPtr, |
| } |
| } |
| } |
| Type::Ptr(tp) => { |
| let rust_conversion = force_rust_conversion.unwrap_or(RustConversionType::None); |
| if is_move_constructor { |
| TypeConversionPolicy { |
| unwrapped_type: ty.clone(), |
| cpp_conversion: CppConversionType::FromPtrToMove, |
| rust_conversion, |
| } |
| } else if is_rvalue_ref { |
| TypeConversionPolicy { |
| unwrapped_type: *tp.elem.clone(), |
| cpp_conversion: CppConversionType::FromPtrToValue, |
| rust_conversion: RustConversionType::FromRValueParamToPtr, |
| } |
| } else { |
| TypeConversionPolicy { |
| unwrapped_type: ty.clone(), |
| cpp_conversion: CppConversionType::None, |
| rust_conversion, |
| } |
| } |
| } |
| _ => { |
| let rust_conversion = force_rust_conversion.unwrap_or(RustConversionType::None); |
| TypeConversionPolicy { |
| unwrapped_type: ty.clone(), |
| cpp_conversion: CppConversionType::None, |
| rust_conversion, |
| } |
| } |
| } |
| } |
| |
| fn convert_return_type( |
| &mut self, |
| rt: &ReturnType, |
| ns: &Namespace, |
| references: &References, |
| sophistication: TypeConversionSophistication, |
| ) -> Result<ReturnTypeAnalysis, ConvertError> { |
| Ok(match rt { |
| ReturnType::Default => ReturnTypeAnalysis::default(), |
| ReturnType::Type(rarrow, boxed_type) => { |
| let annotated_type = |
| self.convert_boxed_type(boxed_type.clone(), ns, references.return_treatment())?; |
| let boxed_type = annotated_type.ty; |
| let ty: &Type = boxed_type.as_ref(); |
| match ty { |
| Type::Path(p) |
| if !self |
| .pod_safe_types |
| .contains(&QualifiedName::from_type_path(p)) => |
| { |
| let tn = QualifiedName::from_type_path(p); |
| if self.moveit_safe_types.contains(&tn) |
| && matches!(sophistication, TypeConversionSophistication::Regular) |
| { |
| // This is a non-POD type we want to return to Rust as an `impl New` so that callers |
| // can decide whether to store this on the stack or heap. |
| // That means, we do not literally _return_ it from C++ to Rust. Instead, our call |
| // from Rust to C++ will include an extra placement parameter into which the object |
| // is constructed. |
| let fnarg = parse_quote! { |
| placement_return_type: *mut #ty |
| }; |
| let (fnarg, analysis) = self.convert_fn_arg( |
| &fnarg, |
| ns, |
| "", |
| &None, |
| &References::default(), |
| false, |
| false, |
| Some(RustConversionType::FromPlacementParamToNewReturn), |
| TypeConversionSophistication::Regular, |
| false, |
| )?; |
| ReturnTypeAnalysis { |
| rt: ReturnType::Default, |
| conversion: Some(TypeConversionPolicy::new_for_placement_return( |
| ty.clone(), |
| )), |
| was_reference: false, |
| deps: annotated_type.types_encountered, |
| placement_param_needed: Some((fnarg, analysis)), |
| } |
| } else { |
| // There are some types which we can't currently represent within a moveit::new::New. |
| // That's either because we are obliged to stick to existing protocols for compatibility |
| // (CxxString) or because they're a concrete type where we haven't attempted to do |
| // the analysis to work out the type's size. For these, we always return a plain old |
| // UniquePtr<T>. These restrictions may be fixed in future. |
| let conversion = |
| Some(TypeConversionPolicy::new_to_unique_ptr(ty.clone())); |
| ReturnTypeAnalysis { |
| rt: ReturnType::Type(*rarrow, boxed_type), |
| conversion, |
| was_reference: false, |
| deps: annotated_type.types_encountered, |
| placement_param_needed: None, |
| } |
| } |
| } |
| _ => { |
| let was_reference = matches!(boxed_type.as_ref(), Type::Reference(_)); |
| let conversion = Some(TypeConversionPolicy::new_unconverted(ty.clone())); |
| ReturnTypeAnalysis { |
| rt: ReturnType::Type(*rarrow, boxed_type), |
| conversion, |
| was_reference, |
| deps: annotated_type.types_encountered, |
| placement_param_needed: None, |
| } |
| } |
| } |
| } |
| }) |
| } |
| |
| /// If a type has explicit constructors, bindgen will generate corresponding |
| /// constructor functions, which we'll have already converted to make_unique methods. |
| /// C++ mandates the synthesis of certain implicit constructors, to which we |
| /// need to create bindings too. We do that here. |
| /// It is tempting to make this a separate analysis phase, to be run later than |
| /// the function analysis; but that would make the code much more complex as it |
| /// would need to output a `FnAnalysisBody`. By running it as part of this phase |
| /// we can simply generate the sort of thing bindgen generates, then ask |
| /// the existing code in this phase to figure out what to do with it. |
| /// |
| /// Also fills out the [`PodAndConstructorAnalysis::constructors`] fields with information useful |
| /// for further analysis phases. |
| fn add_constructors_present(&mut self, mut apis: ApiVec<FnPrePhase1>) -> ApiVec<FnPrePhase2> { |
| let all_items_found = find_constructors_present(&apis); |
| for (self_ty, items_found) in all_items_found.iter() { |
| if self.config.exclude_impls { |
| // Remember that `find_constructors_present` mutates `apis`, so we always have to |
| // call that, even if we don't do anything with the return value. This is kind of |
| // messy, see the comment on this function for why. |
| continue; |
| } |
| if self |
| .config |
| .is_on_constructor_blocklist(&self_ty.to_cpp_name()) |
| { |
| continue; |
| } |
| let path = self_ty.to_type_path(); |
| if items_found.implicit_default_constructor_needed() { |
| self.synthesize_special_member( |
| items_found, |
| "default_ctor", |
| &mut apis, |
| SpecialMemberKind::DefaultConstructor, |
| parse_quote! { this: *mut #path }, |
| References::default(), |
| ); |
| } |
| if items_found.implicit_move_constructor_needed() { |
| self.synthesize_special_member( |
| items_found, |
| "move_ctor", |
| &mut apis, |
| SpecialMemberKind::MoveConstructor, |
| parse_quote! { this: *mut #path, other: *mut #path }, |
| References { |
| rvalue_ref_params: [make_ident("other")].into_iter().collect(), |
| ..Default::default() |
| }, |
| ) |
| } |
| if items_found.implicit_copy_constructor_needed() { |
| self.synthesize_special_member( |
| items_found, |
| "const_copy_ctor", |
| &mut apis, |
| SpecialMemberKind::CopyConstructor, |
| parse_quote! { this: *mut #path, other: *const #path }, |
| References { |
| ref_params: [make_ident("other")].into_iter().collect(), |
| ..Default::default() |
| }, |
| ) |
| } |
| if items_found.implicit_destructor_needed() { |
| self.synthesize_special_member( |
| items_found, |
| "destructor", |
| &mut apis, |
| SpecialMemberKind::Destructor, |
| parse_quote! { this: *mut #path }, |
| References::default(), |
| ); |
| } |
| } |
| |
| // Also, annotate each type with the constructors we found. |
| let mut results = ApiVec::new(); |
| convert_apis( |
| apis, |
| &mut results, |
| Api::fun_unchanged, |
| |name, details, analysis| { |
| let items_found = all_items_found.get(&name.name); |
| Ok(Box::new(std::iter::once(Api::Struct { |
| name, |
| details, |
| analysis: PodAndConstructorAnalysis { |
| pod: analysis, |
| constructors: if let Some(items_found) = items_found { |
| PublicConstructors::from_items_found(items_found) |
| } else { |
| PublicConstructors::default() |
| }, |
| }, |
| }))) |
| }, |
| Api::enum_unchanged, |
| Api::typedef_unchanged, |
| ); |
| results |
| } |
| |
| #[allow(clippy::too_many_arguments)] // it's true, but sticking with it for now |
| fn synthesize_special_member( |
| &mut self, |
| items_found: &ItemsFound, |
| label: &str, |
| apis: &mut ApiVec<FnPrePhase1>, |
| special_member: SpecialMemberKind, |
| inputs: Punctuated<FnArg, Comma>, |
| references: References, |
| ) { |
| let self_ty = items_found.name.as_ref().unwrap(); |
| let ident = make_ident(self.config.uniquify_name_per_mod(&format!( |
| "{}_synthetic_{}", |
| self_ty.name.get_final_item(), |
| label |
| ))); |
| let cpp_name = if matches!(special_member, SpecialMemberKind::DefaultConstructor) { |
| // Constructors (other than move or copy) are identified in `analyze_foreign_fn` by |
| // being suffixed with the cpp_name, so we have to produce that. |
| self.nested_type_name_map |
| .get(&self_ty.name) |
| .cloned() |
| .or_else(|| Some(self_ty.name.get_final_item().to_string())) |
| } else { |
| None |
| }; |
| let fake_api_name = |
| ApiName::new_with_cpp_name(self_ty.name.get_namespace(), ident.clone(), cpp_name); |
| let self_ty = &self_ty.name; |
| let ns = self_ty.get_namespace().clone(); |
| let mut any_errors = ApiVec::new(); |
| apis.extend( |
| report_any_error(&ns, &mut any_errors, || { |
| self.analyze_foreign_fn_and_subclasses( |
| fake_api_name, |
| Box::new(FuncToConvert { |
| self_ty: Some(self_ty.clone()), |
| ident, |
| doc_attrs: make_doc_attrs(format!("Synthesized {}.", special_member)), |
| inputs, |
| output: ReturnType::Default, |
| vis: parse_quote! { pub }, |
| virtualness: Virtualness::None, |
| cpp_vis: CppVisibility::Public, |
| special_member: Some(special_member), |
| unused_template_param: false, |
| references, |
| original_name: None, |
| synthesized_this_type: None, |
| is_deleted: false, |
| add_to_trait: None, |
| synthetic_cpp: None, |
| provenance: Provenance::SynthesizedOther, |
| variadic: false, |
| }), |
| ) |
| }) |
| .into_iter() |
| .flatten(), |
| ); |
| apis.append(&mut any_errors); |
| } |
| } |
| |
| /// Attempts to determine whether this function name is a constructor, and if so, |
| /// returns the suffix. |
| fn constructor_with_suffix<'a>(rust_name: &'a str, nested_type_ident: &str) -> Option<&'a str> { |
| let suffix = rust_name.strip_prefix(nested_type_ident); |
| suffix.and_then(|suffix| { |
| if suffix.is_empty() || suffix.parse::<u32>().is_ok() { |
| Some(suffix) |
| } else { |
| None |
| } |
| }) |
| } |
| |
| impl Api<FnPhase> { |
| pub(crate) fn name_for_allowlist(&self) -> QualifiedName { |
| match &self { |
| Api::Function { analysis, .. } => match analysis.kind { |
| FnKind::Method { ref impl_for, .. } => impl_for.clone(), |
| FnKind::TraitMethod { ref impl_for, .. } => impl_for.clone(), |
| FnKind::Function => { |
| QualifiedName::new(self.name().get_namespace(), make_ident(&analysis.rust_name)) |
| } |
| }, |
| Api::RustSubclassFn { subclass, .. } => subclass.0.name.clone(), |
| Api::IgnoredItem { |
| name, |
| ctx: Some(ctx), |
| .. |
| } => match ctx.get_type() { |
| ErrorContextType::Method { self_ty, .. } => { |
| QualifiedName::new(name.name.get_namespace(), self_ty.clone()) |
| } |
| ErrorContextType::Item(id) => { |
| QualifiedName::new(name.name.get_namespace(), id.clone()) |
| } |
| _ => name.name.clone(), |
| }, |
| _ => self.name().clone(), |
| } |
| } |
| |
| /// Whether this API requires generation of additional C++. |
| /// This seems an odd place for this function (as opposed to in the [codegen_cpp] |
| /// module) but, as it happens, even our Rust codegen phase needs to know if |
| /// more C++ is needed (so it can add #includes in the cxx mod). |
| /// And we can't answer the question _prior_ to this function analysis phase. |
| pub(crate) fn needs_cpp_codegen(&self) -> bool { |
| matches!( |
| &self, |
| Api::Function { |
| analysis: FnAnalysis { |
| cpp_wrapper: Some(..), |
| ignore_reason: Ok(_), |
| externally_callable: true, |
| .. |
| }, |
| .. |
| } | Api::StringConstructor { .. } |
| | Api::ConcreteType { .. } |
| | Api::CType { .. } |
| | Api::RustSubclassFn { .. } |
| | Api::Subclass { .. } |
| | Api::Struct { |
| analysis: PodAndDepAnalysis { |
| pod: PodAnalysis { |
| kind: TypeKind::Pod, |
| .. |
| }, |
| .. |
| }, |
| .. |
| } |
| ) |
| } |
| |
| pub(crate) fn cxxbridge_name(&self) -> Option<Ident> { |
| match self { |
| Api::Function { ref analysis, .. } => Some(analysis.cxxbridge_name.clone()), |
| Api::StringConstructor { .. } |
| | Api::Const { .. } |
| | Api::IgnoredItem { .. } |
| | Api::RustSubclassFn { .. } => None, |
| _ => Some(self.name().get_final_ident()), |
| } |
| } |
| } |