diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3eb54d1db..367b7299a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,6 +139,9 @@ jobs: - name: Build non-trivial-type-on-stack example working-directory: ./examples/non-trivial-type-on-stack run: cargo build + - name: Build reference-wrappers example + working-directory: ./examples/reference-wrappers + run: cargo build # We do not build the LLVM example because even 'apt-get install llvm-13-dev' # does not work to install the LLVM 13 headers. diff --git a/Cargo.toml b/Cargo.toml index a9e64a979..d3756e079 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ moveit = { version = "0.5", features = [ "cxx" ] } [workspace] members = ["parser", "engine", "gen/cmd", "gen/build", "macro", "demo", "tools/reduce", "tools/mdbook-preprocessor", "integration-tests"] -exclude = ["examples/s2", "examples/steam-mini", "examples/subclass", "examples/chromium-fake-render-frame-host", "examples/pod", "examples/non-trivial-type-on-stack", "examples/llvm", "tools/stress-test"] +exclude = ["examples/s2", "examples/steam-mini", "examples/subclass", "examples/chromium-fake-render-frame-host", "examples/pod", "examples/non-trivial-type-on-stack", "examples/llvm", "examples/reference-wrappers", "tools/stress-test"] #[patch.crates-io] #cxx = { path="../cxx" } diff --git a/engine/src/conversion/analysis/fun/function_wrapper.rs b/engine/src/conversion/analysis/fun/function_wrapper.rs index f2f6aaafe..01c319a5b 100644 --- a/engine/src/conversion/analysis/fun/function_wrapper.rs +++ b/engine/src/conversion/analysis/fun/function_wrapper.rs @@ -10,7 +10,8 @@ use crate::{ conversion::api::SubclassName, types::{Namespace, QualifiedName}, }; -use syn::{parse_quote, Ident, Type}; +use quote::ToTokens; +use syn::{parse_quote, Ident, Type, TypeReference}; #[derive(Clone, Debug)] pub(crate) enum CppConversionType { @@ -23,6 +24,34 @@ pub(crate) enum CppConversionType { /// Ignored in the sense that it isn't passed into the C++ function. IgnoredPlacementPtrParameter, FromReturnValueToPlacementPtr, + FromPointerToReference, // unwrapped_type is always Type::Ptr + FromReferenceToPointer, // unwrapped_type is always Type::Ptr +} + +#[derive(Clone, Debug)] +pub(crate) enum RustConversionType { + None, + FromReturnValueToPlacementPtr, // like None + FromValueToValueToMove, + FromUniquePtrToUniquePtrToValue, + FromStrToUniquePtrToValue, + ToBoxedUpHolderToMove(SubclassName), + FromPinMaybeUninitToPtrToPtr, + FromPinMoveRefToPtrToPtr, + FromValueToPtrToPtr, + FromValueParamToPtrToValue, + FromPlacementParamToNewReturnToNone, + FromRValueParamToPtrToValue, + FromReferenceWrapperToPointerToReference, // unwrapped_type is always Type::Ptr + FromReferenceToPointerToReferenceWrapper, // unwrapped_type is always Type::Ptr +} + +pub(crate) enum ConversionHint { + None, + FromValueToPtrToPtr, + FromPlacementParamToNewReturnToNone, + FromPinMaybeUninitToPtrToPtr, + FromPinMoveRefToPtrToPtr, } impl CppConversionType { @@ -36,28 +65,17 @@ impl CppConversionType { CppConversionType::FromValueToUniquePtr } CppConversionType::FromValueToUniquePtr => CppConversionType::FromUniquePtrToValue, + CppConversionType::FromPointerToReference => CppConversionType::FromReferenceToPointer, + CppConversionType::FromReferenceToPointer => CppConversionType::FromPointerToReference, _ => panic!("Did not expect to have to invert this conversion"), } } } -#[derive(Clone, Debug)] -pub(crate) enum RustConversionType { - None, - FromStr, - ToBoxedUpHolder(SubclassName), - FromPinMaybeUninitToPtr, - FromPinMoveRefToPtr, - FromTypeToPtr, - FromValueParamToPtr, - FromPlacementParamToNewReturn, - FromRValueParamToPtr, -} - impl RustConversionType { pub(crate) fn requires_mutability(&self) -> Option { match self { - Self::FromPinMoveRefToPtr => Some(parse_quote! { mut }), + Self::FromPinMoveRefToPtrToPtr => Some(parse_quote! { mut }), _ => None, } } @@ -74,19 +92,53 @@ impl RustConversionType { /// * Finally, the actual C++ API receives a `std::string` by value. /// The implementation here is distributed across this file, and /// `function_wrapper_rs` and `function_wrapper_cpp`. +/// TODO: we should make this into a single enum, with the Type as enum +/// variant params. That would remove the possibility of various runtime +/// panics by enforcing (for example) that conversion from a pointer always +/// has a Type::Ptr. #[derive(Clone)] pub(crate) struct TypeConversionPolicy { - pub(crate) unwrapped_type: Type, + unwrapped_type: Type, pub(crate) cpp_conversion: CppConversionType, pub(crate) rust_conversion: RustConversionType, } impl TypeConversionPolicy { pub(crate) fn new_unconverted(ty: Type) -> Self { - TypeConversionPolicy { + Self::new(ty, CppConversionType::None, RustConversionType::None) + } + + pub(crate) fn new( + ty: Type, + cpp_conversion: CppConversionType, + rust_conversion: RustConversionType, + ) -> Self { + Self { unwrapped_type: ty, - cpp_conversion: CppConversionType::None, - rust_conversion: RustConversionType::None, + cpp_conversion, + rust_conversion, + } + } + + pub(crate) fn cxxbridge_type(&self) -> &Type { + &self.unwrapped_type + } + + pub(crate) fn return_reference_into_wrapper(ty: Type) -> Self { + let (unwrapped_type, is_mut) = match ty { + Type::Reference(TypeReference { + elem, mutability, .. + }) => (*elem, mutability.is_some()), + _ => panic!("Not a ptr: {}", ty.to_token_stream()), + }; + TypeConversionPolicy { + unwrapped_type: if is_mut { + parse_quote! { *mut #unwrapped_type } + } else { + parse_quote! { *const #unwrapped_type } + }, + cpp_conversion: CppConversionType::FromReferenceToPointer, + rust_conversion: RustConversionType::FromReferenceToPointerToReferenceWrapper, } } @@ -94,7 +146,7 @@ impl TypeConversionPolicy { TypeConversionPolicy { unwrapped_type: ty, cpp_conversion: CppConversionType::FromValueToUniquePtr, - rust_conversion: RustConversionType::None, + rust_conversion: RustConversionType::FromUniquePtrToUniquePtrToValue, } } @@ -105,7 +157,7 @@ impl TypeConversionPolicy { // Rust conversion is marked as none here, since this policy // will be applied to the return value, and the Rust-side // shenanigans applies to the placement new *parameter* - rust_conversion: RustConversionType::None, + rust_conversion: RustConversionType::FromReturnValueToPlacementPtr, } } @@ -158,9 +210,11 @@ impl TypeConversionPolicy { pub(crate) fn bridge_unsafe_needed(&self) -> bool { matches!( self.rust_conversion, - RustConversionType::FromValueParamToPtr - | RustConversionType::FromRValueParamToPtr - | RustConversionType::FromPlacementParamToNewReturn + RustConversionType::FromValueParamToPtrToValue + | RustConversionType::FromRValueParamToPtrToValue + | RustConversionType::FromPlacementParamToNewReturnToNone + | RustConversionType::FromReferenceToPointerToReferenceWrapper { .. } + | RustConversionType::FromReferenceWrapperToPointerToReference { .. } ) } diff --git a/engine/src/conversion/analysis/fun/mod.rs b/engine/src/conversion/analysis/fun/mod.rs index b196840fa..6598e3d2f 100644 --- a/engine/src/conversion/analysis/fun/mod.rs +++ b/engine/src/conversion/analysis/fun/mod.rs @@ -41,7 +41,7 @@ use proc_macro2::Span; use quote::quote; use syn::{ parse_quote, punctuated::Punctuated, token::Comma, FnArg, Ident, Pat, ReturnType, Type, - TypePtr, Visibility, + TypePath, TypePtr, TypeReference, Visibility, }; use crate::{ @@ -54,7 +54,7 @@ use crate::{ use self::{ bridge_name_tracker::BridgeNameTracker, - function_wrapper::RustConversionType, + function_wrapper::{RustConversionType, ConversionHint}, implicit_constructors::{find_constructors_present, ItemsFound}, overload_tracker::OverloadTracker, subclass::{ @@ -183,7 +183,7 @@ pub(crate) struct ArgumentAnalysis { pub(crate) is_placement_return_destination: bool, } -struct ReturnTypeAnalysis { +pub(crate) struct ReturnTypeAnalysis { rt: ReturnType, conversion: Option, was_reference: bool, @@ -737,7 +737,7 @@ impl<'a> FnAnalyzer<'a> { &fun.references, true, false, - None, + ConversionHint::None, sophistication, false, ) @@ -840,7 +840,7 @@ impl<'a> FnAnalyzer<'a> { let arg_is_reference = matches!( param_details .get(1) - .map(|param| ¶m.conversion.unwrapped_type), + .map(|param| param.conversion.cxxbridge_type()), Some(Type::Reference(_)) ); // Some exotic forms of copy constructor have const and/or volatile qualifiers. @@ -1002,7 +1002,7 @@ impl<'a> FnAnalyzer<'a> { &rust_name, &mut params, &mut param_details, - None, + ConversionHint::None, sophistication, true, false, @@ -1021,7 +1021,7 @@ impl<'a> FnAnalyzer<'a> { &rust_name, &mut params, &mut param_details, - Some(RustConversionType::FromTypeToPtr), + ConversionHint::FromValueToPtrToPtr, sophistication, false, false, @@ -1045,7 +1045,7 @@ impl<'a> FnAnalyzer<'a> { &rust_name, &mut params, &mut param_details, - Some(RustConversionType::FromPinMaybeUninitToPtr), + ConversionHint::FromPinMaybeUninitToPtrToPtr, sophistication, false, false, @@ -1070,7 +1070,7 @@ impl<'a> FnAnalyzer<'a> { &rust_name, &mut params, &mut param_details, - Some(RustConversionType::FromPinMaybeUninitToPtr), + ConversionHint::FromPinMaybeUninitToPtrToPtr, sophistication, false, false, @@ -1083,7 +1083,7 @@ impl<'a> FnAnalyzer<'a> { &rust_name, &mut params, &mut param_details, - Some(RustConversionType::FromPinMoveRefToPtr), + ConversionHint::FromPinMoveRefToPtrToPtr, sophistication, false, true, @@ -1213,6 +1213,11 @@ impl<'a> FnAnalyzer<'a> { let ret_type_conversion_needed = ret_type_conversion .as_ref() .map_or(false, |x| x.cpp_work_needed()); + let return_needs_rust_conversion = ret_type_conversion + .as_ref() + .map(|ra| ra.rust_work_needed()) + .unwrap_or_default(); + // 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 = @@ -1350,9 +1355,10 @@ impl<'a> FnAnalyzer<'a> { .any(|pd| pd.conversion.rust_work_needed()); let rust_wrapper_needed = match kind { + _ if any_param_needs_rust_conversion || return_needs_rust_conversion => true, FnKind::TraitMethod { .. } => true, - FnKind::Method { .. } => any_param_needs_rust_conversion || cxxbridge_name != rust_name, - _ => any_param_needs_rust_conversion, + FnKind::Method { .. } => cxxbridge_name != rust_name, + _ => false, }; // Naming, part two. @@ -1412,7 +1418,7 @@ impl<'a> FnAnalyzer<'a> { rust_name: &str, params: &mut Punctuated, param_details: &mut [ArgumentAnalysis], - force_rust_conversion: Option, + force_rust_conversion: ConversionHint, sophistication: TypeConversionSophistication, construct_into_self: bool, is_move_constructor: bool, @@ -1581,7 +1587,7 @@ impl<'a> FnAnalyzer<'a> { references: &References, treat_this_as_reference: bool, is_move_constructor: bool, - force_rust_conversion: Option, + force_rust_conversion: ConversionHint, sophistication: TypeConversionSophistication, construct_into_self: bool, ) -> Result<(FnArg, ArgumentAnalysis), ConvertError> { @@ -1646,10 +1652,11 @@ impl<'a> FnAnalyzer<'a> { } _ => old_pat, }; + let is_placement_return_destination = is_placement_return_destination || matches!( force_rust_conversion, - Some(RustConversionType::FromPlacementParamToNewReturn) + ConversionHint::FromPlacementParamToNewReturnToNone ); let annotated_type = self.convert_boxed_type(pt.ty, ns, pointer_treatment)?; let conversion = self.argument_conversion_details( @@ -1657,6 +1664,8 @@ impl<'a> FnAnalyzer<'a> { is_move_constructor, force_rust_conversion, sophistication, + self_type.is_some(), + is_placement_return_destination, ); let new_ty = annotated_type.ty; pt.pat = Box::new(new_pat.clone()); @@ -1696,8 +1705,10 @@ impl<'a> FnAnalyzer<'a> { &self, annotated_type: &Annotated>, is_move_constructor: bool, - force_rust_conversion: Option, + force_rust_conversion: ConversionHint, sophistication: TypeConversionSophistication, + is_self: bool, + is_placement_return_destination: bool, ) -> TypeConversionPolicy { let is_subclass_holder = match &annotated_type.kind { type_converter::TypeKind::SubclassHolder(holder) => Some(holder), @@ -1707,6 +1718,9 @@ impl<'a> FnAnalyzer<'a> { annotated_type.kind, type_converter::TypeKind::RValueReference ); + let is_reference = + matches!(annotated_type.kind, type_converter::TypeKind::Reference) || is_self; + let rust_conversion_forced = !matches!(force_rust_conversion, ConversionHint::None); let ty = &*annotated_type.ty; if let Some(holder_id) = is_subclass_holder { let subclass = SubclassName::from_holder_name(holder_id); @@ -1714,91 +1728,127 @@ impl<'a> FnAnalyzer<'a> { let ty = parse_quote! { rust::Box<#holder_id> }; - TypeConversionPolicy { - unwrapped_type: ty, - cpp_conversion: CppConversionType::Move, - rust_conversion: RustConversionType::ToBoxedUpHolder(subclass), - } + TypeConversionPolicy::new( + ty, + CppConversionType::Move, + RustConversionType::ToBoxedUpHolderToMove(subclass), + ) }; } else if matches!( force_rust_conversion, - Some(RustConversionType::FromPlacementParamToNewReturn) + ConversionHint::FromPlacementParamToNewReturnToNone ) && matches!(sophistication, TypeConversionSophistication::Regular) { - return TypeConversionPolicy { - unwrapped_type: ty.clone(), - cpp_conversion: CppConversionType::IgnoredPlacementPtrParameter, - rust_conversion: RustConversionType::FromPlacementParamToNewReturn, - }; + return TypeConversionPolicy::new( + ty.clone(), + CppConversionType::IgnoredPlacementPtrParameter, + RustConversionType::FromPlacementParamToNewReturnToNone, + ); } match ty { Type::Path(p) => { let ty = ty.clone(); let tn = QualifiedName::from_type_path(p); - if self.pod_safe_types.contains(&tn) { + if matches!( + self.config.unsafe_policy, + UnsafePolicy::ReferencesWrappedAllFunctionsSafe + ) && is_reference + && !rust_conversion_forced + // must be std::pin::Pin<&mut T> + { + let unwrapped_type = extract_type_from_pinned_mut_ref(p); + TypeConversionPolicy::new( + parse_quote! { *mut #unwrapped_type }, + CppConversionType::FromPointerToReference, + RustConversionType::FromReferenceWrapperToPointerToReference, + ) + } else 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, - } + TypeConversionPolicy::new( + ty, + CppConversionType::Move, + RustConversionType::FromValueToValueToMove, + ) } 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, - } + TypeConversionPolicy::new( + ty, + CppConversionType::FromUniquePtrToValue, + RustConversionType::FromStrToUniquePtrToValue, + ) } else if matches!( sophistication, TypeConversionSophistication::SimpleForSubclasses ) { - TypeConversionPolicy { - unwrapped_type: ty, - cpp_conversion: CppConversionType::FromUniquePtrToValue, - rust_conversion: RustConversionType::None, - } + TypeConversionPolicy::new( + ty, + CppConversionType::FromUniquePtrToValue, + RustConversionType::FromUniquePtrToUniquePtrToValue, + ) } else { - TypeConversionPolicy { - unwrapped_type: ty, - cpp_conversion: CppConversionType::FromPtrToValue, - rust_conversion: RustConversionType::FromValueParamToPtr, - } + TypeConversionPolicy::new( + ty, + CppConversionType::FromPtrToValue, + RustConversionType::FromValueParamToPtrToValue, + ) } } 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, + TypeConversionPolicy::new( + ty.clone(), + CppConversionType::FromPtrToMove, rust_conversion, - } + ) } else if is_rvalue_ref { - TypeConversionPolicy { - unwrapped_type: *tp.elem.clone(), - cpp_conversion: CppConversionType::FromPtrToValue, - rust_conversion: RustConversionType::FromRValueParamToPtr, - } + TypeConversionPolicy::new( + *tp.elem.clone(), + CppConversionType::FromPtrToValue, + RustConversionType::FromRValueParamToPtrToValue, + ) + } else if matches!( + self.config.unsafe_policy, + UnsafePolicy::ReferencesWrappedAllFunctionsSafe + ) && is_reference + && !rust_conversion_forced + && !is_placement_return_destination + { + TypeConversionPolicy::new( + ty.clone(), + CppConversionType::FromPointerToReference, + RustConversionType::FromReferenceWrapperToPointerToReference, + ) } else { - TypeConversionPolicy { - unwrapped_type: ty.clone(), - cpp_conversion: CppConversionType::None, - rust_conversion, - } + TypeConversionPolicy::new(ty.clone(), CppConversionType::None, rust_conversion) } } + Type::Reference(TypeReference { + elem, mutability, .. + }) if matches!( + self.config.unsafe_policy, + UnsafePolicy::ReferencesWrappedAllFunctionsSafe + ) && !rust_conversion_forced + && !is_placement_return_destination => + { + let is_mut = mutability.is_some(); + TypeConversionPolicy::new( + if is_mut { + panic!("Never expected to find &mut T at this point, we should be Pin<&mut T> by now") + } else { + parse_quote! { *const #elem } + }, + CppConversionType::FromPointerToReference, + RustConversionType::FromReferenceWrapperToPointerToReference, + ) + } _ => { let rust_conversion = force_rust_conversion.unwrap_or(RustConversionType::None); - TypeConversionPolicy { - unwrapped_type: ty.clone(), - cpp_conversion: CppConversionType::None, - rust_conversion, - } + TypeConversionPolicy::new(ty.clone(), CppConversionType::None, rust_conversion) } } } @@ -1843,7 +1893,7 @@ impl<'a> FnAnalyzer<'a> { &References::default(), false, false, - Some(RustConversionType::FromPlacementParamToNewReturn), + ConversionHint::FromPlacementParamToNewReturnToNone, TypeConversionSophistication::Regular, false, )?; @@ -1874,8 +1924,19 @@ impl<'a> FnAnalyzer<'a> { } } _ => { - let was_reference = matches!(boxed_type.as_ref(), Type::Reference(_)); - let conversion = Some(TypeConversionPolicy::new_unconverted(ty.clone())); + let was_reference = references.ref_return; + let conversion = Some( + if was_reference + && matches!( + self.config.unsafe_policy, + UnsafePolicy::ReferencesWrappedAllFunctionsSafe + ) + { + TypeConversionPolicy::return_reference_into_wrapper(ty.clone()) + } else { + TypeConversionPolicy::new_unconverted(ty.clone()) + }, + ); ReturnTypeAnalysis { rt: ReturnType::Type(*rarrow, boxed_type), conversion, @@ -2142,3 +2203,24 @@ impl Api { } } } + +fn extract_type_from_pinned_mut_ref(ty: &TypePath) -> Type { + match ty + .path + .segments + .last() + .expect("was not std::pin::Pin") + .arguments + { + syn::PathArguments::AngleBracketed(ref ab) => { + match ab.args.first().expect("did not have angle bracketed args") { + syn::GenericArgument::Type(ref ty) => match ty { + Type::Reference(ref tyr) => tyr.elem.as_ref().clone(), + _ => panic!("pin did not contain a reference"), + }, + _ => panic!("argument was not a type"), + } + } + _ => panic!("did not find angle bracketed args"), + } +} diff --git a/engine/src/conversion/api.rs b/engine/src/conversion/api.rs index 24b6bb7f0..c5a1b607f 100644 --- a/engine/src/conversion/api.rs +++ b/engine/src/conversion/api.rs @@ -712,3 +712,10 @@ impl Api { Ok(Box::new(std::iter::once(Api::Enum { name, item }))) } } + +/// Whether a type is a pointer of some kind. +pub(crate) enum Pointerness { + Not, + ConstPtr, + MutPtr, +} diff --git a/engine/src/conversion/codegen_cpp/function_wrapper_cpp.rs b/engine/src/conversion/codegen_cpp/function_wrapper_cpp.rs index b2e1ea384..536762623 100644 --- a/engine/src/conversion/codegen_cpp/function_wrapper_cpp.rs +++ b/engine/src/conversion/codegen_cpp/function_wrapper_cpp.rs @@ -6,8 +6,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use syn::{Type, TypePtr}; + use crate::conversion::{ analysis::fun::function_wrapper::{CppConversionType, TypeConversionPolicy}, + api::Pointerness, ConvertError, }; @@ -30,12 +33,39 @@ impl TypeConversionPolicy { pub(super) fn converted_type(&self, cpp_name_map: &CppNameMap) -> Result { match self.cpp_conversion { CppConversionType::FromValueToUniquePtr => self.unique_ptr_wrapped_type(cpp_name_map), + CppConversionType::FromReferenceToPointer => { + let (const_string, ty) = match self.cxxbridge_type() { + Type::Ptr(TypePtr { + mutability: Some(_), + elem, + .. + }) => ("", elem.as_ref()), + Type::Ptr(TypePtr { elem, .. }) => ("const ", elem.as_ref()), + _ => panic!("Not a pointer"), + }; + Ok(format!( + "{}{}*", + const_string, + type_to_cpp(ty, cpp_name_map)? + )) + } _ => self.unwrapped_type_as_string(cpp_name_map), } } fn unwrapped_type_as_string(&self, cpp_name_map: &CppNameMap) -> Result { - type_to_cpp(&self.unwrapped_type, cpp_name_map) + type_to_cpp(self.cxxbridge_type(), cpp_name_map) + } + + pub(crate) fn is_a_pointer(&self) -> Pointerness { + match self.cxxbridge_type() { + Type::Ptr(TypePtr { + mutability: Some(_), + .. + }) => Pointerness::MutPtr, + Type::Ptr(_) => Pointerness::ConstPtr, + _ => Pointerness::Not, + } } fn unique_ptr_wrapped_type( @@ -60,6 +90,7 @@ impl TypeConversionPolicy { CppConversionType::None | CppConversionType::FromReturnValueToPlacementPtr => { Some(var_name.to_string()) } + CppConversionType::FromPointerToReference { .. } => Some(format!("(*{})", var_name)), CppConversionType::Move => Some(format!("std::move({})", var_name)), CppConversionType::FromUniquePtrToValue | CppConversionType::FromPtrToMove => { Some(format!("std::move(*{})", var_name)) @@ -78,6 +109,7 @@ impl TypeConversionPolicy { }) } CppConversionType::IgnoredPlacementPtrParameter => None, + CppConversionType::FromReferenceToPointer { .. } => Some(format!("&{}", var_name)), }) } } diff --git a/engine/src/conversion/codegen_cpp/mod.rs b/engine/src/conversion/codegen_cpp/mod.rs index 0e4975c81..0e6dccedb 100644 --- a/engine/src/conversion/codegen_cpp/mod.rs +++ b/engine/src/conversion/codegen_cpp/mod.rs @@ -559,7 +559,7 @@ impl<'a> CppCodeGenerator<'a> { underlying_function_call = match placement_param { Some(placement_param) => { - let tyname = type_to_cpp(&ret.unwrapped_type, &self.original_name_map)?; + let tyname = type_to_cpp(ret.cxxbridge_type(), &self.original_name_map)?; format!("new({}) {}({})", placement_param, tyname, call_itself) } None => format!("return {}", call_itself), diff --git a/engine/src/conversion/codegen_rs/fun_codegen.rs b/engine/src/conversion/codegen_rs/fun_codegen.rs index 7c32b1bca..db222a643 100644 --- a/engine/src/conversion/codegen_rs/fun_codegen.rs +++ b/engine/src/conversion/codegen_rs/fun_codegen.rs @@ -6,6 +6,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use autocxx_parser::IncludeCppConfig; use indexmap::set::IndexSet as HashSet; use std::borrow::Cow; @@ -23,15 +24,15 @@ use super::{ function_wrapper_rs::RustParamConversion, maybe_unsafes_to_tokens, unqualify::{unqualify_params, unqualify_ret_type}, - ImplBlockDetails, MaybeUnsafeStmt, RsCodegenResult, TraitImplBlockDetails, Use, + ImplBlockDetails, ImplBlockKey, MaybeUnsafeStmt, RsCodegenResult, TraitImplBlockDetails, Use, }; use crate::{ conversion::{ analysis::fun::{ - ArgumentAnalysis, FnAnalysis, FnKind, MethodKind, RustRenameStrategy, - TraitMethodDetails, + function_wrapper::TypeConversionPolicy, ArgumentAnalysis, FnAnalysis, FnKind, + MethodKind, RustRenameStrategy, TraitMethodDetails, }, - api::UnsafetyNeeded, + api::{Pointerness, UnsafetyNeeded}, }, types::{Namespace, QualifiedName}, }; @@ -89,6 +90,7 @@ pub(super) fn gen_function( analysis: FnAnalysis, cpp_call_name: String, non_pod_types: &HashSet, + config: &IncludeCppConfig, ) -> RsCodegenResult { if analysis.ignore_reason.is_err() || !analysis.externally_callable { return RsCodegenResult::default(); @@ -96,6 +98,7 @@ pub(super) fn gen_function( let cxxbridge_name = analysis.cxxbridge_name; let rust_name = &analysis.rust_name; let ret_type = analysis.ret_type; + let ret_conversion = analysis.ret_conversion; let param_details = analysis.param_details; let wrapper_function_needed = analysis.cpp_wrapper.is_some(); let params = analysis.params; @@ -119,6 +122,9 @@ pub(super) fn gen_function( always_unsafe_due_to_trait_definition, doc_attrs: &doc_attrs, non_pod_types, + ret_type: &ret_type, + ret_conversion: &ret_conversion, + reference_wrappers: config.unsafe_policy.requires_cpprefs(), }; // In rare occasions, we might need to give an explicit lifetime. let (lifetime_tokens, params, ret_type) = add_explicit_lifetime_if_necessary( @@ -148,15 +154,14 @@ pub(super) fn gen_function( impl_entry = Some(fn_generator.generate_method_impl( matches!(method_kind, MethodKind::Constructor { .. }), impl_for, - &ret_type, )); } FnKind::TraitMethod { ref details, .. } => { - trait_impl_entry = Some(fn_generator.generate_trait_impl(details, &ret_type)); + trait_impl_entry = Some(fn_generator.generate_trait_impl(details)); } _ => { // Generate plain old function - bindgen_mod_items.push(fn_generator.generate_function_impl(&ret_type)); + bindgen_mod_items.push(fn_generator.generate_function_impl()); } } } @@ -225,20 +230,23 @@ pub(super) fn gen_function( #[derive(Clone)] struct FnGenerator<'a> { param_details: &'a [ArgumentAnalysis], + ret_conversion: &'a Option, + ret_type: &'a ReturnType, cxxbridge_name: &'a Ident, rust_name: &'a str, unsafety: &'a UnsafetyNeeded, always_unsafe_due_to_trait_definition: bool, doc_attrs: &'a Vec, non_pod_types: &'a HashSet, + reference_wrappers: bool, } impl<'a> FnGenerator<'a> { fn common_parts<'b>( - &self, + &'b self, avoid_self: bool, parameter_reordering: &Option>, - ret_type: &'b ReturnType, + ret_type: Option, ) -> ( Option, Punctuated, @@ -249,15 +257,20 @@ impl<'a> FnGenerator<'a> { let mut local_variables = Vec::new(); let mut arg_list = Vec::new(); let mut ptr_arg_name = None; - let mut ret_type = Cow::Borrowed(ret_type); + let mut ret_type: Cow<'a, _> = ret_type + .map(Cow::Owned) + .unwrap_or(Cow::Borrowed(self.ret_type)); let mut any_conversion_requires_unsafe = false; + let mut variable_counter = 0usize; for pd in self.param_details { let wrapper_arg_name = if pd.self_type.is_some() && !avoid_self { parse_quote!(self) } else { pd.name.clone() }; - let rust_for_param = pd.conversion.rust_conversion(wrapper_arg_name.clone()); + let rust_for_param = pd + .conversion + .rust_conversion(parse_quote! { #wrapper_arg_name }, &mut variable_counter); match rust_for_param { RustParamConversion::Param { ty, @@ -305,6 +318,39 @@ impl<'a> FnGenerator<'a> { }, any_conversion_requires_unsafe || matches!(self.unsafety, UnsafetyNeeded::JustBridge), ); + let context_is_unsafe = matches!(self.unsafety, UnsafetyNeeded::Always) + || self.always_unsafe_due_to_trait_definition; + let (call_body, ret_type) = match self.ret_conversion { + Some(ret_conversion) if ret_conversion.rust_work_needed() => { + let expr = maybe_unsafes_to_tokens(vec![call_body], context_is_unsafe); + let conv = + ret_conversion.rust_conversion(parse_quote! { #expr }, &mut variable_counter); + let (conversion, requires_unsafe, ty) = match conv { + RustParamConversion::Param { + local_variables, .. + } if !local_variables.is_empty() => panic!("return type required variables"), + RustParamConversion::Param { + conversion, + conversion_requires_unsafe, + ty, + .. + } => (conversion, conversion_requires_unsafe, ty), + _ => panic!( + "Unexpected - return type is supposed to be converted to a return type" + ), + }; + ( + if requires_unsafe { + MaybeUnsafeStmt::NeedsUnsafe(conversion) + } else { + MaybeUnsafeStmt::Normal(conversion) + }, + Cow::Owned(parse_quote! { -> #ty }), + ) + } + _ => (call_body, ret_type), + }; + let call_stmts = if let Some(ptr_arg_name) = ptr_arg_name { let mut closure_stmts = local_variables; closure_stmts.push(MaybeUnsafeStmt::binary( @@ -323,8 +369,6 @@ impl<'a> FnGenerator<'a> { call_stmts.push(call_body); call_stmts }; - let context_is_unsafe = matches!(self.unsafety, UnsafetyNeeded::Always) - || self.always_unsafe_due_to_trait_definition; let call_body = maybe_unsafes_to_tokens(call_stmts, context_is_unsafe); (lifetime_tokens, wrapper_params, ret_type, call_body) } @@ -334,13 +378,44 @@ impl<'a> FnGenerator<'a> { &self, avoid_self: bool, impl_block_type_name: &QualifiedName, - ret_type: &ReturnType, ) -> Box { let (lifetime_tokens, wrapper_params, ret_type, call_body) = - self.common_parts(avoid_self, &None, ret_type); + self.common_parts(avoid_self, &None, None); let rust_name = make_ident(self.rust_name); let unsafety = self.unsafety.wrapper_token(); let doc_attrs = self.doc_attrs; + let receiver_pointerness = self + .param_details + .iter() + .next() + .map(|pd| pd.conversion.is_a_pointer()) + .unwrap_or(Pointerness::Not); + let ty = impl_block_type_name.get_final_ident(); + let ty = if self.reference_wrappers { + match receiver_pointerness { + Pointerness::MutPtr => ImplBlockKey { + ty: parse_quote! { + CppMutRef< 'a, #ty> + }, + lifetime: Some(parse_quote! { 'a }), + }, + Pointerness::ConstPtr => ImplBlockKey { + ty: parse_quote! { + CppRef< 'a, #ty> + }, + lifetime: Some(parse_quote! { 'a }), + }, + Pointerness::Not => ImplBlockKey { + ty: parse_quote! { # ty }, + lifetime: None, + }, + } + } else { + ImplBlockKey { + ty: parse_quote! { # ty }, + lifetime: None, + } + }; Box::new(ImplBlockDetails { item: ImplItem::Method(parse_quote! { #(#doc_attrs)* @@ -348,18 +423,14 @@ impl<'a> FnGenerator<'a> { #call_body } }), - ty: impl_block_type_name.get_final_ident(), + ty, }) } /// Generate an 'impl Trait for Type { methods-go-here }' in its entrety. - fn generate_trait_impl( - &self, - details: &TraitMethodDetails, - ret_type: &ReturnType, - ) -> Box { + fn generate_trait_impl(&self, details: &TraitMethodDetails) -> Box { let (lifetime_tokens, wrapper_params, ret_type, call_body) = - self.common_parts(details.avoid_self, &details.parameter_reordering, ret_type); + self.common_parts(details.avoid_self, &details.parameter_reordering, None); let doc_attrs = self.doc_attrs; let unsafety = self.unsafety.wrapper_token(); let key = details.trt.clone(); @@ -381,25 +452,28 @@ impl<'a> FnGenerator<'a> { ) -> Box { let ret_type: ReturnType = parse_quote! { -> impl autocxx::moveit::new::New }; let (lifetime_tokens, wrapper_params, ret_type, call_body) = - self.common_parts(true, &None, &ret_type); + self.common_parts(true, &None, Some(ret_type)); let rust_name = make_ident(&self.rust_name); let doc_attrs = self.doc_attrs; let unsafety = self.unsafety.wrapper_token(); - Box::new(ImplBlockDetails { - item: ImplItem::Method(parse_quote! { + let ty = impl_block_type_name.get_final_ident(); + let ty = parse_quote! { #ty }; + let stuff = quote! { #(#doc_attrs)* pub #unsafety fn #rust_name #lifetime_tokens ( #wrapper_params ) #ret_type { #call_body } - }), - ty: impl_block_type_name.get_final_ident(), + }; + Box::new(ImplBlockDetails { + item: ImplItem::Method(parse_quote! { #stuff }), + ty: ImplBlockKey { ty, lifetime: None }, }) } /// Generate a function call wrapper - fn generate_function_impl(&self, ret_type: &ReturnType) -> Item { + fn generate_function_impl(&self) -> Item { let (lifetime_tokens, wrapper_params, ret_type, call_body) = - self.common_parts(false, &None, ret_type); + self.common_parts(false, &None, None); let rust_name = make_ident(self.rust_name); let doc_attrs = self.doc_attrs; let unsafety = self.unsafety.wrapper_token(); diff --git a/engine/src/conversion/codegen_rs/function_wrapper_rs.rs b/engine/src/conversion/codegen_rs/function_wrapper_rs.rs index a3fc71ff7..6a09cc359 100644 --- a/engine/src/conversion/codegen_rs/function_wrapper_rs.rs +++ b/engine/src/conversion/codegen_rs/function_wrapper_rs.rs @@ -7,7 +7,7 @@ // except according to those terms. use proc_macro2::TokenStream; -use syn::{Pat, Type, TypePtr}; +use syn::{Expr, Type, TypePtr}; use crate::{ conversion::analysis::fun::function_wrapper::{RustConversionType, TypeConversionPolicy}, @@ -32,22 +32,21 @@ pub(super) enum RustParamConversion { } impl TypeConversionPolicy { - /// If returns `None` then this parameter should be omitted entirely. - pub(super) fn rust_conversion(&self, var: Pat) -> RustParamConversion { + pub(super) fn rust_conversion(&self, var: Expr, counter: &mut usize) -> RustParamConversion { match self.rust_conversion { - RustConversionType::None => RustParamConversion::Param { + RustConversionType::None | RustConversionType::FromUniquePtrToUniquePtrToValue | RustConversionType::FromReturnValueToPlacementPtr | RustConversionType::FromValueToValueToMove => RustParamConversion::Param { ty: self.converted_rust_type(), local_variables: Vec::new(), conversion: quote! { #var }, conversion_requires_unsafe: false, }, - RustConversionType::FromStr => RustParamConversion::Param { + RustConversionType::FromStrToUniquePtrToValue => RustParamConversion::Param { ty: parse_quote! { impl ToCppString }, local_variables: Vec::new(), conversion: quote! ( #var .into_cpp() ), conversion_requires_unsafe: false, }, - RustConversionType::ToBoxedUpHolder(ref sub) => { + RustConversionType::ToBoxedUpHolderToMove(ref sub) => { let holder_type = sub.holder(); let id = sub.id(); let ty = parse_quote! { autocxx::subclass::CppSubclassRustPeerHolder< @@ -62,8 +61,8 @@ impl TypeConversionPolicy { conversion_requires_unsafe: false, } } - RustConversionType::FromPinMaybeUninitToPtr => { - let ty = match &self.unwrapped_type { + RustConversionType::FromPinMaybeUninitToPtrToPtr => { + let ty = match self.cxxbridge_type() { Type::Ptr(TypePtr { elem, .. }) => &*elem, _ => panic!("Not a ptr"), }; @@ -79,8 +78,8 @@ impl TypeConversionPolicy { conversion_requires_unsafe: true, } } - RustConversionType::FromPinMoveRefToPtr => { - let ty = match &self.unwrapped_type { + RustConversionType::FromPinMoveRefToPtrToPtr => { + let ty = match self.cxxbridge_type() { Type::Ptr(TypePtr { elem, .. }) => &*elem, _ => panic!("Not a ptr"), }; @@ -98,8 +97,8 @@ impl TypeConversionPolicy { conversion_requires_unsafe: true, } } - RustConversionType::FromTypeToPtr => { - let ty = match &self.unwrapped_type { + RustConversionType::FromValueToPtrToPtr => { + let ty = match self.cxxbridge_type() { Type::Ptr(TypePtr { elem, .. }) => &*elem, _ => panic!("Not a ptr"), }; @@ -113,23 +112,21 @@ impl TypeConversionPolicy { conversion_requires_unsafe: false, } } - RustConversionType::FromValueParamToPtr | RustConversionType::FromRValueParamToPtr => { + RustConversionType::FromValueParamToPtrToValue | RustConversionType::FromRValueParamToPtrToValue => { let (handler_type, param_trait) = match self.rust_conversion { - RustConversionType::FromValueParamToPtr => ("ValueParamHandler", "ValueParam"), - RustConversionType::FromRValueParamToPtr => { + RustConversionType::FromValueParamToPtrToValue => ("ValueParamHandler", "ValueParam"), + RustConversionType::FromRValueParamToPtrToValue => { ("RValueParamHandler", "RValueParam") } _ => unreachable!(), }; let handler_type = make_ident(handler_type); let param_trait = make_ident(param_trait); - let var_name = if let Pat::Ident(pti) = &var { - &pti.ident - } else { - panic!("Unexpected non-ident parameter name"); - }; - let space_var_name = make_ident(format!("{}_space", var_name)); - let ty = &self.unwrapped_type; + let var_counter = *counter; + *counter += 1; + let space_var_name = format!("space{}", var_counter); + let space_var_name = make_ident(space_var_name); + let ty = self.cxxbridge_type(); let ty = parse_quote! { impl autocxx::#param_trait<#ty> }; // This is the usual trick to put something on the stack, then // immediately shadow the variable name so it can't be accessed or moved. @@ -148,7 +145,7 @@ impl TypeConversionPolicy { }, ), MaybeUnsafeStmt::needs_unsafe( - quote! { #space_var_name.as_mut().populate(#var_name); }, + quote! { #space_var_name.as_mut().populate(#var); }, ), ], conversion: quote! { @@ -160,13 +157,56 @@ impl TypeConversionPolicy { // This type of conversion means that this function parameter appears in the cxx::bridge // but not in the arguments for the wrapper function, because instead we return an // impl New which uses the cxx::bridge function's pointer parameter. - RustConversionType::FromPlacementParamToNewReturn => { - let ty = match &self.unwrapped_type { + RustConversionType::FromPlacementParamToNewReturnToNone => { + let ty = match self.cxxbridge_type() { Type::Ptr(TypePtr { elem, .. }) => *(*elem).clone(), _ => panic!("Not a ptr"), }; RustParamConversion::ReturnValue { ty } } + RustConversionType::FromReferenceToPointerToReferenceWrapper => { + let (is_mut, ty) = match self.cxxbridge_type() { + Type::Ptr(TypePtr { + mutability, elem, .. + }) => (mutability.is_some(), elem.as_ref()), + _ => panic!("Not a pointer"), + }; + let (ty, wrapper_name) = if is_mut { + (parse_quote! { CppMutRef<'a, #ty> }, "CppMutRef") + } else { + (parse_quote! { CppRef<'a, #ty> }, "CppRef") + }; + let wrapper_name = make_ident(wrapper_name); + RustParamConversion::Param { + ty, + local_variables: Vec::new(), + conversion: quote! { + #wrapper_name (#var, std::marker::PhantomData) + }, + conversion_requires_unsafe: false, + } + } + RustConversionType::FromReferenceWrapperToPointerToReference => { + let (is_mut, ty) = match self.cxxbridge_type() { + Type::Ptr(TypePtr { + mutability, elem, .. + }) => (mutability.is_some(), elem.as_ref()), + _ => panic!("Not a pointer"), + }; + let ty = if is_mut { + parse_quote! { &mut CppMutRef<'a, #ty> } + } else { + parse_quote! { &CppRef<'a, #ty> } + }; + RustParamConversion::Param { + ty, + local_variables: Vec::new(), + conversion: quote! { + #var .0 + }, + conversion_requires_unsafe: false, + } + } } } } diff --git a/engine/src/conversion/codegen_rs/lifetime.rs b/engine/src/conversion/codegen_rs/lifetime.rs index ea2c782b5..f0d0343ee 100644 --- a/engine/src/conversion/codegen_rs/lifetime.rs +++ b/engine/src/conversion/codegen_rs/lifetime.rs @@ -51,7 +51,7 @@ pub(crate) fn add_explicit_lifetime_if_necessary<'r>( pd.has_lifetime || matches!( pd.conversion.rust_conversion, - RustConversionType::FromValueParamToPtr + RustConversionType::FromValueParamToPtrToValue ) }); let return_type_is_impl = return_type_is_impl(&ret_type); diff --git a/engine/src/conversion/codegen_rs/mod.rs b/engine/src/conversion/codegen_rs/mod.rs index b41aca966..25517d180 100644 --- a/engine/src/conversion/codegen_rs/mod.rs +++ b/engine/src/conversion/codegen_rs/mod.rs @@ -23,7 +23,8 @@ use itertools::Itertools; use proc_macro2::{Span, TokenStream}; use syn::{ parse_quote, punctuated::Punctuated, token::Comma, Attribute, Expr, FnArg, ForeignItem, - ForeignItemFn, Ident, ImplItem, Item, ItemForeignMod, ItemMod, TraitItem, TypePath, + ForeignItemFn, Ident, ImplItem, Item, ItemForeignMod, ItemMod, Lifetime, TraitItem, Type, + TypePath, }; use crate::{ @@ -61,10 +62,16 @@ use super::{ use super::{convert_error::ErrorContext, ConvertError}; use quote::quote; +#[derive(Clone, Hash, PartialEq, Eq)] +struct ImplBlockKey { + ty: Type, + lifetime: Option, +} + /// An entry which needs to go into an `impl` block for a given type. struct ImplBlockDetails { item: ImplItem, - ty: Ident, + ty: ImplBlockKey, } struct TraitImplBlockDetails { @@ -130,6 +137,90 @@ fn get_string_items() -> Vec { .to_vec() } +fn get_cppref_items() -> Vec { + [ + Item::Struct(parse_quote! { + #[repr(transparent)] + pub struct CppRef<'a, T>(pub *const T, pub ::std::marker::PhantomData<&'a T>); + }), + Item::Impl(parse_quote! { + impl<'a, T> autocxx::CppRef<'a, T> for CppRef<'a, T> { + fn as_ptr(&self) -> *const T { + self.0 + } + } + }), + Item::Struct(parse_quote! { + #[repr(transparent)] + pub struct CppMutRef<'a, T>(pub *mut T, pub ::std::marker::PhantomData<&'a T>); + }), + Item::Impl(parse_quote! { + impl<'a, T> autocxx::CppRef<'a, T> for CppMutRef<'a, T> { + fn as_ptr(&self) -> *const T { + self.0 + } + } + }), + Item::Impl(parse_quote! { + impl<'a, T> autocxx::CppMutRef<'a, T> for CppMutRef<'a, T> { + fn as_mut_ptr(&self) -> *mut T { + self.0 + } + } + }), + Item::Impl(parse_quote! { + impl<'a, T: ::cxx::private::UniquePtrTarget> CppMutRef<'a, T> { + /// Create a const C++ reference from this mutable C++ reference. + pub fn as_cpp_ref(&self) -> CppRef<'a, T> { + use autocxx::CppRef; + CppRef(self.as_ptr(), ::std::marker::PhantomData) + } + } + }), + Item::Struct(parse_quote! { + /// "Pins" a `UniquePtr` to an object, so that C++-compatible references can be created. + #[repr(transparent)] + pub struct CppUniquePtrPin(::cxx::UniquePtr); + }), + Item::Impl(parse_quote! { + impl<'a, T: 'a + ::cxx::private::UniquePtrTarget> autocxx::CppPin<'a, T> for CppUniquePtrPin + { + type CppRef = CppRef<'a, T>; + type CppMutRef = CppMutRef<'a, T>; + fn as_ptr(&self) -> *const T { + // TODO add as_ptr to cxx to avoid the ephemeral reference + self.0.as_ref().unwrap() as *const T + } + fn as_mut_ptr(&mut self) -> *mut T { + unsafe { ::std::pin::Pin::into_inner_unchecked(self.0.as_mut().unwrap()) as *mut T } + } + fn as_cpp_ref(&self) -> Self::CppRef { + CppRef(self.as_ptr(), ::std::marker::PhantomData) + } + fn as_cpp_mut_ref(&mut self) -> Self::CppMutRef { + CppMutRef(self.as_mut_ptr(), ::std::marker::PhantomData) + } + } + }), + Item::Impl(parse_quote! { + impl CppUniquePtrPin { + pub fn new(item: ::cxx::UniquePtr) -> Self { + Self(item) + } + } + }), + Item::Fn(parse_quote! { + /// Pin this item so that we can create C++ references to it. + /// This makes it impossible to hold Rust references because Rust + /// references are fundamentally incompatible with C++ references. + pub fn cpp_pin_uniqueptr (item: ::cxx::UniquePtr) -> CppUniquePtrPin { + CppUniquePtrPin::new(item) + } + }) + ] + .to_vec() +} + /// Type which handles generation of Rust code. /// In practice, much of the "generation" involves connecting together /// existing lumps of code within the Api structures. @@ -222,6 +313,9 @@ impl<'a> RsCodeGenerator<'a> { let mut extern_rust_mod_items = extern_rust_mod_items.into_iter().flatten().collect(); // And a list of global items to include at the top level. let mut all_items: Vec = all_items.into_iter().flatten().collect(); + if self.config.unsafe_policy.requires_cpprefs() { + all_items.append(&mut get_cppref_items()) + } // And finally any C++ we need to generate. And by "we" I mean autocxx not cxx. let has_additional_cpp_needs = additional_cpp_needs.into_iter().any(std::convert::identity); extern_c_mod_items.extend(self.build_include_foreign_items(has_additional_cpp_needs)); @@ -360,23 +454,24 @@ impl<'a> RsCodeGenerator<'a> { } fn append_uses_for_ns(&mut self, items: &mut Vec, ns: &Namespace) { + let mut imports_from_super = vec!["cxxbridge"]; + if !self.config.exclude_utilities() { + imports_from_super.push("ToCppString"); + } + if self.config.unsafe_policy.requires_cpprefs() { + imports_from_super.extend(["CppRef", "CppMutRef"]); + } + let imports_from_super = imports_from_super.into_iter().map(make_ident); let super_duper = std::iter::repeat(make_ident("super")); // I'll get my coat let supers = super_duper.clone().take(ns.depth() + 2); items.push(Item::Use(parse_quote! { #[allow(unused_imports)] use self:: #(#supers)::* - ::cxxbridge; + ::{ + #(#imports_from_super),* + }; })); - if !self.config.exclude_utilities() { - let supers = super_duper.clone().take(ns.depth() + 2); - items.push(Item::Use(parse_quote! { - #[allow(unused_imports)] - use self:: - #(#supers)::* - ::ToCppString; - })); - } let supers = super_duper.take(ns.depth() + 1); items.push(Item::Use(parse_quote! { #[allow(unused_imports)] @@ -410,8 +505,10 @@ impl<'a> RsCodeGenerator<'a> { } } for (ty, entries) in impl_entries_by_type.into_iter() { + let lt = ty.lifetime.map(|lt| quote! { < #lt > }); + let ty = ty.ty; output_items.push(Item::Impl(parse_quote! { - impl #ty { + impl #lt #ty { #(#entries)* } })) @@ -490,6 +587,7 @@ impl<'a> RsCodeGenerator<'a> { analysis, cpp_call_name, non_pod_types, + self.config, ), Api::Const { const_item, .. } => RsCodegenResult { bindgen_mod_items: vec![Item::Const(const_item)], @@ -1075,7 +1173,10 @@ impl<'a> RsCodeGenerator<'a> { fn #method(_uhoh: autocxx::BindingGenerationFailure) { } }, - ty: self_ty, + ty: ImplBlockKey { + ty: parse_quote! { #self_ty }, + lifetime: None, + }, })), None, None, diff --git a/examples/reference-wrappers/Cargo.toml b/examples/reference-wrappers/Cargo.toml new file mode 100644 index 000000000..c02447caa --- /dev/null +++ b/examples/reference-wrappers/Cargo.toml @@ -0,0 +1,21 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 or the MIT license +# , at your +# option. This file may not be copied, modified, or distributed +# except according to those terms. + +[package] +name = "autocxx-reference-wrapper-example" +version = "0.22.1" +authors = ["Adrian Taylor "] +edition = "2021" + +[dependencies] +cxx = "1.0.68" +autocxx = { path = "../..", version="0.22.1" } + +[build-dependencies] +autocxx-build = { path = "../../gen/build", version="0.22.1" } +miette = { version="4.3", features=["fancy"]} diff --git a/examples/reference-wrappers/build.rs b/examples/reference-wrappers/build.rs new file mode 100644 index 000000000..64c573d44 --- /dev/null +++ b/examples/reference-wrappers/build.rs @@ -0,0 +1,18 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn main() -> miette::Result<()> { + let path = std::path::PathBuf::from("src"); + let mut b = autocxx_build::Builder::new("src/main.rs", &[&path]).build()?; + b.flag_if_supported("-std=c++14") + .file("src/input.cc").compile("autocxx-reference-wrapper-example"); + + println!("cargo:rerun-if-changed=src/main.rs"); + println!("cargo:rerun-if-changed=src/input.h"); + Ok(()) +} diff --git a/examples/reference-wrappers/src/input.cc b/examples/reference-wrappers/src/input.cc new file mode 100644 index 000000000..32bf76d1b --- /dev/null +++ b/examples/reference-wrappers/src/input.cc @@ -0,0 +1,11 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "input.h" + +Goat the_goat; \ No newline at end of file diff --git a/examples/reference-wrappers/src/input.h b/examples/reference-wrappers/src/input.h new file mode 100644 index 000000000..ce8abc0c7 --- /dev/null +++ b/examples/reference-wrappers/src/input.h @@ -0,0 +1,42 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#pragma once + +#include +#include +#include +#include + +class Goat { +public: + Goat() : horns(0) {} + void add_a_horn(); + std::string describe() const; +private: + uint32_t horns; +}; + + +inline void Goat::add_a_horn() { horns++; } +inline std::string Goat::describe() const { + std::ostringstream oss; + std::string plural = horns == 1 ? "" : "s"; + oss << "This goat has " << horns << " horn" << plural << "."; + return oss.str(); +} + +class Field { +public: + const Goat& get_goat() const { + return the_goat; + } + +private: + Goat the_goat; +}; \ No newline at end of file diff --git a/examples/reference-wrappers/src/main.rs b/examples/reference-wrappers/src/main.rs new file mode 100644 index 000000000..097016e56 --- /dev/null +++ b/examples/reference-wrappers/src/main.rs @@ -0,0 +1,30 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use autocxx::prelude::*; + +include_cpp! { + #include "input.h" + safety!(unsafe_references_wrapped) + generate!("Goat") + generate!("Field") +} + +fn main() { + let field = ffi::Field::new().within_unique_ptr(); + let field = ffi::cpp_pin_uniqueptr(field); + let another_goat = field.as_cpp_ref().get_goat(); + assert_eq!( + another_goat + .describe() + .as_ref() + .unwrap() + .to_string_lossy(), + "This goat has 0 horns." + ); +} diff --git a/integration-tests/src/lib.rs b/integration-tests/src/lib.rs index 17dbbf626..352c8ab5b 100644 --- a/integration-tests/src/lib.rs +++ b/integration-tests/src/lib.rs @@ -21,7 +21,7 @@ use autocxx_engine::{ use log::info; use once_cell::sync::OnceCell; use proc_macro2::{Span, TokenStream}; -use quote::{quote, TokenStreamExt}; +use quote::{format_ident, quote, TokenStreamExt}; use syn::Token; use tempfile::{tempdir, TempDir}; @@ -205,6 +205,7 @@ pub fn run_test( None, None, None, + "unsafe_ffi", ) .unwrap() } @@ -259,6 +260,7 @@ pub fn run_test_ex( builder_modifier, code_checker, extra_rust, + "unsafe_ffi", ) .unwrap() } @@ -290,6 +292,7 @@ pub fn run_test_expect_fail( None, None, None, + "unsafe_ffi", ) .expect_err("Unexpected success"); } @@ -311,6 +314,7 @@ pub fn run_test_expect_fail_ex( builder_modifier, code_checker, extra_rust, + "unsafe_ffi", ) .expect_err("Unexpected success"); } @@ -360,14 +364,16 @@ pub fn do_run_test( builder_modifier: Option, rust_code_checker: Option, extra_rust: Option, + safety_policy: &str, ) -> Result<(), TestError> { let hexathorpe = Token![#](Span::call_site()); + let safety_policy = format_ident!("{}", safety_policy); let unexpanded_rust = quote! { use autocxx::prelude::*; include_cpp!( #hexathorpe include "input.h" - safety!(unsafe_ffi) + safety!(#safety_policy) #directives ); diff --git a/integration-tests/tests/cpprefs_test.rs b/integration-tests/tests/cpprefs_test.rs new file mode 100644 index 000000000..4241decc2 --- /dev/null +++ b/integration-tests/tests/cpprefs_test.rs @@ -0,0 +1,96 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Tests specific to reference wrappers. + +use autocxx_integration_tests::{directives_from_lists, do_run_test}; +use indoc::indoc; +use proc_macro2::TokenStream; +use quote::quote; + +/// A positive test, we expect to pass. +fn run_cpprefs_test( + cxx_code: &str, + header_code: &str, + rust_code: TokenStream, + generate: &[&str], + generate_pods: &[&str], +) { + do_run_test( + cxx_code, + header_code, + rust_code, + directives_from_lists(generate, generate_pods, None), + None, + None, + None, + "unsafe_references_wrapped", + ) + .unwrap() +} + +#[test] +fn test_method_call_mut() { + run_cpprefs_test( + "", + indoc! {" + #include + #include + + class Goat { + public: + Goat() : horns(0) {} + void add_a_horn(); + private: + uint32_t horns; + }; + + inline void Goat::add_a_horn() { horns++; } + "}, + quote! { + let goat = ffi::Goat::new().within_unique_ptr(); + let mut goat = ffi::CppUniquePtrPin::new(goat); + goat.as_cpp_mut_ref().add_a_horn(); + }, + &["Goat"], + &[], + ) +} + +#[test] +fn test_method_call_const() { + run_cpprefs_test( + "", + indoc! {" + #include + #include + + class Goat { + public: + Goat() : horns(0) {} + std::string describe() const; + private: + uint32_t horns; + }; + + inline std::string Goat::describe() const { + std::ostringstream oss; + std::string plural = horns == 1 ? \"\" : \"s\"; + oss << \"This goat has \" << horns << \" horn\" << plural << \".\"; + return oss.str(); + } + "}, + quote! { + let goat = ffi::Goat::new().within_unique_ptr(); + let goat = ffi::cpp_pin_uniqueptr(goat); + goat.as_cpp_ref().describe(); + }, + &["Goat"], + &[], + ) +} diff --git a/integration-tests/tests/integration_test.rs b/integration-tests/tests/integration_test.rs index 82144e1a6..00a5203bf 100644 --- a/integration-tests/tests/integration_test.rs +++ b/integration-tests/tests/integration_test.rs @@ -5920,6 +5920,7 @@ fn test_double_destruction() { None, None, None, + "unsafe_ffi", ) { Err(TestError::CppBuild(_)) => {} // be sure this fails due to a static_assert // rather than some runtime problem diff --git a/integration-tests/tests/lib.rs b/integration-tests/tests/lib.rs index 17f076a0a..8d9eba929 100644 --- a/integration-tests/tests/lib.rs +++ b/integration-tests/tests/lib.rs @@ -8,4 +8,5 @@ mod builder_modifiers; mod code_checkers; +mod cpprefs_test; mod integration_test; diff --git a/parser/src/config.rs b/parser/src/config.rs index 4204acf33..8d302493e 100644 --- a/parser/src/config.rs +++ b/parser/src/config.rs @@ -33,6 +33,7 @@ use quote::quote; pub enum UnsafePolicy { AllFunctionsSafe, AllFunctionsUnsafe, + ReferencesWrappedAllFunctionsSafe, } impl Default for UnsafePolicy { @@ -50,8 +51,13 @@ impl Parse for UnsafePolicy { Some(id) => { if id == "unsafe_ffi" { Ok(UnsafePolicy::AllFunctionsSafe) + } else if id == "unsafe_references_wrapped" { + Ok(UnsafePolicy::ReferencesWrappedAllFunctionsSafe) } else { - Err(syn::Error::new(id.span(), "expected unsafe_ffi")) + Err(syn::Error::new( + id.span(), + "expected unsafe_ffi or unsafe_references_wrapped", + )) } } None => Ok(UnsafePolicy::AllFunctionsUnsafe), @@ -70,10 +76,20 @@ impl ToTokens for UnsafePolicy { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { if *self == UnsafePolicy::AllFunctionsSafe { tokens.extend(quote! { unsafe }) + } else if *self == UnsafePolicy::ReferencesWrappedAllFunctionsSafe { + tokens.extend(quote! { unsafe_references_wrapped }) } } } +impl UnsafePolicy { + /// Whether we are treating C++ references as a different thing from Rust + /// references and therefore have to generate lots of code for a CppRef type + pub fn requires_cpprefs(&self) -> bool { + matches!(self, Self::ReferencesWrappedAllFunctionsSafe) + } +} + /// An entry in the allowlist. #[derive(Hash, Debug)] pub enum AllowlistEntry { diff --git a/parser/src/directives.rs b/parser/src/directives.rs index 160829ec2..70c88fc7a 100644 --- a/parser/src/directives.rs +++ b/parser/src/directives.rs @@ -268,10 +268,8 @@ impl Directive for Safety { ) -> Box + 'a> { let policy = &config.unsafe_policy; match config.unsafe_policy { - crate::UnsafePolicy::AllFunctionsSafe => { - Box::new(std::iter::once(policy.to_token_stream())) - } crate::UnsafePolicy::AllFunctionsUnsafe => Box::new(std::iter::empty()), + _ => Box::new(std::iter::once(policy.to_token_stream())), } } } diff --git a/src/lib.rs b/src/lib.rs index 659201129..935d77775 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,10 +14,13 @@ // do anything - all the magic is handled entirely by // autocxx_macro::include_cpp_impl. +mod reference_wrapper; mod rvalue_param; pub mod subclass; mod value_param; +pub use reference_wrapper::{CppMutRef, CppPin, CppRef}; + #[cfg_attr(doc, aquamarine::aquamarine)] /// Include some C++ headers in your Rust project. /// @@ -613,6 +616,9 @@ pub mod prelude { pub use crate::c_void; pub use crate::cpp_semantics; pub use crate::include_cpp; + pub use crate::CppMutRef; + pub use crate::CppPin; + pub use crate::CppRef; pub use crate::PinMut; pub use crate::RValueParam; pub use crate::ValueParam; diff --git a/src/reference_wrapper.rs b/src/reference_wrapper.rs new file mode 100644 index 000000000..fe05a87f2 --- /dev/null +++ b/src/reference_wrapper.rs @@ -0,0 +1,109 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/// A C++ const reference. These are different from Rust's `&T` in that +/// these may exist even while the object is nutated elsewhere. +/// +/// This is a trait not a struct due to the nuances of Rust's orphan rule +/// - implemntations of this trait are found in each set of generated bindings +/// but they are essentially the same. +pub trait CppRef<'a, T> { + /// Retrieve the underlying C++ pointer. + fn as_ptr(&self) -> *const T; + + /// Get a regular Rust reference out of this C++ reference. + /// + /// # Safety + /// + /// Callers must guarantee that the referent is not modified by any other + /// C++ or Rust code while the returned reference exists. Callers must + /// also guarantee that no mutable Rust reference is created to the + /// referent while the returned reference exists. + unsafe fn as_ref(&self) -> &T { + &*self.as_ptr() + } +} + +/// A C++ non-const reference. These are different from Rust's `&mut T` in that +/// several C++ references can exist to the same underlying data ("aliasing") +/// and that's not permitted in Rust. +/// +/// This is a trait not a struct due to the nuances of Rust's orphan rule +/// - implemntations of this trait are found in each set of generated bindings +/// but they are essentially the same. +pub trait CppMutRef<'a, T>: CppRef<'a, T> { + /// Retrieve the underlying C++ pointer. + fn as_mut_ptr(&self) -> *mut T; + + /// Get a regular Rust mutable reference out of this C++ reference. + /// + /// # Safety + /// + /// Callers must guarantee that the referent is not modified by any other + /// C++ or Rust code while the returned reference exists. Callers must + /// also guarantee that no other Rust reference is created to the referent + /// while the returned reference exists. + unsafe fn as_mut(&mut self) -> &mut T { + &mut *self.as_mut_ptr() + } +} + +/// Any newtype wrapper which causes the contained object to obey C++ reference +/// semantics rather than Rust reference semantics. +/// +/// The complex generics here are working around the orphan rule - the only +/// important generic is `T` which is the underlying stored type. +/// +/// C++ references are permitted to alias one another, and commonly do. +/// Rust references must alias according only to the narrow rules of the +/// borrow checker. +/// +/// If you need C++ to access your Rust object, first imprison it in one of these +/// objects, then use [`Self::as_cpp_ref`] to obtain C++ references to it. +pub trait CppPin<'a, T: 'a> { + /// The type of C++ reference created to the contained object. + type CppRef: CppRef<'a, T>; + + /// The type of C++ mutable reference created to the contained object.. + type CppMutRef: CppMutRef<'a, T>; + + /// Get an immutable pointer to the underlying object. + fn as_ptr(&self) -> *const T; + + /// Get a mutable pointer to the underlying object. + fn as_mut_ptr(&mut self) -> *mut T; + + /// Returns a reference which obeys C++ reference semantics + fn as_cpp_ref(&self) -> Self::CppRef; + + /// Returns a mutable reference which obeys C++ reference semantics. + /// + /// Note that this requires unique ownership of `self`, but this is + /// advisory since the resulting reference can be cloned. + fn as_cpp_mut_ref(&mut self) -> Self::CppMutRef; + + /// Get a normal Rust reference to the underlying object. This is unsafe. + /// + /// # Safety + /// + /// You must guarantee that C++ will not mutate the object while the + /// reference exists. + unsafe fn as_ref(&self) -> &T { + &*self.as_ptr() + } + + /// Get a normal Rust mutable reference to the underlying object. This is unsafe. + /// + /// # Safety + /// + /// You must guarantee that C++ will not mutate the object while the + /// reference exists. + unsafe fn as_mut(&mut self) -> &mut T { + &mut *self.as_mut_ptr() + } +}