From 7bf667bbfaba14687b0f6d92c2e86c8d1952e494 Mon Sep 17 00:00:00 2001 From: Brian Silverman Date: Sun, 22 May 2022 14:15:30 -0700 Subject: [PATCH] Add a function to upcast UniquePtr Upcasting can change the value of a pointer in C++. This means anything upcasting needs both the parent and child class defined, which is awkward in user code when wrapping functions that accept ownership of a superclass object (it requires C++ code that depends on autocxx-generated C++ code that depends on the main C++ code, which then needs to be depended on by another iteration of autocxx to call it from Rust). Ideally this would be a static function in C++ to avoid some potential name conflicts that this approach produces (with C++ types sharing a name in separate namespaces), but I don't think cxx supports those yet (dtolnay/cxx#1036 and dtolnay/cxx#447). --- engine/src/conversion/codegen_cpp/mod.rs | 9 ++- engine/src/conversion/codegen_rs/mod.rs | 12 ++++ integration-tests/tests/integration_test.rs | 68 +++++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/engine/src/conversion/codegen_cpp/mod.rs b/engine/src/conversion/codegen_cpp/mod.rs index 02e92b22f..0e4975c81 100644 --- a/engine/src/conversion/codegen_cpp/mod.rs +++ b/engine/src/conversion/codegen_cpp/mod.rs @@ -684,6 +684,13 @@ impl<'a> CppCodeGenerator<'a> { "{}& As_{}_mut() {{ return *this; }}", super_name, super_name )); + self.additional_functions.push(ExtraCpp { + declaration: Some(format!( + "inline std::unique_ptr<{}> {}_As_{}_UniquePtr(std::unique_ptr<{}> u) {{ return std::unique_ptr<{}>(u.release()); }}", + superclass.to_cpp_name(), subclass.cpp(), super_name, subclass.cpp(), superclass.to_cpp_name(), + )), + ..Default::default() + }); // And now constructors let mut constructor_decls: Vec = Vec::new(); for constructor in constructors { @@ -700,7 +707,7 @@ impl<'a> CppCodeGenerator<'a> { } self.additional_functions.push(ExtraCpp { type_definition: Some(format!( - "class {} : {}\n{{\npublic:\n{}\n{}\nvoid {}() const;\nprivate:rust::Box<{}> obs;\nvoid really_remove_ownership();\n\n}};", + "class {} : public {}\n{{\npublic:\n{}\n{}\nvoid {}() const;\nprivate:rust::Box<{}> obs;\nvoid really_remove_ownership();\n\n}};", subclass.cpp(), superclass.to_cpp_name(), constructor_decls.join("\n"), diff --git a/engine/src/conversion/codegen_rs/mod.rs b/engine/src/conversion/codegen_rs/mod.rs index c77dfd05e..c996808df 100644 --- a/engine/src/conversion/codegen_rs/mod.rs +++ b/engine/src/conversion/codegen_rs/mod.rs @@ -723,6 +723,10 @@ impl<'a> RsCodeGenerator<'a> { extern_c_mod_items.push(parse_quote! { fn #as_mut_id(self: Pin<&mut #cpp_id>) -> Pin<&mut #super_cxxxbridge_id>; }); + let as_unique_ptr_id = make_ident(format!("{}_As_{}_UniquePtr", cpp_id, super_name)); + extern_c_mod_items.push(parse_quote! { + fn #as_unique_ptr_id(u: UniquePtr<#cpp_id>) -> UniquePtr<#super_cxxxbridge_id>; + }); bindgen_mod_items.push(parse_quote! { impl AsRef<#super_path> for super::super::super::#id { fn as_ref(&self) -> &cxxbridge::#super_cxxxbridge_id { @@ -740,6 +744,14 @@ impl<'a> RsCodeGenerator<'a> { } } }); + let rs_as_unique_ptr_id = make_ident(format!("as_{}_unique_ptr", super_name)); + bindgen_mod_items.push(parse_quote! { + impl super::super::super::#id { + pub fn #rs_as_unique_ptr_id(u: cxx::UniquePtr<#cpp_id>) -> cxx::UniquePtr { + cxxbridge::#as_unique_ptr_id(u) + } + } + }); let remove_ownership = sub.remove_ownership(); global_items.push(parse_quote! { #[allow(non_snake_case)] diff --git a/integration-tests/tests/integration_test.rs b/integration-tests/tests/integration_test.rs index 5cf262eb0..e89d89071 100644 --- a/integration-tests/tests/integration_test.rs +++ b/integration-tests/tests/integration_test.rs @@ -8278,6 +8278,74 @@ fn test_pv_subclass_calls() { ); } +#[test] +fn test_pv_subclass_as_superclass() { + let hdr = indoc! {" + #include + #include + + class TestObserver { + public: + TestObserver() {} + virtual void a() const = 0; + virtual ~TestObserver() {} + }; + + inline void call_observer(std::unique_ptr obs) { obs->a(); } + "}; + run_test_ex( + "", + hdr, + quote! { + use autocxx::subclass::CppSubclass; + let obs = MyTestObserver::new_cpp_owned( + MyTestObserver::default() + ); + let obs = MyTestObserver::as_TestObserver_unique_ptr(obs); + assert!(!Lazy::force(&STATUS).lock().unwrap().dropped); + ffi::call_observer(obs); + assert!(Lazy::force(&STATUS).lock().unwrap().sub_a_called); + assert!(Lazy::force(&STATUS).lock().unwrap().dropped); + *Lazy::force(&STATUS).lock().unwrap() = Default::default(); + }, + quote! { + generate!("call_observer") + subclass!("TestObserver",MyTestObserver) + }, + None, + None, + Some(quote! { + use once_cell::sync::Lazy; + use std::sync::Mutex; + + use ffi::TestObserver_methods; + #[autocxx::subclass::subclass] + #[derive(Default)] + pub struct MyTestObserver { + } + impl TestObserver_methods for MyTestObserver { + fn a(&self) { + assert!(!Lazy::force(&STATUS).lock().unwrap().dropped); + Lazy::force(&STATUS).lock().unwrap().sub_a_called = true; + } + } + impl Drop for MyTestObserver { + fn drop(&mut self) { + Lazy::force(&STATUS).lock().unwrap().dropped = true; + } + } + + #[derive(Default)] + struct Status { + sub_a_called: bool, + dropped: bool, + } + + static STATUS: Lazy> = Lazy::new(|| Mutex::new(Status::default())); + }), + ); +} + #[test] fn test_cycle_nonpod_simple() { let hdr = indoc! {"