From d58e25a22f5307770672e6dc2c4b661443f5a6a3 Mon Sep 17 00:00:00 2001 From: Andrew Hayzen Date: Fri, 30 Jun 2023 15:42:41 +0100 Subject: [PATCH] tests: ensure that extern "Rust" methods on C++ types work This allows for developers and crates that are generating both C++ and Rust code to have a C++ method implemented in Rust without having to use a free method and passing through the C++ "this" as an argument. --- tests/cxx_gen.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ tests/ffi/lib.rs | 9 +++++++++ tests/ffi/tests.h | 3 +++ 3 files changed, 57 insertions(+) diff --git a/tests/cxx_gen.rs b/tests/cxx_gen.rs index e91675d9d..eb7ee7405 100644 --- a/tests/cxx_gen.rs +++ b/tests/cxx_gen.rs @@ -32,3 +32,48 @@ fn test_impl_annotation() { let output = str::from_utf8(&generated.implementation).unwrap(); assert!(output.contains("ANNOTATION void cxxbridge1$do_cpp_thing(::rust::Str foo)")); } + +const BRIDGE1: &str = r#" + #[cxx::bridge] + mod ffi { + unsafe extern "C++" { + type CppType; + } + + extern "Rust" { + fn rust_method_cpp_context(self: Pin<&mut CppType>); + } + } +"#; + +// Ensure that implementing a Rust method on a C++ type only causes generation +// of the implementation. +// +// The header should be implemented in the C++ class definition and the Rust +// implementation in the usual way. +// +// This allows for developers and crates that are generating both C++ and Rust +// code to have a C++ method implemented in Rust without having to use a +// free method and passing through the C++ "this" as an argument. +#[test] +fn test_extern_rust_method_on_c_type() { + let opt = Opt::default(); + let source = BRIDGE1.parse().unwrap(); + let generated = generate_header_and_cc(source, &opt).unwrap(); + let header = str::from_utf8(&generated.header).unwrap(); + let implementation = str::from_utf8(&generated.implementation).unwrap(); + + // To avoid continual breakage we won't test every byte. + // Let's look for the major features. + + // Check that the header doesn't have the Rust method + assert!(!header.contains("rust_method_cpp_context")); + + // Check that there is a cxxbridge to the Rust method + assert!(implementation + .contains("void cxxbridge1$CppType$rust_method_cpp_context(::CppType &self) noexcept;")); + + // Check that there is a implementation on the C++ class calling the Rust method + assert!(implementation.contains("void CppType::rust_method_cpp_context() noexcept {")); + assert!(implementation.contains("cxxbridge1$CppType$rust_method_cpp_context(*this);")); +} diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs index d6a5f0286..78f272ce5 100644 --- a/tests/ffi/lib.rs +++ b/tests/ffi/lib.rs @@ -309,6 +309,8 @@ pub mod ffi { fn set(self: &mut R, n: usize) -> usize; fn r_method_on_shared(self: &Shared) -> String; fn r_get_array_sum(self: &Array) -> i32; + // Ensure that a Rust method can be implemented on a C++ type + fn r_method_on_c_get_mut(self: Pin<&mut C>) -> &mut usize; #[cxx_name = "rAliasedFunction"] fn r_aliased_function(x: i32) -> String; @@ -419,6 +421,13 @@ impl ffi::Array { } } +// A Rust method implemented on the C++ type +impl ffi::C { + pub fn r_method_on_c_get_mut(self: core::pin::Pin<&mut Self>) -> &mut usize { + self.getMut() + } +} + #[derive(Default)] #[repr(C)] pub struct Buffer([c_char; 12]); diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h index dc02e4ff8..45509a9ce 100644 --- a/tests/ffi/tests.h +++ b/tests/ffi/tests.h @@ -53,6 +53,9 @@ class C { std::vector &get_v(); rust::String cOverloadedMethod(int32_t x) const; rust::String cOverloadedMethod(rust::Str x) const; + // Note that the implementation of this method is generated by CXX itself + // which is then bridged to a Rust method but with the C++ type as self + size_t &r_method_on_c_get_mut() noexcept; private: size_t n;